Files
yellowjacket/.planning/phases/12-library-crud-data-integrity/12-01-SUMMARY.md
T
yonlu 27c773b3d0 docs(12-01): complete library CRUD backend plan
- SUMMARY.md with 2 task commits, 6 files, 6 requirements completed
- STATE.md advanced to Phase 12 Plan 1/2, added Phase 12 decisions
- ROADMAP.md marked 12-01 complete (1/2 plans)
- REQUIREMENTS.md marked LIB-01, LIB-02, LIB-03, DATA-02, DATA-03, PLAY-04 complete
2026-03-12 19:42:41 -04:00

7.3 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established requirements-completed duration completed
12-library-crud-data-integrity 01 library
crud
orphan-cleanup
data-integrity
queue-compaction
phantom-tracks
events
phase provides
11-per-library-scan-pipeline ScanLibrary, ScanAllLibraries, scan queue coordinator
phase provides
10-schema-migration libraries table, library_id FK, phantom columns on playlist_tracks
AddLibrary, RenameLibrary, RemoveLibrary, GetRemovalImpact backend API
Orphan cleanup pipeline (recordings → genres → release_groups → artist_credits → artists → cover_art)
Queue CompactAfterLibraryRemoval method
Phantom metadata population before cascade delete
LibraryAdded, LibraryRenamed, LibraryRemoved events
12-02-frontend-library-ui
13-library-views-phantom-tracks
added patterns
RemovalHooks callback struct — breaks circular dependency between library, player, and queue packages
Bottom-up orphan cleanup in single transaction — reference-counting DELETE WHERE NOT IN subqueries
Pre-populate phantom metadata BEFORE cascade delete — avoids lost join data
querySingleInt64 helper for hand-crafted SQL returning single aggregate values
created modified
backend/library/crud.go
backend/library/library.go
backend/events/events.go
frontend/src/events.ts
backend/queue/queue.go
backend/app.go
Application-level name uniqueness check (iterate GetAllLibraries) rather than DB UNIQUE constraint — avoids migration 7
RemovalHooks struct pattern (StopPlayback + CompactQueue callbacks) wired in app.go — mirrors existing RescanHooks pattern
querySingleInt64 helper wraps DB.QueryContext returning *sql.Rows since DB has no QueryRowContext method
Sentinel errors for all validation (errLibraryNameEmpty, errLibraryNameTooLong, errLibraryNameDuplicate, errLibraryPathNotExist) per err113 linter rule
Context parameter placed first in querySingleInt64 per revive context-as-argument rule
RemovalHooks callback struct for cross-package lifecycle coordination
querySingleInt64 for hand-crafted aggregate SQL queries
LIB-01
LIB-02
LIB-03
DATA-02
DATA-03
PLAY-04
6min 2026-03-12

Phase 12 Plan 01: Library CRUD Backend API Summary

Backend CRUD API with AddLibrary/RenameLibrary/RemoveLibrary, full orphan cleanup pipeline, phantom track preservation, FTS5 rebuild, queue compaction, and event emission

Performance

  • Duration: 6 min
  • Started: 2026-03-12T23:32:27Z
  • Completed: 2026-03-12T23:38:30Z
  • Tasks: 2
  • Files created: 1
  • Files modified: 5

