1# queue design 2 3## overview 4 5The 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. 6 7## server implementation 8 9- `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`. 10- `QueueService` keeps a TTL LRU cache (`maxsize 100`, `ttl 5m`). Cache entries include both the raw state and the hydrated track list. 11- 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. 12- `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"`). 13- `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. 14- 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. 15 16## client implementation (Svelte 5) 17 18- 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. 19- A 250 ms debounce batches PUTs. We skip background GETs while a PUT is pending/in-flight to avoid stomping optimistic state. 20- 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. 21- Before unload / visibility change flushes pending work to reduce data loss when navigating away. 22- Helper getters (`getCurrentTrack`, `getUpNextEntries`) supplement state but UI components bind directly to `$state` so Svelte reactivity tracks mutations correctly. 23- 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. 24 25## UI behavior 26 27- sidebar shows "now playing" card with prev/next buttons 28- shuffle control in player footer (always visible) 29- "up next" lists tracks beyond `currentIndex` 30- drag-and-drop reordering supported for upcoming tracks 31- removing a track updates local state and syncs to server 32- `queue.playNow(track)` inserts track at position 0, preserves existing up-next order 33- duplicate tracks allowed - same track can appear multiple times in queue 34- auto-play preference controls automatic advancement to next track 35- persisted via `/preferences/` API and localStorage 36- queue toggle button opens/closes sidebar 37- responsive positioning for mobile viewports 38- cannot remove currently playing track (index 0) 39 40## shuffle 41 42- shuffle is an action, not a toggle mode 43- each shuffle operation randomly reorders upcoming tracks (after current track) 44- preserves everything before and including the current track 45- uses fisher-yates algorithm with retry logic to ensure different permutation 46- original order preserved in `original_order_ids` for server persistence 47 48## cross-tab synchronization 49 50- uses BroadcastChannel API for same-browser tab sync 51- each tab has unique `tabId` stored in sessionStorage 52- queue updates broadcast to other tabs via `queue-updated` message 53- tabs ignore their own broadcasts and duplicate revisions 54- receiving tabs fetch latest state from server 55- `lastUpdateWasLocal` flag tracks update origin 56 57## future work 58 59- realtime push via SSE/WebSocket for instant cross-device updates 60- UI affordances for "queue updated on another device" notifications 61- repeat modes (currently not implemented) 62- clear up-next functionality exposed in UI