Files
EventSnap/docs/CONCEPT_DIASHOW.md
MechaCat02 9a0ceeced7 docs: realign blueprint with shipped state + add feature/journey/ideas docs
- PROJECT.md, README.md, TEST_GUIDE.md: status line refreshed; rate-limiter
  doc-vs-code drift fixed; HTML export section rewritten for the SvelteKit-
  static viewer; SSE event names + new events documented; config seed block
  extended with planned toggles + privacy_note; decision log entries added.
- docs/CONCEPT_HTML_VIEWER.md, docs/CONCEPT_MOBILE_UI.md: banner the design
  intent as shipped; point at the source-of-truth code paths.
- docs/CONCEPT_DIASHOW.md: planned-then-shipped design for the live diashow
  (two-queue policy, pluggable transitions, data-mode aware).
- docs/FEATURES.md: capability matrix by role (Guest / Host / Admin) plus
  prose per area (auth, posting, feed, moderation, admin, export, gestures,
  data mode, quotas, privacy note, extensibility).
- docs/USER_JOURNEYS.md: step-by-step flows for every supported scenario,
  including PIN reset by host, data mode, privacy note, gestures, and the
  admin toggles.
- docs/IDEAS.md: speculative extensions (global diashow, reactions,
  multi-tenancy, animation pack, etc.) — explicitly out of v0.16 scope.
- backend/migrations/README.md, frontend/src/lib/README.md: codify the
  "never edit a shipped migration" rule and the lib/ conventions
  (one store per concern, gestures via actions, sheets via ContextSheet,
  transitions as drop-in components).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 14:31:06 +02:00

197 lines
7.8 KiB
Markdown

# 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 `<img>`/`<video>` to avoid flashing.
- **Media source respects the user's data mode**
(see [FEATURES.md §2.5](FEATURES.md)). In Saver mode the diashow loads `preview_url`;
in Original mode it loads the original. The data-usage warning is shown once when the
mode is toggled in My Account — the diashow itself stays silent.
### Pluggable transitions
Each transition is a **drop-in Svelte component** under
`frontend/src/lib/diashow/transitions/` (path finalised at implementation time). The
interface is intentionally tiny:
```ts
// pseudocode — the real shape lands with the feature
export interface SlideTransition {
id: string; // 'crossfade', 'kenburns', ...
label: string; // shown in the dwell/transition picker
durationMs: number; // default; can be overridden per-event
// The actual motion is implemented by mounting the component with `from` / `to` slides.
}
```
A small registry maps `id → component`; the settings popover renders that registry as a
dropdown. **Adding a new animation is one new file plus one line in the registry — no
other changes required.** This is the maintainability target called out in
[FEATURES.md §2.9](FEATURES.md) and [IDEAS.md](IDEAS.md) ("Animation pack").
The same pattern is a candidate for whole-event "themes" later — a bundle of (transition
+ dwell + optional background-music defaults).
### Edge cases
| Case | Behaviour |
|--------------------------------------------|--------------------------------------------------------|
| Empty event | Placeholder card; live SSE will trigger the first show |
| All known uploads are still compressing | Same placeholder — wait for `upload-processed` |
| Network drop / SSE reconnect | EventSource auto-reconnects; queues survive |
| Current slide gets deleted | Advance immediately |
| Event is closed (no new uploads possible) | Diashow keeps running on shuffle queue indefinitely |
| Banned user's content (`uploads_hidden`) | Removed via `upload-deleted` signal (see Deletion) |
---
## Backend changes
**Essentially none.** The diashow reuses:
- `GET /api/v1/feed` (initial pool)
- `GET /api/v1/stream` SSE (`upload-processed`, `upload-deleted`)
Optional small additions:
1. Emit `upload-deleted` (or a new `upload-hidden`) when a host bans a user with
`hide_uploads=true`, so that diashow clients can scrub the relevant slides without
reloading.
2. Consider raising the cap on `GET /api/v1/feed?limit=` for diashow clients (or paginate
the initial pool in the background — preferred, no API change needed).
---
## Future extensions (not in scope for v1)
The big ones live in [IDEAS.md](IDEAS.md) under "Diashow extensions" — most notably the
**global / synchronised diashow** where multiple screens share one server-side cursor.
Short list of others kept here for context:
- **Curated highlights mode** — only show uploads tagged with a specific hashtag, or
Host-pinned "Story" uploads (depends on the story-highlights feature).
- **Audio bed** — host can pick a background track; mute videos so they don't fight the
music.
- **Slide caption / uploader chyron** — small lower-third with the uploader's name and
caption. Out by default to keep the visual clean.