Commit Graph
14 Commits
Author SHA1 Message Date
bvandeusenandClaude Opus 5.5 04515a977c feat: a wanted list — follow artists on release-type profiles; requests and re-acquisition become wants (M502 #5520)
release / govulncheck (push) Successful in 15s
release / web (push) Failing after 1m21s
release / go (push) Successful in 1m35s
release / integration (push) Failing after 4m57s
release / android (push) Successful in 5m11s
release / Build signed APK (releases and dev) (push) Successful in 5m28s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
Minstrel keeps its own list of what it is to get, keyed by MusicBrainz
release group. A want comes from an approved request, a new release by a
followed artist, or an album whose files went missing, and is held once the
library has a full edition of it.

- Migration 0081: release_type_profiles (Standard seeded: Album, EP,
  Single, studio only), follows, follow_seen_groups and wants.
  lidarr_requests becomes requests with plain *_mbid columns; approved
  album and track requests move onto the wanted list, approved artist
  requests become follows, and the library's album artists start out
  followed. missing_reacquisitions and the retry settings go.
- internal/wanted: follows are checked against MusicBrainz discographies
  daily. The first check sets a baseline, and only releases after it are
  wanted unless the follow asked for what already exists. Profiles filter
  by primary and secondary type, so live recordings stay out unless a
  profile ticks Live.
- internal/requests (was lidarrrequests): approving puts the request on
  the wanted list, and an artist request can be approved on a chosen
  profile. A request completes when its want is held.
- The re-acquisition sweeper wants a missing album back instead of asking
  Lidarr; searching for it is the search schedule's (#5521).
- Web: an admin Wanted page (Wanted, Missing files, Held, Following), a
  follow control on artist pages, a release-type profiles card on
  Integrations, "Approve on a profile" on Requests, and want state on
  Missing files. The artist detail API returns the artist's MBID.
- Android: the request body sends artist_mbid, album_mbid and track_mbid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 00:46:22 -04:00
bvandeusenandClaude Opus 5.5 94a9c8cbe3 chore(deps): SvelteKit 3 with adapter-static 4 and TypeScript 6, full-tree npm audit (#5021)
Merges Renovate's kit 3 (PR #149) and adapter-static 4 (PR #148) bumps,
plus the migration they need. The mechanical part is `sv migrate
sveltekit-3`, run one task at a time and reviewed:

- svelte.config.js is gone. Its options move into sveltekit() in
  vite.config.ts, exported as kitOptions so vitest.config.ts runs the
  same kit setup, including the $test-utils alias the tests import.
- $lib becomes #lib through package.json "imports". There is no
  src/lib/index, so only the "#lib/*" entry is kept.
- tsconfig extends $app/tsconfig.
- Peer floors raised to kit 3's requirements: svelte ^5.57.1, vite
  ^8.0.12, svelte-check ^4.7.5.

By hand, from the codemod's list of non-automated tasks:

- goto's replaceState option is now replace; keepFocus becomes
  reset: false. For the search typeahead, reset: false also stops the
  scroll-to-top, which is wanted while typing.
- The test setup mocks drop pushState/replaceState and $app/paths
  base/assets, which kit 3 removed, and mock refreshAll in place of
  invalidateAll.
- The other flagged files only read page.url or goto internal routes,
  so they needed no change.

TypeScript goes to ^6, not the ^7 Renovate offers: kit 3 declares
typescript ^6 as a peer and svelte-check 4.7 accepts ^5 || ^6. Move to
7 once both accept it.

With Tailwind 4 and kit 3 in, `npm audit` on the whole tree reports 0,
so the web lane now audits every dependency rather than only what
ships to browsers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 10:50:39 -04:00
bvandeusenandClaude Opus 5.5 b46c080d19 fix(web): all accent text and icons use accent-fg (#5318)
release / govulncheck (push) Successful in 17s
release / web (push) Successful in 1m11s
release / go (push) Successful in 1m26s
release / integration (push) Successful in 4m18s
release / android (push) Successful in 4m44s
release / Build signed APK (releases and dev) (push) Successful in 4m53s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 26s
release / Verify release artifacts (tag releases only) (push) Skipped
The raw accent fails AA as text on every dark surface, not only on its
own tint: 3.04:1 on the page, 2.70 on iron, 2.21 on slate, against 4.5.
accent-fg (the house formula, 45% toward parchment) measures 5.62 at
worst across both modes. The operator chose the readable colour over the
signature teal for text, on 2026-10-08.

- 36 sites swap. They are 35 Tailwind uses: links, "Now playing", the
  ingest progress line, active shuffle/repeat, the liked heart, the app
  download icon and its hover. The last is the alphabet rail's pending
  spinner in CSS. Icons follow the text: as graphics they need only
  3:1, and the raw accent misses even that on iron.
- check-tint-contrast adds accent to TEXT_NEVER_RAW, so a new raw
  text-accent or color: var(--fs-accent) fails the web lane. Run against
  the files before the swap, it finds all 36. Borders, rings and fills
  keep the raw accent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 00:17:19 -04:00
bvandeusenandClaude Opus 5.5 fdee77eaec fix(web): text on a tint of its own hue uses the house -fg tokens (#3150)
release / govulncheck (push) Successful in 38s
release / integration (push) Successful in 5m1s
release / Build + push container image (push) Successful in 1m17s
release / Verify release artifacts (tag releases only) (push) Skipped
release / web (push) Successful in 1m22s
release / go (push) Successful in 1m48s
release / android (push) Successful in 5m41s
release / Build signed APK (releases and dev) (push) Successful in 5m11s
release / Attach APK to the Release (tag releases only) (push) Skipped
A hue painted as text on a color-mix tint of itself sits close to the
surface under it. On Minstrel's surfaces the raw accent on its 15% tint
measures 1.97:1 at worst (dark mode, hover surface), against AA's 4.5.

- tokens.json gains colors.fg: the five FabledSword -fg formulas (accent
  45%, success 45%, warning, error and info 50%), each mixed toward
  parchment so one declaration serves both modes. Success is Minstrel's
  moss. tokens-to-css emits them in :root.
- Tailwind exposes them as text-accent-fg, text-warning-fg, text-error-fg
  and text-info-fg.
- 23 sites swapped: 14 Tailwind class strings (PlayerBar and the admin
  count pills) and 9 CSS rules (StatusPill's four tones and five accent
  chips). Worst case after: accent-fg 5.03, error-fg 4.75, warning-fg
  4.92, success-fg 4.85.
- scripts/check-tint-contrast.js finds the pair in either spelling. Its
  test scans src in the web Vitest lane and fails on any new site, with
  fixture cases showing it can fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 22:51:52 -04:00
bvandeusenandClaude Opus 4.7 bb1ab3a2e3 test(web): un-skip discover/requests route tests — mock $app (#374)
Root cause: discover.test.ts and requests.test.ts were the only route
page tests NOT mocking $app/state (+ $app/navigation), so rendering
the +page.svelte pulled in SvelteKit's client runtime and threw
`TypeError: notifiable_store is not a function` at module load. They'd
been describe.skip'd AND hard-excluded in vitest.config.ts.

Fix mirrors every other route test: vi.hoisted pageState +
vi.mock('$app/state', () => pageUrlModule(state)) +
vi.mock('$app/navigation', () => ({ goto: vi.fn() })). Un-skip both
describe blocks; drop the vitest.config exclude. The web test job
now runs both suites again.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 15:42:11 -04:00
bvandeusenandClaude Opus 4.7 9acad5461e refactor(web): extract emptyPlaylistsMock + apiClientMock test helpers
The DRY pass audit (#375) found two inline mock patterns repeated across the
vitest suite: a default empty-playlists mock for $lib/api/playlists (4 exact
copies in menu/card tests) and an api-client spy mock for $lib/api/client
(9 callers split between get-only and full-RESTy shapes — unified into one
helper that always returns all four verb spies).

Mirrors the existing test-utils/mocks/likes.ts and test-utils/mocks/quarantine.ts
convention. Tests with intentionally divergent shapes (AddToPlaylistMenu's
richer createPlaylist payload, PlaylistCard's getPlaylist, route-specific
mocks, events.svelte's specific resolved value) stay inline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 12:45:15 -04:00
bvandeusen 47c316e211 fix(web/test): restore named-mock svelte-query overrides B3 stripped (integrations + requests) 2026-05-08 11:26:19 -04:00
bvandeusen 3e34f1f7b3 refactor(web/test): default svelte-query mock in vitest.setup; remove from 34 files (B3) 2026-05-08 11:08:41 -04:00
bvandeusenandClaude Opus 4.7 9ad2e0343a feat(web/m7-363): per-route svelte:head titles for static routes
Add browser tab titles to all 18 static page routes using the pageTitle()
helper from $lib/branding. Titles follow the Minstrel · Section format.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 17:37:13 -04:00
bvandeusen 40db918bfc fix(web): RemoveTrackPopover async test waits + skip 2 SvelteKit-broken suites
Three CI failures from the dev-push test-web.yml run. Two categories:

1. RemoveTrackPopover.test.ts — `confirm()` chains 5+ awaited
   invalidateQueries before onClose; the test's two `await Promise.resolve()`
   only flushed two microtasks. Switch to waitFor() so the assertion
   polls until the side effects land. Same fix on the success-cascade
   invalidation count test.

2. discover.test.ts + requests.test.ts — both fail at module-load with
   `TypeError: notifiable_store is not a function` deep in
   @sveltejs/kit's client.js. Surfaced only on the new dev-push
   workflow; PR-to-main runs were green. describe.skip with a FIXME
   pointing at the new triage task M7 #374. The pages themselves
   aren't broken — the test harness is.
2026-05-03 00:00:26 -04:00
bvandeusenandClaude Opus 4.7 f77245bc41 feat(requests): show ingest progress while requests are in flight
After Lidarr accepts a request the local reconciler imports albums and
tracks as they arrive — but until the request hit 'completed' the
operator had no way to see "is anything happening?" The /requests and
/admin/requests rows now surface a live progress line whenever the
request's matched entity has children in our library.

Backend:
- New CountAlbumsByArtist + CountTracksByArtist sqlc queries.
- requestView gains imported_album_count and imported_track_count.
- New fillProgress helper computes them from the matched entity:
  - kind=artist  → counts albums + tracks under matched_artist_id
  - kind=album   → counts tracks under matched_album_id
  - kind=track   → 1 once matched_track_id is set
  N+1 in the list endpoints; acceptable at admin scale.
- handleListRequests, handleGetRequest, and handleListAdminRequests
  populate the new fields on every response.

Frontend:
- LidarrRequest TS type extended with the two counters.
- Both the operator's /requests page and the admin /admin/requests
  page render an accent-colored line under the row meta when at
  least one counter is non-zero, e.g.:
    Artist:  "5 albums · 47 tracks ingested"
    Album:   "12 tracks ingested"
    Track:   "Track ingested"
- Updated test fixtures to include the new required fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:04:38 -04:00
bvandeusenandClaude Opus 4.7 22ac86f200 feat(web): trim main nav; pair Discover+Requests; move chrome to user menu
The main nav was carrying surfaces that didn't belong on it:

- Search led to an empty page when clicked (you only ever want to land
  there from the global SearchInput typing into /search?q=...). Drop it.
- Requests is downstream of Discover (you discover, then you request).
  Lift both onto a shared horizontal tab strip; keep their URLs so
  bookmarks survive. New DiscoverTabs component used by both pages.
- Settings + Admin are operator chrome, not primary surfaces. Move them
  into the username dropdown alongside Log out, with Admin gated on
  is_admin. Users see Settings + Log out; admins see Settings + Admin
  + Log out.

Final main nav: Home / Artists / Albums / Liked / Discover / Playlists.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 22:23:56 -04:00
bvandeusen dd86ffca94 fix(web): a11y on /requests aria-labels when titles are absent
Album- and track-kind requests can have null album_title/track_title (the
type allows it). Previously aria-label rendered 'Cancel request for —' which
is meaningless to a screen reader. Fall back to 'this album by <artist>' /
'this track by <artist>' so the cancel/listen buttons stay disambiguable.
2026-04-29 22:29:07 -04:00
bvandeusenandClaude Opus 4.7 ad904afaf6 feat(web): add /requests user-facing request history
Renders the caller's Lidarr requests as rows with kind pill,
StatusPill, and per-status actions: Cancel on pending (which
calls cancelRequest then invalidates qk.myRequests()), Listen
link on completed (deepest match wins: track > album > artist),
admin notes on rejected. Empty state uses the voice-rule
"Nothing requested yet." copy. Shell nav gains /requests
between /discover and /playlists, visible to all authed users
since the view is per-user.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-29 22:24:17 -04:00