- Created 14-02-SUMMARY.md with execution results
- Updated STATE.md with Phase 14 position and decisions
- Updated ROADMAP.md progress for Phase 14 (1/4 plans)
- contain: layout style on :host of all 6 scroll-heavy components
- contain: paint + will-change: transform on all scroll containers for GPU compositing
- content-visibility: auto + contain-intrinsic-size on .album-card for off-screen skip
- cover-grid, track-list, queue-panel, artists-view, genres-view, playlist-view
- Primary views (tracks, albums, artists, genres, playlists, settings) created once and kept in DOM
- Navigation toggles display:none/display:'' instead of destroying/recreating components
- viewCache Map bounded to 6 entries — no memory leaks
- Detail views (artist-details, playlist-details, genre-details) remain ephemeral
- Scroll positions naturally preserved by keeping DOM alive
- No virtualizer reinit, no data refetch, no image reload on navigation
- contain: layout style on .content-area to isolate main+queue from header/sidebar
- contain: strict on .main-panel for maximum layout isolation
- contain: layout style paint on .main-panel > * for paint containment per view
- contain: layout style paint on sidebar to isolate from main panel
- contain: layout style on .bottom-bar to isolate footer from content reflows
Sections start collapsed showing only heading, description, and a
chevron icon. Click to expand and reveal the fields. The open property
allows sections to start expanded if needed.
Progress bar now renders below the library row being scanned, with
a small status label (phase + percent) after the library name. Removed
the separate status-bar progress display.
Add Library now sits in the same row as Scan and Full Rescan, sharing
the same btn-primary style and size. Removed the standalone button
below the library list.
Scan and Full Rescan buttons now sit above the library list for easier
access. Libraries start unchecked — both buttons are disabled until at
least one library is selected.
Go's error interface has no exported fields, so json.Marshal produced
{} for the error message. Converting to string at the addWarning call
site preserves the actual error text in the JSON payload.
Contentless FTS5 cannot delete individual rows, but stale entries are
filtered out by the JOIN against track_metadata in search queries.
The index is rebuilt on the next full rescan. Removes ~10s of overhead
for a 25K-track library removal.
artist_credit_artist.credit_id references artist_credit.id, so the
child table must be cleaned before the parent. Same FK ordering fix
as the earlier recording_genres/recordings swap.
getLibraryRoot() returned "" after DirectoryPath was removed from
config.toml during multi-library migration. M3U8 relative paths could
not be resolved to absolute paths, causing every playlist track to
appear as phantom. Now falls back to the first library path from the
database when the legacy config field is empty.
Tracks from the pre-multi-library schema have library_id=0 and aren't
counted by CountAudioFilesByLibrary, causing a permanent count mismatch
that triggers a full scan on every launch. SoftScanAllLibraries now
claims matching orphans before comparing counts.
SoftScanAllLibraries compares audio file count on disk vs DB track count
per library. Unchanged libraries are silently skipped (no progress bar,
no events). Only libraries where files were added or removed since the
last scan get queued for a full scan.
recording_genres and release_group_recordings reference recordings.id,
so they must be deleted BEFORE the recordings table is cleaned.
Also extends toast duration to 8s for readability.
RemoveLibrary now polls until the cancelled scan goroutine finishes
before proceeding with the removal transaction. Also show removal
errors as toast messages instead of only logging to console, and
explicitly reload library list after successful removal.
Tracks from pre-multi-library schema have library_id=0 (default).
When AddLibrary creates a new library row, UPDATE orphaned tracks
whose file_path falls under the library's directory to use the new
library_id. This prevents the scan from hitting UNIQUE constraints
on every file and ensures track counts are correct immediately.
- Add checkboxes to library list with select-all header
- Soft Scan operates on selected libraries (queues each individually)
- Full Rescan stays global (nukes all data, rescans all libraries)
- Remove redundant 'Scan All Libraries' button
- Fix FullRescan Go backend to scan all libraries after wipe, not just first
Files that fail to save (e.g. UNIQUE constraint from pre-existing
tracks with a different library_id) were not counted in any progress
counter, leaving the progress bar stuck at 0%. Now increments skipped
so processed = added + skipped + updated reflects all files visited.
Scan() wrapper was deleted in Phase 11 but config-page.ts and
library-manager.ts still imported it. Use ScanAllLibraries() for
soft scan since multi-library model scans all libraries.
- Remove 'libraries' from View type union in app-sidebar.ts
- Remove Libraries nav item from sidebar navigation list
- Remove case 'libraries' view routing in index.ts
- Remove library-manager component import from index.ts
- Add CompactAfterLibraryRemoval on Queue: reloads surviving tracks from DB, resets currentIndex, unloads player if current track was removed, clears shuffle order
- Wire RemovalHooks in app.go OnStartup: StopPlayback → player.UnloadTrack, CompactQueue → queue.CompactAfterLibraryRemoval
- All existing tests pass with no regressions
- Add libraryId, libraryName, queuedCount to ScanProgress interface
- Replace CancelScan import with CancelCurrentScan, CancelAllScans
- Add ScanAllLibraries import and Scan All Libraries button
- Show library name in progress label (Scanning: [Library Name])
- Show queue count below progress bar when libraries queued
- Cancel dialog shows scope choice (Cancel This Library / Cancel All) when queue > 0
- Subscribe to LibraryScanQueued and LibraryScanQueueDrained events
- Track scanQueuedCount state for queue-aware UI behavior
Task 1: Schema, events, and progress types
- Add library_id to CreateAudioFile SQL INSERT and regenerate sqlc code
- Add LibraryScanQueued and LibraryScanQueueDrained event constants
- Regenerate TypeScript events via genevents
- Add LibraryID, LibraryName, QueuedCount to ScanProgress
- Add LibraryID, LibraryName to ScanMetrics
- Add libraryID field to importResult for threading through pipeline
Task 2: Scan queue coordinator and per-library scanning
- Create scan_queue.go with ScanLibrary(id), ScanAllLibraries()
- Add CancelCurrentScan(), CancelAllScans() for queue-aware cancellation
- FIFO scan queue with silent dedup (same library already scanning or queued)
- Refactor Scan() -> scanInternal(libraryID, libraryName, libraryPath)
- Replace GetAllAudioFiles with GetAudioFilesByLibrary for per-library loading
- Thread libraryID through DB writer to set CreateAudioFileParams.LibraryID
- drainQueue auto-starts next queued library or emits LibraryScanQueueDrained
- Pause freezes current scan AND queue
- Add GetScanQueueLength() and QueuedLibraryNames() for UI
- Mark CancelScan() and Scan() as deprecated
On existing databases, CREATE TABLE IF NOT EXISTS audio_files is a no-op
but the standalone CREATE INDEX on library_id would fail because the
column doesn't exist until migration 6 runs. The migration already
creates this index, so removing it from the schema file is correct.