# queue design ## overview The queue is a cross-device, server-authoritative data model with optimistic local updates. Every device performs queue mutations locally, pushes a full snapshot to the API, and receives hydrated track metadata back. Servers keep an in-memory cache (per process) in sync via Postgres LISTEN/NOTIFY so horizontally scaled instances observe the latest queue state without adding Redis or similar infra. ## server implementation - `queue_state` table (`did`, `state`, `revision`, `updated_at`). `state` is JSONB containing `track_ids`, `current_index`, `current_track_id`, `shuffle`, `repeat_mode`, `original_order_ids`. - `QueueService` keeps a TTL LRU cache (`maxsize 100`, `ttl 5m`). Cache entries include both the raw state and the hydrated track list. - On startup the service opens an asyncpg connection, registers a `queue_changes` listener, and reconnects on failure. Notifications simply invalidate the cache entry; consumers fetch on demand. - `GET /queue/` returns `{ state, revision, tracks }`. `tracks` is hydrated server-side by joining against `tracks`+`artists`. Duplicate queue entries are preserved—hydration walks the `track_ids` array by index so the same `file_id` can appear multiple times. Response includes an ETag (`"revision"`). - `PUT /queue/` expects an optional `If-Match: "revision"`. Mismatched revisions return 409. Successful writes increment the revision, emit LISTEN/NOTIFY, and rehydrate so the response mirrors GET semantics. - Hydration preserves order even when duplicates exist by pairing each `track_id` position with the track returned by the DB. We never de-duplicate on the server. ## client implementation (Svelte 5) - Global queue store (`frontend/src/lib/queue.svelte.ts`) uses runes-backed `$state` fields for `tracks`, `currentIndex`, `shuffle`, etc. Methods mutate these states synchronously so the UI remains responsive. - A 250 ms debounce batches PUTs. We skip background GETs while a PUT is pending/in-flight to avoid stomping optimistic state. - Conflict handling: on 409 the client performs a forced `fetchQueue(true)` which ignores local ETag and applies the server snapshot if the revision is newer. Older revisions received out-of-order are ignored. - Before unload / visibility change flushes pending work to reduce data loss when navigating away. - Helper getters (`getCurrentTrack`, `getUpNextEntries`) supplement state but UI components bind directly to `$state` so Svelte reactivity tracks mutations correctly. - Duplicates: adding the same track repeatedly simply appends another copy. Removal is disabled for the currently playing entry (conceptually index 0); the queue sidebar only allows removing future items. ## UI behavior - sidebar shows "now playing" card with prev/next buttons - shuffle control in player footer (always visible) - "up next" lists tracks beyond `currentIndex` - drag-and-drop reordering supported for upcoming tracks - removing a track updates local state and syncs to server - `queue.playNow(track)` inserts track at position 0, preserves existing up-next order - duplicate tracks allowed - same track can appear multiple times in queue - auto-play preference controls automatic advancement to next track - persisted via `/preferences/` API and localStorage - queue toggle button opens/closes sidebar - responsive positioning for mobile viewports - cannot remove currently playing track (index 0) ## shuffle - shuffle is an action, not a toggle mode - each shuffle operation randomly reorders upcoming tracks (after current track) - preserves everything before and including the current track - uses fisher-yates algorithm with retry logic to ensure different permutation - original order preserved in `original_order_ids` for server persistence ## cross-tab synchronization - uses BroadcastChannel API for same-browser tab sync - each tab has unique `tabId` stored in sessionStorage - queue updates broadcast to other tabs via `queue-updated` message - tabs ignore their own broadcasts and duplicate revisions - receiving tabs fetch latest state from server - `lastUpdateWasLocal` flag tracks update origin ## future work - realtime push via SSE/WebSocket for instant cross-device updates - UI affordances for "queue updated on another device" notifications - repeat modes (currently not implemented) - clear up-next functionality exposed in UI