Commit Graph
572 Commits
Author SHA1 Message Date
yonlu a677a44d49 test(16-02): add FLAC writer round-trip tests with 7 test functions
- TestWriteFlacTags_TextFields: round-trip 9 text fields via metadata.ExtractTags
- TestWriteFlacTags_CoverArt: JPEG embed and read-back verification
- TestWriteFlacTags_ClearCoverArt: embed then clear art, verify removal
- TestWriteFlacTags_PartialUpdate: update 2 of 8 fields, verify others preserved
- TestWriteFlacTags_PreservesStreamInfo: verify StreamInfo block survives tag writes
- TestWriteFlacTags_ReplaceComment: write twice, verify no duplicate Vorbis Comments
- TestWriteFlacTags_AtomicSafety: verify corrupt/invalid writes leave original intact
- Create shared helpers_test.go with testLogger, tinyJPEG, assertEqual, etc.
2026-03-17 10:32:46 -04:00
yonlu 3642cbe0d5 feat(16-02): add go-flac dependencies and implement FLAC tag writer
- Add go-flac/go-flac/v2, flacvorbis/v2, flacpicture/v2 dependencies
- Create tagwriter.go with TagChanges type, field constants, format detection, MIME detection
- Create flac.go with writeFlacTags using Vorbis Comments + PICTURE blocks + AtomicWrite
- Implement replaceVorbisComment helper for case-insensitive field replacement
- Handle cover art add/replace/clear via PICTURE metadata blocks
2026-03-17 10:32:28 -04:00
yonlu 4463d3e3e1 docs(16): create phase plan — tag writing & database sync 2026-03-17 10:07:30 -04:00
yonlu 0da3448949 docs(16): research phase domain 2026-03-17 09:56:10 -04:00
yonlu 7d0d68a915 docs(16): capture phase context 2026-03-17 09:34:26 -04:00
yonlu 4e8449b400 docs(phase-15): complete phase execution and verification 2026-03-16 18:21:14 -04:00
yonlu 6a1cd44b25 docs(15-02): complete atomic write utility plan
- SUMMARY.md with 2 task commits, 2 auto-fixed lint deviations
- STATE.md: Phase 15 complete (2/2 plans), ready for Phase 16
- ROADMAP.md: Phase 15 marked complete
- REQUIREMENTS.md: SCHEMA-02, WRITE-05 marked complete
2026-03-16 18:16:22 -04:00
yonlu 4876d21fc5 docs(15-01): complete FTS5 migration plan
- Create 15-01-SUMMARY.md
- Update STATE.md with position, decisions, metrics
- Update ROADMAP.md plan progress
- Mark SCHEMA-01 requirement complete
2026-03-16 18:15:26 -04:00
yonlu 0cdfe48e2e test(15-02): add comprehensive tests for AtomicWrite
- TestAtomicWrite_Success: overwrite with permission preservation
- TestAtomicWrite_NewFile: create new file with 0644 default
- TestAtomicWrite_CallbackError: rollback on callback failure
- TestAtomicWrite_OrphanCleanup: clean orphaned .yj-tmp files
- TestAtomicWrite_SameDirectoryTempFile: verify same-dir constraint
- TestAtomicWrite_PermissionPreservation: table-driven 644/755/600
- TestAtomicWrite_SyncAndClose: 1MiB write with size verification
2026-03-16 18:13:35 -04:00
yonlu 56cd7e38a5 test(15-01): add FTS5 row deletion and update cycle tests
- TestDeleteSearchIndex: table-driven test for delete existing/non-existent rowid
- TestSearchIndexUpdateCycle: verifies delete+reinsert produces no ghost entries
- TestClearSearchIndexPreservesSchema: verifies drop/recreate preserves contentless_delete=1
- Update TestInsertAndDeleteSearchIndex to expect successful delete
2026-03-16 18:12:06 -04:00
yonlu 4d64b5dcfe feat(15-02): create backend/fileutil package with AtomicWrite
- Callback API: AtomicWrite(logger, targetPath, func(tmp *os.File) error)
- Deterministic .yj-tmp suffix for orphan cleanup
- Permission preservation (stat target, chmod temp before rename)
- Cross-device rejection via ErrCrossDevice sentinel
- Orphan cleanup on startup of each write operation
- Sync for durability before rename
2026-03-16 18:11:41 -04:00
yonlu cb5155b890 feat(15-01): migrate FTS5 search_index to contentless_delete=1
- Add contentless_delete=1 to search_index.sql schema file
- Update ClearSearchIndex CREATE statement to match schema
- Replace DeleteSearchIndex no-op with real DELETE WHERE rowid
- Add migration 8: drop/recreate/repopulate FTS5 table
- Update library.go comment about FTS entry lifecycle
2026-03-16 18:10:57 -04:00
yonlu 73d9be8e26 docs(15): capture phase context 2026-03-16 17:19:34 -04:00
yonlu c158d6825c docs: create milestone v1.2 roadmap (5 phases) 2026-03-16 16:44:11 -04:00
yonlu 3cf016aec9 docs: define milestone v1.2 requirements 2026-03-16 16:38:07 -04:00
yonlu a6752c8295 docs: complete tag editing project research 2026-03-16 16:29:49 -04:00
yonlu 909958c03a docs: start milestone v1.2 Tag Editing 2026-03-16 16:15:49 -04:00
yonlu 98842a7e14 chore: complete v1.1 milestone 2026-03-16 16:08:27 -04:00
yonlu 8f8af48a12 docs: update retrospective for v1.1 2026-03-16 16:08:19 -04:00
yonlu 86024d59bf docs(phase-13): complete phase execution 2026-03-16 13:08:17 -04:00
yonlu 0a5877c177 docs(13-02): complete library filter UI & phantom resolution plan
- SUMMARY.md updated with checkpoint approval and all bugfix commits
- STATE.md updated with Phase 13 completion and new decisions
- ROADMAP.md plan checkboxes and milestone status updated
- REQUIREMENTS.md coverage updated: 31/31 v1.1 requirements complete
2026-03-16 13:03:35 -04:00
yonlu 9f595b7ac1 fix(13-02): resolve phantom playlist tracks using M3U8 paths after scan
- Add ScanHooks callback struct to library package (follows RemovalHooks pattern)
- Move phantom resolution from library to playlist service via hook
- New ResolvePhantomTracksAfterScan reads M3U8 files and resolves paths
  against current audio_files using multi-root resolution
