feat(web): use static SVG asset for album fallback cover

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) <noreply@anthropic.com>
This commit is contained in:
2026-05-01 23:21:25 -04:00
parent e6552f4ae2
commit 564e0bf7ca
3 changed files with 20 additions and 25 deletions
+4 -2
View File
@@ -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 () => {
+7 -23
View File
@@ -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(
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet">
<rect width="100" height="100" fill="#1f1f23"/>
<g fill="#7a7a82">
<ellipse cx="30" cy="72" rx="10" ry="8"/>
<rect x="38" y="34" width="4" height="40"/>
<ellipse cx="68" cy="64" rx="10" ry="8"/>
<rect x="76" y="26" width="4" height="40"/>
<polygon points="38,34 80,26 80,34 38,42"/>
</g>
</svg>`
);
// 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`;