From 8cc79ee612dffda2f768d7c8c00c19846696e46c Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sun, 3 May 2026 20:44:33 -0400 Subject: [PATCH] feat(web/m7-364): queue persistence write/restore on player store --- web/src/lib/player/store.svelte.ts | 58 ++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/web/src/lib/player/store.svelte.ts b/web/src/lib/player/store.svelte.ts index 1da64ceb..87d97732 100644 --- a/web/src/lib/player/store.svelte.ts +++ b/web/src/lib/player/store.svelte.ts @@ -1,5 +1,7 @@ import type { TrackRef, RadioResponse } from '$lib/api/types'; import { api } from '$lib/api/client'; +import { user } from '$lib/auth/store.svelte'; +import { readPersistedQueue, writePersistedQueue } from './persisted'; export type PlayerState = 'idle' | 'loading' | 'playing' | 'paused' | 'error'; export type RepeatMode = 'off' | 'all' | 'one'; @@ -319,6 +321,20 @@ export function closeQueueDrawer(): void { _queueDrawerOpen = false; } +export function restoreQueue(userId: string): void { + if (!userId) return; + const persisted = readPersistedQueue(userId); + if (!persisted) return; + if (persisted.queue.length === 0) return; + _queue = persisted.queue; + _index = Math.max(0, Math.min(persisted.index, persisted.queue.length - 1)); + _position = persisted.position; + _duration = 0; + _state = 'paused'; + _error = null; + _radioSeedId = null; +} + // M4c: auto-refresh radio queue when 80% consumed. // Fires whenever the consumption ratio crosses 80% AND the queue was // seeded by playRadio() AND no refresh is currently in-flight. @@ -355,3 +371,45 @@ $effect.root(() => { }); }); }); + +// 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 +// effect" for the alternative debounce shape. +$effect.root(() => { + let lastPositionWriteAt = 0; + + // Immediate write on queue or index changes. + $effect(() => { + const userId = user.value?.id; + if (!userId) return; + // Read these to register the dependency. Position is also read but + // its standalone changes are handled by the second effect below. + const _q = _queue; + const _i = _index; + void _q; + void _i; + writePersistedQueue(userId, { + queue: _queue, + index: _index, + position: _position + }); + lastPositionWriteAt = Date.now(); + }); + + // Throttled write on position changes. + $effect(() => { + const userId = user.value?.id; + if (!userId) return; + const _p = _position; + void _p; + const now = Date.now(); + if (now - lastPositionWriteAt < 5000) return; + writePersistedQueue(userId, { + queue: _queue, + index: _index, + position: _position + }); + lastPositionWriteAt = now; + }); +});