- Handles pre-existing phantoms (match by M3U8 position) and new ones
  (match by phantom_file_path)
- Delete old resolvePhantomTracks method that required phantom_file_path
- Wire ScanHooks in app.go OnStartup
2026-03-16 12:47:42 -04:00
yonlu 93262b9ae0 fix(13-02): auto-resolve phantom playlist tracks after library scan
- Add phantom_file_path column to playlist_tracks (migration 7)
- Store original file_path during RemoveLibrary phantom metadata population
- After each successful scan, UPDATE phantom tracks whose phantom_file_path
  now matches an audio_files row, re-linking them and clearing phantom metadata
- Update schema file, sqlc generated code, and database test for new column
2026-03-16 12:29:06 -04:00
yonlu f05d2bb603 fix(13-02): defer virtualizer event delegation until element exists
- Event delegation handlers were attached in firstUpdated(), but the
  virtualizer is conditionally rendered (hidden when tracks empty/loading)
- On first render, tracks are [] so virtualizer doesn't exist, and
  firstUpdated() never fires again — delegation was never attached
- Move delegation to a guarded helper called from both firstUpdated()
  and updated(), so it attaches as soon as the virtualizer appears
- Fixes click, multi-select, context menu, double-click, and drag in
  both track-list and queue-panel components