Accomplishments

  • AddLibrary(path) — validates path exists, auto-names from folder base, creates DB row via sqlc, emits LibraryAdded, starts async ScanLibrary
  • RenameLibrary(id, newName) — validates 1-50 char length, checks name uniqueness across all libraries (application-level), updates via sqlc, emits LibraryRenamed
  • GetRemovalImpact(libraryID) — read-only queries returning track count, affected playlists count, queue items count for confirmation dialog
  • RemoveLibrary(id) — the critical 23-step method:
    1. Cancel active scan for library
    2. Stop playback if current track belongs to library
    3. Pre-count metrics for summary
    4. Begin transaction
    5. Populate phantom metadata on playlist_tracks (BEFORE cascade delete)
    6. DELETE audio_files WHERE library_id (CASCADE on queue_tracks, SET NULL on playlist_tracks)
    7. Bottom-up orphan cleanup: recordings → recording_genres → release_group_recordings → release_groups → artist_credits (dual FK check) → artist_credit_artists → artists → genres → cover_art
    8. DELETE library row
    9. Commit transaction
    10. Post-commit: RebuildSearchIndex (FTS5), delete cover art files, CompactQueue, emit events
  • CompactAfterLibraryRemoval() on Queue — reloads surviving tracks from DB, detects if current track survived, resets index, unloads player if needed, clears shuffle order, emits QueueChanged
  • RemovalHooks wired in app.go: StopPlayback → player.UnloadTrack(), CompactQueue → queue.CompactAfterLibraryRemoval()
  • Three new event constants: LibraryAdded, LibraryRenamed, LibraryRemoved — auto-generated to frontend events.ts

Task Commits

Each task was committed atomically:

  1. Task 1: Implement Library CRUD methods and orphan cleanup pipelinebd44f83 (feat)
    • Created backend/library/crud.go (525 lines) with all CRUD methods
    • Added 3 event constants to backend/events/events.go
    • Added removalHooks field to Library struct
    • Regenerated frontend/src/events.ts
  2. Task 2: Add queue compaction method and wire removal hooks in app.go5995dfd (feat)
    • Added CompactAfterLibraryRemoval() to backend/queue/queue.go (80 lines)
    • Wired RemovalHooks in backend/app.go OnStartup

Files Created/Modified

  • backend/library/crud.go (NEW) — AddLibrary, RenameLibrary, RemoveLibrary, GetRemovalImpact, cancelLibraryScan, currentTrackBelongsToLibrary, querySingleInt64, RemovalHooks type, sentinel errors
  • backend/library/library.go — Added removalHooks RemovalHooks field to Library struct
  • backend/events/events.go — Added LibraryAdded, LibraryRenamed, LibraryRemoved constants
  • frontend/src/events.ts — Regenerated with new library CRUD event constants
  • backend/queue/queue.go — Added CompactAfterLibraryRemoval method
  • backend/app.go — Wired RemovalHooks in OnStartup (StopPlayback + CompactQueue callbacks)

Decisions Made

  • Application-level name uniqueness: Iterate GetAllLibraries to check for duplicate names rather than adding a UNIQUE constraint to the libraries table. Avoids needing migration 7; the check is only done during rename which is infrequent.
  • RemovalHooks callback struct: Follows the existing RescanHooks pattern to break circular dependencies between library → player and library → queue packages. Wired in app.go where all subsystems are accessible.
  • querySingleInt64 helper: The project's database.DB type exposes QueryContext returning *sql.Rows but no QueryRowContext. The helper wraps the full scan-close cycle for single-value aggregate queries.
  • Sentinel errors per err113: Defined errLibraryNameEmpty, errLibraryNameTooLong, errLibraryNameDuplicate, errLibraryPathNotExist as package-level vars to satisfy the golangci-lint err113 rule.
  • Context-first parameter order: querySingleInt64(ctx, db, query, args...) follows revive linter's context-as-argument rule.

Deviations from Plan

None — plan executed exactly as written.

Issues Encountered

None.

User Setup Required

None — no external service configuration required.

Next Plan Readiness

  • Plan 12-01 complete — backend CRUD API fully implemented
  • Ready for Plan 12-02: Frontend library management UI in settings + sidebar cleanup
  • All Wails-bindable methods (AddLibrary, RenameLibrary, RemoveLibrary, GetRemovalImpact) are available for frontend consumption
  • Events (LibraryAdded, LibraryRenamed, LibraryRemoved) are defined for frontend reactive updates

Self-Check: PASSED

All files verified present, all commits verified in git log.


Phase: 12-library-crud-data-integrity Completed: 2026-03-12