Files
EventSnap/frontend/src/lib
MechaCat02 db1e5b8833 fix: post-review punch-list — upload concurrency cap, atomic release, ban SSE
Follow-up to the adversarial re-review of the audit-fix branch.

MUST-FIX:
- H3: bound aggregate upload RAM. The body limit alone didn't cap concurrency,
  so ~N parallel 550MB uploads could OOM the box. Add an Arc<Semaphore>
  (UPLOAD_MAX_CONCURRENCY=4) in AppState, acquired at the top of the upload
  handler before the multipart body is read, so waiting requests hold only a
  connection — peak buffered RAM ≈ 4×550MB. (Matches the CompressionWorker
  semaphore pattern; avoids tower's non-default `limit` feature.)
- M8: release_gallery now runs the claim UPDATE + both job INSERTs in one
  transaction, so the row lock is held until the jobs exist — closing the
  cross-table TOCTOU where two concurrent presses could both spawn workers
  racing the same output files. Export temp filenames are now per-run
  (Gallery.{uuid}.zip.tmp, viewer_tmp_{event}_{uuid}, Memories.{uuid}.zip.tmp)
  so overlapping runs can't truncate each other. Final served names unchanged.
- M11: 'user-banned' was missing from sse.ts KNOWN_EVENTS, so EventSource
  silently dropped the frame and the forced-logout handler never fired. Added.

SHOULD-FIX:
- M8-3: re-release is now allowed when nothing is in progress and at least one
  job failed (was: only when *every* job failed), so a one-sided failure (zip
  done, html failed) is no longer permanently unrecoverable.
- M18: two light-mode contrast spots the earlier sweep missed — the LightboxModal
  char-counter class: directives and the account PIN-missing notice.
- M15: the diashow auto-advance is now slowed to a ≥30s floor under
  prefers-reduced-motion (WCAG 2.2.2), making the app.css comment accurate.

Verified: cargo build + 6 tests, npm run check (0 errors), and a live smoke test
against Postgres — first release 204, second 400, one-sided-failure re-release
204, no SQL errors; a normal upload still returns 201 through the new permit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 17:40:54 +02:00
..

lib/ conventions

Short rules. The patterns we already follow as of v0.16 — write new code that fits.

One store per cross-cutting concern. A single *-store.ts file owns each one:

  • auth.ts — JWT / PIN in localStorage, isAuthenticated writable
  • ui-store.ts — bottom-nav visibility, upload-sheet open state, FAB badge count
  • data-mode-store.ts — Saver vs Original media-loading preference
  • privacy-note-store.ts — admin-configured Datenschutzhinweis text
  • quota-store.ts — live per-user storage snapshot
  • upload-queue.ts — IndexedDB-persisted upload queue + processing state

Don't import these into other stores unless strictly necessary; let pages compose them.

DTOs mirror Rust types. All TS interfaces live in types.ts. Each one carries a // mirrors backend/src/path::TypeName comment so the two stay searchable. If you add a Rust DTO, add the TS twin in the same PR.

Gestures via Svelte actions in actions/. Long-press, double-tap, future swipe — each is a use: action that fires a CustomEvent. Components stay free of gesture plumbing.

Reusable bottom sheets via ContextSheet.svelte. Pass an actions: ContextAction[] array. Any page that needs a long-press / kebab context menu uses the same primitive.

SSE relays are listed in sse.ts::KNOWN_EVENTS. New server event → add one entry to that array, that's it.

Diashow transitions live in diashow/transitions/. Each is a Svelte component plus one entry in transitions/index.ts. Adding a new animation is two-line work; no diashow code needs to change.

No new global stores beyond the list above unless the new concept is genuinely app-wide. Page state belongs in the page.