docs: correct the claims that no longer match the code

Checked each against the implementation and fixed the doc, never the code:

- FEATURES claimed the ban modal offers a choice about hiding existing uploads. There
  is no such choice — a ban always hides. USER_JOURNEYS §9 was already right.
- FEATURES claimed hosts may demote other hosts. It is admin-only, enforced in the
  backend, and the two documents contradicted each other on it.
- FEATURES showed the quota widget as guest-facing; it is deliberately staff-only.
- The first-visit tour has six steps, not four.
- USER_JOURNEYS §12.7 said export downloads are rate-limited per IP. They are per USER
  — a materially different thing at a shared-NAT venue, where per-IP would have locked
  out the fourth guest to fetch their keepsake.
- §15 still described "Event verlassen"; that button is now Abmelden / Auf allen
  Geräten abmelden.
- §4, §13, §14, §16 and §18 were marked "(planned)" and have shipped.
- The lightbox row now describes what exists after this branch: prev/next controls,
  arrow keys and swipe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Fabian Hamm (Privat)
2026-08-03 18:37:09 +02:00
parent 2b1500e624
commit 46bb2e5174
2 changed files with 98 additions and 57 deletions

View File

@@ -19,8 +19,9 @@ can do what" overview, see [FEATURES.md](FEATURES.md). For manual QA, see
this PIN is the only way to sign in on another device. PIN is also written to
`localStorage`.
6. Guest taps **Weiter zur Galerie** → lands in the feed (`/feed`).
7. The **first-visit onboarding overlay** appears: dismissible steps (welcome, upload,
hashtags, PIN, and a brief pointer to the **Datenschutzhinweis** in My Account).
7. The **first-visit onboarding overlay** appears: six dismissible steps (welcome, upload,
hashtags, long-press for more actions, hell/dunkel design pick, and PIN — the PIN step
also points at the **Datenschutzhinweis** in My Account when the admin has set one).
`localStorage('eventsnap_guide_seen') = 'true'` after dismiss.
8. Guest sees the bottom nav: **🏠 Feed · [📷+ FAB] · 👤 Account**.
@@ -41,7 +42,7 @@ can do what" overview, see [FEATURES.md](FEATURES.md). For manual QA, see
5. Wrong PIN: up to 3 attempts. After the third, the account is locked for 15 minutes
(`pin_locked_until` is set; further attempts return HTTP 429 with a localized message).
## 4. PIN forgotten — Host or Admin resets it (planned)
## 4. PIN forgotten — Host or Admin resets it
The PIN is visible in **My Account** as long as `localStorage` is intact on at least one
of the user's devices. If lost everywhere, the user asks a Host (or Admin) for a reset.
@@ -126,14 +127,23 @@ the Host can clean up later).
3. **Event settings** — toggle to lock new uploads (likes / comments / browsing stay open;
broadcasts `event-closed` SSE so all clients show a "uploads are locked" banner).
4. **Galerie freigeben** — releases the export. Enqueues two export jobs (ZIP + HTML
viewer). Progress is visible in the Admin dashboard's Export tab; SSE
`export-progress` keeps it live; `export-available` notifies all guests when ready.
viewer). Progress is visible on the Host dashboard *and* in the Admin dashboard's Export
tab; SSE `export-progress` keeps it live; `export-available` notifies all guests when
ready. Once released the button is disabled and reads *"Galerie bereits freigegeben"*
a second release is a 409. If a keepsake half fails, both dashboards show the reason and
offer **Erneut versuchen** (rebuild); a ready keepsake can also be rebuilt behind a
confirm, during which guest downloads are briefly unavailable.
5. **Nutzerverwaltung** — search users; per-user controls:
- **Sperren** opens a confirmation modal. Banning **always hides** the user's existing
uploads (a banned user's content is "gone" everywhere) — there is no opt-out. Submitting
calls `POST /host/users/{id}/ban` (no body).
- **Entsperren** lifts the ban. Same authority boundary as ban (below): a plain Host may
only unban Guests; only an Admin may unban a Host.
- **Entsperren** lifts the ban — and does two further things the confirm sheet now spells
out, because both are visible to the whole party: it clears `uploads_hidden`, so **all**
of that user's previously hidden uploads reappear in the gallery, the diashow and the
export; and it invalidates and rebuilds a released keepsake (`invalidate_and_arm`), so
every guest's download is unavailable for as long as that takes. A ban does the mirror
image of both. Same authority boundary as ban: a plain Host may only unban Guests; only
an Admin may unban a Host.
- **Host** promotes a guest to host (Hosts and Admins may do this).
- **Degradieren** — demote a Host back to guest. **Only an Admin may change a Host's
role.** A plain Host may *not* demote a peer Host: doing so would let them then ban or
@@ -181,14 +191,18 @@ the Host can clean up later).
- **Stats**: live counts and disk-usage widget (via `sysinfo`).
- **Config**: per-file limits (image MB / video MB), rate limits (upload / feed /
export), quota tolerance, estimated guest count, compression-worker concurrency,
plus the **Datenschutzhinweis** free-text editor and **on/off toggles** for the rate
limiters and quotas (planned — see §16). Whitelist on the server side rejects
unknown keys. Values are read from the `config` table on each request — no restart
needed.
- **Export**: list of past export jobs with status badges (pending / running / done /
failed) and progress bars; refresh button re-polls.
- **Nutzer**: same user list as Host, with the additional Demote action and (planned)
PIN-reset on host rows.
plus the colour-theme picker, the **Datenschutzhinweis** free-text editor and
**on/off toggles** for the rate limiters and quotas (see §18). Whitelist on the
server side rejects unknown keys. Values are read from the `config` table on each
request — no restart needed.
- **Export**: the gallery-release control plus the list of export jobs with status
badges (pending / running / done / failed) and progress bars; refresh button
re-polls. The release control mirrors the Host dashboard exactly — disabled once
released (a second tap would only 409), live keepsake status with progress and the
failure reason, and a **Neu erstellen / Erneut versuchen** rebuild as the recovery
path for a failed or stale keepsake.
- **Nutzer**: same user list as Host, with the additional Demote action (admin-only,
see §9) and PIN-reset on host rows.
## 12. Releasing the export and downloading
@@ -224,11 +238,14 @@ the Host can clean up later).
buttons. Tapping the HTML download first shows an in-app guide modal explaining:
"Entpacke die ZIP, öffne `index.html`". Tapping **Herunterladen** triggers the
browser download.
7. Downloads are rate-limited per IP (default 3 / day).
7. Downloads are rate-limited **per user** (default 3 / day), keyed on the user id in
`enforce_export_rate` — deliberately not per IP. At a venue where a hundred guests share
one NAT'd wifi, a per-IP budget would be exhausted by the third person to tap
**Herunterladen** and lock everyone else out of their own keepsake.
## 13. Diashow (planned)
## 13. Diashow
See [CONCEPT_DIASHOW.md](CONCEPT_DIASHOW.md). Summary of the planned flow:
See [CONCEPT_DIASHOW.md](CONCEPT_DIASHOW.md). Summary of the flow:
1. User taps a **Diashow / Präsentation** action (feed header on tablet/desktop, Account
on mobile).
@@ -240,7 +257,7 @@ See [CONCEPT_DIASHOW.md](CONCEPT_DIASHOW.md). Summary of the planned flow:
immediately.
6. Tap or Escape reveals an overlay (pause, dwell selector, exit).
## 14. Picking a data mode (planned)
## 14. Picking a data mode
1. Guest opens **My Account** → scrolls to **Datennutzung**.
2. Two options: **Datensparer (empfohlen)** and **Original**. Saver is the default.
@@ -252,14 +269,24 @@ See [CONCEPT_DIASHOW.md](CONCEPT_DIASHOW.md). Summary of the planned flow:
5. The viewer (offline HTML export) is unaffected — it already ships with its own pre-
bundled `_thumb` / `_full` variants.
## 15. Leaving an event
## 15. Signing out
1. User opens **My Account** → taps **🚪 Event verlassen**.
2. Bottom-sheet confirmation: "Event verlassen?" with **Abmelden** and **Bleiben**.
3. Confirming calls `DELETE /api/v1/session` (invalidates the session row), clears the JWT
and PIN from `localStorage`, and redirects to the join page.
The wording matters here: nothing is *left* and nothing is deleted. The account, the PIN
and every uploaded photo survive — the user signs back in with name + PIN whenever they
like. ("Event verlassen" read as leaving for good, which the confirmation sheet then
contradicted by saying you can come back.)
## 16. Reading the Datenschutzhinweis (planned)
1. User opens **My Account** → Konto section. Two separate actions:
- **Abmelden** — this device only (`DELETE /api/v1/session`).
- **Auf allen Geräten abmelden** — every session of theirs (`DELETE /api/v1/sessions`),
for a lost or borrowed phone.
2. Bottom-sheet confirmation naming which of the two it is, with **Abmelden** / **Abbrechen**.
3. Confirming invalidates the session row(s), wipes the local JWT, PIN and the IndexedDB
upload queue (so the next guest on a shared phone doesn't inherit pending uploads), and
redirects to the join page. The session-delete call is best-effort: the token is gone
locally either way, so a network failure never traps the user on the page.
## 16. Reading the Datenschutzhinweis
1. User opens **My Account** → scrolls to **Datenschutzhinweis**.
2. The note is rendered inside a preformatted block (`<pre>`-style: monospace, whitespace
@@ -270,11 +297,15 @@ See [CONCEPT_DIASHOW.md](CONCEPT_DIASHOW.md). Summary of the planned flow:
4. Admin sets / edits the note in **Admin Dashboard → Config → Datenschutzhinweis**: a
tall textarea with a save button. Saved to a single `config` key.
## 17. Mobile-first gestures (planned)
## 17. Mobile-first gestures (partly shipped)
EventSnap's UI is mobile-first; gestures replace explicit buttons where they're more
ergonomic. Buttons are always present as fallback for desktop and accessibility.
Shipped today: long-press context sheets on posts and comments, and lightbox navigation
(on-screen prev/next controls, ← / → arrow keys, and left/right swipe). The remaining rows
below are still planned.
| Gesture | Action |
|-------------------------------------------|-------------------------------------------------------|
| Long-press on a post (own) | Bottom sheet → Löschen, Original anzeigen, Teilen |
@@ -294,7 +325,7 @@ Inspiration: Instagram (double-tap heart, swipe stories), WhatsApp (long-press f
context), Telegram (swipe-to-reply on messages — could inform comment threads if those
land).
## 18. Admin toggles a rate limit or quota off (planned)
## 18. Admin toggles a rate limit or quota off
1. Admin opens **Admin Dashboard → Config**.
2. **Rate-Limits** section: a master switch and per-endpoint switches (upload / feed /
@@ -305,8 +336,9 @@ land).
limiter entirely.
5. **Quoten** section mirrors the pattern: master toggle plus per-area toggles (storage
bytes / upload count).
6. When the storage-quota toggle is off, the **"X von Y MB genutzt"** widget in the
guest's My Account and upload screen hides itself (no quota → no number to show).
6. When the storage-quota toggle is off, the **"Speicher: X / Y"** widget in My Account and
on the upload screen hides itself (no quota → no number to show). That widget is
staff-only in any case — guests never see it.
Suggested defaults at deploy time: all toggles **on**, sensible numeric limits.
Toggling off is the explicit escape hatch for testing or trusted internal events.