Cover tiles (worst in the "You might like" home row, which surfaces
unplayed items whose art is often not yet backfilled) sat empty while
loading and stayed blank on a 404. The server returns a fast 404; the
gap was missing client-side loading/fallback states.
Web: new shared Cover.svelte owns the loading placeholder + onerror
fallback (static cover, or Disc3 for artists). AlbumCard, ArtistCard and
CompactTrackCard now reuse it instead of three hand-rolled <img> tags
that disagreed on fallback handling — notably ArtistCard had no onerror.
Android: ServerImage tracks Coil's load state so the per-caller fallback
doubles as a placeholder (loading) and an error state (404 / unreachable),
instead of only guarding the null-URL case. All five call sites pass an
explicit size modifier, so the new Box wrapper is layout-safe.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CompactTrackCard is a short one-line row but reused CardActionCluster,
which corner-splits Like+Add (top) and the kebab menu (bottom). That split
is right for the tall square Album/Artist cards but makes the two groups
collide on the compact row's hover state. Give the compact card a single
inline, vertically-centred right cluster (Like + Add + menu in one group)
and widen its right padding reserve to match.
In the desktop PlayerBar, the left info column was a fixed w-72 (title kept
truncating) while the seek column was flex-1 (the scrubber hogged the slack
on wide screens). Let the left column grow up to max-w-md while holding its
288px floor at md, and cap the seek/transport column at max-w-xl centred, so
freed width flows to the title instead of stretching the bar.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Operator clarification: the 'compact like the app' request was for
Most Played, not Rediscover.
- CompactTrackCard rewritten as a horizontal Row: cover thumbnail
left, title + artist column right. w-72 (~288 px) matches the
Android CompactTrackTile's 176 dp visual weight. Most Played's
existing chunked-rows-in-shared-scroller layout now reads like
the Android multi-row LazyHorizontalGrid.
- Rediscover steps back from the compact w-32/w-28 widths to w-40/
w-36 (the pre-PR-#66 sizes) — the size hierarchy still works
with the regular AlbumCard treatment.
The MediaCard consolidation in the discovery doc would have needed
~14 props to cover the differentiating bits (wrapping element a vs
button, art-shape, art-fallback strategy, click semantics, title
align/size, subtitle count). That's a god-prop blob that's harder to
reason about than three explicit cards.
Narrowed to the actually-drifted bit: the action cluster
(LikeButton + Plus + bottom-right menu slot, including the
stopPropagation defensive wrappers). One small component covers
AlbumCard / ArtistCard / CompactTrackCard so the focus-ring and
button-styling drift can't recur.
Each card keeps its own data-fetching, art container, and wrapping
element since those have load-bearing differences. The consolidation
addresses drift, not LOC for its own sake.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cleans up the 8 svelte-check warnings I deferred in prior CI runs.
Two distinct categories — handling them differently:
REAL REACTIVITY BUGS — fixed:
- AlbumMenu.svelte / ArtistMenu.svelte: cachedTracks initialized
from the `tracks` prop only at component creation. When the menu
is reused across rows (operator opens menu on one album, then
another), the cache from the first open serves stale tracks.
Fix: initialize cachedTracks to null; ensureTracks() reads the
current `tracks` prop on each call before falling through to
the network fetch.
INTENTIONAL PATTERNS LINT FLAGGED — suppressed with explanatory
comments:
- AlbumMenu / ArtistMenu / PlaylistCard menu containers: the
onclick={(e) => e.stopPropagation()} is a guard against the
window-level "any click closes the menu" listener — not
user-facing interaction. A paired keyboard handler is
intentionally absent (Escape-to-close lives on svelte:window
onkeydown). Suppressed a11y_click_events_have_key_events with
explanatory comments.
- CompactTrackCard overlay div: catches clicks on its inner
LikeButton + queue button so they don't bubble to the wrapping
play-card button. Inner buttons carry their own keyboard
handling. Suppressed both a11y_click_events_have_key_events and
a11y_no_static_element_interactions.
- theme.svelte.ts module-scope `_resolved = $state(resolve(_theme))`:
module-init reads _theme to seed _resolved; subsequent updates
go through setTheme() and the matchMedia listener. $derived
doesn't apply at module scope. Suppressed state_referenced_locally
with an explanatory comment.
Promised in the prior fix-forward: "I'll fix bugs when I see them
or open a follow-up task with a specific name, not park them in an
umbrella." Following through.
Small 140px clickable card showing album cover, title, and artist name.
Clicking calls playQueue(sectionTracks, index) to play through the full
section list. Includes Vitest tests for click and render behaviour.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>