Complete rewrite of the artist image pipeline:
STORAGE:
- Migration 16: artist_images table tracking source, URL, path,
primary flag, dimensions per image (up to 10 per artist)
- Directory structure: artist-images/{mbid[:2]}/{mbid}/ with
primary.jpg + primary_sm.jpg/_md.jpg/_lg.jpg thumbnails
- Miss marker (.miss file) prevents re-fetching artists with no image
SOURCES (priority order):
1. MusicBrainz direct image relations (Wikimedia Commons)
2. Wikidata P18 property (Wikimedia Commons)
3. Wikipedia lead image (NEW — via Wikidata sitelinks → Wikipedia API)
Each source is checked, deduplicated, and the first available
image becomes the primary with sm/md/lg thumbnail generation
(100px/200px/400px, matching cover art tier sizes).
ASSET SERVING:
- /artist-images/ path registered with Wails asset handler
- Serves files via http.FileServer from the artist-images directory
- Same pattern as /covers/ for cover art
ARTIST MODEL:
- Artist struct gains ImageSmall/ImageMedium/ImageLarge fields
- resolveArtistImages does bulk MBID lookup → disk stat for each
- Populated in GetAllArtists and GetAllArtistsByLibrary
GRID VIEW:
- artists-view uses model URLs directly (no more base64 data URLs)
- Size selection based on imageSize * devicePixelRatio (like cover-grid)
- Removed batch GetArtistImages call and in-memory cache — no longer needed
Track details dialog now shows:
1. Green checkmark badge next to the track title when the recording
has a MusicBrainz ID (hover: 'Metadata verified by MusicBrainz')
2. MusicBrainz section at the bottom with clickable MBID links for:
- Recording (track) → musicbrainz.org/recording/{mbid}
- Release Group (album) → musicbrainz.org/release-group/{mbid}
- Artist → musicbrainz.org/artist/{mbid}
Links open in the system browser. Only shown for entities that
have MBIDs from audio file tags.
Backend: GetTrackMBIDs(filePath) Wails binding queries recording,
release_group, and artist mbid columns via a single JOIN query.
Frontend: loaded async when the dialog opens, non-blocking.
COALESCE(last_played, '') returned empty string which can't scan into
time.Time. Removed COALESCE, use sql.NullTime instead. Format to string
only when Valid.
Backend:
- GetAllTracksWithFullMetadata queries now select play_count and last_played
- mapTrackRow accepts and passes through PlayCount/LastPlayed
- PlayCount + LastPlayed added to library.Track struct
- sqlcgen Row types updated with new fields
Frontend:
- PlayCount + LastPlayed added to library.Track TypeScript model
- 'Plays' column added to track-list column definitions (60px, right-aligned, sortable)
Queries without play data (search, genre, album) pass 0/empty defaults.
Backend:
- Added play_count and days_since_played to rule engine field whitelist
- days_since_played uses julianday() expression with COALESCE for NULL handling
- Never-played tracks (NULL last_played) match 'greater_than' but not 'less_than'
- Added PlayCount + LastPlayed to library.Track struct
- Evaluate query selects play_count and last_played from track_metadata
- Added play_count to sort field options
Frontend:
- Added play_count and days_since_played to field and numeric field lists
- Added play_count to sort options
All 49 rule engine + 15 service tests pass unchanged.
The GetAudioFilesByReleaseGroup SQL query only selected 6 columns,
missing audio properties (sample_rate, bit_depth, channels, bitrate,
file_size) and metadata (album, genre, year, composer, file_type).
This caused track details opened from the album view to show dashes
instead of actual values. Expanded the query to match
GetAllTracksWithFullMetadata and updated GetAlbumTracks to use the
shared mapTrackRow helper.
- Fix 10 err113 violations: extract dynamic errors to package-level sentinels
- Fix 12 errcheck violations: handle unchecked error returns in player,
metadata, and config packages
- Fix 4 revive stutter warnings: rename player.PlayerState to player.State,
player.PlayerVolume to player.Volume, queue.QueueTrack to queue.Track,
queue.QueueState to queue.State
- Fix 2 staticcheck SA4001: simplify *&x to x in assets handler
- Fix 5 unused constants: remove dead AudioFileType iota block in models
- Fix gci/gofumpt/wsl formatting issues across multiple files
- Add gofumpt module-path setting to .golangci.yml for correct import grouping
- Fix player test: gate integration test behind YELLOWJACKET_INTEGRATION env var
instead of only skipping in CI, and replace t.Errorf+t.Failed with t.Fatalf
- Remove continue-on-error from golangci-lint CI step so linting is now required