A parked upload has three ways to be released, and two were weaker than the toast that promises "wird gesendet, sobald die Sperre aufgehoben ist": * The live user-shown / event-opened events only reach a tab with an open stream, and streams are opened by /feed, /diashow, /export, /host and /admin — NOT /upload, which is exactly where the toast sends the guest to watch their queue. * The boot-time release ran once and swallowed any failure, so a single failed request on venue wifi — the condition the whole parking mechanism exists for — skipped it for the entire session, leaving the row reading "Du bist gesperrt." after the ban was long lifted. Now retried once. Deliberately NOT on a 401: api.get already answered that by clearing auth and redirecting to /join, so a second attempt can only fire a second redirect two seconds later, by which time the guest may have navigated away. (That is not hypothetical — it made an existing browser-chaos spec fail while I was writing this.) Guarded rather than an early return, so the SSE listener registrations below still run. Also: noteDelivered mapped upload-processed to p.id, but that payload carries upload_id, so it recorded nothing — the docstring claimed a property the code did not have. And it recorded user-shown against a `carried` clause that only ever means "hidden", where it could only suppress a later genuine signal.
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 inlocalStorage,isAuthenticatedwritableui-store.ts— bottom-nav visibility, upload-sheet open state, FAB badge countdata-mode-store.ts— Saver vs Original media-loading preferenceprivacy-note-store.ts— admin-configured Datenschutzhinweis textquota-store.ts— live per-user storage snapshotupload-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.