--- phase: 13-library-views-phantom-tracks plan: 02 subsystem: ui, api tags: [lit, wails, library-filter, phantom-tracks, playlist-resolution, typescript] # Dependency graph requires: - phase: 13-library-views-phantom-tracks plan: 01 provides: Library-filtered sqlc queries and Go methods for tracks, albums, artists, genres, search - phase: 12-library-crud-data-integrity provides: Library CRUD API, orphan cleanup, phantom track metadata pre-population - phase: 10-schema-migration provides: Libraries table, library_id FK, phantom columns on playlist_tracks provides: - Library filter dropdown UI in top bar with All Libraries default - All browse views (tracks, albums, artists, genres) respect active library filter - Search respects active library filter - Detail views (artist-details, genre-details, album tracks) respect library filter - Phantom track auto-resolution after library scan via ScanHooks callback - phantom_file_path column on playlist_tracks for phantom-to-track matching affects: [multi-library-complete, v1.1-milestone] # Tech tracking tech-stack: added: [] patterns: - "Conditional Wails binding dispatch: store methods call ByLibrary variant when filter active, default variant otherwise" - "ScanHooks callback for cross-package phantom resolution (mirrors RemovalHooks/RescanHooks pattern)" - "Deferred event delegation retry in updated() for virtualizer race condition" - "phantom_file_path stored on removal for post-scan matching" key-files: created: - frontend/src/components/library-filter/library-filter.ts - backend/playlist/playlist.go (ResolvePhantomTracksAfterScan) modified: - frontend/src/store/library-store.ts - frontend/src/store/controllers/library-controller.ts - frontend/index.html - frontend/index.ts - frontend/src/components/cover-grid/cover-grid.ts - frontend/src/components/cover-grid/album-selection.ts - frontend/src/components/artists-view/artists-view.ts - frontend/src/components/genres-view/genres-view.ts - frontend/src/components/genre-details/genre-details.ts - frontend/src/components/track-list/track-list.ts - frontend/src/components/queue-panel/queue-panel.ts - backend/library/crud.go - backend/library/library.go - backend/database/database.go - backend/database/sql/schemas/playlist_tracks.sql - backend/app.go key-decisions: - "Client-side search with filtered data source — rankTracks filters already-loaded tracks; no backend SearchTracksByLibrary call needed" - "Native select for library filter dropdown — compact, accessible, matches top bar height; no custom component overhead" - "getAlbumsByArtistNameCached returns null when filter active — avoids stale cross-library data; forces backend query" - "ScanHooks callback pattern for phantom resolution — mirrors RemovalHooks/RescanHooks for cross-package communication" - "phantom_file_path column on playlist_tracks — enables post-scan matching of phantoms to re-added tracks" - "M3U8-based phantom resolution — reads playlist files to match phantoms by position and file path" patterns-established: - "Conditional ByLibrary dispatch: check selectedLibraryId, call ByLibrary variant or unfiltered default" - "ScanHooks pattern: cross-package callbacks registered at app init to avoid circular imports" - "Deferred delegation guard: retry event delegation in updated() when virtualizer not ready on firstUpdated()" requirements-completed: [VIEW-01, VIEW-02, VIEW-03, VIEW-04, PLAY-01, PLAY-02, PLAY-03] # Metrics duration: 45min completed: 2026-03-16 --- # Phase 13 Plan 02: Library Filter UI & Phantom Track Resolution Summary **Library filter dropdown in top bar with conditional ByLibrary queries across all views, plus ScanHooks-based phantom track auto-resolution after library re-scan** ## Performance - **Duration:** ~45 min (including checkpoint verification and bugfixes) - **Started:** 2026-03-16T13:32:45Z - **Completed:** 2026-03-16T14:18:00Z - **Tasks:** 2 (1 auto + 1 checkpoint:human-verify — APPROVED) - **Files modified:** 33 (across 4 code commits) ## Accomplishments - Created `` dropdown component in top bar — shows "All Libraries" default plus all configured libraries - Wired all browse views (tracks, albums, artists, genres) and detail views (artist-details, genre-details, album track expansion) to respect active library filter via conditional ByLibrary Wails binding calls - Search inherits library filter automatically — client-side rankTracks operates on filtered track data - Playlists remain unfiltered (cross-library by design) — verified during checkpoint - Fixed pre-existing virtualizer event delegation race condition from Phase 14-03 optimization - Added phantom track auto-resolution: ScanHooks callback triggers M3U8-based phantom matching after library scan - Added `phantom_file_path` column (migration 7) to playlist_tracks for reliable phantom→track matching - All 7 Phase 13 requirements verified end-to-end: VIEW-01 through VIEW-04, PLAY-01 through PLAY-03 ## Task Commits Each task was committed atomically: 1. **Task 1: Add library filter state + dropdown + wire all views** - `42b8cf9` (feat) 2. **Task 2: Verify library filter, cross-library playlists, and phantom tracks** - Checkpoint APPROVED ### Bugfix Commits (during checkpoint verification) 3. **Fix: Defer virtualizer event delegation until element exists** - `f05d2bb` (fix) 4. **Fix: Auto-resolve phantom playlist tracks after library scan** - `93262b9` (fix) 5. **Fix: Resolve phantom playlist tracks using M3U8 paths after scan** - `9f595b7` (fix) **Plan metadata:** `70e3814` (docs: complete library filter UI plan) ## Files Created/Modified ### Frontend — Library filter UI (Task 1) - `frontend/src/components/library-filter/library-filter.ts` — NEW: Library filter dropdown component (native select, design tokens) - `frontend/src/store/library-store.ts` — selectedLibraryId state, conditional ByLibrary dispatch, getLibraries(), invalidation - `frontend/src/store/controllers/library-controller.ts` — Pass-through for selectedLibraryId, setSelectedLibrary, getLibraries - `frontend/index.html` — `` element added to top bar header - `frontend/index.ts` — Import for library-filter component - `frontend/src/components/cover-grid/cover-grid.ts` — GetAlbumTracksByLibrary for album expansion dropdown - `frontend/src/components/cover-grid/album-selection.ts` — Library-aware fetchAlbumTracks helper - `frontend/src/components/artists-view/artists-view.ts` — GetAlbumsByArtistByLibrary + GetAlbumTracksByLibrary - `frontend/src/components/genres-view/genres-view.ts` — GetTracksByGenreByLibrary for context menu - `frontend/src/components/genre-details/genre-details.ts` — GetTracksByGenreByLibrary for track loading - `frontend/wailsjs/go/library/Library.d.ts` — Regenerated with ByLibrary bindings - `frontend/wailsjs/go/library/Library.js` — Regenerated with ByLibrary bindings ### Frontend — Virtualizer race condition fix - `frontend/src/components/track-list/track-list.ts` — Deferred event delegation with guard flag in updated() - `frontend/src/components/queue-panel/queue-panel.ts` — Deferred event delegation with guard flag in updated() ### Backend — Phantom track auto-resolution - `backend/database/database.go` — Migration 7: phantom_file_path column on playlist_tracks - `backend/database/sql/schemas/playlist_tracks.sql` — phantom_file_path column definition - `backend/database/sql/sqlcgen/models.go` — Generated model with PhantomFilePath field - `backend/database/sql/sqlcgen/playlists.sql.go` — Generated query updates - `backend/library/crud.go` — Store file_path as phantom_file_path on RemoveLibrary - `backend/library/library.go` — ScanHooks registration, phantom resolution trigger after scan - `backend/playlist/playlist.go` — ResolvePhantomTracksAfterScan: M3U8-based phantom matching - `backend/app.go` — ScanHooks wiring at app initialization - `frontend/wailsjs/go/models.ts` — Updated generated models - `frontend/wailsjs/go/playlist/Service.d.ts` — Updated generated bindings - `frontend/wailsjs/go/playlist/Service.js` — Updated generated bindings ## Decisions Made - **Client-side search filtering:** rankTracks already operates on filtered track data from library store — no separate backend SearchTracksByLibrary call needed - **Native `