|
| 1 | +# Add Favorites for Sessions and Folders |
| 2 | + |
| 3 | +## Summary |
| 4 | + |
| 5 | +Add a favorites system that lets users star individual sessions and folder paths so they can quickly access frequently-used items. Favorited items display a ★ indicator and can be filtered with `F`. All group-header favorites resolve to folder paths as the canonical storage type. |
| 6 | + |
| 7 | +## Description |
| 8 | + |
| 9 | +Power users accumulate hundreds of Copilot sessions across many repositories and branches. Currently there's no way to pin or bookmark the items you care about most — you have to search, scroll, or remember pivot/filter combos every time. |
| 10 | + |
| 11 | +A favorites feature would let users mark sessions and folders as favorites with a single keypress (`*`). Favorites persist across restarts and surface visually with a star indicator (★). Users can toggle favorites on and off, and filter to show only favorites. |
| 12 | + |
| 13 | +This mirrors the existing **hidden sessions** pattern (`h`/`H` keys, `HiddenSessions` config field, `hiddenSet` map) but inverts the intent — instead of hiding items, it highlights them. |
| 14 | + |
| 15 | +### Why It Matters |
| 16 | + |
| 17 | +- **Speed**: Jump to your most important sessions without searching |
| 18 | +- **Context switching**: When working across multiple repos/branches, favorites act as a personal "pinned" workspace |
| 19 | +- **Discoverability**: With hundreds of sessions, visual markers reduce cognitive load |
| 20 | + |
| 21 | +## Technical Details |
| 22 | + |
| 23 | +### Architecture: Follow the Hidden Sessions Pattern |
| 24 | + |
| 25 | +The hidden sessions feature is the closest analog and should serve as the implementation blueprint: |
| 26 | + |
| 27 | +| Aspect | Hidden Sessions (existing) | Favorites (proposed) | |
| 28 | +|--------|---------------------------|---------------------| |
| 29 | +| Config field | `HiddenSessions []string` | `FavoriteSessions []string`, `FavoriteFolders []string` | |
| 30 | +| Runtime set | `hiddenSet map[string]struct{}` | `favoriteSessions map[string]struct{}`, `favoriteFolders map[string]struct{}` | |
| 31 | +| Toggle key | `h` (hide) | `*` (star/favorite) | |
| 32 | +| Show/filter key | `H` (toggle hidden visibility) | `F` (filter to favorites only) | |
| 33 | +| Visual indicator | Dimmed styling | `★` prefix on item label | |
| 34 | +| Persistence | Config JSON | Config JSON | |
| 35 | + |
| 36 | +### Keyboard Bindings |
| 37 | + |
| 38 | +**Currently used keys** (must NOT collide): |
| 39 | + |
| 40 | +| Key | Action | |
| 41 | +|-----|--------| |
| 42 | +| `↑`/`k`, `↓`/`j` | Navigate | |
| 43 | +| `←`, `→` | Collapse/expand folder | |
| 44 | +| `Enter` | Launch/toggle | |
| 45 | +| `Space` | Multi-select | |
| 46 | +| `q`, `Ctrl+C` | Quit | |
| 47 | +| `/` | Search | |
| 48 | +| `Esc` | Clear/back | |
| 49 | +| `f` | Filter panel | |
| 50 | +| `s` / `S` | Sort field / sort direction | |
| 51 | +| `Tab` / `Shift+Tab` | Pivot / reverse pivot | |
| 52 | +| `p` | Preview | |
| 53 | +| `r` | Reindex | |
| 54 | +| `?` | Help | |
| 55 | +| `,` | Config/settings | |
| 56 | +| `1`-`4` | Time range filters | |
| 57 | +| `h` / `H` | Hide / toggle hidden | |
| 58 | +| `w`, `t`, `e` | Launch window/tab/pane | |
| 59 | +| `PgUp` / `PgDn` | Preview scroll | |
| 60 | +| `n` | Jump next attention | |
| 61 | +| `!` | Filter attention | |
| 62 | +| `O` | Open all selected | |
| 63 | +| `a` / `d` | Select all / deselect all | |
| 64 | +| `o` | Conversation sort | |
| 65 | + |
| 66 | +**Proposed new bindings:** |
| 67 | + |
| 68 | +| Key | Action | Rationale | |
| 69 | +|-----|--------|-----------| |
| 70 | +| `*` | Toggle favorite on selected item | Universal "star" metaphor; unused; doesn't conflict with any existing binding | |
| 71 | +| `F` (Shift+F) | Toggle "favorites only" filter | Parallels `H` for hidden; uppercase `F` is free (`f` is filter panel) | |
| 72 | + |
| 73 | +**Why `*`:** The asterisk is universally associated with starring/favoriting (Gmail, GitHub, file managers). It's a single keypress (Shift+8 on US layout), mnemonic, and not used by any existing binding. Alternative candidates (`+`, `#`) are less intuitive. |
| 74 | + |
| 75 | +### Data Model Changes |
| 76 | + |
| 77 | +#### `internal/config/config.go` |
| 78 | + |
| 79 | +Add two new fields to the `Config` struct: |
| 80 | + |
| 81 | +```go |
| 82 | +type Config struct { |
| 83 | + // ... existing fields ... |
| 84 | + FavoriteSessions []string `json:"favorite_sessions,omitempty"` |
| 85 | + FavoriteFolders []string `json:"favorite_folders,omitempty"` |
| 86 | +} |
| 87 | +``` |
| 88 | + |
| 89 | +- **FavoriteSessions**: Session IDs (same format as `HiddenSessions`) |
| 90 | +- **FavoriteFolders**: Folder/cwd paths — the canonical storage type for all group-level favorites |
| 91 | + |
| 92 | +These are stored in the same `config.json` file and follow the same sanitization rules. |
| 93 | + |
| 94 | +#### `internal/tui/components/sessionlist.go` |
| 95 | + |
| 96 | +Add favorite tracking sets parallel to `hiddenSet`: |
| 97 | + |
| 98 | +```go |
| 99 | +type SessionList struct { |
| 100 | + // ... existing fields ... |
| 101 | + favoriteSessions map[string]struct{} |
| 102 | + favoriteFolders map[string]struct{} |
| 103 | + favoritesOnly bool // filter mode: show only favorites |
| 104 | +} |
| 105 | +``` |
| 106 | + |
| 107 | +### Key Behaviors |
| 108 | + |
| 109 | +#### Toggle Favorite (`*` key) |
| 110 | + |
| 111 | +When pressed on a **session item** (any pivot mode): |
| 112 | +1. If session ID is in `favoriteSessions` → remove it (unfavorite) |
| 113 | +2. If session ID is NOT in `favoriteSessions` → add it (favorite) |
| 114 | +3. Save config immediately (same pattern as `handleHideSession()`) |
| 115 | +4. Rebuild visible items to update visual indicator |
| 116 | + |
| 117 | +When pressed on a **folder group header** (folder pivot): |
| 118 | +1. Use the folder path directly as the key |
| 119 | +2. Toggle in `favoriteFolders` set |
| 120 | +3. Save and rebuild |
| 121 | + |
| 122 | +When pressed on a **repo group header** (repo pivot): |
| 123 | +1. Collect all unique folder paths (cwds) from sessions within that repo group |
| 124 | +2. If **one folder** → toggle that folder path directly in `favoriteFolders` |
| 125 | +3. If **multiple folders** → show a sub-selection picker listing the folder paths; user picks which to favorite/unfavorite |
| 126 | +4. Save and rebuild |
| 127 | + |
| 128 | +When pressed on a **branch group header** (branch pivot): |
| 129 | +1. Same resolution as repo: collect unique folder paths from sessions in that branch group |
| 130 | +2. If **one folder** → toggle directly |
| 131 | +3. If **multiple folders** → show sub-selection picker |
| 132 | +4. Save and rebuild |
| 133 | + |
| 134 | +When pressed on a **date group header** (date pivot): |
| 135 | +→ No-op. Favoriting a date is not supported. |
| 136 | + |
| 137 | +#### Filter Favorites (`F` key) |
| 138 | + |
| 139 | +Toggle `favoritesOnly` mode: |
| 140 | +- **ON**: Only show items where the session is individually favorited OR the session's cwd matches a favorited folder |
| 141 | +- **OFF**: Show all items (default), favorites still visually marked |
| 142 | +- Visual indicator in the status bar when favorites filter is active (e.g., "★ Favorites") |
| 143 | + |
| 144 | +#### Visual Rendering |
| 145 | + |
| 146 | +- Favorited sessions: Prepend `★ ` to the session title in the list |
| 147 | +- Favorited folder headers: Prepend `★ ` to the folder label |
| 148 | +- Sessions whose cwd matches a favorited folder: Show `★` even if not individually favorited (inherited from folder) |
| 149 | +- Use a distinct color for the star (e.g., yellow/gold from the theme's accent palette) |
| 150 | +- In repo/branch pivot modes: group headers show `★` if ALL sessions in the group belong to favorited folders |
| 151 | + |
| 152 | +#### Sort Integration |
| 153 | + |
| 154 | +- When sorting, favorited items should optionally sort to the top (a boolean "favorites first" toggle, or simply respected when `F` filter is active) |
| 155 | +- Consider adding `favorites` as a sort field option (cycle with `s` key) |
| 156 | + |
| 157 | +### Files to Modify |
| 158 | + |
| 159 | +1. **`internal/config/config.go`** — Add `FavoriteSessions`, `FavoriteFolders` fields |
| 160 | +2. **`internal/tui/keys.go`** — Add `ToggleFavorite` (`*`) and `FilterFavorites` (`F`) key bindings |
| 161 | +3. **`internal/tui/components/sessionlist.go`** — Add favorite sets, filter logic, visual rendering, sub-selection picker for multi-folder resolution |
| 162 | +4. **`internal/tui/model.go`** — Add `handleToggleFavorite()` and `handleFilterFavorites()` in key handler, load favorites from config on init |
| 163 | +5. **`internal/tui/styles.go`** — Add star style (color for `★` indicator) |
| 164 | +6. **`docs/KEYBOARD_BINDINGS.md`** — Document new keybindings (if this file exists) |
| 165 | + |
| 166 | +### Edge Cases |
| 167 | + |
| 168 | +- **Favoriting a session that gets deleted**: On next load, stale IDs in config are harmless (session just won't appear). Optionally prune stale favorites on reindex. |
| 169 | +- **Favoriting a folder path that changes**: Folder favorites are by exact path string. If a repo moves, the old favorite becomes inert. Acceptable tradeoff. |
| 170 | +- **Favoriting + hiding**: A session can be both favorited and hidden. Hidden takes precedence for visibility (unless `H` is toggled to show hidden). The star still renders on hidden-but-visible items. |
| 171 | +- **Multi-folder sub-selection picker**: When `*` is pressed on a repo/branch group header with multiple cwds, a small overlay lists the folders. User selects with arrow keys + Enter/Space. Pressing Esc cancels without changes. |
| 172 | +- **Empty favorites filter**: If `F` is toggled but no favorites exist, show an empty state message: "No favorites yet. Press * to favorite an item." |
| 173 | +- **Config migration**: Old configs without favorite fields deserialize cleanly due to `omitempty` — slices default to nil/empty. |
| 174 | +- **Date pivot**: `*` is a no-op on date group headers — no visual affordance or error, just ignored. |
| 175 | + |
| 176 | +## Acceptance Criteria |
| 177 | + |
| 178 | +- [ ] `*` key toggles favorite on the currently selected session (any pivot mode) |
| 179 | +- [ ] `*` key on a folder group header toggles favorite on that folder path |
| 180 | +- [ ] `*` key on a repo/branch group header resolves to folder path(s); if multiple, shows a sub-selection picker |
| 181 | +- [ ] `*` key on a date group header is a no-op |
| 182 | +- [ ] `F` key toggles a "favorites only" filter showing individually-favorited sessions and sessions whose cwd matches a favorited folder |
| 183 | +- [ ] Favorited items display a `★` visual indicator in the list |
| 184 | +- [ ] Sessions inherit `★` from favorited parent folder (without being individually favorited) |
| 185 | +- [ ] Favorites persist across application restarts via `config.json` |
| 186 | +- [ ] New key bindings (`*`, `F`) do not collide with any existing bindings |
| 187 | +- [ ] Favoriting + hiding coexist without conflict (hidden takes visibility precedence) |
| 188 | +- [ ] Help overlay (`?`) documents the new `*` and `F` keybindings |
| 189 | +- [ ] Status bar indicates when favorites filter is active |
| 190 | +- [ ] Empty favorites filter shows a helpful message |
| 191 | + |
| 192 | +## Related |
| 193 | + |
| 194 | +- **Existing pattern**: Hidden sessions (`h`/`H` keys, `HiddenSessions` config, `hiddenSet` map) — direct blueprint |
| 195 | +- **Files**: `internal/config/config.go`, `internal/tui/keys.go`, `internal/tui/model.go`, `internal/tui/components/sessionlist.go` |
| 196 | +- **Config location**: `%APPDATA%\dispatch\config.json` (Windows), `~/.config/dispatch/config.json` (Unix) |
| 197 | +- **Storage types**: Only two — `FavoriteSessions` (session IDs) and `FavoriteFolders` (cwd paths). All group-header favorites resolve to folder paths. |
0 commit comments