diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 1ac7caa..7d4d740 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -30,7 +30,7 @@ Requirements for the consolidation milestone. Each maps to roadmap phases. - [ ] **PERF-01**: Queue single-track mutations (add, remove) use incremental INSERT/DELETE via existing sqlc queries instead of full table rewrite - [ ] **PERF-02**: SetQueue Phase 2 (resolveRemainingTracks) skips file paths already resolved in Phase 1, avoiding redundant database lookups -- [ ] **PERF-03**: Library store constructor no longer calls eagerFetch(); data loads lazily on first access via existing getTracks()/getAlbums()/etc. getters +- [x] **PERF-03**: Library store constructor no longer calls eagerFetch(); data loads lazily on first access via existing getTracks()/getAlbums()/etc. getters - [x] **PERF-04**: SQLite connection applies performance PRAGMAs (synchronous=NORMAL, cache_size=-8000, mmap_size=67108864) at database open - [ ] **PERF-05**: Frontend track/album lists use Lit repeat() directive with stable keys (filePath/albumId) for efficient DOM reuse, and store notifications are debounced via queueMicrotask() during rapid updates @@ -104,7 +104,7 @@ Which phases cover which requirements. Updated during roadmap creation. | QUAL-04 | Phase 6: SQL Consolidation & Code Quality | Complete | | PERF-01 | Phase 7: Backend Performance | Pending | | PERF-02 | Phase 7: Backend Performance | Pending | -| PERF-03 | Phase 7: Backend Performance | Pending | +| PERF-03 | Phase 7: Backend Performance | Complete | | PERF-04 | Phase 3: Test Infrastructure | Complete | | PERF-05 | Phase 8: Frontend Performance & UX | Pending | | TEST-01 | Phase 3: Test Infrastructure | Complete | diff --git a/.planning/STATE.md b/.planning/STATE.md index 412b3d2..92bda62 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -2,13 +2,13 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone -status: completed -last_updated: "2026-03-05T00:38:32.082Z" +status: in-progress +last_updated: "2026-03-05T01:54:49Z" progress: - total_phases: 6 + total_phases: 8 completed_phases: 6 - total_plans: 11 - completed_plans: 11 + total_plans: 13 + completed_plans: 12 --- # YellowJacket — Consolidation Milestone State @@ -16,17 +16,17 @@ progress: ## Project Reference **Core value:** The music player works reliably and feels solid — every interaction is correct, responsive, and trustworthy. -**Current focus:** Phase 6 complete — VIEW consolidation, event codegen, SAFETY comments all done. Ready for Phase 7. +**Current focus:** Phase 7 in progress — deferred library loading complete (Plan 02). Plan 01 pending. **Milestone:** Consolidation (correctness, performance, code quality, UX polish, test coverage) ## Current Position -**Phase:** 06-sql-consolidation-code-quality (complete) -**Plan:** 3/3 (all complete) -**Status:** Milestone complete +**Phase:** 07-backend-performance (in progress) +**Plan:** 1/2 (Plan 02 complete) +**Status:** In progress ``` -Phase Progress: [######..] 6/8 phases — Phase 6: 3/3 plans complete ✓ +Phase Progress: [######..] 6/8 phases — Phase 7: 1/2 plans complete ``` ## Performance Metrics @@ -34,7 +34,7 @@ Phase Progress: [######..] 6/8 phases — Phase 6: 3/3 plans complete ✓ | Metric | Value | |--------|-------| | Phases complete | 6/8 | -| Plans complete | 3/3 (Phase 6) | +| Plans complete | 1/2 (Phase 7) | | Requirements delivered | 18/26 | | Tests added | 84 | | Bugs fixed | 9 | @@ -51,6 +51,7 @@ Phase Progress: [######..] 6/8 phases — Phase 6: 3/3 plans complete ✓ | Phase 06 P01 | 2 min | 2 tasks | 4 files | | Phase 06 P02 | 2 min | 2 tasks | 3 files | | Phase 06 P03 | 6 min | 2 tasks | 7 files | +| Phase 07 P02 | 1 min | 1 tasks | 1 files | ## Accumulated Context @@ -79,6 +80,7 @@ Phase Progress: [######..] 6/8 phases — Phase 6: 3/3 plans complete ✓ | AST-based event codegen | Iterate f.Decls directly for deterministic declaration-order output; atomic writes via temp+rename | Phase 6 | | sqlc.slice() for batch lookups | LookupTrackMetaByPaths uses track_metadata VIEW; chunking preserved at 900 since sqlc.slice() doesn't auto-chunk | Phase 6 | | SAFETY comment convention | Two-part format (why + safety assurance); cross-references from library.go/rescan.go to search.go | Phase 6 | +| DOMContentLoaded over load event | Fires earlier (after HTML parsed) without waiting for all resources; still defers past module evaluation | Phase 7 | ### TODOs @@ -122,18 +124,18 @@ None currently. ### Last Session **Date:** 2026-03-05 -**What happened:** Executed Phase 6 Plan 03 — migrated lookupChunk to sqlc-generated query, added SAFETY comments to all 12 hand-crafted SQL statements -**Where we stopped:** Completed 06-03-PLAN.md (2 tasks, all verification passed). Phase 6 complete (3/3 plans). -**Next action:** `/gsd-plan-phase 07` to plan Phase 7 (performance/startup optimization) +**What happened:** Executed Phase 7 Plan 02 — deferred LibraryStore eagerFetch from constructor to DOMContentLoaded event +**Where we stopped:** Completed 07-02-PLAN.md (1 task, all verification passed). Phase 7: 1/2 plans complete. +**Next action:** Execute 07-01-PLAN.md (lazy module loading) or continue to next phase ### Context for Next Session -- Phase 6 fully complete: VIEW consolidation, event codegen, SAFETY comments -- All hand-crafted SQL documented with // SAFETY: comments -- lookupChunk now uses sqlc-generated LookupTrackMetaByPaths -- Ready for Phase 7 performance/startup optimization +- Phase 7 Plan 02 complete: deferred library loading +- LibraryStore no longer fires backend roundtrips during module evaluation +- App shell renders before data fetches begin +- Plan 01 (lazy module loading) still pending --- *State initialized: 2026-02-27* -Last activity: 2026-03-05 - Completed 06-03: lookupChunk sqlc migration + SAFETY comments on all hand-crafted SQL +Last activity: 2026-03-05 - Completed 07-02: deferred LibraryStore eagerFetch to DOMContentLoaded *Last updated: 2026-03-05* diff --git a/.planning/phases/07-backend-performance/07-02-SUMMARY.md b/.planning/phases/07-backend-performance/07-02-SUMMARY.md new file mode 100644 index 0000000..9b6a902 --- /dev/null +++ b/.planning/phases/07-backend-performance/07-02-SUMMARY.md @@ -0,0 +1,91 @@ +--- +phase: 07-backend-performance +plan: 02 +subsystem: ui +tags: [performance, startup, deferred-loading, dom-ready, wails] + +# Dependency graph +requires: + - phase: 06-sql-consolidation-code-quality + provides: stable frontend store and library data access patterns +provides: + - Deferred LibraryStore eagerFetch — app shell renders before backend data roundtrips +affects: [08-frontend-polish] + +# Tech tracking +tech-stack: + added: [] + patterns: [deferred-initialization via DOMContentLoaded event] + +key-files: + created: [] + modified: + - frontend/src/store/library-store.ts + +key-decisions: + - "DOMContentLoaded over load event — fires earlier (after HTML parsed) without waiting for all resources, still defers past module evaluation" + +patterns-established: + - "Deferred singleton initialization: singleton constructors should not fire async work; defer to DOM ready events" + +requirements-completed: [PERF-03] + +# Metrics +duration: 1min +completed: 2026-03-05 +--- + +# Phase 7 Plan 2: Defer Library Data Loading Summary + +**Deferred LibraryStore eagerFetch from constructor to DOMContentLoaded event, ensuring app shell renders instantly before 4 backend data roundtrips begin** + +## Performance + +- **Duration:** 1 min +- **Started:** 2026-03-05T01:53:27Z +- **Completed:** 2026-03-05T01:54:49Z +- **Tasks:** 1 +- **Files modified:** 1 + +## Accomplishments +- Removed `eagerFetch()` call from LibraryStore constructor so module evaluation no longer triggers 4 backend roundtrips +- Added `deferEagerFetch()` method that waits for `DOMContentLoaded` event (or calls immediately if DOM already parsed) +- App shell now renders before data fetching competes for resources +- All 4 data types (tracks, albums, artists, genres) still eagerly loaded once DOM is ready +- Post-scan invalidation behavior unchanged — `invalidate()` still calls `eagerFetch()` directly + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Defer eagerFetch from constructor to post-DOM-ready** - `cd98ad6` (perf) + +## Files Created/Modified +- `frontend/src/store/library-store.ts` - Removed eagerFetch from constructor, added deferEagerFetch with DOMContentLoaded listener + +## Decisions Made +- Used `DOMContentLoaded` instead of `load` event — fires earlier (after HTML parsed, before stylesheets/images finish) which minimizes delay in data availability while still deferring past the initial module evaluation. The `load` event would unnecessarily wait for all resources before beginning data fetches. + +## Deviations from Plan + +None - plan executed exactly as written. + +## Issues Encountered +None + +## User Setup Required +None - no external service configuration required. + +## Next Phase Readiness +- Plan 02 complete — deferred library loading implemented +- Plan 01 (lazy module loading) may still be pending +- Frontend data loading is now deferred to post-DOM-ready, providing instant app shell render + +## Self-Check: PASSED + +- [x] `frontend/src/store/library-store.ts` exists +- [x] Commit `cd98ad6` exists in git history + +--- +*Phase: 07-backend-performance* +*Completed: 2026-03-05* diff --git a/backend/queue/queue.go b/backend/queue/queue.go index 5703e13..22b74f6 100644 --- a/backend/queue/queue.go +++ b/backend/queue/queue.go @@ -250,7 +250,7 @@ func (q *Queue) SetQueue( return } - go q.resolveRemainingTracks(gen, filePaths, playingPath) + go q.resolveRemainingTracks(gen, filePaths, playingPath, batchMeta) } // resolveRemainingTracks runs in a goroutine to batch-resolve all tracks @@ -258,12 +258,30 @@ func (q *Queue) SetQueue( // results to avoid overwriting a newer SetQueue call. playingPath is the // file path of the track that is currently playing so the correct // currentIndex can be located in the rebuilt track list. +// phase1Meta contains metadata already resolved in Phase 1; those paths +// are skipped to avoid redundant database lookups. func (q *Queue) resolveRemainingTracks( gen int64, filePaths []string, playingPath string, + phase1Meta map[string]trackMeta, ) { - allMeta := q.lookupTrackMetaBatch(filePaths) + // Exclude paths already resolved in Phase 1. + var unresolvedPaths []string + + for _, fp := range filePaths { + if _, alreadyResolved := phase1Meta[fp]; !alreadyResolved { + unresolvedPaths = append(unresolvedPaths, fp) + } + } + + // Only look up paths that Phase 1 didn't cover. + allMeta := q.lookupTrackMetaBatch(unresolvedPaths) + + // Merge Phase 1 results into the lookup. + for k, v := range phase1Meta { + allMeta[k] = v + } // Check if we have been superseded before acquiring the mutex. if q.setQueueGen.Load() != gen {