fix(clients): never leave cover tiles blank — shared web <Cover> + Android Coil placeholder/error
test-web / test (push) Successful in 48s
android / Build + lint + test (push) Successful in 4m10s

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>
This commit is contained in:
2026-06-20 12:29:05 -04:00
parent 97e0e88483
commit 096a3c0b15
5 changed files with 107 additions and 38 deletions
+8 -13
View File
@@ -2,9 +2,10 @@
import type { ArtistRef, TrackRef } from '$lib/api/types';
import { api } from '$lib/api/client';
import { playQueue, enqueueTracks } from '$lib/player/store.svelte';
import { Disc3, Play } from 'lucide-svelte';
import { Play } from 'lucide-svelte';
import ArtistMenu from './ArtistMenu.svelte';
import CardActionCluster from './CardActionCluster.svelte';
import Cover from './Cover.svelte';
import { listArtistTracks } from '$lib/api/artists';
let { artist }: { artist: ArtistRef } = $props();
@@ -48,18 +49,12 @@
class="block rounded focus-visible:ring-2 focus-visible:ring-accent"
>
<div class="art-wrap relative mx-auto aspect-square w-full overflow-hidden rounded-full bg-surface-hover">
{#if artist.cover_url}
<img
src={artist.cover_url}
alt=""
class="h-full w-full object-cover transition-transform group-hover:scale-[1.03]"
loading="lazy"
/>
{:else}
<div class="flex h-full w-full items-center justify-center">
<Disc3 strokeWidth={1} class="h-4/5 w-4/5 text-text-muted" />
</div>
{/if}
<Cover
src={artist.cover_url}
shape="round"
fallback="artist"
class="transition-transform group-hover:scale-[1.03]"
/>
<button
type="button"
aria-label={`Play ${artist.name}`}