diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index fbed9ba..6f22674 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -205,10 +205,11 @@ Which phases cover which requirements. Updated during roadmap creation. | DATA-04 | Phase 10 | Complete | **Coverage:** -- v1.1 requirements: 31 total (20 complete + 11 pending) -- Mapped to phases: 31/31 ✓ (Phase 9: 8, Phase 10: 5, Phase 11: 4, Phase 12: 7 of 7 — 11 pending in Phase 13) +- v1.1 requirements: 31 total (31 complete) +- Mapped to phases: 31/31 ✓ (Phase 9: 8, Phase 10: 5, Phase 11: 4, Phase 12: 7, Phase 13: 7) - No orphaned requirements +- All v1.1 requirements complete as of 2026-03-16 --- *Requirements defined: 2026-03-06* -*Last updated: 2026-03-08 — restructured for multi-library support, deferred TAG/SMRT/GAP/MB/LAYOUT/PLUG* +*Last updated: 2026-03-16 — all v1.1 requirements complete (31/31)* diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 51be0af..7e66607 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -7,8 +7,8 @@ ## Milestones - ✅ **v1.0 Consolidation** — Phases 1-8 (shipped 2026-03-05) — [archive](milestones/v1.0-ROADMAP.md) -- 🔄 **v1.1 Multi-Library Support** — Phase 9 complete, Phases 10-13 in progress -- 🔄 **Performance Optimization** — Phase 14 (cross-cutting, parallel to v1.1) +- ✅ **v1.1 Multi-Library Support** — Phases 9-13 complete (shipped 2026-03-16) +- ✅ **Performance Optimization** — Phase 14 complete (shipped 2026-03-15) ## Phases @@ -110,8 +110,8 @@ Plans: 5. When a library is removed, its tracks in playlists become phantom entries — visually distinguished (greyed out / icon) with preserved title, artist, album metadata instead of disappearing **Plans:** 2/2 plans complete Plans: -- [ ] 13-01-PLAN.md — Backend library-filtered sqlc queries + Go methods + FTS search -- [ ] 13-02-PLAN.md — Frontend library filter store + dropdown UI + all view/search wiring + verification +- [x] 13-01-PLAN.md — Backend library-filtered sqlc queries + Go methods + FTS search +- [x] 13-02-PLAN.md — Frontend library filter store + dropdown UI + all view/search wiring + verification ### Phase 14: Performance Optimization **Goal:** Scrolling, navigation, and rendering are as smooth and fast as possible — scrolling feels like a native animation, navigation is instant, no unnecessary re-renders @@ -125,10 +125,10 @@ Plans: 5. A profiling guide documents how to diagnose performance issues using pprof (backend) and DevTools (frontend) **Plans:** 4/4 plans complete Plans: -- [ ] 14-01-PLAN.md — CSS containment + GPU layer promotion on all scroll containers -- [ ] 14-02-PLAN.md — View caching navigation system (replace innerHTML destruction) -- [ ] 14-03-PLAN.md — Render hot-path optimization (closure elimination, store granularity) -- [ ] 14-04-PLAN.md — Scroll event optimization, profiling guide, performance verification checkpoint +- [x] 14-01-PLAN.md — CSS containment + GPU layer promotion on all scroll containers +- [x] 14-02-PLAN.md — View caching navigation system (replace innerHTML destruction) +- [x] 14-03-PLAN.md — Render hot-path optimization (closure elimination, store granularity) +- [x] 14-04-PLAN.md — Scroll event optimization, profiling guide, performance verification checkpoint ## Progress @@ -143,12 +143,12 @@ Plans: | 7. Backend Performance | v1.0 | 2/2 | Complete | 2026-03-05 | | 8. Frontend Performance & UX | v1.0 | 4/4 | Complete | 2026-03-05 | | 9. Scan Cancellation & Keyboard Shortcuts | v1.1 | 5/5 | Complete | 2026-03-07 | -| 10. Schema & Migration | 2/2 | Complete | 2026-03-09 | - | -| 11. Per-Library Scan Pipeline | 3/3 | Complete | 2026-03-09 | - | -| 12. Library CRUD & Data Integrity | v1.1 | Complete | 2026-03-15 | 2026-03-15 | -| 13. Library Views & Phantom Tracks | 2/2 | Complete | 2026-03-16 | - | -| 14. Performance Optimization | 4/4 | Complete | 2026-03-15 | - | +| 10. Schema & Migration | v1.1 | 2/2 | Complete | 2026-03-09 | +| 11. Per-Library Scan Pipeline | v1.1 | 3/3 | Complete | 2026-03-09 | +| 12. Library CRUD & Data Integrity | v1.1 | 2/2 | Complete | 2026-03-15 | +| 13. Library Views & Phantom Tracks | v1.1 | 2/2 | Complete | 2026-03-16 | +| 14. Performance Optimization | Perf | 4/4 | Complete | 2026-03-15 | --- *Roadmap created: 2026-02-27* -*Last updated: 2026-03-14 — Phase 14 (Performance Optimization) added with 4 plans* +*Last updated: 2026-03-16 — v1.1 milestone complete (Phases 9-14 all done)* diff --git a/.planning/STATE.md b/.planning/STATE.md index 904a370..d00759a 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -155,6 +155,9 @@ Decisions from v1.0 are archived in PROJECT.md Key Decisions table. Key patterns | 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 for consistency | +| ScanHooks callback for phantom resolution | Mirrors RemovalHooks/RescanHooks pattern; avoids circular dependency between library and playlist packages | +| phantom_file_path column on playlist_tracks | Stored at removal time for post-scan matching; enables automatic phantom resolution | +| M3U8-based phantom resolution over SQL-only | Reads playlist files to match by position and file path; handles both pre-existing and new phantoms | ### Warnings (carry forward) @@ -182,9 +185,9 @@ Decisions from v1.0 are archived in PROJECT.md Key Decisions table. Key patterns ### Last Session **Date:** 2026-03-16 -**What happened:** Executed Plan 13-02 — added library filter dropdown to top bar, wired all browse/detail views to respect library filter via ByLibrary conditional queries. 1 task (auto), 12 files modified. Checkpoint pending for human verification. -**Where we stopped:** Completed 13-02-PLAN.md — Phase 13 complete (2/2 plans), awaiting human verification -**Next action:** Human verification of library filter, cross-library playlists, and phantom tracks +**What happened:** Completed Plan 13-02 — library filter dropdown in top bar, all views wired to ByLibrary queries, phantom track auto-resolution via ScanHooks. Checkpoint APPROVED — all 7 requirements verified (VIEW-01–04, PLAY-01–03). Additional bugfixes: virtualizer event delegation race condition, phantom auto-resolution after re-scan. +**Where we stopped:** Completed 13-02-PLAN.md — Phase 13 complete (2/2 plans), v1.1 milestone complete +**Next action:** v1.1 milestone shipped — all phases (9-14) complete --- *State initialized: 2026-02-27* diff --git a/.planning/phases/13-library-views-phantom-tracks/13-02-SUMMARY.md b/.planning/phases/13-library-views-phantom-tracks/13-02-SUMMARY.md index c2c48af..62118b7 100644 --- a/.planning/phases/13-library-views-phantom-tracks/13-02-SUMMARY.md +++ b/.planning/phases/13-library-views-phantom-tracks/13-02-SUMMARY.md @@ -1,32 +1,40 @@ --- phase: 13-library-views-phantom-tracks plan: 02 -subsystem: frontend, ui -tags: [lit, wails, library-filter, dropdown, multi-library] +subsystem: ui, api +tags: [lit, wails, library-filter, phantom-tracks, playlist-resolution, typescript] # Dependency graph requires: - phase: 13-library-views-phantom-tracks - provides: Library-filtered Go query methods and Wails bindings + 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 component in top bar - - All browse views (tracks, albums, artists, genres) filter by selected library - - Detail views (artist-details, genre-details) respect library filter - - Search results respect library filter (via filtered data source) - - Playlists remain unfiltered (cross-library by design) -affects: [frontend-navigation, queue-context] + - 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 ByLibrary/unfiltered call pattern in library-store - - Library filter state as store singleton (null = All Libraries) - - Native select dropdown for compact filter UI + - "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 @@ -37,73 +45,111 @@ key-files: - 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/wailsjs/go/library/Library.d.ts - - frontend/wailsjs/go/library/Library.js + - 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 library-filtered data source — no backend SearchTracksByLibrary needed since search is already client-side via rankTracks" - - "Null selectedLibraryId = All Libraries (no persistence, resets on restart)" - - "Native select element for dropdown — compact, accessible, no custom widget overhead" - - "getAlbumsByArtistNameCached returns null when library filter active — forces backend query for consistency" + - "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: - - "ByLibrary conditional call pattern: check selectedLibraryId, call ByLibrary or unfiltered variant" + - "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: 9min +duration: 45min completed: 2026-03-16 --- -# Phase 13 Plan 02: Library Filter UI & View Wiring Summary +# Phase 13 Plan 02: Library Filter UI & Phantom Track Resolution Summary -**Library filter dropdown in top bar with conditional ByLibrary queries across all browse views, detail views, and search — playlists remain unfiltered** +**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:** 9 min +- **Duration:** ~45 min (including checkpoint verification and bugfixes) - **Started:** 2026-03-16T13:32:45Z -- **Completed:** 2026-03-16T13:41:49Z -- **Tasks:** 1 (of 2 — Task 2 is human verification checkpoint) -- **Files modified:** 12 +- **Completed:** 2026-03-16T14:18:00Z +- **Tasks:** 2 (1 auto + 1 checkpoint:human-verify — APPROVED) +- **Files modified:** 33 (across 4 code commits) ## Accomplishments -- Created library-filter dropdown component that loads library list from backend and sets store filter -- Modified library-store to conditionally call ByLibrary or unfiltered queries based on selectedLibraryId -- Wired all browse views (tracks, albums, artists, genres) to automatically use filtered data -- Wired detail views (genre-details direct Wails call, artist-details via store, cover-grid album dropdown) to respect filter -- Updated album-selection helper for library-aware drag/context-menu file path resolution -- Verified playlists use separate PlaylistStore — not affected by library filter -- Verified search is client-side (rankTracks on loaded data) — inherits filter automatically +- 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 -**Task 2:** checkpoint:human-verify (pending user verification) +### 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/src/components/library-filter/library-filter.ts` - NEW: Library filter dropdown component -- `frontend/src/store/library-store.ts` - selectedLibraryId state, ByLibrary conditional calls, getLibraries() -- `frontend/src/store/controllers/library-controller.ts` - Pass-through for selectedLibraryId, setSelectedLibrary, getLibraries -- `frontend/index.html` - Added `` to top bar -- `frontend/index.ts` - Import library-filter component -- `frontend/src/components/cover-grid/cover-grid.ts` - GetAlbumTracksByLibrary in 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 — 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 -- Search is entirely client-side (rankTracks filters already-loaded tracks), so no backend SearchTracksByLibrary call needed — the library filter naturally scopes search results via the filtered track list -- Used native `` for library filter:** Compact, accessible, 32px height matching search bar, no custom dropdown overhead +- **getAlbumsByArtistNameCached returns null when filter active:** Forces backend query to avoid showing stale cross-library cached albums +- **ScanHooks callback for phantom resolution:** Mirrors established RemovalHooks/RescanHooks pattern — avoids circular dependency between library and playlist packages +- **M3U8-based phantom resolution over SQL-only:** Reads playlist files to match phantoms by both position and phantom_file_path — handles pre-existing phantoms (match by position) and new ones (match by stored path) +- **phantom_file_path column:** Stored at removal time so post-scan resolution can match even when M3U8 position changes ## Deviations from Plan @@ -111,36 +157,59 @@ Each task was committed atomically: **1. [Rule 3 - Blocking] Wails bindings not yet generated for ByLibrary methods** - **Found during:** Task 1 (start) -- **Issue:** Plan 13-01 added Go methods but bindings weren't regenerated +- **Issue:** Plan 13-01 added Go methods but Wails bindings weren't regenerated - **Fix:** Ran `wails generate module` before implementation - **Files modified:** frontend/wailsjs/go/library/Library.d.ts, Library.js - **Verification:** All ByLibrary imports resolve correctly -- **Committed in:** 42b8cf9 +- **Committed in:** `42b8cf9` -**2. [Rule 1 - Bug] Info type uses lowercase property names (id, name) not uppercase** +**2. [Rule 1 - Bug] Info type uses lowercase property names** - **Found during:** Task 1 (TypeScript typecheck) - **Issue:** library-filter.ts used `lib.ID` and `lib.Name` but Wails-generated Info type uses `lib.id` and `lib.name` - **Fix:** Changed to lowercase property access - **Files modified:** frontend/src/components/library-filter/library-filter.ts - **Verification:** `tsc --noEmit` passes cleanly -- **Committed in:** 42b8cf9 +- **Committed in:** `42b8cf9` + +**3. [Rule 1 - Bug] Virtualizer event delegation race condition** +- **Found during:** Checkpoint verification (Task 2) +- **Issue:** Pre-existing race from Phase 14-03 — event delegation on virtualizer failed when element wasn't rendered yet on firstUpdated() +- **Fix:** Added retry in updated() with guard flag; delegation happens once virtualizer exists +- **Files modified:** frontend/src/components/track-list/track-list.ts, frontend/src/components/queue-panel/queue-panel.ts +- **Verification:** Track list and queue panel click/context-menu events work reliably on app launch +- **Committed in:** `f05d2bb` + +**4. [Rule 2 - Missing Critical] Phantom track auto-resolution after library scan** +- **Found during:** Checkpoint verification (Task 2) +- **Issue:** Phantom tracks not auto-resolved when library re-added and scanned — users would need to manually resolve each one +- **Fix:** Added phantom_file_path column (migration 7), store file_path on removal, ResolvePhantomTracksAfterScan via ScanHooks callback with M3U8 path comparison +- **Files modified:** backend/database/database.go, backend/database/sql/schemas/playlist_tracks.sql, backend/library/crud.go, backend/library/library.go, backend/playlist/playlist.go, backend/app.go +- **Verification:** Remove library → re-add → scan → phantoms automatically resolve to real tracks +- **Committed in:** `93262b9`, `9f595b7` --- -**Total deviations:** 2 auto-fixed (1 blocking, 1 bug) -**Impact on plan:** Both fixes required for compilation. No scope creep. +**Total deviations:** 4 auto-fixed (2 bugs, 1 blocking, 1 missing critical) +**Impact on plan:** All fixes essential for correctness. Virtualizer fix resolved pre-existing race condition exposed by multi-library testing. Phantom auto-resolution is critical UX — users should not need to manually fix playlist tracks after re-adding a library. ## Issues Encountered -None +None beyond the deviations documented above. ## User Setup Required None - no external service configuration required. ## Next Phase Readiness -- All automated implementation complete — awaiting human verification checkpoint (Task 2) -- After verification: Phase 13 complete, multi-library support fully delivered -- v1.1 milestone only needs final Phase 13 checkpoint approval +- **Phase 13 complete** — All 7 requirements verified (VIEW-01 through VIEW-04, PLAY-01 through PLAY-03) +- **v1.1 Multi-Library Support milestone complete** — Phases 9-13 all done +- **Phase 14 (Performance Optimization) already complete** — executed in parallel during v1.1 development +- All browse views, search, playlists, and phantom tracks working correctly in multi-library context + +## Self-Check: PASSED + +- All 10 key files exist on disk +- All 5 commits found in git log (42b8cf9, f05d2bb, 93262b9, 9f595b7, 70e3814) +- SUMMARY.md exists at expected path --- *Phase: 13-library-views-phantom-tracks*