diff --git a/web/src/lib/serverEvents.svelte.ts b/web/src/lib/serverEvents.svelte.ts new file mode 100644 index 00000000..d3ab5bcc --- /dev/null +++ b/web/src/lib/serverEvents.svelte.ts @@ -0,0 +1,37 @@ +import { queryClient } from '$lib/query/client'; +import { qk } from '$lib/api/queries'; +import { user } from '$lib/auth/store.svelte'; + +// Inbound server events over SSE (#968). The web client's OUTBOUND half +// (play events) lives in player/events.svelte.ts; this is the inbound +// listener it never had. Today it reacts to `playlist.system_rebuilt` — the +// daily 03:00 rebuild or a manual refresh — by invalidating the home + +// system-playlist query caches so a tab left open across the rebuild stops +// serving yesterday's snapshot (the stale-browse-view bug). Active playback +// self-heals separately on the failure path (store.handleLoadFailure); we +// deliberately do NOT yank a playing queue here — that would interrupt a +// mid-song listen to restart the new mix at track 0. + +function onSystemRebuilt(): void { + queryClient.invalidateQueries({ queryKey: qk.home() }); + // Prefix match invalidates every kind ('user' | 'system' | 'all'). + queryClient.invalidateQueries({ queryKey: ['playlists'] }); + queryClient.invalidateQueries({ queryKey: qk.systemPlaylistsStatus() }); +} + +// Wire once from +layout.svelte's mount. Opens the stream only while +// authenticated and closes it on logout; EventSource auto-reconnects on +// transient network drops. +export function useServerEvents(): void { + $effect(() => { + if (!user.value) return; + // Absent under SSR / jsdom — the listener simply no-ops there. + if (typeof EventSource === 'undefined') return; + const es = new EventSource('/api/events/stream'); + es.addEventListener('playlist.system_rebuilt', onSystemRebuilt); + return () => { + es.removeEventListener('playlist.system_rebuilt', onSystemRebuilt); + es.close(); + }; + }); +} diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index e6eb4821..4b71d934 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -23,6 +23,7 @@ } from '$lib/player/store.svelte'; import { useMediaSession } from '$lib/player/mediaSession.svelte'; import { useEventsDispatcher } from '$lib/player/events.svelte'; + import { useServerEvents } from '$lib/serverEvents.svelte'; import { useGlobalShortcuts } from '$lib/player/shortcuts.svelte'; import { applyMetaThemeColor } from '$lib/theme/applyMetaThemeColor.svelte'; import { audioLoader } from '$lib/player/audioLoader'; @@ -132,6 +133,7 @@ useMediaSession(); useEventsDispatcher(); + useServerEvents(); useGlobalShortcuts(); applyMetaThemeColor();