From 65057473c68514572b6d3417a214b7ba3923c56d Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sun, 10 May 2026 15:29:30 -0400 Subject: [PATCH] =?UTF-8?q?refactor(web):=20break=20auth=20=E2=86=94=20pla?= =?UTF-8?q?yer=20import=20cycle=20via=20session-end=20signal=20(#375)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit auth/store.svelte.ts no longer imports from $lib/player. Logout broadcasts via auth/sessionEnd.svelte.ts (a tiny tick + outgoing userId pair); player subscribes through $effect.root and owns the player-specific teardown (clearPersistedQueue + playQueue([]) + closeQueueDrawer()). Cycle fully inverted — auth is now a pure leaf for player; future session-end consumers (download cache, recent-search history) plug in by adding their own effect on the same signal. Co-Authored-By: Claude Opus 4.7 (1M context) --- web/src/lib/auth/sessionEnd.svelte.ts | 17 +++++++++++++++++ web/src/lib/auth/store.svelte.ts | 9 ++------- web/src/lib/player/store.svelte.ts | 19 ++++++++++++++++++- 3 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 web/src/lib/auth/sessionEnd.svelte.ts diff --git a/web/src/lib/auth/sessionEnd.svelte.ts b/web/src/lib/auth/sessionEnd.svelte.ts new file mode 100644 index 00000000..657c5b13 --- /dev/null +++ b/web/src/lib/auth/sessionEnd.svelte.ts @@ -0,0 +1,17 @@ +// Logout broadcast for cross-domain teardown. Auth bumps the tick +// (with the outgoing user id) and downstream modules react via $effect +// on `sessionEnd.tick`. Keeps auth from importing player — the cycle +// stays inverted (player imports auth, never the reverse). + +let _tick = $state(0); +let _userId = $state(null); + +export const sessionEnd = { + get tick() { return _tick; }, + get userId() { return _userId; }, +}; + +export function signalSessionEnd(userId: string | null): void { + _userId = userId; + _tick++; +} diff --git a/web/src/lib/auth/store.svelte.ts b/web/src/lib/auth/store.svelte.ts index 005c6026..990439fa 100644 --- a/web/src/lib/auth/store.svelte.ts +++ b/web/src/lib/auth/store.svelte.ts @@ -1,7 +1,6 @@ import { api, type User, type LoginResponse } from '$lib/api/client'; import { queryClient } from '$lib/query/client'; -import { clearPersistedQueue } from '$lib/player/persisted'; -import { playQueue, closeQueueDrawer } from '$lib/player/store.svelte'; +import { signalSessionEnd } from './sessionEnd.svelte'; import { user, setUser } from './user.svelte'; // Re-export so existing `import { user } from '$lib/auth/store.svelte'` @@ -55,11 +54,7 @@ export async function logout(opts: { silent?: boolean } = {}): Promise { // best effort — server-side session may already be gone } } + signalSessionEnd(userId ?? null); setUser(null); queryClient.clear(); - // M7 #364: clear queue persistence + reset in-memory queue + close drawer - // so a subsequent login doesn't inherit the previous user's playback state. - if (userId) clearPersistedQueue(userId); - playQueue([]); - closeQueueDrawer(); } diff --git a/web/src/lib/player/store.svelte.ts b/web/src/lib/player/store.svelte.ts index 9f730b20..362242e4 100644 --- a/web/src/lib/player/store.svelte.ts +++ b/web/src/lib/player/store.svelte.ts @@ -2,7 +2,8 @@ import type { TrackRef, RadioResponse } from '$lib/api/types'; import { untrack } from 'svelte'; import { api } from '$lib/api/client'; import { user } from '$lib/auth/user.svelte'; -import { readPersistedQueue, writePersistedQueue } from './persisted'; +import { sessionEnd } from '$lib/auth/sessionEnd.svelte'; +import { readPersistedQueue, writePersistedQueue, clearPersistedQueue } from './persisted'; export type PlayerState = 'idle' | 'loading' | 'playing' | 'paused' | 'error'; export type RepeatMode = 'off' | 'all' | 'one'; @@ -385,6 +386,22 @@ $effect.root(() => { }); }); +// Session-end teardown (#375): auth signals via sessionEnd; player +// owns the player-specific cleanup (clear persisted queue for the +// outgoing user, reset in-memory queue, close drawer) without auth +// having to import any player APIs. +$effect.root(() => { + let lastTick = sessionEnd.tick; + $effect(() => { + if (sessionEnd.tick === lastTick) return; + lastTick = sessionEnd.tick; + const prevId = sessionEnd.userId; + if (prevId) clearPersistedQueue(prevId); + playQueue([]); + closeQueueDrawer(); + }); +}); + // Persistence (M7 #364): split-effect shape. Queue/index changes write // immediately (rare, user-driven); position changes throttle to once // per 5s during steady playback. See spec section "Persistence write