2026-03-16 11:23:35 -04:00
yonlu 70e3814985 docs(13-02): complete library filter UI plan
- SUMMARY.md with library filter implementation details
- STATE.md updated: Phase 13 complete (2/2 plans)
- ROADMAP.md updated with plan progress
- REQUIREMENTS.md: PLAY-01, PLAY-02, PLAY-03 marked complete
2026-03-16 09:44:29 -04:00
yonlu 42b8cf9f52 feat(13-02): add library filter dropdown and wire all views to respect active filter
- Add selectedLibraryId state + ByLibrary conditional calls to library-store
- Add library filter pass-through methods to library-controller
- Create library-filter dropdown component in top bar
- Wire genre-details, cover-grid, artists-view, genres-view to use ByLibrary queries
- Update album-selection helper to respect library filter
- Regenerate Wails bindings for new ByLibrary methods
- Search remains client-side (automatically filtered by loaded data)
- Playlists remain unfiltered (use separate playlist store)
2026-03-16 09:41:27 -04:00
yonlu 5f7de5060a feat(13-01): add library-filtered Go query methods and FTS search
- GetAllTracksByLibrary, GetAllAlbumsByLibrary, GetAllArtistsByLibrary
- GetAlbumsByArtistByLibrary, GetAllGenresWithCountsByLibrary
- GetTracksByGenreByLibrary, GetAlbumTracksByLibrary
- SearchTracksByLibrary wraps SearchFTSTracksByLibrary on DB
- SearchFTSTracksByLibrary filters FTS results by library_id
2026-03-16 09:29:08 -04:00
yonlu 5cc58ce66a feat(13-01): add library-filtered sqlc queries for all browse views
- GetAllTracksWithFullMetadataByLibrary for tracks filtered by library
- GetAudioFilesByReleaseGroupByLibrary for album tracks in a library
- GetAllAlbumsWithDetailsByLibrary for albums with tracks in a library
- GetAlbumsByArtistByLibrary for artist albums in a library
- GetAlbumArtistsByLibrary for artists with albums in a library
- GetAllGenresWithCountsByLibrary for genre counts within a library
- GetTracksByGenreByLibrary for genre tracks within a library
2026-03-16 09:26:42 -04:00
yonlu 66ba5ad1a6 docs(13): create phase plan — library views and phantom tracks 2026-03-16 08:46:05 -04:00
yonlu 963487f892 docs(13): capture phase context 2026-03-16 02:40:44 -04:00
yonlu a586e13a3a docs(quick-19): fix phantom playlist tracks 2026-03-16 01:10:40 -04:00
yonlu e358b57ddf docs(quick-19): complete fix phantom playlist tracks plan
- Add 19-SUMMARY.md with execution results
- Update STATE.md quick tasks table
2026-03-16 01:10:05 -04:00
yonlu a902850471 test(quick-19): add multi-root path resolution tests
- Add TestResolveM3UPath with 7 cases: abs, relative, second root, no match, empty roots, nil knownPaths
- Add TestToRelativePathMultiRoot with 5 cases: first root, second root, no root, empty roots, empty string
- Add TestRemoveM3UEntriesMultiRoot: entries from different roots removed correctly
- Add TestFindM3UEntryMultiRoot: entry relative to second root found
- Add TestReplaceM3UEntryPathsMultiRoot: replacement under second root applied
2026-03-16 01:08:48 -04:00
yonlu 9144dedc27 fix(quick-19): multi-root path resolution for playlist M3U8 tracks
- Replace getLibraryRoot() with getAllLibraryRoots() returning all library paths
- Add resolveM3UPath() for multi-root resolution with knownPaths lookup
- Add toRelativePathMultiRoot() to save relative paths using correct root
- Update removeM3UEntries, replaceM3UEntryPaths, findM3UEntry for []string roots
- Update all 8 call sites in playlist.go for multi-root resolution
- Update existing test signatures for new []string parameter types
- Fix pre-existing golines formatting in scan_queue.go
2026-03-16 01:07:45 -04:00
yonlu 08216752b2 docs(phase-12): complete phase execution 2026-03-15 10:29:12 -04:00
yonlu 07277a4bcc docs(12-02): complete frontend library management UI plan
- Created 12-02-SUMMARY.md documenting 25 commits of library UI work
- Updated STATE.md: Phase 12 complete (2/2 plans)
- Updated ROADMAP.md: Phase 12 marked complete
- Updated REQUIREMENTS.md: LIB-06 marked complete (20/31 requirements done)
2026-03-15 10:24:55 -04:00
yonlu a4eac394ce perf: inline SVGs, memoize grid slices, batch store notifications
- Replace wa-icon shadow DOM with inline SVG in queue-panel (xmark)
  and album-dropdown (fav icons) to eliminate per-item shadow roots
