- 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 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
- 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
- TestCachedUpsertArtistCredit: cache hit returns same ID on second call
- TestCachedLinkArtist: skips duplicate INSERT via linkedCredits cache
- TestCachedLinkArtist_MultiCredit: same artist linked to different credits
- TestCachedUpsertGenre: genre cache returns same ID on repeated calls
- TestResolveReleaseGroup: creates release group, updates cover art on cache hit
- TestResolveReleaseGroup_CacheHit: pre-populated cache returns cached ID
- TestOrphanDeletion: DeleteAudioFile removes row, documents contentless FTS5 limitation
- TestEntityCache_EmptyFields: empty artist name, empty album, AlbumArtist reuse
- TestGetRecordingName: title present, fallback to filename sans extension
- TestToNullInt64: zero as null, positive/negative as valid
- TestToNullString: empty as null, non-empty as valid
- TestSplitGenres: empty/single/multiple genre splitting on || delimiter
- TestMapTrackRow: all 16 columns including string TrackLength, NullInt64 fields
- ToVolume/ToUserVolume at all boundary values (0, 25, 50, 75, 100)
- Out-of-range behavior for both conversion directions
- Full roundtrip characterization (0-100) with ±1 tolerance
- clampVolume at and beyond both boundaries
- stateToMediaControls for all states including unknown fallback
- Full roundtrip: SaveState/RestoreState preserves all fields (tracks, index, shuffle, repeat, shuffleOrder)
- Edge cases: empty queue, single track, no prior save state
- Track order: 10-track order preservation verification
- Overwrite: second SaveState replaces first
- 6 tests all passing with -race
- Add testhelper.go with exported NewTestDB(t *testing.T) *DB
- Opens :memory: SQLite with same connection params as production
- Shares applyPRAGMAs, schema application loop, and runMigrations
- Registers t.Cleanup for automatic DB close
- No orphan cleanup, no functional options, no error return
- Extract inline foreign_keys PRAGMA into shared applyPRAGMAs function
- Add synchronous=NORMAL, cache_size=-8000, mmap_size=67108864 PRAGMAs
- NewDB now calls applyPRAGMAs instead of inline PRAGMA exec
- applyPRAGMAs will be reused by NewTestDB for production-mirroring tests
- Add ScanWarning struct with FilePath/Phase/Err to ScanMetrics
- Add mutex-protected addWarning method for concurrent use
- Reclassify walk, extraction, commit, orphan, variant, FTS failures as warnings
- Update commitBatch to return only fatal tx.Commit errors
- Update cachedLinkArtist to check errors via database.IsUniqueViolation
- Update handleConfigUpdate to capture and log scan warning count
- OnPause, OnPlayPause, OnStop, OnSeek now check errors and log at Warn level
- All four MPRIS closures use yj.logger.Warn for non-fatal error reporting
- No more silently discarded player.Pause() / player.Seek() errors
- Move startupErr from package-level var to YellowJacketApp struct field
- Update OnStartup and OnDomReady to reference yj.startupErr
- Change config.Save() file permissions from 0o666 to 0o644
- Fix nlreturn lint in database/errors.go (pre-existing, blocking commit hook)
- Add PinDefault bool to favorites.Config struct with TOML tag
- Add GetPinDefaultPlaylist() and SetPinDefaultPlaylist() methods
- Include PinDefault in emitFavoritesChanged event payload
- Default to true (pinned) for new config installations
- Add CreatedAt and UpdatedAt string fields to Summary struct
- Format as RFC3339 at all construction sites in playlist.go and favorites.go
- Update TypeScript bindings with new fields in models.ts
- Add uniquePlaylistName method to auto-rename duplicate names with (1), (2), etc.
- Call uniquePlaylistName only from ImportPlaylist, not CreatePlaylist
- Update PlaylistFilePicker to return []string via OpenMultipleFilesDialog
- Add ImportPlaylists method for sequential batch import with partial success
- Replaced two separate Lock/Unlock pairs with single Lock/defer Unlock
- Both p.ctx assignment and p.restoreStateLocked() now run under same lock hold
- Prevents observing partially-initialized state between the two operations
- Queue.SetContext acquires existing q.mu before writing q.ctx
- Library struct gets new mu sync.Mutex; SetContext and SetRescanHooks acquire it
- Playlist Service struct gets new mu sync.Mutex; SetContext and SetFavoritesConfig acquire it
- Library and Playlist release lock before calling post-init methods (registerEventHandlers, migrateExistingPlaylists)
Wire up Go's standard profiling toolkit so it's automatically available
in dev builds and completely absent from production. The profiling
package uses build tags (dev/!dev) to eliminate all pprof, trace, and
timing code from release binaries with zero new dependencies.
- backend/profiling: pprof HTTP server on :6060, /debug/trace endpoint,
block/mutex profiling, and TimeOp helper for structured operation timing
- scripts/profile.sh: interactive menu-driven script that auto-selects
free ports (8080-8089) so multiple profiles can be open simultaneously
- Instrumented key operations: app init, database init, player load/restore,
queue set/restore
- Makefile targets: profile, profile-cpu, profile-heap, profile-trace
- .gitignore: exclude trace-*.out and *.pprof artifacts