--- phase: quick-17 plan: 1 type: execute wave: 1 depends_on: [] files_modified: - frontend/src/components/playlist-details/playlist-details.ts - frontend/src/components/playlist-view/playlist-view.ts - frontend/index.ts autonomous: true requirements: [QUICK-17] must_haves: truths: - "Clicking a playlist in playlist-view navigates to a dedicated playlist-details subpage" - "Playlist-details shows a header with back button, playlist icon, playlist name, and track count" - "Playlist-details shows the full track list for that playlist with all existing interactions (click, dblclick, context menu, drag, phantom handling)" - "Back button in playlist-details navigates back to the playlist list" - "Playlist-view no longer expands/collapses playlists inline" artifacts: - path: "frontend/src/components/playlist-details/playlist-details.ts" provides: "Playlist detail subpage component" - path: "frontend/src/components/playlist-view/playlist-view.ts" provides: "Playlist list view (no more inline track expansion)" - path: "frontend/index.ts" provides: "Navigation routing for playlist-details" key_links: - from: "frontend/src/components/playlist-view/playlist-view.ts" to: "frontend/index.ts" via: "CustomEvent('navigate', { view: 'playlist-details', playlistId, playlistName })" pattern: "navigate.*playlist-details" - from: "frontend/src/components/playlist-details/playlist-details.ts" to: "frontend/index.ts" via: "CustomEvent('navigate', { view: 'playlists' })" pattern: "navigate.*playlists" --- Refactor the playlist view from an expand/collapse dropdown pattern to a subpage navigation pattern, matching how genre-details and artist-details work. Clicking a playlist navigates to a dedicated playlist-details page with a header (back button + playlist icon + title + track count) and the full track list below. Purpose: Consistent navigation UX across playlists, genres, and artists — all use the subpage pattern. Output: New `playlist-details` component, simplified `playlist-view`, updated navigation routing. @/home/caleb/.config/Claude/get-shit-done/workflows/execute-plan.md @/home/caleb/.config/Claude/get-shit-done/templates/summary.md @frontend/src/components/genre-details/genre-details.ts (reference pattern — header + back button + content) @frontend/src/components/genres-view/genres-view.ts (reference pattern — click navigates to details) @frontend/src/components/playlist-view/playlist-view.ts (source — refactor this) @frontend/index.ts (navigation routing — add playlist-details case) @frontend/wailsjs/go/playlist/Service.d.ts (available backend APIs) @frontend/src/store/playlist-store.ts (playlist data store) @frontend/src/store/search-store.ts (search store — update SEARCHABLE_VIEWS if needed) From frontend/src/components/genre-details/genre-details.ts: ```typescript // Header: back-button → avatar → genre-info (title + track count) // Content: // Back navigation: dispatches CustomEvent('navigate', { view: 'genres' }) ``` From frontend/index.ts — navigation handler: ```typescript // Genre details routing (line 73-82): case 'genre-details': { const { genreName } = (e as CustomEvent).detail; const genreEl = document.createElement('genre-details'); genreEl.setAttribute('genre-name', genreName); mainContent.innerHTML = ''; mainContent.appendChild(genreEl); break; } ``` From frontend/wailsjs/go/playlist/Service.d.ts: ```typescript export function GetPlaylistTracks(arg1:number):Promise>; export function GetAllPlaylistsWithTracks():Promise>; export function AddTracksToPlaylist(arg1:number,arg2:Array):Promise; export function RemoveTracksFromPlaylist(arg1:number,arg2:Array):Promise; export function RenamePlaylist(arg1:number,arg2:string):Promise; export function DeletePlaylist(arg1:number):Promise; export function RemovePhantomTracks(arg1:number,arg2:Array):Promise; export function FindDuplicateTracksInPlaylist(arg1:number,arg2:Array):Promise; ``` From frontend/src/components/playlist-view/playlist-view.ts: ```typescript // PlaylistEntry = { summary: playlist.Summary, expanded: boolean, tracks: playlist.Track[] } // SelectionHost + ContextMenuHost interfaces // Uses: PlayerController, PlaylistController, SearchController, SelectionController, ContextMenuController, FavoritesController // Track interactions: click (select), dblclick (play), contextmenu, drag source, phantom handling // Playlist-level: context menu (rename, delete, set-default), drag target, multi-select, sort ``` From frontend/wailsjs/go/models.ts: ```typescript // playlist.Summary: { ID: number, Name: string, CreatedAt: string, UpdatedAt: string } // playlist.Track: { ID, Position, FilePath, Title, Artist, Album, CoverArtPath, CoverArtSmall, CoverArtMedium, CoverArtLarge, Duration, Phantom } // playlist.WithTracks: { Summary: Summary, Tracks: Track[] } ``` Task 1: Create playlist-details component frontend/src/components/playlist-details/playlist-details.ts, frontend/index.ts Create `frontend/src/components/playlist-details/playlist-details.ts` — a new LitElement component modeled closely on `genre-details.ts` but with all the track interaction capabilities currently in `playlist-view`'s `renderPlaylistBody`. **Component structure:** 1. **Properties:** `playlistId` (Number attribute), `playlistName` (String attribute). 2. **Header** (same layout as genre-details): - Back button (dispatches `navigate` with `{ view: 'playlists' }`) - Playlist avatar: 80×80 rounded square with `list` icon (wa-icon name="list") centered inside, using the same gradient background as genre-details' `.genre-avatar` - Playlist info: h1 title + track count subtitle 3. **Content area:** Render playlist tracks using the same track-item rendering pattern currently in `playlist-view`'s `renderPlaylistBody` method. This includes: - "Play All" button at the top - Track list with `` for normal tracks and phantom row rendering for phantom tracks - All track interactions: click (selection), dblclick (play from playlist), right-click context menu, drag source - Phantom track interactions: click (select), right-click context menu, locate button, remove button - Active track highlighting (uses PlayerController) - Track selection (uses SelectionController — this component is the SelectionHost) 4. **Context menus:** Move the track-level context menu (play, add to queue, play next, remove from playlist, add to playlist submenu, favorites toggle, track details) and the playlist submenu from `playlist-view` into this component. Include ``, ``, and `` elements. 5. **Data loading:** - On connectedCallback, call `GetPlaylistTracks(this.playlistId)` to load tracks - Listen for `Events.PlaylistTracksChanged` and `Events.PlaylistDeleted` to refresh/navigate back - Implement a `refreshTracks()` method that re-fetches tracks after mutations 6. **Drag target:** Support dropping tracks onto this playlist (from queue, track-list, or other playlists). Use the same `AddTracksToPlaylist` + `FindDuplicateTracksInPlaylist` flow currently in `playlist-view`'s `onPlaylistDrop`. 7. **Drag source:** Support dragging tracks from this playlist to queue or other destinations. Same pattern as `playlist-view`'s `onTrackDragStart`/`onTrackDragEnd`. 8. **Search integration:** Wire up SearchController. When search term is active, filter visible tracks (same logic as `playlist-view`'s `getVisibleTracks`). Add `playlist-details` to `SEARCHABLE_VIEWS` in `search-store.ts`. 9. **Styles:** Use `designTokens` mixin. Copy the relevant styles from `genre-details` for the header section (`.back-button`, avatar, title, track-count). Copy track-item styles (`.track-item`, `.track-item.active`, `.track-item.selected`, `.phantom-row`, etc.) and context menu styles from `playlist-view`. Include `.play-all-button` styles. 10. **Select-all support:** Listen for `shortcut:select-all` event (same as playlist-view does). **In `frontend/index.ts`:** - Add `import '@components/playlist-details/playlist-details.ts';` at the top with the other imports - Add a `case 'playlist-details':` block in the navigation switch, following the same pattern as `genre-details`: ``` case 'playlist-details': { const { playlistId, playlistName } = (e as CustomEvent).detail; const el = document.createElement('playlist-details'); el.setAttribute('playlist-id', String(playlistId)); el.setAttribute('playlist-name', playlistName); mainContent.innerHTML = ''; mainContent.appendChild(el); break; } ``` `npx tsc --noEmit` passes. The new component file exists with @customElement('playlist-details'), has back button, header, track rendering, context menus, drag support, and the index.ts routing case exists. playlist-details component renders a header with back button + playlist icon + title + track count, displays playlist tracks with all interactions (select, play, context menu, drag, phantom handling), and navigating to `playlist-details` view works via index.ts routing. Task 2: Simplify playlist-view to navigate instead of expand frontend/src/components/playlist-view/playlist-view.ts Refactor `playlist-view.ts` to remove all inline track expansion and instead navigate to `playlist-details` on click. This is a significant simplification: **Remove from playlist-view:** 1. The `expanded` field from `PlaylistEntry` interface (no longer needed — set type to just `{ summary: playlist.Summary; tracks: playlist.Track[] }`, keep tracks for count display and drag-drop track resolution) 2. The `renderPlaylistBody` method entirely 3. The chevron icon in `renderPlaylistItem` (no more expand/collapse) 4. All track-level interaction handlers: `handleTrackClick`, `handleTrackDblClick`, `handleTrackContextMenu`, `onTrackDragStart`, `onTrackDragEnd`, `ensureSelectionScope`, `getSelectedTrackIDs`, `getSelectedFilePaths`, `removeSelectedTracks`, `removeSelectedPhantoms`, `handlePhantomClick`, `handlePhantomContextMenu`, `openPhantomResolver`, `removePhantomTrack` 5. The `SelectionController` (no more track selection in the list view) and `SelectionHost` implementation (`getItemKey`, `getItemCount`, `onSelectionChanged`) 6. The `ContextMenuController` and `ContextMenuHost` implementation, and all track-level context menu rendering (the `#context-menu` popup, `#playlist-submenu` popup) 7. Remove `activePlaylistIndex` state 8. The ``, ``, and `` elements from the render method (they move to playlist-details) 9. The `isActiveTrack`, `isPhantomSelection`, `resolvePlaylistCoverArt`, `openTrackDetails` methods 10. The `getVisibleTracks` and `filteredEntries` search-related track filtering (search on the list page can just filter playlist names) 11. Remove `SelectionHost` and `ContextMenuHost` from the class declaration 12. Remove imports that are no longer needed: `SelectionController`, track-info, track-details, phantom-resolver, duplicate-tracks-dialog, etc. 13. Remove the `clearSelectionHandler` and `handleSelectAll` handlers since track selection is gone 14. Remove the search-triggered auto-expand logic in `updated()` (the part that sets `expanded: true` based on track matches) **Keep in playlist-view:** 1. Playlist list rendering (the grid of playlist items with name + track count) 2. Playlist-level context menu (rename, delete, set-default) — the `#playlist-context-menu` popup 3. Playlist-level selection (Ctrl/Shift+Click for multi-select of playlists for bulk delete) 4. Create playlist functionality (new playlist button, create form) 5. Import playlist button 6. Sort toolbar (sort by name, created, modified, tracks) 7. Drag-and-drop TARGET: dropping tracks onto a playlist item to add them (keep `onPlaylistDragOver`, `onPlaylistDragLeave`, `onPlaylistDrop`). Also keep empty zone drop and new-button drop. 8. Search filtering (but simplified to just filter by playlist name, not tracks) 9. Scroll position persistence 10. PlaylistController for data loading **Modify `handlePlaylistHeaderClick`:** - Remove the expand/collapse toggle behavior for plain clicks - Instead, plain click dispatches navigation: ```typescript this.dispatchEvent( new CustomEvent('navigate', { bubbles: true, composed: true, detail: { view: 'playlist-details', playlistId: entry.summary.ID, playlistName: entry.summary.Name, }, }), ); ``` - Keep Ctrl+Click and Shift+Click for playlist multi-selection (same as before) **Modify `renderPlaylistItem`:** - Remove the chevron icon - Remove the `${entry.expanded ? this.renderPlaylistBody(...) : nothing}` conditional - The playlist-header row now just shows: playlist-icon (if favorites) + playlist-name + track-count - Keep the right-click context menu handler on the header - Keep the drag-over styling for drop targets **Simplify `filteredEntries`:** - Only filter by playlist name (remove the track title/artist matching since tracks aren't shown inline) **Remove styles that are no longer needed:** - `.chevron`, `.chevron.expanded` - `.playlist-body` - `.playlist-actions`, `.play-all-button` - `.track-item` and all its variants (`.active`, `.selected`, `.phantom`) - `.phantom-row`, `.phantom-caution`, `.phantom-path`, `.phantom-actions`, `.phantom-icon-btn` - `.tracks-empty` - Context menu styles for track-level menus **Update `loadPlaylists`/`refreshPlaylists`:** - Remove `expanded` from the mapped entries **Remove the `contextMenuStyles` import** if no longer needed (check if playlist-level context menu uses it — it does, so keep it). **Note:** The `PlaylistController`, `PlayerController`, `SearchController`, `FavoritesController` may still be needed. Keep `FavoritesController` for the favorites icon display. Remove `PlayerController` since active track highlighting is gone from the list. Keep `SearchController` for name-based search. Keep `PlaylistController` for data. `npx tsc --noEmit` passes. Verify playlist-view no longer has any `expanded`, `renderPlaylistBody`, `handleTrackClick`, `SelectionController`, or chevron references. Playlist-view shows a clean list of playlists without inline track expansion. Plain-clicking a playlist navigates to playlist-details. Ctrl/Shift+Click still multi-selects. Right-click context menu still works for rename/delete/set-default. Drag-drop onto playlists still works. Create/import still works. Sort still works. Search filters by playlist name only. 1. `npx tsc --noEmit` — TypeScript compilation passes with no errors 2. Navigate to Playlists view — shows list of playlists without chevrons or expandable sections 3. Click a playlist — navigates to playlist-details with header (back button, icon, title, track count) and track list 4. Click back button — returns to playlist list 5. Double-click a track in playlist-details — plays the track 6. Right-click track in playlist-details — context menu works (play, queue, remove, etc.) 7. Right-click playlist in list — context menu works (rename, delete, set-default) 8. Ctrl+Click playlists in list — multi-select works for bulk delete 9. Drag tracks from another view onto a playlist in the list — adds tracks 10. Drag tracks from playlist-details to queue — works 11. Create new playlist — form still works 12. Import playlist — button still works 13. Sort playlists — toolbar still works 14. Search — filters playlist names in list view, filters tracks in detail view - Playlist navigation matches genre/artist pattern: list view → click → detail subpage → back button - No inline track expansion/collapse in playlist-view - All existing track interactions work in playlist-details (play, select, context menu, drag, phantom handling) - All existing playlist management works in playlist-view (create, import, rename, delete, sort, drag-drop target) - TypeScript compiles without errors After completion, create `.planning/quick/17-refactor-playlist-view-to-use-subpages-l/17-SUMMARY.md`