docs(16-03): complete WriteTrackTags pipeline plan

- SUMMARY.md with pipeline architecture, 5 tests, 7 files
- STATE.md: Phase 16 complete (3/3), decisions, session
- ROADMAP.md: Phase 16 marked complete
- REQUIREMENTS.md: SYNC-01/02/03/04 and WRITE-06 marked complete
This commit is contained in:
2026-03-17 10:58:13 -04:00
parent 64322f9353
commit bdbcd54e92
4 changed files with 152 additions and 27 deletions
@@ -0,0 +1,120 @@
---
phase: 16-tag-writing-database-sync
plan: 03
subsystem: tagwriter, database, library
tags: [tag-writing, db-sync, fts5, orphan-cleanup, pipeline, wails-binding, mutual-exclusion]
# Dependency graph
requires:
- phase: 16-tag-writing-database-sync
provides: writeMp3Tags and writeFlacTags format-specific writers, TagChanges type, orphan-counting sqlc queries
- phase: 15-schema-migration-write-safety
provides: AtomicWrite utility, FTS5 contentless_delete=1 migration
provides:
- WriteTrackTags single entry point for file write + DB sync + event emission
- syncDatabase transactional DB sync (entity relink, FTS5, orphan cleanup)
- TagWriter Wails binding accessible from frontend
- TrackMetadataChanged event constant (Go + TypeScript)
- pipelineMu scan/write mutual exclusion on Library
- PlayerStopper and PipelineLocker interfaces for dependency inversion
affects: [17-single-track-edit, 18-batch-edit]
# Tech tracking
tech-stack:
added: []
patterns: [pipeline-mutex, player-adapter-interface, transactional-db-sync]
key-files:
created:
- backend/tagwriter/pipeline.go
- backend/tagwriter/dbsync.go
- backend/tagwriter/pipeline_test.go
modified:
- backend/app.go
- backend/events/events.go
- backend/library/library.go
- frontend/src/events.ts
key-decisions:
- "PlayerStopper interface to break tagwriter→player import cycle with playerAdapter in app.go"
- "pipelineMu sync.Mutex on Library for scan/write mutual exclusion (not RWMutex — only one pipeline at a time)"
- "FTS5 delete+insert within same DB transaction for consistency"
- "Global genre orphan cleanup via DELETE WHERE id NOT IN (SELECT DISTINCT genre_id FROM recording_genres)"
patterns-established:
- "Pipeline mutex: AcquirePipelineLock/ReleasePipelineLock wrapping both scan and write pipelines"
- "Transactional DB sync: single tx for entity relink + FTS5 + orphan cleanup"
- "Player safety check: CurrentFilePath() + StopAndRelease() before file write"
requirements-completed: [SYNC-01, SYNC-02, SYNC-03, SYNC-04, WRITE-06]
# Metrics
duration: 9min
completed: 2026-03-17
---
# Phase 16 Plan 03: WriteTrackTags Pipeline + DB Sync Summary
**WriteTrackTags pipeline orchestrating format-specific file write → transactional DB sync (entity relink + FTS5 + orphan cleanup) → TrackMetadataChanged event emission, with player safety and scan/write mutual exclusion**
## Performance
- **Duration:** 9 min
- **Started:** 2026-03-17T14:46:40Z
- **Completed:** 2026-03-17T14:55:31Z
- **Tasks:** 2
- **Files modified:** 7
## Accomplishments
- Complete WriteTrackTags entry point that Phase 17's UI will call — one function does everything
- Transactional DB sync handling artist/album/genre entity relink with upsert-and-relink pattern
- Orphan cleanup for artist_credits, release_groups, and genres within the same transaction
- FTS5 search index updated atomically (delete old + insert new) inside the transaction
- Player auto-stopped before writing currently-playing file via PlayerStopper interface
- Scan/write mutual exclusion via pipelineMu on Library (scan blocks write and vice versa)
- TrackMetadataChanged event emitted after successful write+sync, auto-generated in TypeScript
- TagWriter wired into app.go as Wails binding (frontend-accessible)
- 5 integration tests covering player safety, scan mutex, orphan cleanup, genre relink, and full DB sync
## Task Commits
Each task was committed atomically:
1. **Task 1: DB sync module** - `2966079` (feat) — syncDatabase with entity relink, FTS5, orphan cleanup
2. **Task 2: Pipeline + wiring + tests** - `64322f9` (feat) — TagWriter, player safety, events, app.go, 5 tests
**Plan metadata:** (this commit)
## Files Created/Modified
- `backend/tagwriter/dbsync.go` — syncDatabase: transactional entity relink, FTS5 update, orphan cleanup with SAFETY comments
- `backend/tagwriter/pipeline.go` — TagWriter struct, WriteTrackTags entry point, PlayerStopper/PipelineLocker interfaces
- `backend/tagwriter/pipeline_test.go` — 5 integration tests with mockPlayer, mockPipelineLocker, in-memory test DB
- `backend/app.go` — playerAdapter, NewTagWriter creation, SetContext, FEBindings registration
- `backend/events/events.go` — TrackMetadataChanged constant
- `backend/library/library.go` — pipelineMu field, AcquirePipelineLock/ReleasePipelineLock methods, pipelineMu wrapping scanInternal
- `frontend/src/events.ts` — Auto-generated TrackMetadataChanged event
## Decisions Made
- **PlayerStopper interface** — Defined in tagwriter package to break circular import (tagwriter cannot import player). playerAdapter in app.go wraps *player.Player to satisfy the interface.
- **pipelineMu sync.Mutex** — Added to Library struct (not the existing `mu`). Scan acquires at start of scanInternal, write acquires before file write. Both defer unlock. Simple mutex (not RWMutex) because only one pipeline should run at a time.
- **FTS5 within transaction** — Execute FTS5 DELETE/INSERT directly on `*sql.Tx` rather than through DB helper methods, ensuring they're part of the same atomic operation.
- **Global genre orphan cleanup** — Instead of tracking old genre IDs (which requires extra bookkeeping since genres are deleted before re-linking), use `DELETE FROM genres WHERE id NOT IN (SELECT DISTINCT genre_id FROM recording_genres)`. Safe and complete.
## Deviations from Plan
None - plan executed exactly as written.
## Issues Encountered
None
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
- Phase 16 complete (all 3 plans done) — MP3 writer, FLAC writer, and WriteTrackTags pipeline
- Ready for Phase 17 (Single Track Edit UI) which calls WriteTrackTags from the frontend
- All format-specific writers, DB sync, player safety, and scan mutex are tested and lint-clean
---
*Phase: 16-tag-writing-database-sync*
*Completed: 2026-03-17*