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