# Live Diashow Concept > **Status: SHIPPED.** Implementation lives at > [frontend/src/lib/diashow/](../frontend/src/lib/diashow/) and > [frontend/src/routes/diashow/+page.svelte](../frontend/src/routes/diashow/+page.svelte). > Treat this doc as the design reference; code is the source of truth. ## Goal A fullscreen, auto-advancing slideshow that any user can start from their device. Suitable for a venue projector or TV running off a single phone/laptop. Two behaviours combine in one view: 1. **Live drain** — when a new post is uploaded mid-event, it appears on the next slide transition. 2. **Shuffle fallback** — between new uploads (and they will be rare in quiet stretches), the diashow rotates through everything posted so far, in shuffled order. The user does **not** need to be Host or Admin. Any guest can start the diashow on their own device. There is no global "the room's diashow" — each device runs its own (though they will look very similar if started at the same time). --- ## Behavioural model Two FIFO queues live in the client: | Queue | Source | Drain priority | |----------------|-------------------------------------------|----------------| | `liveQueue` | SSE events for new processed uploads | First | | `shuffleQueue` | Snapshot of all known uploads, shuffled | When live empty | ### Slide-advance algorithm ```ts function nextSlide(): Slide | null { // 1. Drain live posts first (FIFO). if (liveQueue.length) return liveQueue.shift()!; // 2. Refill shuffle queue from `allKnown` if drained. if (!shuffleQueue.length) { shuffleQueue = shuffle( [...allKnown.values()].filter(s => !recentlyShown.has(s.id)) ); } return shuffleQueue.shift() ?? null; } ``` A small ring buffer `recentlyShown` (last ~5 IDs) prevents the same picture coming back within seconds when the shuffle queue is rebuilt. ### Live insertion ```ts sseClient.on('upload-processed', (msg) => { if (allKnown.has(msg.upload_id)) return; const slide = await fetchUpload(msg.upload_id); // or use payload directly allKnown.set(slide.id, slide); liveQueue.push(slide); }); ``` Listen on **`upload-processed`**, not `new-upload` — the preview/thumbnail must exist before we try to display the slide. ### Deletion / hiding ```ts sseClient.on('upload-deleted', ({ upload_id }) => { allKnown.delete(upload_id); liveQueue = liveQueue.filter(s => s.id !== upload_id); shuffleQueue = shuffleQueue.filter(s => s.id !== upload_id); if (currentSlide?.id === upload_id) advanceImmediately(); }); ``` Hidden uploads (banned user with `uploads_hidden=true`) need either a new SSE event or to piggyback `upload-deleted` for diashow purposes. Simplest path: emit `upload-deleted` for hidden posts to all subscribers (the live feed already filters them via `v_feed`, so this is a backwards-compatible signal). --- ## Initial pool On start: 1. Call `GET /api/v1/feed?limit=200` (or paginate-and-drain in the background while the diashow runs). 2. Push every returned upload into `allKnown`. 3. Build the first `shuffleQueue` from it. 4. Open the SSE stream and route `upload-processed` / `upload-deleted` into the queues. If the event is empty, show a friendly placeholder: *"Noch keine Beiträge — neue erscheinen automatisch."* --- ## Frontend surface ### Entry point A small **Diashow / "Präsentation starten"** action visible: - In the feed header (icon next to the list/grid toggle) on tablet/desktop layouts. - In the Account page on mobile (less prominent — diashow is primarily a venue-screen feature). Tapping it navigates to the `/diashow` route (full-screen, bottom nav hidden). ### Route: `/diashow` - Fullscreen request via `element.requestFullscreen()` after first user gesture. - **Screen Wake Lock**: `navigator.wakeLock.request('screen')` to keep the screen on during long shows. Renew on `visibilitychange` if needed. - Default dwell: **6 seconds** per slide. Configurable via overlay control: 3 / 6 / 10 s. - Tap or `Escape` reveals an overlay with: pause/resume, dwell selector, **transition picker**, exit. - Transitions: crossfade (≈400 ms) by default; Ken Burns, zoom, slide, etc. available as pluggable components — see "Pluggable transitions" below. - Videos: autoplay muted, fit-to-screen, advance on `ended` or after `max(dwell, 12 s)`, whichever first. - Preload the next slide's media into a hidden ``/`