From 9a0ceeced72f50d5133bb2a9faf8d00fd93272e7 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sat, 16 May 2026 14:31:06 +0200 Subject: [PATCH] docs: realign blueprint with shipped state + add feature/journey/ideas docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- PROJECT.md | 122 +++++++------- README.md | 44 +++-- TEST_GUIDE.md | 52 +++--- backend/migrations/README.md | 28 ++++ docs/CONCEPT_DIASHOW.md | 196 ++++++++++++++++++++++ docs/CONCEPT_HTML_VIEWER.md | 10 ++ docs/CONCEPT_MOBILE_UI.md | 57 ++++++- docs/FEATURES.md | 313 +++++++++++++++++++++++++++++++++++ docs/IDEAS.md | 199 ++++++++++++++++++++++ docs/USER_JOURNEYS.md | 292 ++++++++++++++++++++++++++++++++ frontend/src/lib/README.md | 34 ++++ 11 files changed, 1241 insertions(+), 106 deletions(-) create mode 100644 backend/migrations/README.md create mode 100644 docs/CONCEPT_DIASHOW.md create mode 100644 docs/FEATURES.md create mode 100644 docs/IDEAS.md create mode 100644 docs/USER_JOURNEYS.md create mode 100644 frontend/src/lib/README.md diff --git a/PROJECT.md b/PROJECT.md index 94c189d..1e009a8 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -42,7 +42,7 @@ A guest scans the QR code on their way in, types their name, and is immediately Mobile-first Progressive Web App (PWA) — accessible via browser, no app store required. ### Status -Idea / Planning phase. Greenfield personal project. +Implementation in progress (~v0.16). Core flows + new features all wired: auth, feed, upload, host/admin dashboards, ZIP + HTML-viewer export, SSE with delta-fetch on reconnect, toggleable rate limits + quotas with live per-user estimate, host PIN reset with one-time modal, data-mode (Saver/Original), Datenschutzhinweis, mobile gestures (long-press context sheet, double-tap to like), and the live Diashow with pluggable transitions. Open items: low-disk alert, event banner UI, chunked resumable upload for very large videos. See [FEATURES.md](docs/FEATURES.md) for the capability matrix. --- @@ -208,9 +208,9 @@ Personal / private use. One event at a time. Up to ~100 users uploading ~1,000 f │ Axum HTTP Server (Rust — Single Binary) │ │ │ │ ┌─────────────┐ ┌──────────────┐ ┌────────────────────────┐ │ -│ │ REST API │ │ SSE Engine │ │ Static File Server │ │ -│ │ /api/v1/* │ │ /api/v1/ │ │ (SvelteKit build │ │ -│ │ │ │ stream │ │ output, embedded) │ │ +│ │ REST API │ │ SSE Engine │ │ Media Static Server │ │ +│ │ /api/v1/* │ │ /api/v1/ │ │ /media/* (originals, │ │ +│ │ │ │ stream │ │ previews, thumbnails) │ │ │ └──────┬──────┘ └──────┬───────┘ └────────────────────────┘ │ │ │ │ │ │ ┌──────▼──────────────────────┐ ┌──────────────────────────┐ │ @@ -245,36 +245,14 @@ Personal / private use. One event at a time. Up to ~100 users uploading ~1,000 f ### Docker Compose Stack +Four services: Postgres, the Rust API (`app`), the SvelteKit Node server (`frontend`), and Caddy. Caddy routes `/api/*` and `/media/*` to the Rust binary and everything else to the SvelteKit server. See [docker-compose.yml](docker-compose.yml) for the authoritative definition. + ```yaml services: - app: - build: ./backend # Multi-stage Rust Dockerfile - env_file: .env - depends_on: [db] - volumes: - - media_data:/media - restart: unless-stopped - - db: - image: postgres:16-alpine - env_file: .env - volumes: - - postgres_data:/var/lib/postgresql/data - restart: unless-stopped - - caddy: - image: caddy:2-alpine - ports: ["80:80", "443:443"] - volumes: - - ./Caddyfile:/etc/caddy/Caddyfile:ro - - caddy_data:/data - depends_on: [app] - restart: unless-stopped - -volumes: - postgres_data: - media_data: - caddy_data: + db: # postgres:16-alpine, persisted in postgres_data volume + app: # ./backend — Rust API on :3000, mounts media_data:/media + frontend: # ./frontend — SvelteKit (adapter-node) on :3001 + caddy: # caddy:2-alpine — terminates TLS on :80/:443, proxies app + frontend ``` ### Caddyfile @@ -345,11 +323,14 @@ COMPRESSION_WORKER_CONCURRENCY=2 |-------|---------|---------| | `new-upload` | `{ id, preview_url, uploader, caption, created_at }` | Upload processing complete | | `new-comment` | `{ id, upload_id, body, uploader, created_at }` | Comment posted | -| `new-like` | `{ upload_id, like_count }` | Like toggled | +| `like-update` | `{ upload_id, like_count }` | Like toggled | | `upload-deleted` | `{ upload_id }` | Upload deleted | | `event-closed` | `{}` | Host locks uploads | | `event-opened` | `{}` | Host unlocks uploads | | `export-available` | `{ types: ["zip","html"] }` | Export generation complete | +| `upload-processed` | `{ upload_id, preview_url, thumbnail_url }` | Server-side compression / preview generation finished | +| `upload-error` | `{ upload_id, message }` | Compression / preview generation failed | +| `export-progress` | `{ type, progress_pct }` | Periodic progress update from an export job | **Client SSE lifecycle:** `visibilitychange: hidden` → close connection · `visible` → reconnect + delta-fetch via `GET /api/v1/feed/delta?since=` @@ -372,7 +353,7 @@ COMPRESSION_WORKER_CONCURRENCY=2 | Real-Time | Axum SSE + `tokio::sync::broadcast` | Native, lightweight, perfect for fan-out at this scale | | ZIP Export | `async-zip` crate | Streaming ZIP generation without buffering the full archive in RAM | | HTML Export | `minijinja` (Rust templating) | Generates `Memories.html` as a single self-contained file | -| Rate Limiting | `tower-governor` | Token-bucket per IP / per user; config from DB; hot-reloadable | +| Rate Limiting | Custom in-memory sliding-window limiter ([services/rate_limiter.rs](backend/src/services/rate_limiter.rs)) | Per IP / per user; limits read from `config` DB table on each request; hot-reloadable without restart | | Reverse Proxy | Caddy 2 | Automatic HTTPS via Let's Encrypt; zero certificate management | | Containerisation | Docker + Docker Compose | Full stack in one file; `.env` for all config; single-command deploy | | Infrastructure | Hetzner CX33 (4 vCPU, 8 GB RAM, 80 GB SSD, 20 TB traffic) | Well-sized; 20 TB/month means post-event bulk downloads are no issue | @@ -417,9 +398,9 @@ No paid third-party services required. | Role | Permissions | |------|------------| -| Guest | Upload (within quota), caption/hashtag, like, comment, delete own content, view feed, download export (after release) | -| Host | All guest permissions + ban/unban users (with upload visibility prompt), delete any content, promote guests to Host, lock/unlock uploads, release gallery export | -| Admin | All Host permissions + configure storage/file/rate limits, quota tolerance, view disk usage, manage app config, trigger export generation | +| Guest | Upload (within quota), caption/hashtag, like, comment, delete own content, view feed, download export (after release), pick data mode, read privacy note | +| Host | All guest permissions + ban/unban users (with upload visibility prompt), delete any content, promote guests to Host, demote *other* Hosts to guest (never self), reset guest PINs (planned), lock/unlock uploads, release gallery export | +| Admin | All Host permissions + reset any non-admin PIN, configure storage/file/rate limits with on/off toggles, edit quota tolerance and per-area quota toggles, edit the Datenschutzhinweis, view disk usage, manage app config, trigger export generation | | Banned Guest | View feed only — cannot upload, like, comment, or export | ### Compliance @@ -473,37 +454,33 @@ Full-quality originals only. File naming: `{date}_{time}_{username}_{original_fi ### Export Type 2: HTML Offline Viewer (`Memories.zip`) +The HTML export is a **pre-built SvelteKit static app** (`adapter-static`, `ssr=false`) shipped together with the event data. It is a non-interactive, read-only clone of the live feed — same components, same Tailwind tokens, same look — minus auth, upload, comment, and any dashboards. Full design rationale in [docs/CONCEPT_HTML_VIEWER.md](docs/CONCEPT_HTML_VIEWER.md). + ``` Memories/ - Memories.html ← single entry point (all CSS + JS inlined; no external deps) - README.txt ← plain-text setup guide (in German, as the UI language) - Photos/ ... - Videos/ ... + index.html ← entry point; open this in any browser + _app/ + immutable/... ← hashed JS/CSS bundles (viewer SPA) + data.json ← event metadata, posts, comments, likes, hashtags + media/ + {id}_thumb.jpg ← grid thumbnails (≈400 px wide) + {id}_full.jpg/.mp4 ← full-size media for the lightbox ``` -**Fully self-contained / true offline:** `Memories.html` is a single file with all CSS and JS inlined as `