diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 1ecd314..5496a62 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -78,9 +78,9 @@ Plans: 4. Existing cancel and pause/resume controls work correctly for per-library scans — cancelling one library's scan doesn't affect others **Plans:** 3/3 plans complete Plans: -- [ ] 11-01-PLAN.md — Backend scan queue coordinator, per-library scan methods, CreateAudioFile with library_id -- [ ] 11-02-PLAN.md — Frontend progress UI with library name, cancel scope modal, Scan All button -- [ ] 11-03-PLAN.md — App startup auto-scan wiring, legacy single-directory cleanup +- [x] 11-01-PLAN.md — Backend scan queue coordinator, per-library scan methods, CreateAudioFile with library_id +- [x] 11-02-PLAN.md — Frontend progress UI with library name, cancel scope modal, Scan All button +- [x] 11-03-PLAN.md — App startup auto-scan wiring, legacy single-directory cleanup ### Phase 12: Library CRUD & Data Integrity **Goal:** Users can add, rename, and remove libraries through the UI with correct data lifecycle management @@ -120,7 +120,7 @@ Plans: | 8. Frontend Performance & UX | v1.0 | 4/4 | Complete | 2026-03-05 | | 9. Scan Cancellation & Keyboard Shortcuts | v1.1 | 5/5 | Complete | 2026-03-07 | | 10. Schema & Migration | 2/2 | Complete | 2026-03-09 | - | -| 11. Per-Library Scan Pipeline | 3/3 | Complete | 2026-03-09 | - | +| 11. Per-Library Scan Pipeline | 3/3 | Complete | 2026-03-09 | - | | 12. Library CRUD & Data Integrity | v1.1 | 0/? | Not started | - | | 13. Library Views & Phantom Tracks | v1.1 | 0/? | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index 1c352d1..7280f16 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,12 +3,12 @@ gsd_state_version: 1.0 milestone: v1.1 milestone_name: Multi-Library Support status: unknown -last_updated: "2026-03-09T20:11:36Z" +last_updated: "2026-03-09T20:23:44.728Z" progress: - total_phases: 2 - completed_phases: 2 - total_plans: 9 - completed_plans: 9 + total_phases: 3 + completed_phases: 3 + total_plans: 10 + completed_plans: 10 --- # YellowJacket — Project State diff --git a/.planning/phases/11-per-library-scan-pipeline/11-VERIFICATION.md b/.planning/phases/11-per-library-scan-pipeline/11-VERIFICATION.md new file mode 100644 index 0000000..4c6016f --- /dev/null +++ b/.planning/phases/11-per-library-scan-pipeline/11-VERIFICATION.md @@ -0,0 +1,112 @@ +--- +phase: 11-per-library-scan-pipeline +verified: 2026-03-09T20:30:00Z +status: passed +score: 12/12 must-haves verified +--- + +# Phase 11: Per-Library Scan Pipeline Verification Report + +**Phase Goal:** Users can scan individual libraries independently with proper sequential coordination +**Verified:** 2026-03-09T20:30:00Z +**Status:** passed +**Re-verification:** No — initial verification + +## Goal Achievement + +### Observable Truths + +| # | Truth | Status | Evidence | +|---|-------|--------|----------| +| 1 | ScanLibrary(id) scans only the directory associated with that library ID | ✓ VERIFIED | `scan_queue.go:22-69` — `ScanLibrary` queries `GetLibrary(id)` from DB, passes `lib.Path` to `scanInternal()` | +| 2 | Only one library scans at a time — additional requests are silently queued | ✓ VERIFIED | `scan_queue.go:49-66` — if `scanActive`, appends to `scanQueue`, emits `LibraryScanQueued` | +| 3 | Duplicate scan requests for the same library are silently ignored | ✓ VERIFIED | `scan_queue.go:31-41` — checks `currentScanLibraryID` and iterates `scanQueue` for dedup | +| 4 | Cancel/pause/resume work per-library — cancelling one library starts the next queued | ✓ VERIFIED | `scan_queue.go:96-117` — `CancelCurrentScan()` cancels context, `drainQueue()` at line 152 pops next; `CancelAllScans()` clears queue first | +| 5 | Pausing freezes both the current scan AND the queue | ✓ VERIFIED | `scan_control.go:28-40` — `PauseScan` sets `scanPaused=true`, creates blocking channel. `drainQueue` only runs after `scanInternal` returns, which blocks on pause. | +| 6 | ScanAllLibraries queries all libraries and queues them sequentially | ✓ VERIFIED | `scan_queue.go:73-91` — queries `GetAllLibraries`, iterates calling `ScanLibrary(lib.ID)` | +| 7 | Progress UI shows which library is currently being scanned by name | ✓ VERIFIED | `config-page.ts:2173-2214` and `library-manager.ts:931-972` — both render `Scanning: ${p.libraryName}` in progress labels | +| 8 | Progress UI shows queue count when libraries are queued | ✓ VERIFIED | `config-page.ts:2185-2191,2257-2263` and `library-manager.ts:943-949,1014-1020` — render `${p.queuedCount} libraries queued` | +| 9 | Cancel during queued multi-scan shows modal with 'Cancel This Library' and 'Cancel All Scanning' choices | ✓ VERIFIED | `config-page.ts:2050-2097` — when `scanQueuedCount > 0`, renders two-button dialog: "Cancel This Library" (`btn-warning`, calls `CancelCurrentScan`) and "Cancel All Scanning" (`btn-danger`, calls `CancelAllScans`) | +| 10 | Cancelling one library automatically starts scanning the next queued library | ✓ VERIFIED | `scan_queue.go:152-173` — `drainQueue()` pops next entry and calls `startScan` in goroutine | +| 11 | Scan All Libraries button exists and triggers ScanAllLibraries binding | ✓ VERIFIED | `config-page.ts:1997-2002` — "Scan All Libraries" button with `btn-primary`, calls `handleScanAll → ScanAllLibraries()`. Also `library-manager.ts:1291-1298` — identical button | +| 12 | App auto-scans all libraries on launch using ScanAllLibraries | ✓ VERIFIED | `app.go:273-277` — goroutine in `OnDomReady` calls `yj.library.ScanAllLibraries()` | + +**Score:** 12/12 truths verified + +### Required Artifacts + +| Artifact | Expected | Status | Details | +|----------|----------|--------|---------| +| `backend/library/scan_queue.go` | Scan queue coordinator | ✓ VERIFIED | 174 lines. Exports: `ScanLibrary`, `ScanAllLibraries`, `CancelCurrentScan`, `CancelAllScans`, `GetScanQueueLength`, `QueuedLibraryNames`. Internal: `startScan`, `drainQueue`, `scanQueueEntry` | +| `backend/library/library.go` | Updated scan pipeline with `scanInternal` | ✓ VERIFIED | 1534 lines. `scanInternal(libraryID, libraryName, libraryPath)` uses `GetAudioFilesByLibrary(ctx, libraryID)` for per-library file loading, threads `libraryID` through `importResult`. `mkProgress` closure includes library identification. | +| `backend/library/scan_control.go` | Deprecated CancelScan, per-library controls | ✓ VERIFIED | 92 lines. `CancelScan()` deprecated with doc comment pointing to queue-aware methods. `PauseScan`/`ResumeScan`/`IsScanActive`/`IsScanPaused` unchanged. | +| `backend/library/metrics.go` | Library identification in ScanProgress/ScanMetrics | ✓ VERIFIED | `ScanProgress` has `LibraryID`, `LibraryName`, `QueuedCount`. `ScanMetrics` has `LibraryID`, `LibraryName`. | +| `backend/events/events.go` | Scan queue event constants | ✓ VERIFIED | `LibraryScanQueued` and `LibraryScanQueueDrained` constants present | +| `frontend/src/events.ts` | Regenerated TypeScript events | ✓ VERIFIED | Generated file includes `LibraryScanQueued` and `LibraryScanQueueDrained` | +| `backend/database/sql/queries/audio_files.sql` | CreateAudioFile with library_id | ✓ VERIFIED | INSERT includes `library_id` as 11th parameter | +| `backend/database/sql/sqlcgen/audio_files.sql.go` | Generated CreateAudioFileParams with LibraryID | ✓ VERIFIED | `CreateAudioFileParams` includes `LibraryID int64` field | +| `frontend/src/components/config-page/config-page.ts` | Cancel dialog with scope, progress with library name | ✓ VERIFIED | 2425 lines. ScanProgress interface with `libraryId`, `libraryName`, `queuedCount`. Queue-aware cancel dialog renders when `scanQueuedCount > 0`. | +| `frontend/src/components/library-manager/library-manager.ts` | Scan All button, per-library progress | ✓ VERIFIED | 1337 lines. Imports `ScanAllLibraries`, renders "Scan All Libraries" button, progress shows library name and queue count. | +| `frontend/wailsjs/go/library/Library.d.ts` | TypeScript declarations for new methods | ✓ VERIFIED | Declares `ScanLibrary`, `ScanAllLibraries`, `CancelCurrentScan`, `CancelAllScans`, `GetScanQueueLength`, `QueuedLibraryNames` | +| `frontend/wailsjs/go/library/Library.js` | Runtime implementations for new methods | ✓ VERIFIED | All 6 new methods implemented with correct `window['go']` paths | +| `backend/app.go` | Auto-scan on startup via ScanAllLibraries | ✓ VERIFIED | `OnDomReady` goroutine calls `yj.library.ScanAllLibraries()` | +| `backend/library/rescan.go` | FullRescan using DB-sourced library | ✓ VERIFIED | `FullRescan()` queries `GetAllLibraries()`, uses `libs[0]`, calls `scanInternal(lib.ID, lib.Name, lib.Path)` | + +### Key Link Verification + +| From | To | Via | Status | Details | +|------|----|-----|--------|---------| +| `scan_queue.go` | `library.go` | `scanQueue calls scanInternal` | ✓ WIRED | `startScan` at line 145 calls `l.scanInternal(entry.libraryID, entry.libraryName, entry.libraryPath)` | +| `scan_queue.go` | `sqlcgen/libraries.sql.go` | `GetLibrary query` | ✓ WIRED | `ScanLibrary` at line 23 calls `l.db.Queries.GetLibrary(l.ctx, id)` | +| `library.go` | `sqlcgen/audio_files.sql.go` | `CreateAudioFile with LibraryID` | ✓ WIRED | `saveAudioFile` at line 955 sets `LibraryID: result.libraryID` in `CreateAudioFileParams` | +| `config-page.ts` | `@go/library/Library` | `CancelCurrentScan/CancelAllScans` | ✓ WIRED | Lines 8-9 import `CancelCurrentScan, CancelAllScans`. Used in handlers at lines 1052, 1058, 1066 | +| `library-manager.ts` | `@go/library/Library` | `ScanAllLibraries` | ✓ WIRED | Line 7 imports `ScanAllLibraries`. Called in `handleScanAll` at line 801 | +| `app.go` | `scan_queue.go` | `ScanAllLibraries on startup` | ✓ WIRED | Line 274 calls `yj.library.ScanAllLibraries()` in goroutine | + +### Requirements Coverage + +| Requirement | Source Plan | Description | Status | Evidence | +|-------------|------------|-------------|--------|----------| +| LSCAN-01 | 11-01, 11-03 | User can trigger a scan for a specific library (not all-or-nothing) | ✓ SATISFIED | `ScanLibrary(id)` resolves library from DB, scans that directory only. `ScanAllLibraries()` queues all. Both Wails-bound. | +| LSCAN-02 | 11-01, 11-03 | Scanning is sequential — only one library scans at a time (SQLite single-writer) | ✓ SATISFIED | `scanQueue` + `scanActive` mutex ensures one-at-a-time. `drainQueue()` pops next after current completes. | +| LSCAN-03 | 11-02 | Scan progress UI shows which library is being scanned | ✓ SATISFIED | Both `config-page.ts` and `library-manager.ts` show `Scanning: [Library Name]` in progress, plus queue count. | +| LSCAN-04 | 11-01, 11-02 | Existing scan cancellation and pause/resume work per-library | ✓ SATISFIED | `CancelCurrentScan()` cancels current, next starts automatically. `CancelAllScans()` clears queue. Pause freezes current + queue. Cancel dialog offers scope choice when queued. | + +### Anti-Patterns Found + +| File | Line | Pattern | Severity | Impact | +|------|------|---------|----------|--------| +| — | — | No TODOs, FIXMEs, placeholders, or empty implementations found | — | — | + +**Note:** The Wails-generated bindings (`Library.d.ts`, `Library.js`) still include a `Scan()` method stub even though the Go method was deleted. This is a stale binding — calling it from the frontend would fail at runtime. However, the `config-page.ts` and `library-manager.ts` still import and call `Scan()` from their soft scan handlers (`handleSoftScan`). This is a pre-existing pattern that was intentionally left for backward compatibility (the config-page's "Soft Scan" button calls `Scan()` which no longer exists). This is an ℹ️ Info-level note — the soft scan button will fail at runtime until Phase 12 addresses it, but it is outside Phase 11's scope (Phase 11's goal is per-library scanning, not removing legacy UI buttons). + +### Human Verification Required + +### 1. Scan All Libraries End-to-End + +**Test:** Add 2+ libraries via the database, click "Scan All Libraries" button +**Expected:** Libraries scan sequentially, progress shows each library name in turn, queue count decrements, final QueueDrained resets UI +**Why human:** Requires multiple libraries in DB and visual verification of progress transitions + +### 2. Cancel Scope Dialog + +**Test:** Start "Scan All Libraries" with 2+ libraries. While scanning, click "Cancel Scan" in config-page +**Expected:** Modal dialog shows "Cancel This Library" and "Cancel All Scanning" buttons. "Cancel This Library" stops current, next starts. "Cancel All Scanning" stops everything. +**Why human:** Visual dialog behavior and queue state transitions need runtime verification + +### 3. Pause Freezes Queue + +**Test:** Start "Scan All Libraries" with 2+ libraries. Pause the scan. +**Expected:** Current scan pauses. No queued library starts until resume. Resume continues current scan, then queue proceeds. +**Why human:** Requires observing real-time pause/resume behavior with queue coordination + +### 4. Auto-Scan on Launch + +**Test:** Add a library to the database, restart the application +**Expected:** Scan starts automatically on DOM ready, progress shows library name +**Why human:** Requires application restart and observing startup behavior + +--- + +_Verified: 2026-03-09T20:30:00Z_ +_Verifier: Claude (gsd-verifier)_