docs(17-02): complete track details save flow & cover art editing plan

- SUMMARY.md with 5 commits (1 feat + 4 fixes during verification)
- STATE.md updated: Phase 17 complete, decisions added
- ROADMAP.md updated: Phase 17 2/2 Complete
- REQUIREMENTS.md updated: EDIT-02, EDIT-03, EDIT-04 all complete
This commit is contained in:
2026-03-18 11:21:46 -04:00
parent 8cd4914842
commit b0976c9217
4 changed files with 184 additions and 20 deletions
@@ -0,0 +1,160 @@
---
phase: 17-single-track-edit
plan: 02
subsystem: ui
tags: [tag-editing, cover-art, wails-bindings, lit-element, dialog, file-picker]
# Dependency graph
requires:
- phase: 17-single-track-edit
provides: WriteTrackTagsByPath, ImageFilePicker, TrackMetadataChanged handler, Track Details context menu
provides:
- Complete single-track tag editor with save flow, cover art editing, and error handling
- ReadFile Go method on FrontendUtil for reading cover art image bytes
- DB sync for cover art (save to cache, thumbnail generation, release_group update)
- Dialog data refresh after save (track + cover art URLs)
affects: [18-batch-edit]
# Tech tracking
tech-stack:
added: []
patterns:
- "Diff-only TagChanges map — only changed fields sent to backend, reducing unnecessary writes"
- "Blob URL preview for cover art — instant client-side preview without server round-trip"
- "Base64 decode for Go []byte return values — Wails serializes []byte as base64 JSON strings"
- "asInt/asBytes helpers for Wails JSON deserialization — JavaScript numbers arrive as float64, arrays as []interface{}"
key-files:
created: []
modified:
- frontend/src/components/track-details/track-details.ts
- backend/frontendutil/frontendutil.go
- backend/tagwriter/tagwriter.go
- backend/tagwriter/dbsync.go
- backend/tagwriter/mp3.go
- backend/tagwriter/flac.go
- frontend/wailsjs/go/frontendutil/FrontendUtil.js
- frontend/wailsjs/go/frontendutil/FrontendUtil.d.ts
key-decisions:
- "ReadFile Go method on FrontendUtil to return file bytes to frontend — needed because Wails native file dialog returns path, but frontend needs bytes for preview + save"
- "asInt/asBytes type coercion helpers in tagwriter — Wails JSON deserialization sends all numbers as float64 and byte arrays as base64 strings"
- "Cover art DB sync saves to covers cache directory with content-hash dedup and thumbnail generation"
patterns-established:
- "Wails float64 coercion: always use asInt() helper for numeric TagChanges values, never direct .(int) assertion"
- "Wails []byte handling: Go []byte serializes as base64 JSON string; frontend must atob() decode before use"
requirements-completed: [EDIT-02, EDIT-03, EDIT-04]
# Metrics
duration: 25min
completed: 2026-03-18
---
# Phase 17 Plan 02: Track Details Save Flow & Cover Art Editing Summary
**Diff-only tag save with cover art replace/remove via native file picker, inline error handling, and automatic dialog + view refresh after write**
## Performance
- **Duration:** ~25 min (implementation) + verification session with bug fixes
- **Started:** 2026-03-18T01:08:53Z
- **Completed:** 2026-03-18T14:54:17Z
- **Tasks:** 2 (1 auto + 1 human-verify)
- **Files modified:** 8
## Accomplishments
- Complete save flow: `saveEdit()` builds diff-only TagChanges map and calls `WriteTrackTagsByPath` — unchanged fields are never sent
- Cover art editing: native file picker for JPEG/PNG with instant blob preview via object URL; remove button (×) clears embedded art
- Inline error handling: errors display in the dialog action bar, edit mode stays active for retry or cancel
- Saving state indicator: Save button shows "Saving…" and both buttons disabled during write
- Dialog data refresh: after save, track data and cover art URLs are re-fetched from the library store
- Cover art DB sync: image saved to covers cache with content-hash dedup + thumbnail generation, release_group updated
- Wails JSON deserialization fixes: asInt/asBytes helpers handle float64 numbers and base64 byte arrays
## Task Commits
Each task was committed atomically:
1. **Task 1: Implement saveEdit, cover art editing, error handling, and saving state** - `265a9ea` (feat)
2. **Task 2: Verify complete single-track edit flow** — human-verify checkpoint, APPROVED
**Bug fixes during verification (committed by orchestrator):**
3. **Fix: refresh track-details dialog data after save** - `ffcdc41` (fix)
4. **Fix: handle float64 numeric values from Wails JSON deserialization** - `900db2e` (fix)
5. **Fix: cover art replace and remove (asBytes, DB sync, base64 decode)** - `d7c2965` (fix)
6. **Fix: refresh cover art URLs after save** - `8cd4914` (fix)
## Files Created/Modified
- `frontend/src/components/track-details/track-details.ts` - Complete save flow, cover art editing UI, error handling, saving state, dialog refresh
- `backend/frontendutil/frontendutil.go` - Added ReadFile method for reading cover art bytes
- `backend/tagwriter/tagwriter.go` - Added asInt/asBytes helpers for Wails JSON deserialization
- `backend/tagwriter/dbsync.go` - Cover art DB sync (save image, update release_group, orphan cleanup)
- `backend/tagwriter/mp3.go` - Use asInt/asBytes helpers for type coercion
- `backend/tagwriter/flac.go` - Use asInt/asBytes helpers for type coercion
- `frontend/wailsjs/go/frontendutil/FrontendUtil.js` - Wails binding for ReadFile
- `frontend/wailsjs/go/frontendutil/FrontendUtil.d.ts` - TypeScript declaration for ReadFile
## Decisions Made
- Added `ReadFile` Go method on FrontendUtil to bridge the gap between native file dialog (returns path) and frontend need for bytes (preview + save). Simplest approach that avoids additional Go-side image processing.
- Created `asInt()` and `asBytes()` type coercion helpers in tagwriter package — Wails JSON deserialization always sends JavaScript numbers as Go `float64` and `[]byte` as base64 strings. Direct `.(int)` assertions silently failed.
- Cover art DB sync saves the image to the covers cache directory using content-hash dedup with thumbnail generation, then updates `release_groups.cover_art_id`. Clear sets `cover_art_id` to NULL.
## Deviations from Plan
### Auto-fixed Issues (by orchestrator during verification)
**1. [Rule 1 - Bug] Dialog showed stale track data after save**
- **Found during:** Task 2 (human verification)
- **Issue:** After save, dialog returned to read-only mode but showed pre-edit values because `this.track` was the original snapshot passed via `show()`
- **Fix:** After successful `WriteTrackTagsByPath`, re-fetch tracks from library store and update `this.track` with fresh data
- **Files modified:** `frontend/src/components/track-details/track-details.ts`
- **Committed in:** `ffcdc41`
**2. [Rule 1 - Bug] Numeric fields silently ignored during save**
- **Found during:** Task 2 (human verification)
- **Issue:** Wails JSON deserialization sends all JavaScript numbers as Go `float64`. All `.(int)` type assertions on year, track_number, and disc_number silently failed (returned zero-value + false), meaning numeric edits were dropped
- **Fix:** Added `asInt()` helper that handles both `float64` and `int` types; replaced all direct `.(int)` assertions across tagwriter package
- **Files modified:** `backend/tagwriter/tagwriter.go`, `backend/tagwriter/dbsync.go`, `backend/tagwriter/mp3.go`, `backend/tagwriter/flac.go`
- **Committed in:** `900db2e`
**3. [Rule 1 - Bug] Cover art replace and remove did not work**
- **Found during:** Task 2 (human verification)
- **Issue:** Three related issues: (a) Cover art bytes from frontend arrived as `[]interface{}` of `float64` — same deserialization issue as numerics. (b) DB sync for cover art was a placeholder no-op — didn't save image to cache or update release_group. (c) Frontend `ReadFile` returns base64 string (Go `[]byte` JSON encoding), not `number[]` — preview blob was corrupted.
- **Fix:** Added `asBytes()` helper for `[]interface{}``[]byte` conversion. Implemented full cover art DB sync (save to covers cache with content-hash dedup + thumbnail generation, upsert cover_art row, update release_groups). Fixed frontend to decode base64 with `atob()` before creating `Uint8Array`.
- **Files modified:** `backend/tagwriter/tagwriter.go`, `backend/tagwriter/dbsync.go`, `backend/tagwriter/flac.go`, `backend/tagwriter/mp3.go`, `frontend/src/components/track-details/track-details.ts`
- **Committed in:** `d7c2965`
**4. [Rule 1 - Bug] Cover art image reverted to old after save**
- **Found during:** Task 2 (human verification)
- **Issue:** After save, dialog refreshed `this.track` but kept stale `this.coverArt` URLs pointing to old content-hash files. The image visually reverted until the dialog was closed and reopened.
- **Fix:** After save, re-fetch albums alongside tracks and re-resolve cover art URLs from updated album data
- **Files modified:** `frontend/src/components/track-details/track-details.ts`
- **Committed in:** `8cd4914`
---
**Total deviations:** 4 auto-fixed (all Rule 1 bugs discovered during human verification)
**Impact on plan:** All fixes were necessary for correct end-to-end functionality. The Wails JSON deserialization issues (float64 numbers, base64 bytes) were a systemic pattern not visible until real runtime testing. No scope creep — all fixes are within the plan's boundary.
## Issues Encountered
None beyond the deviations documented above. All issues were discovered and resolved during the human verification checkpoint.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
- Phase 17 is now complete (2/2 plans done)
- Single-track editing works end-to-end: tag writes, cover art replacement/removal, error handling, view refresh
- Ready for Phase 18 (Batch Edit) which builds on this foundation
- The `asInt()`/`asBytes()` Wails deserialization helpers established in this plan will be essential for Phase 18
## Self-Check: PASSED
All 6 key files verified on disk. All 5 commits verified in git history.
---
*Phase: 17-single-track-edit*
*Completed: 2026-03-18*