From 413d729711625fa187c8101f79f2d9cd068fe805 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Tue, 2 Jun 2026 18:25:52 -0400 Subject: [PATCH] =?UTF-8?q?fix(web):=20drift=20audit=20batch=205=20?= =?UTF-8?q?=E2=80=94=20SSR=20redirect=20+=20radio=20exclude=20cap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two web-side findings from the 2026-06-02 drift audit (#552): - **#559** /library and /playlists each had a +page.server.ts file calling redirect(308, ...). The app is configured as adapter-static + ssr=false (+layout.ts:5), so +page.server.ts files only run at build time / dev server — NEVER at runtime in the deployed build. Direct navigation to /library or /playlists (mobile bookmarks, hand-typed URLs) hit a blank page or 404. We worked around this earlier today by linking the nav directly to /library/artists, but bookmarks stayed broken. Converted both files to +page.ts (universal load) — same redirect logic, runs client-side in the SPA, which is what actually executes. - **#554** Radio auto-refresh built its exclude= query parameter from the ENTIRE queue, growing unbounded each refresh as new tracks were appended. UUIDs are ~36 chars + comma; with the common 8KB query-string limit, ~220 tracks is the ceiling. A multi-hour radio session eventually 414'd; the .catch() ate the error and the player silently stopped topping up — dead radio with no user-visible signal. Cap exclude to the most recent 100 ids; the server's RecentlyPlayedHours filter already handles broader history dedup so the request-side cap only needs to cover the visible queue's recent tail. --- web/src/lib/player/store.svelte.ts | 13 ++++++++++++- .../routes/library/{+page.server.ts => +page.ts} | 5 +++++ .../routes/playlists/{+page.server.ts => +page.ts} | 4 ++++ 3 files changed, 21 insertions(+), 1 deletion(-) rename web/src/routes/library/{+page.server.ts => +page.ts} (60%) rename web/src/routes/playlists/{+page.server.ts => +page.ts} (69%) diff --git a/web/src/lib/player/store.svelte.ts b/web/src/lib/player/store.svelte.ts index 2a7577d3..42650296 100644 --- a/web/src/lib/player/store.svelte.ts +++ b/web/src/lib/player/store.svelte.ts @@ -462,7 +462,18 @@ $effect.root(() => { _radioRefreshInFlight = true; const seed = _radioSeedId; - const exclude = _queue.map((t) => t.id).join(','); + // Drift #554: cap the exclude list so a multi-hour radio session + // doesn't grow the query string past common 8KB limits and start + // 414-ing /api/radio. The .catch() below would silently swallow + // that failure and the player would stop topping up — a dead + // radio. The server's RecentlyPlayedHours filter already handles + // broader history dedup, so the request-side exclude only needs + // to cover the visible queue's recent tail. + const RADIO_EXCLUDE_CAP = 100; + const exclude = _queue + .slice(-RADIO_EXCLUDE_CAP) + .map((t) => t.id) + .join(','); api .get( `/api/radio?seed_track=${encodeURIComponent(seed)}&exclude=${exclude}` diff --git a/web/src/routes/library/+page.server.ts b/web/src/routes/library/+page.ts similarity index 60% rename from web/src/routes/library/+page.server.ts rename to web/src/routes/library/+page.ts index 11a5e992..a5547a20 100644 --- a/web/src/routes/library/+page.server.ts +++ b/web/src/routes/library/+page.ts @@ -4,6 +4,11 @@ import { redirect } from '@sveltejs/kit'; // always renders the active sub-page. Bare hits go to the default tab // (Artists, matching Android's LibraryScreen default). 308 = permanent // + preserve method, so SPA navigations and direct loads behave the same. +// +// Drift #559: this was a +page.server.ts which DOES NOT run in +// production — the app is configured as adapter-static + ssr=false +// (see +layout.ts). +page.ts (universal load) runs client-side, which +// is what the SPA actually executes when a route is hit. export const load = () => { throw redirect(308, '/library/artists'); }; diff --git a/web/src/routes/playlists/+page.server.ts b/web/src/routes/playlists/+page.ts similarity index 69% rename from web/src/routes/playlists/+page.server.ts rename to web/src/routes/playlists/+page.ts index a425747b..d506d638 100644 --- a/web/src/routes/playlists/+page.server.ts +++ b/web/src/routes/playlists/+page.ts @@ -5,6 +5,10 @@ import { redirect } from '@sveltejs/kit'; // still lives here at /routes/playlists/[id]/+page.svelte — that URL // matches the server API shape and is unchanged. // 308 = permanent + preserve method; old bookmarks land on the new URL. +// +// Drift #559: this was a +page.server.ts which DOES NOT run in +// production (adapter-static + ssr=false, see +layout.ts). +page.ts +// universal load runs client-side and IS what the SPA executes. export const load = () => { throw redirect(308, '/library/playlists'); };