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:
@@ -14,12 +14,12 @@ Status legend: **✓ shipped** · **◐ partial** · **◯ planned** · **✗ ou
|
|||||||
|---------------------------------------------------------|:-----:|:-----:|:-----:|-----------------------------------------------------------------------|
|
|---------------------------------------------------------|:-----:|:-----:|:-----:|-----------------------------------------------------------------------|
|
||||||
| **Onboarding & sessions** | | | | |
|
| **Onboarding & sessions** | | | | |
|
||||||
| Join via shared event link / QR code | ✓ | ✓ | ✓ | Name-only registration; server issues JWT + 4-digit PIN |
|
| Join via shared event link / QR code | ✓ | ✓ | ✓ | Name-only registration; server issues JWT + 4-digit PIN |
|
||||||
| First-visit guided tour (4 steps) | ✓ | ✓ | ✓ | Dismissed once, flag in `localStorage` |
|
| First-visit guided tour (6 steps) | ✓ | ✓ | ✓ | Welcome, upload, hashtags, long-press, hell/dunkel, PIN (+ privacy-note pointer when one is set). Dismissed once, flag in `localStorage` |
|
||||||
| Persistent 30-day session | ✓ | ✓ | ✓ | JWT in `localStorage`; refreshed on activity |
|
| Persistent 30-day session | ✓ | ✓ | ✓ | JWT in `localStorage`; refreshed on activity |
|
||||||
| Sign in on another device using name + PIN | ✓ | ✓ | ✓ | 3 wrong PINs → 15-min lockout |
|
| Sign in on another device using name + PIN | ✓ | ✓ | ✓ | 3 wrong PINs → 15-min lockout |
|
||||||
| "Ich habe bereits einen Account" link on the join page | ✓ | ✓ | ✓ | Small inline link → `/recover` (name + PIN) |
|
| "Ich habe bereits einen Account" link on the join page | ✓ | ✓ | ✓ | Small inline link → `/recover` (name + PIN) |
|
||||||
| View / copy own PIN any time ("My Account") | ✓ | ✓ | ✓ | Read from `localStorage`; never sent back from the server |
|
| View / copy own PIN any time ("My Account") | ✓ | ✓ | ✓ | Read from `localStorage`; never sent back from the server |
|
||||||
| Log out / "Leave event" | ✓ | ✓ | ✓ | Confirmation bottom-sheet; invalidates the session row |
|
| Log out ("Abmelden" / "Auf allen Geräten abmelden") | ✓ | ✓ | ✓ | Confirmation bottom-sheet; `DELETE /session` (this device) or `DELETE /sessions` (all devices). Nothing is deleted — account, PIN and uploads stay |
|
||||||
| Rename own display name | ◯ | ◯ | ◯ | Not yet wired; PIN-protected change |
|
| Rename own display name | ◯ | ◯ | ◯ | Not yet wired; PIN-protected change |
|
||||||
| Pick **data mode** (Saver / Original) in My Account | ✓ | ✓ | ✓ | Saver = compressed (default). Original = full files + data-usage warning. Applies to feed and diashow. Per-device, in `localStorage` |
|
| Pick **data mode** (Saver / Original) in My Account | ✓ | ✓ | ✓ | Saver = compressed (default). Original = full files + data-usage warning. Applies to feed and diashow. Per-device, in `localStorage` |
|
||||||
| Read the **Datenschutzhinweis** (privacy note) | ✓ | ✓ | ✓ | Free text set by Admin during setup; rendered preformatted in My Account; first-visit guide briefly points to it |
|
| Read the **Datenschutzhinweis** (privacy note) | ✓ | ✓ | ✓ | Free text set by Admin during setup; rendered preformatted in My Account; first-visit guide briefly points to it |
|
||||||
@@ -42,7 +42,7 @@ Status legend: **✓ shipped** · **◐ partial** · **◯ planned** · **✗ ou
|
|||||||
| 3-column grid feed with toggle | ✓ | ✓ | ✓ | Video play badges, duration |
|
| 3-column grid feed with toggle | ✓ | ✓ | ✓ | Video play badges, duration |
|
||||||
| Search & autocomplete (uploader + hashtag) | ✓ | ✓ | ✓ | Grid view; derived in-memory, no extra API calls |
|
| Search & autocomplete (uploader + hashtag) | ✓ | ✓ | ✓ | Grid view; derived in-memory, no extra API calls |
|
||||||
| Active filter chips (OR within type, AND across types) | ✓ | ✓ | ✓ | Multiple hashtags = OR; uploader + hashtag = AND |
|
| Active filter chips (OR within type, AND across types) | ✓ | ✓ | ✓ | Multiple hashtags = OR; uploader + hashtag = AND |
|
||||||
| Fullscreen lightbox with swipe | ✓ | ✓ | ✓ | Swipe navigates the filtered set |
|
| Fullscreen lightbox with prev/next navigation | ✓ | ✓ | ✓ | Navigates the *filtered* set. On-screen prev/next controls, ← / → arrow keys, and swipe left/right on touch |
|
||||||
| Like / unlike any post | ✓ | ✓ | ✓ | Single toggle; SSE `like-update` |
|
| Like / unlike any post | ✓ | ✓ | ✓ | Single toggle; SSE `like-update` |
|
||||||
| Read comments on any post | ✓ | ✓ | ✓ | |
|
| Read comments on any post | ✓ | ✓ | ✓ | |
|
||||||
| Add a comment | ✓ | ✓ | ✓ | Hashtags in comments also parsed |
|
| Add a comment | ✓ | ✓ | ✓ | Hashtags in comments also parsed |
|
||||||
@@ -56,11 +56,11 @@ Status legend: **✓ shipped** · **◐ partial** · **◯ planned** · **✗ ou
|
|||||||
| | | | | |
|
| | | | | |
|
||||||
| **Moderation (Host)** | | | | |
|
| **Moderation (Host)** | | | | |
|
||||||
| List all event users | | ✓ | ✓ | Includes upload count, total bytes |
|
| List all event users | | ✓ | ✓ | Includes upload count, total bytes |
|
||||||
| Ban / unban a user | | ✓ | ✓ | Modal asks: hide their existing uploads, or keep visible? |
|
| Ban / unban a user | | ✓ | ✓ | Ban **always hides** their existing uploads — no opt-out, the modal is a plain confirm. Unban restores them. Both invalidate and rebuild a released keepsake. See USER_JOURNEYS §9/§10 |
|
||||||
| Delete any upload | | ✓ | ✓ | |
|
| Delete any upload | | ✓ | ✓ | |
|
||||||
| Delete any comment | | ✓ | ✓ | |
|
| Delete any comment | | ✓ | ✓ | |
|
||||||
| Promote guest to Host | | ✓ | ✓ | |
|
| Promote guest to Host | | ✓ | ✓ | |
|
||||||
| Demote Host to guest | | ✓ | ✓ | Hosts may demote other Hosts. Cannot demote self. Admins cannot be demoted by hosts. |
|
| Demote Host to guest | | | ✓ | **Admin only** — the backend 403s a Host demoting a peer Host (F1: it would launder past the ban / PIN-reset peer guards). Nobody may change their own role; Admins cannot be demoted at all. The button is hidden for non-admin Hosts |
|
||||||
| Reset a guest's PIN (Host) / any non-admin PIN (Admin) | | ✓ | ✓ | New PIN shown once in modal; Host shows/shares it with the guest |
|
| Reset a guest's PIN (Host) / any non-admin PIN (Admin) | | ✓ | ✓ | New PIN shown once in modal; Host shows/shares it with the guest |
|
||||||
| Lock new uploads ("Event schließen") | | ✓ | ✓ | Likes + comments + browsing remain open |
|
| Lock new uploads ("Event schließen") | | ✓ | ✓ | Likes + comments + browsing remain open |
|
||||||
| Unlock new uploads | | ✓ | ✓ | |
|
| Unlock new uploads | | ✓ | ✓ | |
|
||||||
@@ -78,18 +78,18 @@ Status legend: **✓ shipped** · **◐ partial** · **◯ planned** · **✗ ou
|
|||||||
| Edit compression-worker concurrency | | | ✓ | |
|
| Edit compression-worker concurrency | | | ✓ | |
|
||||||
| Edit **Datenschutzhinweis** (privacy note, free text) | | | ✓ | Plain text, whitespace + newlines preserved, no HTML. SSE `event-updated` broadcasts edits live. |
|
| Edit **Datenschutzhinweis** (privacy note, free text) | | | ✓ | Plain text, whitespace + newlines preserved, no HTML. SSE `event-updated` broadcasts edits live. |
|
||||||
| Inspect export job list & progress | | | ✓ | |
|
| Inspect export job list & progress | | | ✓ | |
|
||||||
| Low-disk alert (< 10 GB free) | | | ◯ | Planned |
|
| Low-disk alert | | ✓ | ✓ | Red banner at the top of the Host dashboard when `disk_low`, naming free space and the keepsake's required bytes. Fails closed to "not low" on an unreadable mount |
|
||||||
| Event banner / cover image | | | ◯ | DB column exists, no UI |
|
| Event banner / cover image | | | ◯ | DB column exists, no UI |
|
||||||
| | | | | |
|
| | | | | |
|
||||||
| **Quota visibility (Guest-facing)** | | | | |
|
| **Quota visibility (staff-only)** | | | | |
|
||||||
| Show current per-user quota estimate | ✓ | ✓ | ✓ | "Du hast X MB von Y MB genutzt." in My Account and on the upload screen. Computed from the live formula. Hidden when quota enforcement is toggled off |
|
| Show current per-user quota estimate | | ✓ | ✓ | **Deliberately not shown to guests** — the storage widget in My Account and on the upload screen is gated on host/admin. Computed from the live formula; also hidden when quota enforcement is toggled off |
|
||||||
| | | | | |
|
| | | | | |
|
||||||
| **Export** | | | | |
|
| **Export** | | | | |
|
||||||
| Wait at locked export page until released | ✓ | ✓ | ✓ | Friendly "not yet available" copy |
|
| Wait at locked export page until released | ✓ | ✓ | ✓ | Friendly "not yet available" copy |
|
||||||
| Download `Gallery.zip` (full-quality originals) | ✓ | ✓ | ✓ | Streamed via `async-zip`; `Photos/` + `Videos/` folders |
|
| Download `Gallery.zip` (full-quality originals) | ✓ | ✓ | ✓ | Streamed via `async-zip`; `Photos/` + `Videos/` folders |
|
||||||
| Download `Memories.zip` (offline HTML viewer) | ✓ | ✓ | ✓ | Self-contained SvelteKit-static app + `data.json` + `media/` |
|
| Download `Memories.zip` (offline HTML viewer) | ✓ | ✓ | ✓ | Self-contained SvelteKit-static app + `data.json` + `media/` |
|
||||||
| HTML-export in-app guide modal before download | ✓ | ✓ | ✓ | Explains: unzip first, open `index.html` |
|
| HTML-export in-app guide modal before download | ✓ | ✓ | ✓ | Explains: unzip first, open `index.html` |
|
||||||
| Per-IP export download rate limit (3 / day) | ✓ | ✓ | ✓ | |
|
| Per-**user** export download rate limit (3 / day) | ✓ | ✓ | ✓ | Keyed on the user id, not the IP — a venue behind one NAT would otherwise share a 3/day budget across every guest |
|
||||||
| | | | | |
|
| | | | | |
|
||||||
| **Banned guest** (subset) | | | | |
|
| **Banned guest** (subset) | | | | |
|
||||||
| Cannot upload, like, or comment | ✗ | | | Returns HTTP 403 |
|
| Cannot upload, like, or comment | ✗ | | | Returns HTTP 403 |
|
||||||
@@ -143,7 +143,7 @@ email, no password, no account portal.
|
|||||||
PIN-recovery form for that account ("Already taken — sign in instead, or pick another
|
PIN-recovery form for that account ("Already taken — sign in instead, or pick another
|
||||||
name"). The join page also surfaces an explicit **"Ich habe bereits einen Account"**
|
name"). The join page also surfaces an explicit **"Ich habe bereits einen Account"**
|
||||||
link routing to `/recover` for users who already know they want to sign in.
|
link routing to `/recover` for users who already know they want to sign in.
|
||||||
- **PIN reset by Host / Admin.** Planned. If a guest loses their PIN and `localStorage` is
|
- **PIN reset by Host / Admin.** If a guest loses their PIN and `localStorage` is
|
||||||
gone everywhere, a Host (for guests) or Admin (for hosts and guests) can hit a
|
gone everywhere, a Host (for guests) or Admin (for hosts and guests) can hit a
|
||||||
**PIN zurücksetzen** action in the user list. A fresh PIN is generated server-side, its
|
**PIN zurücksetzen** action in the user list. A fresh PIN is generated server-side, its
|
||||||
bcrypt stored, and the plaintext is shown **once** in a modal to the requesting
|
bcrypt stored, and the plaintext is shown **once** in a modal to the requesting
|
||||||
@@ -153,9 +153,13 @@ email, no password, no account portal.
|
|||||||
Admins can.
|
Admins can.
|
||||||
- **Roles.** `guest` (default), `host`, `admin`. The Admin role is seeded from the
|
- **Roles.** `guest` (default), `host`, `admin`. The Admin role is seeded from the
|
||||||
`ADMIN_PASSWORD_HASH` env var; admins log in at `/admin/login` with a password (separate
|
`ADMIN_PASSWORD_HASH` env var; admins log in at `/admin/login` with a password (separate
|
||||||
JWT, 1-day expiry, in `sessionStorage`). Hosts are guests promoted by an admin. **Hosts
|
JWT, 1-day expiry, in `sessionStorage`). Hosts are guests promoted by a host or an admin.
|
||||||
may also demote other Hosts to guests** (planned) — but never themselves, to avoid
|
**Only an Admin may change a Host's role** — a plain Host may promote guests but may not
|
||||||
locking the event out of moderation. Admins can demote anyone except admins.
|
demote a peer Host, because demoting one would then let them ban or PIN-reset (→
|
||||||
|
`/recover` takeover) that ex-peer, the guards keying off the target's *current* role (F1).
|
||||||
|
Nobody may change their own role, so the event can't be locked out of moderation, and
|
||||||
|
Admins are un-demotable and un-bannable. Enforced in `handlers::host::set_role`; the
|
||||||
|
button is hidden for non-admin Hosts.
|
||||||
|
|
||||||
### 2.2 Posting pipeline
|
### 2.2 Posting pipeline
|
||||||
|
|
||||||
@@ -188,8 +192,9 @@ The upload pipeline is built for flaky mobile networks:
|
|||||||
the loaded uploads, so typing never hits the server.
|
the loaded uploads, so typing never hits the server.
|
||||||
- **Filter chips** — multiple hashtags combine with OR; multiple uploaders combine with OR;
|
- **Filter chips** — multiple hashtags combine with OR; multiple uploaders combine with OR;
|
||||||
hashtag + uploader combine with AND. Matches the redesign concept exactly.
|
hashtag + uploader combine with AND. Matches the redesign concept exactly.
|
||||||
- **Lightbox** — fullscreen view, swipe navigates the *filtered* set, with embedded
|
- **Lightbox** — fullscreen view with embedded like/comment UI. Navigation across the
|
||||||
like/comment UI.
|
*filtered* set is available three ways: on-screen prev/next controls, the ← / → arrow
|
||||||
|
keys, and a left/right swipe on touch.
|
||||||
- **Real-time** — SSE delivers `new-upload`, `upload-processed`, `like-update`,
|
- **Real-time** — SSE delivers `new-upload`, `upload-processed`, `like-update`,
|
||||||
`new-comment`, `upload-deleted`, `event-closed`/`event-opened`, `export-progress`,
|
`new-comment`, `upload-deleted`, `event-closed`/`event-opened`, `export-progress`,
|
||||||
`export-available`. Client pauses SSE on `visibilitychange: hidden` and reopens on visible.
|
`export-available`. Client pauses SSE on `visibilitychange: hidden` and reopens on visible.
|
||||||
@@ -197,15 +202,18 @@ The upload pipeline is built for flaky mobile networks:
|
|||||||
### 2.4 Host / Admin tooling
|
### 2.4 Host / Admin tooling
|
||||||
|
|
||||||
- **Host dashboard** — three collapsible sections: Stats, Event-Einstellungen,
|
- **Host dashboard** — three collapsible sections: Stats, Event-Einstellungen,
|
||||||
Nutzerverwaltung. Ban modal asks explicitly whether to hide the user's existing uploads
|
Nutzerverwaltung. The ban modal is a plain confirm: banning **always hides** the user's
|
||||||
from the public feed. Promote/demote, lock/unlock, release-gallery are one-tap.
|
existing uploads, there is no keep-visible option. Promote/demote/unban/release are each
|
||||||
- **Admin dashboard** — same dashboard plus three more inner tabs (Stats, Config, Export,
|
behind a confirmation sheet that spells out the consequence, not one-tap.
|
||||||
|
- **Admin dashboard** — same user list plus four inner tabs (Stats, Config, Export,
|
||||||
Nutzer). Config form covers per-file limits, rate limits, quota tolerance, estimated
|
Nutzer). Config form covers per-file limits, rate limits, quota tolerance, estimated
|
||||||
guest count, and compression concurrency — all stored in the `config` table and read on
|
guest count and the colour theme — all stored in the `config` table and read on
|
||||||
each request, so changes take effect without a restart. Disk widget pulls from the
|
each request, so changes take effect without a restart. Disk widget pulls from the
|
||||||
`sysinfo` crate live.
|
`sysinfo` crate live. The Export tab mirrors the host dashboard's release controls:
|
||||||
|
the release button is disabled once released, the live keepsake status (progress /
|
||||||
|
ready / failure reason) is shown, and a rebuild is offered as the recovery path.
|
||||||
|
|
||||||
### 2.5 Data mode (planned)
|
### 2.5 Data mode
|
||||||
|
|
||||||
Each device picks a **data mode** in My Account; the setting lives in `localStorage` so a
|
Each device picks a **data mode** in My Account; the setting lives in `localStorage` so a
|
||||||
guest can be on Saver on their phone and Original on their laptop.
|
guest can be on Saver on their phone and Original on their laptop.
|
||||||
@@ -218,7 +226,7 @@ guest can be on Saver on their phone and Original on their laptop.
|
|||||||
Applies uniformly to the live app's feed/lightbox **and** the diashow. The viewer (offline
|
Applies uniformly to the live app's feed/lightbox **and** the diashow. The viewer (offline
|
||||||
HTML export) is unaffected — it's already a snapshot of pre-bundled media variants.
|
HTML export) is unaffected — it's already a snapshot of pre-bundled media variants.
|
||||||
|
|
||||||
### 2.6 Rate limits and quotas — toggleable (planned)
|
### 2.6 Rate limits and quotas — toggleable
|
||||||
|
|
||||||
The Admin Config tab gains explicit on/off toggles in addition to the numeric inputs:
|
The Admin Config tab gains explicit on/off toggles in addition to the numeric inputs:
|
||||||
|
|
||||||
@@ -230,13 +238,14 @@ The Admin Config tab gains explicit on/off toggles in addition to the numeric in
|
|||||||
- **Per-area quota switch.** Storage-bytes quota and upload-count quota can be disabled
|
- **Per-area quota switch.** Storage-bytes quota and upload-count quota can be disabled
|
||||||
independently.
|
independently.
|
||||||
|
|
||||||
When a feature is toggled off, the relevant UI in the guest-facing app should adapt: e.g.
|
When a feature is toggled off, the dependent UI adapts: the "Speicher: X / Y" widget hides
|
||||||
the "Du hast X von Y MB genutzt" widget hides itself when storage quota is disabled. The
|
itself when the storage quota is disabled. The quota estimate is computed from the same
|
||||||
quota estimate is computed from the same formula the server uses
|
formula the server uses (`(free_disk × tolerance) / max(active_uploaders, 1)`) and is
|
||||||
(`(free_disk × tolerance) / max(active_uploaders, 1)`) — surfaced in My Account *and* on
|
surfaced in My Account *and* on the upload preview screen — but **only to hosts and
|
||||||
the upload preview screen so guests know before they pick files.
|
admins**. Guests never see it: the number moves as other people upload and as disk frees
|
||||||
|
up, which reads as a broken or unfair limit to someone who can't see why.
|
||||||
|
|
||||||
### 2.7 Privacy note (Datenschutzhinweis, planned)
|
### 2.7 Privacy note (Datenschutzhinweis)
|
||||||
|
|
||||||
Admin sets a free-text **Datenschutzhinweis** during instance setup (Admin Dashboard →
|
Admin sets a free-text **Datenschutzhinweis** during instance setup (Admin Dashboard →
|
||||||
Config). It's stored as a single config key (plain text, whitespace and newlines
|
Config). It's stored as a single config key (plain text, whitespace and newlines
|
||||||
|
|||||||
@@ -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
|
this PIN is the only way to sign in on another device. PIN is also written to
|
||||||
`localStorage`.
|
`localStorage`.
|
||||||
6. Guest taps **Weiter zur Galerie** → lands in the feed (`/feed`).
|
6. Guest taps **Weiter zur Galerie** → lands in the feed (`/feed`).
|
||||||
7. The **first-visit onboarding overlay** appears: dismissible steps (welcome, upload,
|
7. The **first-visit onboarding overlay** appears: six dismissible steps (welcome, upload,
|
||||||
hashtags, PIN, and a brief pointer to the **Datenschutzhinweis** in My Account).
|
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.
|
`localStorage('eventsnap_guide_seen') = 'true'` after dismiss.
|
||||||
8. Guest sees the bottom nav: **🏠 Feed · [📷+ FAB] · 👤 Account**.
|
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
|
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).
|
(`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
|
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.
|
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;
|
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).
|
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
|
4. **Galerie freigeben** — releases the export. Enqueues two export jobs (ZIP + HTML
|
||||||
viewer). Progress is visible in the Admin dashboard's Export tab; SSE
|
viewer). Progress is visible on the Host dashboard *and* in the Admin dashboard's Export
|
||||||
`export-progress` keeps it live; `export-available` notifies all guests when ready.
|
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:
|
5. **Nutzerverwaltung** — search users; per-user controls:
|
||||||
- **Sperren** opens a confirmation modal. Banning **always hides** the user's existing
|
- **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
|
uploads (a banned user's content is "gone" everywhere) — there is no opt-out. Submitting
|
||||||
calls `POST /host/users/{id}/ban` (no body).
|
calls `POST /host/users/{id}/ban` (no body).
|
||||||
- **Entsperren** lifts the ban. Same authority boundary as ban (below): a plain Host may
|
- **Entsperren** lifts the ban — and does two further things the confirm sheet now spells
|
||||||
only unban Guests; only an Admin may unban a Host.
|
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).
|
- **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
|
- **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
|
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`).
|
- **Stats**: live counts and disk-usage widget (via `sysinfo`).
|
||||||
- **Config**: per-file limits (image MB / video MB), rate limits (upload / feed /
|
- **Config**: per-file limits (image MB / video MB), rate limits (upload / feed /
|
||||||
export), quota tolerance, estimated guest count, compression-worker concurrency,
|
export), quota tolerance, estimated guest count, compression-worker concurrency,
|
||||||
plus the **Datenschutzhinweis** free-text editor and **on/off toggles** for the rate
|
plus the colour-theme picker, the **Datenschutzhinweis** free-text editor and
|
||||||
limiters and quotas (planned — see §16). Whitelist on the server side rejects
|
**on/off toggles** for the rate limiters and quotas (see §18). Whitelist on the
|
||||||
unknown keys. Values are read from the `config` table on each request — no restart
|
server side rejects unknown keys. Values are read from the `config` table on each
|
||||||
needed.
|
request — no restart needed.
|
||||||
- **Export**: list of past export jobs with status badges (pending / running / done /
|
- **Export**: the gallery-release control plus the list of export jobs with status
|
||||||
failed) and progress bars; refresh button re-polls.
|
badges (pending / running / done / failed) and progress bars; refresh button
|
||||||
- **Nutzer**: same user list as Host, with the additional Demote action and (planned)
|
re-polls. The release control mirrors the Host dashboard exactly — disabled once
|
||||||
PIN-reset on host rows.
|
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
|
## 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:
|
buttons. Tapping the HTML download first shows an in-app guide modal explaining:
|
||||||
"Entpacke die ZIP, öffne `index.html`". Tapping **Herunterladen** triggers the
|
"Entpacke die ZIP, öffne `index.html`". Tapping **Herunterladen** triggers the
|
||||||
browser download.
|
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
|
1. User taps a **Diashow / Präsentation** action (feed header on tablet/desktop, Account
|
||||||
on mobile).
|
on mobile).
|
||||||
@@ -240,7 +257,7 @@ See [CONCEPT_DIASHOW.md](CONCEPT_DIASHOW.md). Summary of the planned flow:
|
|||||||
immediately.
|
immediately.
|
||||||
6. Tap or Escape reveals an overlay (pause, dwell selector, exit).
|
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**.
|
1. Guest opens **My Account** → scrolls to **Datennutzung**.
|
||||||
2. Two options: **Datensparer (empfohlen)** and **Original**. Saver is the default.
|
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-
|
5. The viewer (offline HTML export) is unaffected — it already ships with its own pre-
|
||||||
bundled `_thumb` / `_full` variants.
|
bundled `_thumb` / `_full` variants.
|
||||||
|
|
||||||
## 15. Leaving an event
|
## 15. Signing out
|
||||||
|
|
||||||
1. User opens **My Account** → taps **🚪 Event verlassen**.
|
The wording matters here: nothing is *left* and nothing is deleted. The account, the PIN
|
||||||
2. Bottom-sheet confirmation: "Event verlassen?" with **Abmelden** and **Bleiben**.
|
and every uploaded photo survive — the user signs back in with name + PIN whenever they
|
||||||
3. Confirming calls `DELETE /api/v1/session` (invalidates the session row), clears the JWT
|
like. ("Event verlassen" read as leaving for good, which the confirmation sheet then
|
||||||
and PIN from `localStorage`, and redirects to the join page.
|
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**.
|
1. User opens **My Account** → scrolls to **Datenschutzhinweis**.
|
||||||
2. The note is rendered inside a preformatted block (`<pre>`-style: monospace, whitespace
|
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
|
4. Admin sets / edits the note in **Admin Dashboard → Config → Datenschutzhinweis**: a
|
||||||
tall textarea with a save button. Saved to a single `config` key.
|
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
|
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.
|
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 |
|
| Gesture | Action |
|
||||||
|-------------------------------------------|-------------------------------------------------------|
|
|-------------------------------------------|-------------------------------------------------------|
|
||||||
| Long-press on a post (own) | Bottom sheet → Löschen, Original anzeigen, Teilen |
|
| 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
|
context), Telegram (swipe-to-reply on messages — could inform comment threads if those
|
||||||
land).
|
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**.
|
1. Admin opens **Admin Dashboard → Config**.
|
||||||
2. **Rate-Limits** section: a master switch and per-endpoint switches (upload / feed /
|
2. **Rate-Limits** section: a master switch and per-endpoint switches (upload / feed /
|
||||||
@@ -305,8 +336,9 @@ land).
|
|||||||
limiter entirely.
|
limiter entirely.
|
||||||
5. **Quoten** section mirrors the pattern: master toggle plus per-area toggles (storage
|
5. **Quoten** section mirrors the pattern: master toggle plus per-area toggles (storage
|
||||||
bytes / upload count).
|
bytes / upload count).
|
||||||
6. When the storage-quota toggle is off, the **"X von Y MB genutzt"** widget in the
|
6. When the storage-quota toggle is off, the **"Speicher: X / Y"** widget in My Account and
|
||||||
guest's My Account and upload screen hides itself (no quota → no number to show).
|
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.
|
Suggested defaults at deploy time: all toggles **on**, sensible numeric limits.
|
||||||
Toggling off is the explicit escape hatch for testing or trusted internal events.
|
Toggling off is the explicit escape hatch for testing or trusted internal events.
|
||||||
|
|||||||
Reference in New Issue
Block a user