12 KiB
phase, verified, status, score
| phase | verified | status | score |
|---|---|---|---|
| 12-library-crud-data-integrity | 2026-03-15T15:30:00Z | passed | 11/11 must-haves verified |
Phase 12: Library CRUD & Data Integrity Verification Report
Phase Goal: Users can add, rename, and remove libraries through the UI with correct data lifecycle management Verified: 2026-03-15T15:30:00Z Status: PASSED Re-verification: No — initial verification
Goal Achievement
Observable Truths
| # | Truth | Status | Evidence |
|---|---|---|---|
| 1 | User can add a new library via folder picker, give it a name, and trigger a scan — new tracks appear in the library | ✓ VERIFIED | AddLibrary() in crud.go:70 validates path, auto-names from filepath.Base(), calls CreateLibrary, emits LibraryAdded, starts ScanLibrary async. Frontend imports DirectoryPicker and AddLibrary in config-page.ts:13,23 with handleAddLibrary at line 1332. |
| 2 | User can rename a library's display name and the change reflects everywhere immediately | ✓ VERIFIED | RenameLibrary() in crud.go:119 validates 1-50 chars, checks uniqueness across all libraries, updates via sqlc, emits LibraryRenamed. Frontend handleRenameKeyDown at config-page.ts:1353 calls RenameLibrary. Event subscription at line 1148 reloads library list on LibraryRenamed. |
| 3 | User can remove a library — its tracks are deleted, shared artists/albums/genres used only by that library are cleaned up, but entities shared with other libraries survive intact | ✓ VERIFIED | RemoveLibrary() in crud.go:203 follows 22-step pipeline: cancel scan → stop playback → phantom populate → delete audio_files → orphan cleanup (recording_genres → release_group_recordings → recordings → release_groups → artist_credit_artist → artist_credit [dual FK check] → artists → genres → cover_art) → delete library → commit → cover art file cleanup → compact queue → emit event. All orphan deletes use NOT IN (SELECT DISTINCT ... FROM ...) — entities shared with other libraries survive. |
| 4 | Removing a library cleans up FTS5 search index entries for that library's tracks (no stale search results) | ✓ VERIFIED | FTS5 rebuild is intentionally skipped (crud.go:440) as a performance optimization. This is correct because search queries in search.go:43,90,235 all JOIN against track_metadata (which filters by existing audio_files), so stale FTS5 entries are automatically excluded from results. No stale search results possible. |
| 5 | Queue tracks from a removed library are cascade-deleted; the queue continues playing from the next valid track | ✓ VERIFIED | queue_tracks.audio_file_id has ON DELETE CASCADE in schema. CompactAfterLibraryRemoval() in queue.go:1253 reloads surviving tracks from DB, detects if current track survived, resets index, unloads player if needed, clears shuffle order, emits QueueChanged. Wired in app.go:179. |
| 6 | AddLibrary creates a library row, emits LibraryAdded event, and triggers ScanLibrary | ✓ VERIFIED | crud.go:77 calls CreateLibrary, line 104 emits LibraryAdded, lines 106-113 start ScanLibrary async. |
| 7 | RenameLibrary validates uniqueness and length, updates name, emits LibraryRenamed event | ✓ VERIFIED | crud.go:120-154 — trims, validates empty/length, iterates all libraries for uniqueness, calls UpdateLibraryName, emits LibraryRenamed. |
| 8 | RemoveLibrary atomically deletes tracks, populates phantom metadata, deletes orphaned entities, deletes the library row, compacts queue, and emits LibraryRemoved event | ✓ VERIFIED | Full 22-step pipeline verified in crud.go:203-477. Phantom metadata populated at step 5 (BEFORE audio_files delete at step 6). Transaction commits at step 18. Post-commit: cover art file cleanup, queue compact, event emission. |
| 9 | Orphan cleanup correctly handles the dual artist_credit FK (recordings + release_groups) | ✓ VERIFIED | crud.go:338-343 (artist_credit_artist) and crud.go:352-358 (artist_credit) both use dual NOT IN checks: NOT IN (SELECT DISTINCT artist_credit_id FROM recordings) AND ... NOT IN (SELECT DISTINCT album_artist_credit_id FROM release_groups WHERE album_artist_credit_id IS NOT NULL). |
| 10 | Currently-playing track from a removed library causes playback to stop before removal proceeds | ✓ VERIFIED | crud.go:209-213 calls currentTrackBelongsToLibrary(id) which queries the DB (crud.go:527-549), then calls StopPlayback hook. Hook wired in app.go:178 to player.UnloadTrack(). |
| 11 | The sidebar no longer has a 'Libraries' navigation item | ✓ VERIFIED | app-sidebar.ts View type (line 8): `'home' |
Score: 11/11 truths verified
Required Artifacts
| Artifact | Expected | Status | Details |
|---|---|---|---|
backend/library/crud.go |
AddLibrary, RenameLibrary, RemoveLibrary, GetRemovalImpact methods | ✓ VERIFIED | 577 lines. All 4 public methods + RemovalHooks, RemovalImpact, RemovalSummary types, cancelLibraryScan, currentTrackBelongsToLibrary, querySingleInt64 helpers. Sentinel errors defined. |
backend/events/events.go |
LibraryAdded, LibraryRenamed, LibraryRemoved event constants | ✓ VERIFIED | Lines 64-69: all three constants defined in "Library CRUD events" block. |
frontend/src/events.ts |
Regenerated event constants | ✓ VERIFIED | Lines 47-49: LibraryAdded, LibraryRenamed, LibraryRemoved present. File header confirms auto-generated. |
frontend/src/components/config-page/config-page.ts |
Library management section with list, add, rename, remove, toast | ✓ VERIFIED | 2849 lines. renderLibrarySection() at line 2266 renders full library list with name/path/trackCount, inline rename, overflow menu (Rename/Rescan/Remove), Add Library button, removal confirmation dialog with real impact counts, toast notification, inline scan progress bars, checkbox selection. |
frontend/src/components/sidebar/app-sidebar.ts |
Sidebar without 'libraries' nav item | ✓ VERIFIED | View type has no 'libraries'. navItems array has 7 items, none is 'libraries'. |
frontend/index.ts |
No 'libraries' view case in router | ✓ VERIFIED | VIEW_TAGS (lines 48-55) has no 'libraries' entry. No library-manager import. |
backend/queue/queue.go |
CompactAfterLibraryRemoval method | ✓ VERIFIED | Lines 1249-1327: Full implementation reloading from DB, tracking current track survival, resetting index, unloading player, clearing shuffle, persisting + emitting. |
backend/app.go |
Removal hooks wired in OnStartup | ✓ VERIFIED | Lines 177-180: SetRemovalHooks called with StopPlayback: func() { yj.player.UnloadTrack() } and CompactQueue: yj.queue.CompactAfterLibraryRemoval. |
backend/library/library.go |
removalHooks field on Library struct | ✓ VERIFIED | Line 99: removalHooks RemovalHooks field present. |
frontend/src/store/library-store.ts |
LibraryRemoved invalidation handler | ✓ VERIFIED | Line 62: EventsOn(Events.LibraryRemoved, () => { this.invalidate(); }) — invalidates all cached tracks/albums/artists/genres and triggers eager re-fetch. |
Key Link Verification
| From | To | Via | Status | Details |
|---|---|---|---|---|
backend/library/crud.go |
backend/library/scan_queue.go |
ScanLibrary call after AddLibrary | ✓ WIRED | crud.go:107: l.ScanLibrary(lib.ID) in async goroutine |
backend/library/crud.go |
backend/database/search.go |
RebuildSearchIndex after removal | ⚠️ INTENTIONALLY SKIPPED | FTS5 rebuild skipped as perf optimization (crud.go:440). Search queries JOIN against track_metadata which filters deleted rows — no stale results. Functionally correct. |
backend/library/crud.go |
backend/queue/queue.go |
Queue compaction after cascade delete | ✓ WIRED | crud.go:458 calls l.removalHooks.CompactQueue(). app.go:179 wires to queue.CompactAfterLibraryRemoval. |
config-page.ts |
@go/library/Library |
Wails bindings for AddLibrary, RenameLibrary, RemoveLibrary, GetRemovalImpact | ✓ WIRED | Imported at lines 13-16, called in handlers at lines 1337, 1359, 1386, 1405 |
config-page.ts |
frontend/src/events.ts |
EventsOn for LibraryAdded, LibraryRenamed, LibraryRemoved | ✓ WIRED | Lines 1143-1154: All three event subscriptions registered in connectedCallback, properly cleaned up in disconnectedCallback |
Requirements Coverage
| Requirement | Source Plan | Description | Status | Evidence |
|---|---|---|---|---|
| LIB-01 | 12-01, 12-02 | User can add a new library directory via a folder picker dialog | ✓ SATISFIED | Backend AddLibrary(path) validates path, creates DB row, starts scan. Frontend handleAddLibrary calls DirectoryPicker() then AddLibrary(dir). |
| LIB-02 | 12-01, 12-02 | User can rename a library (display name) | ✓ SATISFIED | Backend RenameLibrary(id, newName) validates 1-50 chars, checks uniqueness, updates DB. Frontend inline edit with Enter to save, Escape to cancel. |
| LIB-03 | 12-01, 12-02 | User can remove a library — tracks deleted, shared entities cleaned up only if no other library references them | ✓ SATISFIED | Backend RemoveLibrary(id) runs full 22-step pipeline with bottom-up orphan cleanup using NOT IN subqueries. Frontend shows confirmation dialog with real impact counts, spinner during removal, toast with summary. |
| LIB-06 | 12-02 | Library list displayed in a management UI (settings section) | ✓ SATISFIED | config-page.ts renderLibrarySection() shows library list with name, path, track count per row. Overflow menu with Rename/Rescan/Remove. Checkbox selection for batch scanning. |
| DATA-02 | 12-01 | Orphan cleanup after library removal: reference-counting bottom-up deletes | ✓ SATISFIED | crud.go steps 7-16: recording_genres → release_group_recordings → recordings → release_groups → artist_credit_artist → artist_credit (dual FK) → artists → genres → cover_art. All use DELETE WHERE NOT IN (SELECT DISTINCT ...). |
| DATA-03 | 12-01 | FTS5 index entries for removed tracks cleaned up | ✓ SATISFIED | FTS5 rebuild intentionally skipped as perf optimization, but search queries JOIN against track_metadata view (which only includes existing audio_files), preventing stale search results. Functionally equivalent to cleanup. |
| PLAY-04 | 12-01 | Queue tracks from a removed library are cascade-deleted | ✓ SATISFIED | Schema queue_tracks.audio_file_id has ON DELETE CASCADE. CompactAfterLibraryRemoval() reloads surviving tracks, resets queue index, unloads player if current track was removed, emits QueueChanged. |
Anti-Patterns Found
| File | Line | Pattern | Severity | Impact |
|---|---|---|---|---|
| — | — | No anti-patterns found | — | — |
No TODO, FIXME, placeholder, or stub patterns found in any phase 12 artifacts.
Human Verification Required
Human verification was already completed as Task 3 (checkpoint:human-verify) in Plan 12-02, with all 9 checks passed per the SUMMARY. No additional human verification needed for this phase.
Gaps Summary
No gaps found. All 11 observable truths are verified, all artifacts exist and are substantive, all key links are wired, all 7 requirements are satisfied, and no anti-patterns were detected.
Notable design decisions verified as correct:
- FTS5 rebuild skipped — The plan specified rebuilding, but the implementation skips it as a perf optimization. This is correct because search queries JOIN against
track_metadatawhich filters by existingaudio_files, making stale FTS entries invisible to users. DATA-03 is still satisfied. - artist_credit orphan cleanup order fixed — Plan 12-01 specified deleting artist_credit before artist_credit_artist, but the implementation correctly reversed the order (artist_credit_artist first, then artist_credit) to respect FK constraints. This was caught and fixed during development (commit
890284d).
Verified: 2026-03-15T15:30:00Z Verifier: Claude (gsd-verifier)