diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md
index 9233a01..32ec5f9 100644
--- a/.planning/REQUIREMENTS.md
+++ b/.planning/REQUIREMENTS.md
@@ -91,32 +91,32 @@ Which phases cover which requirements. Updated during roadmap creation.
| Requirement | Phase | Status |
|-------------|-------|--------|
-| SCHEMA-01 | — | Pending |
-| SCHEMA-02 | — | Pending |
-| WRITE-01 | — | Pending |
-| WRITE-02 | — | Pending |
-| WRITE-03 | — | Pending |
-| WRITE-04 | — | Pending |
-| WRITE-05 | — | Pending |
-| WRITE-06 | — | Pending |
-| SYNC-01 | — | Pending |
-| SYNC-02 | — | Pending |
-| SYNC-03 | — | Pending |
-| SYNC-04 | — | Pending |
-| EDIT-01 | — | Pending |
-| EDIT-02 | — | Pending |
-| EDIT-03 | — | Pending |
-| EDIT-04 | — | Pending |
-| BATCH-01 | — | Pending |
-| BATCH-02 | — | Pending |
-| BATCH-03 | — | Pending |
-| BATCH-04 | — | Pending |
+| SCHEMA-01 | Phase 15 | Pending |
+| SCHEMA-02 | Phase 15 | Pending |
+| WRITE-01 | Phase 16 | Pending |
+| WRITE-02 | Phase 16 | Pending |
+| WRITE-03 | Phase 19 | Pending |
+| WRITE-04 | Phase 16 | Pending |
+| WRITE-05 | Phase 15 | Pending |
+| WRITE-06 | Phase 16 | Pending |
+| SYNC-01 | Phase 16 | Pending |
+| SYNC-02 | Phase 16 | Pending |
+| SYNC-03 | Phase 16 | Pending |
+| SYNC-04 | Phase 16 | Pending |
+| EDIT-01 | Phase 17 | Pending |
+| EDIT-02 | Phase 17 | Pending |
+| EDIT-03 | Phase 17 | Pending |
+| EDIT-04 | Phase 17 | Pending |
+| BATCH-01 | Phase 18 | Pending |
+| BATCH-02 | Phase 18 | Pending |
+| BATCH-03 | Phase 18 | Pending |
+| BATCH-04 | Phase 18 | Pending |
**Coverage:**
- v1.2 requirements: 20 total
-- Mapped to phases: 0
-- Unmapped: 20
+- Mapped to phases: 20
+- Unmapped: 0 ✓
---
*Requirements defined: 2026-03-16*
-*Last updated: 2026-03-16 after initial definition*
+*Last updated: 2026-03-16 — traceability updated with phase mappings (Phases 15-19)*
diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 355d7bb..cc4d6b0 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -1,14 +1,14 @@
# Roadmap: YellowJacket
**Created:** 2026-02-27
-**Last updated:** 2026-03-08
-**Current milestone:** v1.1 Multi-Library Support
+**Last updated:** 2026-03-16
+**Current milestone:** v1.2 Tag Editing
## Milestones
- ✅ **v1.0 Consolidation** — Phases 1-8 (shipped 2026-03-05) — [archive](milestones/v1.0-ROADMAP.md)
-- ✅ **v1.1 Multi-Library Support** — Phases 9-13 complete (shipped 2026-03-16)
-- ✅ **Performance Optimization** — Phase 14 complete (shipped 2026-03-15)
+- ✅ **v1.1 Multi-Library Support** — Phases 9-14 (shipped 2026-03-16) — [archive](milestones/v1.1-ROADMAP.md)
+- 🔨 **v1.2 Tag Editing** — Phases 15-19
## Phases
@@ -26,109 +26,82 @@
-### v1.1 Multi-Library Support (Phases 9-13)
+
+✅ v1.1 Multi-Library Support (Phases 9-14) — SHIPPED 2026-03-16
-- [x] **Phase 9: Scan Cancellation & Keyboard Shortcuts** — Cancellable library scans and configurable keyboard shortcuts
-- [x] **Phase 10: Schema & Migration** — Libraries table, library_id FK, playlist_tracks phantom rebuild, config migration (completed 2026-03-09)
-- [x] **Phase 11: Per-Library Scan Pipeline** — Scan pipeline refactored for per-library scanning with sequential coordination (completed 2026-03-09)
-- [x] **Phase 12: Library CRUD & Data Integrity** — Library management API, orphan cleanup, queue/playlist lifecycle, library manager UI (completed 2026-03-15)
-- [x] **Phase 13: Library Views & Phantom Tracks** — Filtered presentation across all views, search, browse, and phantom track display (completed 2026-03-16)
+- [x] Phase 9: Scan Cancellation & Keyboard Shortcuts (5/5 plans) — completed 2026-03-07
+- [x] Phase 10: Schema & Migration (2/2 plans) — completed 2026-03-09
+- [x] Phase 11: Per-Library Scan Pipeline (3/3 plans) — completed 2026-03-09
+- [x] Phase 12: Library CRUD & Data Integrity (2/2 plans) — completed 2026-03-15
+- [x] Phase 13: Library Views & Phantom Tracks (2/2 plans) — completed 2026-03-16
+- [x] Phase 14: Performance Optimization (4/4 plans) — completed 2026-03-15
+
+
+
+### v1.2 Tag Editing (Phases 15-19)
+
+- [ ] **Phase 15: Schema Migration & Write Safety** — FTS5 contentless_delete migration and atomic file write utility
+- [ ] **Phase 16: Tag Writing & Database Sync** — Format-specific tag writers (MP3, FLAC, cover art) with inline DB + FTS5 update pipeline
+- [ ] **Phase 17: Single Track Edit** — End-to-end single track editing: UI → file write → DB sync → view refresh
+- [ ] **Phase 18: Batch Edit** — Multi-select batch editing with three-state field model, progress, and batch cover art
+- [ ] **Phase 19: OGG Vorbis Tag Writing** — Custom OGG page rewriter for Vorbis Comment tag writing (stretch)
## Phase Details
-### Phase 9: Scan Cancellation & Keyboard Shortcuts
-**Goal:** Users can control library scans (cancel/pause/resume) and operate the entire app via keyboard
-**Depends on:** Nothing (builds on v1.0 foundation)
-**Requirements:** SCAN-01, SCAN-02, SCAN-03, KEY-01, KEY-02, KEY-03, KEY-04, KEY-05
+### Phase 15: Schema Migration & Write Safety
+**Goal:** The database and file system infrastructure supports safe, reversible tag editing — FTS5 rows can be deleted/updated and file writes never corrupt audio files
+**Depends on:** Nothing (builds on v1.1 foundation)
+**Requirements:** SCHEMA-01, SCHEMA-02, WRITE-05
**Success Criteria** (what must be TRUE):
- 1. User can click a cancel button during a library scan and the scan stops within seconds — no database corruption, no orphaned tracks
- 2. User can pause a running scan and resume it later without re-processing files that were already scanned
- 3. Default keyboard shortcuts work immediately after install — play/pause, next/prev, volume up/down, search focus, queue toggle, shuffle, repeat all respond to keys
- 4. User can open a settings UI, rebind any shortcut to a different key, and the new binding takes effect immediately — conflicts are warned about before saving
- 5. Keyboard shortcuts are context-aware — typing in a search box doesn't trigger player shortcuts (except Escape to blur)
-**Plans:** 5 plans
-Plans:
-- [x] 09-01-PLAN.md — Backend scan control (cancel/pause/resume methods, events, metrics)
-- [x] 09-02-PLAN.md — Backend shortcuts config + frontend keyboard shortcut service
-- [x] 09-03-PLAN.md — Frontend scan control UI (buttons, cancel dialog)
-- [x] 09-04-PLAN.md — Frontend shortcut settings UI (record-style capture, conflict detection)
-- [x] 09-05-PLAN.md — Integration verification checkpoint
+ 1. FTS5 search_index uses `contentless_delete=1` — deleting or updating a track's metadata in the DB correctly removes the old FTS5 entry without stale ghost results appearing in search
+ 2. Existing search functionality is unaffected — all current queries, ranking, and library-filtered search continue to work identically after migration
+ 3. The atomic write utility writes to a temp file in the same directory as the target, then renames — if the process crashes mid-write, the original file is intact and the temp file is cleaned up on next startup
+ 4. Unit tests verify atomic write behavior: successful write, crash simulation (temp file left behind), and cross-directory rejection
+**Plans:** TBD
-### Phase 10: Schema & Migration
-**Goal:** The database supports multiple libraries and phantom tracks — existing users upgrade seamlessly
-**Depends on:** Phase 9 (builds on existing schema and scan infrastructure)
-**Requirements:** DATA-01, DATA-04, LIB-04, LIB-05, LSCAN-05
+### Phase 16: Tag Writing & Database Sync
+**Goal:** The backend can write metadata tags and cover art to MP3 and FLAC files, then synchronize all changes to the database and search index in a single atomic operation
+**Depends on:** Phase 15 (requires atomic write utility and FTS5 contentless_delete)
+**Requirements:** WRITE-01, WRITE-02, WRITE-04, WRITE-06, SYNC-01, SYNC-02, SYNC-03, SYNC-04
**Success Criteria** (what must be TRUE):
- 1. A fresh install creates a `libraries` table and `audio_files.library_id` FK — new audio files are always associated with a library
- 2. An existing user's database is migrated on first launch: their single directory becomes a named library, all existing audio_files get that library_id, and everything works without any user action
- 3. The `playlist_tracks` table supports nullable `audio_file_id` with phantom metadata columns — the schema is ready for phantom track preservation
- 4. All migration operations complete atomically — a crash mid-migration leaves the database unchanged (not half-migrated)
-**Plans:** 2/2 plans complete
-Plans:
-- [x] 10-01-PLAN.md — Schema definitions + Migration 6 (libraries table, library_id FK, phantom columns, track_metadata VIEW, backup, TOML migration)
-- [x] 10-02-PLAN.md — sqlc queries for libraries + updated playlist phantom queries + migration integration tests
+ 1. A Go function can accept a track ID and a set of changed metadata fields, write those tags to an MP3 file (ID3v2), and the tags are readable back by the existing metadata reader — round-trip correctness verified by unit tests with real audio files
+ 2. The same function works for FLAC files (Vorbis Comments) — including files with existing padding blocks and multiple metadata blocks
+ 3. Cover art images (JPEG/PNG) can be embedded in both MP3 and FLAC files — the embedded image is readable back and the existing cover art pipeline (extraction, thumbnails) works with the newly embedded art
+ 4. After a tag write, the database reflects the new metadata within the same operation: artist/album/genre entities are created or relinked (never mutated in-place), orphaned entities with zero remaining references are cleaned up, and the FTS5 index is updated — no library rescan needed
+ 5. If the currently-playing track is being edited, playback is stopped before the file write begins — the user does not experience a crash or corrupted audio stream
+**Plans:** TBD
-### Phase 11: Per-Library Scan Pipeline
-**Goal:** Users can scan individual libraries independently with proper sequential coordination
-**Depends on:** Phase 10 (requires libraries table and library_id FK)
-**Requirements:** LSCAN-01, LSCAN-02, LSCAN-03, LSCAN-04
+### Phase 17: Single Track Edit
+**Goal:** Users can edit any track's metadata and cover art from within the app and see changes reflected everywhere immediately
+**Depends on:** Phase 16 (requires tag writers and DB sync pipeline)
+**Requirements:** EDIT-01, EDIT-02, EDIT-03, EDIT-04
**Success Criteria** (what must be TRUE):
- 1. User can trigger a scan for a specific library and only that library's directory is scanned — other libraries are untouched
- 2. Only one library scans at a time — requesting a second scan while one is running either queues it or is rejected with clear feedback
- 3. Scan progress UI identifies which library is currently being scanned (library name visible in progress indicator)
- 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:
-- [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
+ 1. User can right-click any track (in track list, album detail, queue, or playlist) and open a tag editor dialog — the editor is accessible from every place tracks appear
+ 2. The editor displays all 8 editable fields (title, artist, album, genre, year, track number, disc number, composer) pre-populated with the track's current values — empty fields show as empty, not "Unknown"
+ 3. The editor displays the track's current cover art (or a placeholder if none) with a button to select a replacement image file from disk
+ 4. Clicking "Save" writes the changes to the audio file, updates the database and search index, and refreshes all visible views (track list, album view, artist view, genre view, queue, now-playing bar) — the user sees the new metadata everywhere without restarting or rescanning
+**Plans:** TBD
-### Phase 12: Library CRUD & Data Integrity
-**Goal:** Users can add, rename, and remove libraries through the UI with correct data lifecycle management
-**Depends on:** Phase 11 (requires per-library scanning for add-then-scan workflow)
-**Requirements:** LIB-01, LIB-02, LIB-03, LIB-06, DATA-02, DATA-03, PLAY-04
+### Phase 18: Batch Edit
+**Goal:** Users can efficiently edit shared metadata across multiple tracks at once with clear visual feedback and safe defaults
+**Depends on:** Phase 17 (requires single-track edit pipeline as foundation)
+**Requirements:** BATCH-01, BATCH-02, BATCH-03, BATCH-04
**Success Criteria** (what must be TRUE):
- 1. User can add a new library via folder picker, give it a name, and trigger a scan — new tracks appear in the library
- 2. User can rename a library's display name and the change reflects everywhere immediately
- 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
- 4. Removing a library cleans up FTS5 search index entries for that library's tracks (no stale search results)
- 5. Queue tracks from a removed library are cascade-deleted; the queue continues playing from the next valid track
-**Plans:** 2/2 plans complete
-Plans:
-- [x] 12-01-PLAN.md — Backend CRUD API + orphan cleanup + queue compaction + events
-- [x] 12-02-PLAN.md — Frontend library management UI in settings + sidebar cleanup
+ 1. User can select multiple tracks (via multi-select in track list or album detail) and open a batch editor — the batch editor is accessible from the same context menu as single-track edit
+ 2. Each field in the batch editor shows one of three states: "keep original" (mixed values, no change), "set to value" (apply this value to all selected tracks), or "clear field" (remove this value from all) — the user can see which fields differ across the selection and choose per-field what to do
+ 3. For batch operations on 10+ tracks, a progress indicator shows how many tracks have been processed — the user is never left staring at a frozen UI wondering if the operation is working
+ 4. User can set cover art for all selected tracks at once — the same image is embedded in every selected file
+**Plans:** TBD
-### Phase 13: Library Views & Phantom Tracks
-**Goal:** Users experience a unified multi-library presentation with optional filtering and graceful playlist preservation
-**Depends on:** Phase 12 (requires library CRUD and data integrity for full integration)
-**Requirements:** VIEW-01, VIEW-02, VIEW-03, VIEW-04, PLAY-01, PLAY-02, PLAY-03
+### Phase 19: OGG Vorbis Tag Writing
+**Goal:** Users can edit tags on OGG Vorbis files with the same experience as MP3 and FLAC — completing full format coverage
+**Depends on:** Phase 16 (requires tag writer interface and DB sync pipeline)
+**Requirements:** WRITE-03
**Success Criteria** (what must be TRUE):
- 1. The default track list shows tracks from all libraries merged — the user sees their complete collection as one unified view
- 2. User can select a specific library from a filter control and all views (tracks, albums, artists, genres) show only that library's content
- 3. Search results respect the active library filter — searching with a library selected returns only matches from that library; with "All Libraries" selected, searches everything
- 4. Playlists can contain tracks from multiple libraries — adding tracks from different libraries to the same playlist works naturally
- 5. When a library is removed, its tracks in playlists become phantom entries — visually distinguished (greyed out / icon) with preserved title, artist, album metadata instead of disappearing
-**Plans:** 2/2 plans complete
-Plans:
-- [x] 13-01-PLAN.md — Backend library-filtered sqlc queries + Go methods + FTS search
-- [x] 13-02-PLAN.md — Frontend library filter store + dropdown UI + all view/search wiring + verification
-
-### Phase 14: Performance Optimization
-**Goal:** Scrolling, navigation, and rendering are as smooth and fast as possible — scrolling feels like a native animation, navigation is instant, no unnecessary re-renders
-**Depends on:** Nothing (cross-cutting, can execute in parallel with v1.1 phases)
-**Requirements:** PERF-SCROLL-01, PERF-SCROLL-02, PERF-SCROLL-03, PERF-NAV-01, PERF-NAV-02, PERF-RENDER-01, PERF-RENDER-02, PERF-DIAG-01
-**Success Criteria** (what must be TRUE):
- 1. Scrolling in all views (tracks, albums, artists, genres, queue, playlists) is smooth at 60fps — no jank, no stuttering, no blank areas
- 2. Navigating between primary views (tracks, albums, artists, genres, playlists, settings) is near-instant — no component destruction/recreation, scroll positions preserved
- 3. Render hot paths (renderTrackRow, renderTrackItem) create zero new closures per frame — all event handling uses delegation
- 4. Store notifications are batched (queueMicrotask) and components only re-render when their relevant data changes
- 5. A profiling guide documents how to diagnose performance issues using pprof (backend) and DevTools (frontend)
-**Plans:** 4/4 plans complete
-Plans:
-- [x] 14-01-PLAN.md — CSS containment + GPU layer promotion on all scroll containers
-- [x] 14-02-PLAN.md — View caching navigation system (replace innerHTML destruction)
-- [x] 14-03-PLAN.md — Render hot-path optimization (closure elimination, store granularity)
-- [x] 14-04-PLAN.md — Scroll event optimization, profiling guide, performance verification checkpoint
+ 1. A Go function can write Vorbis Comment metadata tags to OGG Vorbis files using a custom OGG page rewriter — the file remains a valid OGG stream after writing (playable by the existing player and by external players)
+ 2. Tag writes to OGG files use the same atomic write-to-temp-then-rename pattern as MP3/FLAC — no corruption risk
+ 3. OGG tag editing is seamlessly integrated into the single-track and batch edit UIs — the user doesn't need to know or care what format a file is; the editor just works
+**Plans:** TBD
## Progress
@@ -146,9 +119,14 @@ Plans:
| 10. Schema & Migration | v1.1 | 2/2 | Complete | 2026-03-09 |
| 11. Per-Library Scan Pipeline | v1.1 | 3/3 | Complete | 2026-03-09 |
| 12. Library CRUD & Data Integrity | v1.1 | 2/2 | Complete | 2026-03-15 |
-| 13. Library Views & Phantom Tracks | v1.1 | Complete | 2026-03-16 | 2026-03-16 |
-| 14. Performance Optimization | Perf | 4/4 | Complete | 2026-03-15 |
+| 13. Library Views & Phantom Tracks | v1.1 | 2/2 | Complete | 2026-03-16 |
+| 14. Performance Optimization | v1.1 | 4/4 | Complete | 2026-03-15 |
+| 15. Schema Migration & Write Safety | v1.2 | 0/? | Not started | - |
+| 16. Tag Writing & Database Sync | v1.2 | 0/? | Not started | - |
+| 17. Single Track Edit | v1.2 | 0/? | Not started | - |
+| 18. Batch Edit | v1.2 | 0/? | Not started | - |
+| 19. OGG Vorbis Tag Writing | v1.2 | 0/? | Not started | - |
---
*Roadmap created: 2026-02-27*
-*Last updated: 2026-03-16 — v1.1 milestone complete (Phases 9-14 all done)*
+*Last updated: 2026-03-16 — v1.2 Tag Editing milestone roadmap created (Phases 15-19)*
diff --git a/.planning/STATE.md b/.planning/STATE.md
index 6d8c36d..68d5693 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -2,13 +2,13 @@
gsd_state_version: 1.0
milestone: v1.2
milestone_name: Tag Editing
-status: defining_requirements
-last_updated: "2026-03-16T20:03:38.282Z"
+status: roadmap_created
+last_updated: "2026-03-16T21:00:00.000Z"
progress:
- total_phases: 6
- completed_phases: 6
- total_plans: 18
- completed_plans: 18
+ total_phases: 5
+ completed_phases: 0
+ total_plans: 0
+ completed_plans: 0
---
# YellowJacket — Project State
@@ -22,53 +22,48 @@ See: .planning/PROJECT.md (updated 2026-03-16)
## Current Position
-Phase: Not started (defining requirements)
+Phase: Phase 15 — Schema Migration & Write Safety (not started)
Plan: —
-Status: Defining requirements for v1.2 Tag Editing
-Last activity: 2026-03-16 — Milestone v1.2 started
+Status: Roadmap created, ready for phase planning
+Last activity: 2026-03-16 — v1.2 roadmap created (5 phases, 20 requirements mapped)
### 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 | Complete (2/2 plans) ✅ |
-| 13. Library Views & Phantom Tracks | Complete (2/2 plans) ✅ |
-| 14. Performance Optimization | Complete (4/4 plans) ✅ |
+| 15. Schema Migration & Write Safety | Not started |
+| 16. Tag Writing & Database Sync | Not started |
+| 17. Single Track Edit | Not started |
+| 18. Batch Edit | Not started |
+| 19. OGG Vorbis Tag Writing | Not started |
+
+### v1.2 Requirement Coverage
+
+| Category | Requirements | Phase(s) |
+|----------|-------------|----------|
+| Schema & Safety | SCHEMA-01, SCHEMA-02 | Phase 15 |
+| Tag Writing | WRITE-01, WRITE-02 | Phase 16 |
+| Tag Writing | WRITE-03 | Phase 19 |
+| Tag Writing | WRITE-04, WRITE-05, WRITE-06 | Phase 15, 16 |
+| Database Sync | SYNC-01, SYNC-02, SYNC-03, SYNC-04 | Phase 16 |
+| Single Track Edit | EDIT-01, EDIT-02, EDIT-03, EDIT-04 | Phase 17 |
+| Batch Edit | BATCH-01, BATCH-02, BATCH-03, BATCH-04 | Phase 18 |
## 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)
+**v1.1 baseline:** 6 phases, 18 plans in 10 days (~85 commits)
+**v1.2 scope:** 5 phases, 20 requirements
| 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 |
-| Phase 12-02 P02 | 38 min | 3 tasks | 19 files |
-| Phase 13 P01 | 5 min | 2 tasks | 10 files |
-| Phase 13 P02 | 9 min | 1 task | 12 files |
+| (v1.2 plans not yet started) | | | | |
## Accumulated Context
### Key Decisions
-Decisions from v1.0 are archived in PROJECT.md Key Decisions table. Key patterns to carry forward:
+Decisions from v1.0 and v1.1 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
@@ -76,117 +71,51 @@ Decisions from v1.0 are archived in PROJECT.md Key Decisions table. Key patterns
- Design tokens via `:host` scoped CSS custom properties
- queueMicrotask coalescing for store notifications
- `.renderItem` + `.keyFunction` (not `repeat()` children) for lit-virtualizer
+- Upsert-and-relink for shared entities (never mutate shared artist/album/genre rows)
+- ScanHooks/RemovalHooks/RescanHooks callback patterns for cross-package coordination
-### v1.1 Roadmap Decisions
+### v1.2 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 |
-| Selectable library checkboxes for scan targeting | User selects which libraries to scan instead of scan-all-or-nothing; Set model with select-all/indeterminate |
-| Inline progress bar per library row | Each library row shows scan phase and percentage, replacing global-only indicator |
-| Collapsible config sections with chevron dropdown | Keeps settings page organized as it grows; new config-section component |
-| Library store invalidation on LibraryRemoved event | Ensures all data views refresh after library removal |
-
-### 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 |
-
-### Phase 13 Decisions
-
-| Decision | Rationale |
-|----------|-----------|
-| IN-subquery pattern for album/artist/genre library filtering | Entities are global, tracks belong to libraries; subquery filters entity IDs by library membership |
-| Empty slice return (not error) for library with no tracks | Empty library is valid state, not error condition; callers handle empty UI |
-| Client-side search with filtered data source | rankTracks filters already-loaded tracks; no backend SearchTracksByLibrary call needed |
-| Native select for library filter dropdown | Compact, accessible, matches top bar height; no custom component overhead |
-| getAlbumsByArtistNameCached returns null when filter active | Avoids stale cross-library data; forces backend query for consistency |
-| ScanHooks callback for phantom resolution | Mirrors RemovalHooks/RescanHooks pattern; avoids circular dependency between library and playlist packages |
-| phantom_file_path column on playlist_tracks | Stored at removal time for post-scan matching; enables automatic phantom resolution |
-| M3U8-based phantom resolution over SQL-only | Reads playlist files to match by position and file path; handles both pre-existing and new phantoms |
+| 5 phases (15-19) for 20 requirements | Natural clustering: foundation → writers → single edit → batch edit → stretch OGG |
+| WRITE-05 in Phase 15 (not 16) | Atomic write utility is foundational infrastructure, not format-specific |
+| Cover art embed (WRITE-04) in Phase 16 | Cover art embedding is format-specific writer work, shares test infrastructure with tag writing |
+| Cover art UI (EDIT-03) in Phase 17 | Cover art selection UX is part of the single-track edit dialog |
+| OGG as separate Phase 19 (stretch) | Custom OGG page rewriter is MEDIUM-HIGH risk; MP3+FLAC covers vast majority of libraries |
+| SYNC-04 (scan pause during edits) in Phase 16 | Scan/edit mutual exclusion is part of the write pipeline, not the UI layer |
+| Phase 18 depends on Phase 17 | Batch editing is N × single with UI complexity on top; pipeline must be solid first |
+| Phase 19 depends on Phase 16 (not 17) | OGG writing is a backend writer addition; UI integration is format-transparent |
### Warnings (carry forward)
-- Player lock ordering (`p.mu` before `speaker.Lock()`, goroutine dispatch in beep callback) — carry forward
+- Player lock ordering (`p.mu` before `speaker.Lock()`, goroutine dispatch in beep callback)
- 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
+- FTS5 contentless can't DELETE rows — **SCHEMA-01 fixes this** with 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.
+- FLAC files require full rewrite for tag changes — atomic write-to-temp-then-rename mandatory
+- Currently-playing file must be stopped before writing (WRITE-06) — Windows file locking is especially strict
+- Shared entity fan-out — editing one track's artist must NOT mutate the shared artist_credit row
### Research Flags
-- **Multi-library research complete** — see `.planning/research/` (STACK.md, FEATURES.md, ARCHITECTURE.md, PITFALLS.md, SUMMARY.md)
+- **Phase 16:** go-flac libraries (44 stars) — verify round-trip with edge-case FLAC files early
+- **Phase 19:** Custom OGG page rewriter — prototype before committing; consider dropping if too complex
+- **Phase 16:** Album artist storage — not currently a separate entity; resolve during planning
+
+### Deferred Improvements
+
+- **Bulk phantom matching performance** — O(n×3) round trips per phantom. Revisit if large external playlist imports occur.
## Session Continuity
### Last Session
**Date:** 2026-03-16
-**What happened:** Completed Plan 13-02 — library filter dropdown in top bar, all views wired to ByLibrary queries, phantom track auto-resolution via ScanHooks. Checkpoint APPROVED — all 7 requirements verified (VIEW-01–04, PLAY-01–03). Additional bugfixes: virtualizer event delegation race condition, phantom auto-resolution after re-scan.
-**Where we stopped:** Completed 13-02-PLAN.md — Phase 13 complete (2/2 plans), v1.1 milestone complete
-**Next action:** v1.1 milestone shipped — all phases (9-14) complete
+**What happened:** Created v1.2 Tag Editing roadmap — 5 phases (15-19) covering all 20 requirements. Derived phases from requirement clustering: schema foundation → tag writers + DB sync → single-track UI → batch UI → OGG stretch.
+**Where we stopped:** Roadmap created, ready for phase planning
+**Next action:** `/gsd-plan-phase 15` — Schema Migration & Write Safety
---
*State initialized: 2026-02-27*
@@ -200,4 +129,4 @@ Decisions from v1.0 are archived in PROJECT.md Key Decisions table. Key patterns
| 19 | fix phantom playlist tracks with multi-root path resolution | 2026-03-16 | 9144ded | [19-fix-phantom-playlist-tracks](./quick/19-fix-phantom-playlist-tracks/) |
Last activity: 2026-03-16 - Completed quick task 19: fix phantom playlist tracks with multi-root path resolution
-*Last updated: 2026-03-16 — Completed quick task 19*
+*Last updated: 2026-03-16 — v1.2 roadmap created*