From 564e0bf7ca5d1e70eec51bdcbf31328c35402ef8 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Fri, 1 May 2026 23:21:25 -0400 Subject: [PATCH] feat(web): use static SVG asset for album fallback cover MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the hand-rolled inline-data-URL placeholder with a static SVG asset at /placeholders/album-fallback.svg. SvelteKit's adapter-static publishes everything in web/static/ at the URL root and the Go binary embeds the build output, so this resolves identically in dev and prod. Operator can swap the artwork by replacing the file — no code changes. Test updated to use toContain rather than strict equality, since jsdom resolves relative URLs against the test origin. Co-Authored-By: Claude Opus 4.7 (1M context) --- web/src/lib/components/AlbumCard.test.ts | 6 +++-- web/src/lib/media/covers.ts | 30 +++++----------------- web/static/placeholders/album-fallback.svg | 9 +++++++ 3 files changed, 20 insertions(+), 25 deletions(-) create mode 100644 web/static/placeholders/album-fallback.svg diff --git a/web/src/lib/components/AlbumCard.test.ts b/web/src/lib/components/AlbumCard.test.ts index d85c8926..81ee840f 100644 --- a/web/src/lib/components/AlbumCard.test.ts +++ b/web/src/lib/components/AlbumCard.test.ts @@ -62,11 +62,13 @@ describe('AlbumCard', () => { expect(screen.queryByText(/1959/)).not.toBeInTheDocument(); }); - test('broken cover falls back to FALLBACK_COVER data URL', async () => { + test('broken cover falls back to FALLBACK_COVER asset', async () => { const { container } = render(AlbumCard, { props: { album } }); const img = container.querySelector('img') as HTMLImageElement; await fireEvent.error(img); - expect(img.src).toBe(FALLBACK_COVER); + // jsdom resolves relative URLs against the test origin; assert containment + // rather than strict equality to keep the test agnostic of the host. + expect(img.src).toContain(FALLBACK_COVER); }); test('+ queue button fetches album detail and calls enqueueTracks', async () => { diff --git a/web/src/lib/media/covers.ts b/web/src/lib/media/covers.ts index 7c53acab..5304a6fb 100644 --- a/web/src/lib/media/covers.ts +++ b/web/src/lib/media/covers.ts @@ -1,26 +1,10 @@ -// Fallback cover — an inline SVG data URL showing a beamed-eighth-notes -// glyph on a slate field. Used by AlbumCard / CompactTrackCard / PlayerBar's -// onerror handler when the real cover fails (e.g. scanner didn't pick up -// embedded art and there's no sidecar). Data URL avoids an extra HTTP -// round-trip; SVG keeps it crisp at any size we render. -// -// The glyph is two filled note heads connected by a slanted beam — bolder -// and more recognisable than a single-note silhouette, while staying muted -// enough to read as a placeholder rather than real content. -export const FALLBACK_COVER = - 'data:image/svg+xml;utf8,' + - encodeURIComponent( - ` - - - - - - - - - ` - ); +// Fallback cover used when the real cover fails to load (e.g. scanner +// didn't pick up embedded art and there's no sidecar). Served as a static +// asset from web/static/placeholders/ so the operator can swap the artwork +// without touching code. SvelteKit + adapter-static publishes everything +// under web/static/ at the URL root, and the Go binary embeds the build +// output, so this resolves identically in dev and production. +export const FALLBACK_COVER = '/placeholders/album-fallback.svg'; export function coverUrl(albumId: string): string { return `/api/albums/${albumId}/cover`; diff --git a/web/static/placeholders/album-fallback.svg b/web/static/placeholders/album-fallback.svg new file mode 100644 index 00000000..a864747f --- /dev/null +++ b/web/static/placeholders/album-fallback.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file