- Memoize getBeforeEntries/getAfterEntries in cover-grid to prevent
  .slice() creating new array refs that trigger virtualizer relayout
- Remove transition: scale and border-radius from album cards to
  avoid per-frame repaints and anti-aliased path clipping
- Add queueMicrotask batching to player-store and favorites-store
  notify() to coalesce rapid-fire updates into single renders
2026-03-15 09:29:40 -04:00
yonlu 54df917ffd fix: preserve scroll position in cached grid views
display:none discards scrollTop in WebKitGTK, so navigating away
from album/artist/genre grids and back reset scroll to top.

Replace inline style.display toggling with a CSS class that uses
visibility:hidden + height:0 + overflow:hidden. This collapses
the element visually while keeping the DOM alive with its scroll
state intact. contain:strict on hidden views ensures zero layout
cost while collapsed.
2026-03-15 09:11:36 -04:00
yonlu 199c91013f perf: reduce software rendering overhead for NVIDIA+Wayland
Targeted optimizations for the DMABuf-disabled rendering path where
every frame is software-composited:

- Replace infinite CSS scroll-text animation with transition-based
  cycle that only repaints during active scroll, not during pauses
- Remove CSS mask-image on scrolling text (mask + animation was the
  single most expensive continuous repaint)
- Replace wa-icon in track rows with inline SVG — eliminates 30-50
  shadow DOM trees (each with SVG fetch/parse) during scroll
- Remove hover transitions on album cards, artist cards, genre cards,
  fav icons, queue remove buttons — each transition was causing
  per-frame software repaints
- Use visibility:hidden instead of opacity:0 for queue remove button
  (binary switch vs per-frame alpha blend)
- Add decoding=async to now-playing cover art images (prevents
  main-thread blocking during image decode on track change)
- Add contain:strict to fixed-height track rows (33px) and queue
  items (49px) — browser skips size contribution calculations
2026-03-15 09:00:59 -04:00
yonlu 3bf7852e1d docs(phase-14): complete phase execution 2026-03-15 08:11:59 -04:00
yonlu 915591aea9 perf: auto-detect NVIDIA+Wayland for DMABuf workaround
Instead of disabling DMABuf for all users (hurting AMD/Intel/X11
performance), detect NVIDIA GPU + Wayland session at startup and
only apply WEBKIT_DISABLE_DMABUF_RENDERER=1 for that combo.

Detection checks /proc/driver/nvidia/version first (fast, no
subprocess), falls back to scanning /proc/modules for nvidia.

GPU policy remains Always for everyone — the DMABuf workaround
only affects buffer sharing between WebKitGTK and the display
server, not GPU-accelerated CSS/layout/paint.
2026-03-14 15:15:53 -04:00
yonlu 3b2e189e7d fix(14-perf): fix scroll jumping and input latency
Root causes addressed:
- track-list had no _itemSize hint for flow layout — virtualizer
  defaulted to 100px, measured actual ~33px rows, then called
  _correctScrollError/scrollTo on every scroll causing visible jumps
- will-change:transform on virtualizer elements caused nested GPU
  layers (virtualizer positions children with transforms internally)
  adding compositor overhead instead of helping
