Files
yellowjacket/.planning/STATE.md
T

184 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
gsd_state_version: 1.0
milestone: v1.1
milestone_name: Multi-Library Support
status: unknown
last_updated: "2026-03-15T12:11:53.757Z"
progress:
total_phases: 5
completed_phases: 4
total_plans: 16
completed_plans: 15
---
# YellowJacket — Project State
## Project Reference
See: .planning/PROJECT.md (updated 2026-03-08)
**Core value:** The music player works reliably and feels solid — every interaction is correct, responsive, and trustworthy.
**Current focus:** v1.1 Multi-Library Support + Performance Optimization
## Current Position
Phase: 14 — Performance Optimization
Plan: 4 of 4 in Phase (Plans 01, 02, 03 complete; 04 awaiting checkpoint)
Status: Plan 14-03 complete — render & store optimization; 14-04 awaiting checkpoint
Progress: ████████████████████ Phase 14 in progress (3/4 plans complete, 1 checkpoint pending)
Last activity: 2026-03-14 — Completed 14-03 render & store optimization
### Phase Overview
| Phase | Status |
|-------|--------|
| 9. Scan Cancellation & Keyboard Shortcuts | Complete (5/5 plans) ✅ |
| 10. Schema & Migration | Complete (2/2 plans) ✅ |
| 11. Per-Library Scan Pipeline | Complete (3/3 plans) ✅ |
| 12. Library CRUD & Data Integrity | In progress (1/2 plans) |
| 13. Library Views & Phantom Tracks | Not started |
| 14. Performance Optimization | In progress (4/4 plans, checkpoint pending) |
## Performance Metrics
**v1.0 baseline:** 8 phases, 17 plans, 34 tasks in 6 days (107 commits)
**v1.1 scope:** Phase 9 complete (5 plans), Phases 10-13 pending (20 requirements across 4 phases)
| Phase | Plan | Duration | Tasks | Files |
|-------|------|----------|-------|-------|
| 09-01 | scan control backend | 16 min | 2 | 5 |
| 09-02 | keyboard shortcuts config & service | 35 min | 2 | 12 |
| 09-04 | keyboard shortcuts settings UI | 5 min | 2 | 2 |
| 09-03 | scan control UI | 2 min | 1 | 3 |
| 09-05 | integration testing & verification | 3 min | 2 | 1 |
| Phase 10-01 P01 | 11 min | 2 tasks | 10 files |
| Phase 10-02 P02 | 5 min | 2 tasks | 9 files |
| Phase 11-01 P01 | 7 min | 2 tasks | 11 files |
| Phase 11-03 P03 | 10 min | 1 task | 3 files |
| Phase 11-02 P02 | 4 min | 2 tasks | 2 files |
| Phase 12-01 P01 | 6 min | 2 tasks | 6 files |
| Phase 14-02 P02 | 1 min | 1 task | 1 file |
| Phase 14-03 P03 | 4 min | 2 tasks | 5 files |
| Phase 14-01 P01 | 3 min | 2 tasks | 7 files |
| Phase 14-04 P04 | 2 min | 2 tasks | 3 files |
## Accumulated Context
### Key Decisions
Decisions from v1.0 are archived in PROJECT.md Key Decisions table. Key patterns to carry forward:
- Mutex-protected setter pattern (lock → write → release → callbacks)
- SAFETY comment convention for hand-crafted SQL
- AST-based codegen for cross-language constant sync
- Design tokens via `:host` scoped CSS custom properties
- queueMicrotask coalescing for store notifications
- `.renderItem` + `.keyFunction` (not `repeat()` children) for lit-virtualizer
### v1.1 Roadmap Decisions
| Decision | Rationale |
|----------|-----------|
| Phase 9 = Scan Cancel + Shortcuts | Quick wins, validate context cancellation and config extension patterns |
| v1.1 restructured for multi-library | Tag editing, smart playlists, gapless, MusicBrainz, layout, plugins deferred to future milestones |
| Hybrid model (library_id on audio_files only) | Physical files belong to libraries; logical entities (artists, albums, genres) are global/shared |
| Libraries in DB, not TOML | CRUD through UI shouldn't require TOML manipulation; DB is source of truth |
| SET NULL for playlist_tracks FK | Phantom tracks preserve playlist structure when library removed |
| CASCADE for queue_tracks FK | Queue is ephemeral, not user-curated like playlists |
| Sequential scanning | SQLite single-writer makes parallel scans pointless |
| Backend filtering, not frontend | Don't load 150K tracks when viewing one library |
| 4 multi-library phases (10-13) | Natural delivery boundaries: schema → scan → CRUD → views, each phase delivers verifiable capability |
### Phase 10 Decisions
| Decision | Rationale |
|----------|-----------|
| Underscore prefix `_libraries.sql` for schema ordering | Go embed.FS ReadDir sorts alphabetically; `_` < `a` ensures libraries table created before audio_files FK |
| Sentinel library id=0 in test DB | Existing tests use DEFAULT library_id=0; sentinel row satisfies FK without modifying every test |
| TOML cleanup via generic map[string]any | Preserves all config sections when removing only DirectoryPath; no dependency on full Config struct |
| sql.NullInt64 for nullable playlist_tracks.audio_file_id | Phantom tracks have NULL audio_file_id; generated sqlc code requires sql.Null types |
| COALESCE fallback chain: live → phantom → empty | Playlist queries use 3-level COALESCE so callers always get usable string values |
| is_phantom computed column via CASE WHEN | Eliminates null-checking logic in callers; simple int64 boolean (0/1) |
### Phase 11 Decisions
| Decision | Rationale |
|----------|-----------|
| Library ID threaded via importResult | Explicit data flow through scan pipeline, avoids mutating shared Library struct state |
| scanInternal returns *ScanMetrics only | Called from goroutine in scan queue, error return impractical; errors logged + accumulated in Warnings |
| Auto worker count per library path | Each library may be on different storage (SSD/HDD), auto-detect per scan |
| Backward-compatible Scan() wrapper | Keeps handleConfigUpdate and FullRescan working without changes |
| Queue-aware cancel dialog scope choice | Two-option "Cancel This Library / Cancel All" only when queuedCount > 0; single-scan keeps existing pattern |
| handleScanComplete defers reset when queue draining | Prevents premature scanning=false before next library starts |
| FullRescan uses first library from GetAllLibraries | Per-library rescan deferred to Phase 12; preserves backward compatibility for config-page "Rescan" button |
| LibraryConfigChanged handler removed entirely | Multi-library model uses CRUD API (Phase 12), not event-driven config updates |
| Scan() wrapper deleted | Only callers were handleConfigUpdate (deleted) and FullRescan (updated to scanInternal) |
### Phase 12 Decisions
| Decision | Rationale |
|----------|-----------|
| Application-level name uniqueness check (iterate GetAllLibraries) | Avoids migration 7; rename is infrequent, check is simple |
| RemovalHooks callback struct (StopPlayback + CompactQueue) | Mirrors RescanHooks pattern; breaks circular dependency between library, player, queue packages |
| querySingleInt64 helper for hand-crafted SQL aggregates | DB type has QueryContext (returns *sql.Rows) but no QueryRowContext; helper wraps scan-close cycle |
| Sentinel errors for all validation per err113 | errLibraryNameEmpty, errLibraryNameTooLong, errLibraryNameDuplicate, errLibraryPathNotExist |
| Pre-populate phantom metadata BEFORE cascade delete | Avoids lost join data — playlist_tracks need track metadata after audio_files rows are gone |
### Phase 14 Decisions
| Decision | Rationale |
|----------|-----------|
| Primary views cached, detail views ephemeral | Detail views depend on entity IDs that change; caching would show stale content |
| Inline style.display toggle over CSS class | Simpler, no specificity issues, empty string restores natural display value |
| viewCache bounded at 6 entries | One per primary view — negligible memory since data is already in store caches |
| contain: strict only on .main-panel | Has explicit dimensions (flex: 1, overflow: hidden); elsewhere use layout style to not break flex |
| will-change: transform only on scroll containers | Not on :host — avoids wasting GPU memory on non-scrolling elements |
| content-visibility: auto on album cards with contain-intrinsic-size | Prevents layout shift during scroll while skipping off-screen rendering |
| Event delegation via data-index + closest() for virtualizer items | Zero per-item closures in renderItem; all events delegated on virtualizer element |
| changeGeneration counter in library store | Simpler than typed subscriptions; skips requestUpdate on loading-only transitions |
| RAF throttle over debounce for scroll saves | Saves position once per frame during scrolling, not just after stop; prevents lost positions on quick navigation |
| Keep monkey-patch alongside overflow-anchor CSS | CSS overflow-anchor disables browser anchoring but not lit-virtualizer's internal _correctScrollError |
### Warnings (carry forward)
- Player lock ordering (`p.mu` before `speaker.Lock()`, goroutine dispatch in beep callback) — carry forward
- modernc.org/libc version must match exactly when updating modernc.org/sqlite
- `@lit-labs/signals` is experimental (v0.2.0) — not blocking but noted
- Scan cancellation: skip orphan cleanup on cancelled scans — Phase 9 ✅ (implemented in 09-01)
- Volume mutations (ChangeVolume/MuteToggle) must emit events + persist state — Phase 9 ✅ (fixed in 09-05)
- ALTER TABLE ADD COLUMN requires DEFAULT for NOT NULL — create libraries table first
- Table rebuild must audit ALL CASCADE FKs (playlist_tracks AND queue_tracks)
- FTS5 contentless can't DELETE rows — stale entries accumulate after library removal; consider contentless_delete migration
- Orphan cleanup must not delete shared entities across libraries (reference-counting bottom-up)
- Existing user migration must be seamless (TOML DirectoryPath to DB libraries table)
### Deferred Improvements
- **Bulk phantom matching performance** — `FindPhantomMatches` runs 3 sequential DB queries per phantom track (basename search, FTS filename, FTS keywords). With hundreds of phantoms this is O(n×3) round trips. Could batch the basename query (WHERE basename IN (...)), pre-load FTS results, or parallelize with goroutines. Not urgent now that the false-phantom bug is fixed (tracks appeared phantom due to empty library root, not actual missing files). Revisit if users import large playlists from external sources with genuinely unresolved paths.
### Research Flags
- **Multi-library research complete** — see `.planning/research/` (STACK.md, FEATURES.md, ARCHITECTURE.md, PITFALLS.md, SUMMARY.md)
## Session Continuity
### Last Session
**Date:** 2026-03-14
**What happened:** Executed Phase 14, Plan 03 — event delegation eliminates per-scroll-frame closures, queueMicrotask batching for queue store, changeGeneration counter for library store. Plan 04 Tasks 1-2 were completed earlier, awaiting checkpoint.
**Where we stopped:** Completed 14-03-PLAN.md — Plans 01, 02, 03 complete; 14-04 awaiting human-verify checkpoint
**Next action:** User verifies scroll smoothness and navigation speed across all views (14-04 Task 3 checkpoint)
---
*State initialized: 2026-02-27*
### Quick Tasks Completed
| # | Description | Date | Commit | Directory |
|---|-------------|------|--------|-----------|
| 16 | add ctrl+a hotkey to multi-select views to select all items | 2026-03-07 | 043c74c | [16-add-ctrl-a-hotkey-to-multi-select-views-](./quick/16-add-ctrl-a-hotkey-to-multi-select-views-/) |
| 17 | refactor playlist view to use subpages | 2026-03-08 | 955cd68 | [17-refactor-playlist-view-to-use-subpages-l](./quick/17-refactor-playlist-view-to-use-subpages-l/) |
| 18 | add multi-column metadata display to playlist-details | 2026-03-08 | ce23177 | [18-add-multi-column-metadata-display-to-pla](./quick/18-add-multi-column-metadata-display-to-pla/) |
Last activity: 2026-03-08 - Completed quick task 18: add multi-column metadata display to playlist-details
*Last updated: 2026-03-14 — Completed 14-03-PLAN.md (Phase 14 Plan 03 complete)*