- Change dev mode default from Debug to Info to prevent stdout flooding
- Add resolveLogLevel() function with YJ_LOG_LEVEL env var support
- Accept debug/info/warn/error values (case-insensitive)
- Prevents neovim display corruption during library scans
The search_index is a contentless FTS5 table (content=''), which
SQLite does not support DELETE on. ClearSearchIndex now drops and
recreates the virtual table. Single-row DeleteSearchIndex becomes
a no-op since contentless FTS5 also cannot delete individual rows;
stale entries are harmless (search JOINs filter them out) and the
index is fully rebuilt during FullRescan.
- Migration 5 rebuilds release_groups with UNIQUE(name, album_artist_credit_id)
- Drops and recreates track_metadata VIEW during table rebuild
- Temporarily disables FK checks for safe table rebuild
- Entity cache now keys by album name + artist credit ID
- Update tests to use composite cache keys
- Change UNIQUE(name) to UNIQUE(name, album_artist_credit_id) in schema
- Update UpsertReleaseGroup ON CONFLICT to match composite key
- Rename GetReleaseGroupByName to GetReleaseGroupByNameAndArtist with two params
- Regenerate sqlc code
- Add height: 49px and overflow: hidden to .track-item CSS
- Add overflow: hidden to .track-details CSS
- Fixed height ensures lit-virtualizer _scrollSize is stable from first render
- Prevents scrollbar lag on large queues (20k+ tracks) caused by dynamic item size averaging
Archive milestone artifacts:
- milestones/v1.0-ROADMAP.md (full roadmap archive)
- milestones/v1.0-REQUIREMENTS.md (26/26 requirements complete)
- milestones/v1.0-phases/ (8 phase directories with plans, summaries, verifications)
Updated:
- PROJECT.md: full evolution review, all consolidation requirements validated
- ROADMAP.md: collapsed to milestone summary with archive link
- STATE.md: reset for next milestone
- MILESTONES.md: created with stats and accomplishments
- RETROSPECTIVE.md: created with lessons learned
Deleted:
- REQUIREMENTS.md (archived, fresh for next milestone)
8 phases, 17 plans, 34 tasks, 84 tests added, 6 days
repeat() as child content of lit-virtualizer bypasses virtualization,
rendering ALL items instead of only visible ones. This caused 2+ minute
loading times and UI freezing with large libraries.
The correct approach: use lit-virtualizer's native .renderItem and
.keyFunction properties which integrate with its scroll-based viewport
management.
- cover-grid dynamic text tiers use --yj-text-xs/sm/md/lg tokens
- track-list sort toolbar, row text, and fav icon use design tokens
- queue-panel header and track text use type scale tokens
- track-details, track-info, artist-details, genre-details use tokens
- designTokens imported and prepended to static styles in all 8 files
- Sidebar: convert all em-based padding/gap to px values, icon sizes to --yj-icon-md
- Now-playing: cover placeholder icon to --yj-icon-lg, text sizes to type tokens
- Search-bar: search icon to --yj-icon-sm, input font to --yj-text-md
- Audio-player: convert em-based gap/padding/margin to px values
- Seek-bar: tooltip font to --yj-text-lg, margin from em to px
- Volume-control: padding/margin from em to px, thumb sizes from em to px
- All components import designTokens and include in static styles
- Hoist searchCtrl.term lookup outside per-column loop in renderTrackRow
- Search highlighting already short-circuits when search term is empty
- Import classMap in queue-panel.ts
- Replace array filter/join class construction with classMap in renderTrackItem
- Eliminates per-row array allocation in queue panel render hot path
- Import classMap from lit/directives/class-map.js
- Replace array filter/join class construction with classMap for track-row
- Convert fav-icon conditional class to classMap
- Convert cell alignment classes to classMap
- Eliminates per-row array allocation and string join in render hot path
- cover-grid: all 3 virtualizers (single, before-split, after-split) use repeat() with album.ID key
- artists-view uses repeat() with artist.ID key
- genres-view uses repeat() with genre.name key
- Removed gridKeyFunction (now inlined in repeat calls)
- Removed .renderItem and .keyFunction properties from all virtualizers
- New tokens.css.ts exports designTokens with --yj-icon-sm/md/lg and --yj-text-xs/sm/md/lg/xl
- Values derived from existing pixel sizes scattered across codebase
- :host scoped for component-level adoption via static styles array
- Library store notify() coalesces multiple calls per microtask tick into one subscriber notification
- Search input debounces store propagation by 150ms while keeping instant visual feedback
- Empty search input clears immediately for responsive UX
- Add phase1Meta parameter to resolveRemainingTracks
- Filter out already-resolved paths before lookupTrackMetaBatch call
- Merge Phase 1 results into Phase 2 lookup map
- Pass batchMeta from SetQueue call site to resolveRemainingTracks
- For 1000-track queue with Phase 1 resolving 50, Phase 2 now queries 950 instead of 1000
- Add persistAddTrack/persistAddTracks for O(1) append operations
- Add persistInsertTracks with variable-N position shift for insert-at operations
- Add persistRemoveTrack with single DELETE + position shift
- Wire AddTrack/AddTracks to use incremental INSERT (no full table rewrite)
- Wire InsertNext/InsertNextTracks/InsertTracksAt to use position shift + INSERT
- Wire RemoveTrack to use single DELETE + shift (no full table rewrite)
- RemoveTracks keeps full persistTracks rewrite (bulk operation per design)
- Preserve shuffle order regeneration in all mutation paths
- Remove eagerFetch() call from LibraryStore constructor
- Add deferEagerFetch() that waits for DOMContentLoaded event
- App shell renders before backend data roundtrips begin
- All 4 data types (tracks, albums, artists, genres) still loaded eagerly
- invalidate() still calls eagerFetch() directly for post-scan refresh
- 7 SAFETY comments in search.go (FTS5 MATCH/INSERT/DELETE operations)
- 3 SAFETY comments in library.go (FTS5 INSERT/DELETE in commitNewAudioFile, updateAudioFileMetadata)
- 1 SAFETY comment in rescan.go (FTS5 DELETE in clearAllLibraryData)
- 1 SAFETY comment in persistence.go (variable-count multi-row INSERT)
- Cross-references link library.go/rescan.go back to search.go
- Two-part format: why sqlc can't handle it + what makes it safe
- Add LookupTrackMetaByPaths sqlc query using track_metadata VIEW with sqlc.slice()
- Replace hand-crafted fmt.Sprintf IN clause in lookupChunk with sqlc-generated call
- Preserve lookupTrackMetaBatch chunking at maxSQLiteVars (900)
- All queue tests pass with -race
- Add backend/events/cmd/genevents/main.go using go/ast for deterministic output
- Add //go:generate directive to backend/events/events.go
- Generate frontend/src/events.ts with all 21 constants including LibraryConfigChanged
- Atomic file writes via temp file + rename
- Comment groups preserved with trailing period stripping