Commit Graph

4 Commits

Author SHA1 Message Date
Jonathan dcf0bb0696 feat: show real on-disk tracklist in the Library album modal (#9)
The Library modal showed MusicBrainz's canonical tracklist, hiding incomplete
downloads / edition or track-order mismatches, and showed nothing for albums
without an rgMbid. Now it shows the REAL files on disk.

- LibraryItem gains trackNames String[] (migration add_library_track_names):
  the album's on-disk "## Title.ext" audio filenames.
- Captured at import (pipeline._import lists the final album folder) and at scan
  (scan._record_owned now also refreshes trackNames on re-scan via ON CONFLICT
  DO UPDATE + xmax=0 to preserve the newly-imported flag). New shared
  library.list_audio_files() helper.
- /api/library exposes trackNames; the AlbumModal prefers on-disk tracks when
  present (parsed "## Title" → position/title, zero network, labeled "On disk ·
  N tracks"), falling back to the MusicBrainz listing otherwise. Items imported
  before this column show the MB fallback until re-scanned.

worker 221 tests / 7-skip, web 153, tsc + build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 12:55:54 +02:00
Jonathan 17bed331a9 feat(web): Follow button + clickable artist in album modals
AlbumModal showed the artist name as static text — no way to follow the artist
or jump to them. Adds a Follow affordance (todo #13):

- AlbumInfo gains optional `artistMbid`. When present, the modal title links the
  artist name to /discover/artist/{mbid} and shows a Follow/Following button that
  reflects live follow state.
- Follow state fetched via a new lightweight GET /api/artists?mbid= check
  (avoids pulling the full watched list + discography); Follow reuses
  POST /api/artists. Non-ok POST surfaces a toast (also chips at todo #14).
- Wired artistMbid in the three discovery contexts where following is valuable:
  Discover, Discover artist preview, and Last.fm. Library/discography callers
  pass none (already-followed artists) so the button stays hidden — graceful.

web 152 tests (the ?mbid= follow-state check asserted), tsc + build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 11:58:01 +02:00
Jonathan bdbf9d237e feat(web): shared Postgres read-through cache for MusicBrainz (ApiCache)
Reopening an artist (discography), an album (tracklist), or repeating a lookup
re-hit MusicBrainz every time with no cache and no throttle. Adds a generic
Postgres-backed read-through cache keyed on the LOGICAL operation (not the
request URL) so the worker and web app share entries.

- New ApiCache(key, json, fetchedAt) model + migration (generic name so the
  Last.fm browse cache can ride the same table).
- lib/apicache.ts cached(): read-through, TTL from fetchedAt (tunable, no
  migration), serve-stale-on-outage, never caches null.
- Wrap the high-level MB fns with logical keys + TTLs: discography &
  tracklists & artist-name 30d, searchReleaseGroup 7d, searchArtists 1d.
  Search keys normalized (trim+lowercase) to match the worker byte-for-byte.
- album-modal: a session-scoped Map<rgMbid,Track[]> so reopening is instant
  with zero network (complements the DB cache).
- Test setup clears ApiCache between tests. web 147 tests, tsc + build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 11:17:51 +02:00
Jonathan 34235a2355 feat(web): CoverArt + reusable AlbumModal
CoverArt loads album art from the Cover Art Archive CDN by release-group MBID
(browser-side, cached, graceful placeholder fallback). AlbumModal shows cover +
tracklist (fetched from MB) with slots for a status chip and context actions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 15:14:34 +02:00