- content-visibility:auto on album cards conflicted with virtualizer's
  own DOM recycling, causing redundant layout recalculation
- track-list visibilityChanged handler wrote to store synchronously
  on every event (per-item during scroll) without any throttling
- IIFE closure in renderTrackRow created a new function per row per render

Fixes applied:
- Add _itemSize:{height:33} + fixed height:33px on .track-row (matches
  queue-panel pattern that already worked smoothly)
- Add overflow-anchor:none on track-list virtualizer
- Remove will-change:transform from all 6 scroll containers
- Remove content-visibility:auto from album cards
- RAF-throttle visibilityChanged scroll position saves
- Replace IIFE with direct cols.map() in template
2026-03-14 14:46:38 -04:00
yonlu 4b7d35d7ec fix(14-01): downgrade main-panel from contain:strict to layout+style+paint
contain:strict includes size containment which caused a timing issue
where the flex-based main-panel height wasn't resolved before the
virtualizer measured its container, resulting in track-list rendering
at ~20% height on first load until something triggered a relayout.
2026-03-14 14:29:19 -04:00
yonlu b514772547 docs(14-03): complete render & store optimization plan
- Created 14-03-SUMMARY.md with event delegation and store batching results
- Updated STATE.md with plan progress, decisions, and session continuity
- Updated ROADMAP.md with Phase 14 plan progress
2026-03-14 13:56:46 -04:00
yonlu 74473d4386 docs(14-04): complete scroll optimization & profiling guide plan
- SUMMARY.md with task commits, decisions, and metrics
- STATE.md updated with plan progress and session continuity
- ROADMAP.md updated with Phase 14 plan progress
2026-03-14 13:54:36 -04:00
yonlu d0c05dc1d4 perf(14-03): add notification batching to queue store and granular change tracking to library store
- Queue store now uses queueMicrotask batching (matching library store pattern)
- Library store adds changeGeneration counter incremented only on actual data changes
- LibraryController checks changeGeneration before requestUpdate, skipping loading-only transitions
- Reduces unnecessary component re-renders during data loading cycles
2026-03-14 13:53:59 -04:00
yonlu 1ec8f82ef8 docs(14-04): create performance profiling guide
- Backend profiling with pprof: profile types, flame graph reading, common hotspots
- Frontend profiling with Chrome DevTools: Performance panel, Memory panel
- Diagnostic workflows for scrolling jank, slow navigation, memory growth
- References scripts/profile.sh for quick access to all profile types
2026-03-14 13:52:43 -04:00
yonlu 2f7ed70304 perf(14-03): eliminate per-item closure allocation in scroll render paths
- Replace inline arrow closures in renderTrackRow with event delegation via data-index
- Replace inline arrow closures in renderTrackItem with event delegation via data-index
- Add delegated click/dblclick/contextmenu/dragstart handlers on virtualizer elements
- Remove button click in queue panel also delegated via closest('.remove-button')
- Zero new function objects created per renderItem call during scroll
2026-03-14 13:52:34 -04:00
yonlu 6ca0b3c5a8 perf(14-04): RAF-throttle scroll position saves and add overflow-anchor to queue panel
- Replace 100ms debounced scroll save in cover grid with requestAnimationFrame throttling
- Position now saves continuously during scrolling (~16ms) instead of only after stop
- Cancel pending RAF in teardown() to prevent leaks
- Add overflow-anchor: none CSS to queue panel lit-virtualizer
- Keep monkey-patch for lit-virtualizer _correctScrollError with expanded comment explaining why CSS alone is insufficient
2026-03-14 13:51:32 -04:00
yonlu ef18f59a9a docs(14-01): complete CSS containment & GPU scroll promotion plan
- SUMMARY.md with 2 tasks, 7 files, 3 min duration
- STATE.md updated with plan position, metrics, decisions
- ROADMAP.md updated with plan progress (2/4)
2026-03-14 13:48:36 -04:00