- Status: Accepted
- Date: 2026-04-15
- Deciders: Jamie White
A medication tracker is mutation-heavy: log dose, edit dose, skip dose, add medication, change preference, etc. We need every mutation to be:
- Auth-checked server-side.
- Auditable (the
audit_logstable records create/update/delete with JSONB diffs). - Tolerant of partial JS — if a user's browser has JS disabled or fails, the form should still submit and re-render.
Calling JSON APIs from the client and managing optimistic UI by hand would multiply the surface area for security mistakes (forgotten ownership checks, missing CSRF protection, request-replay holes).
Use SvelteKit form actions with use:enhance for every mutation.
No client-side fetch to internal JSON endpoints for write paths.
Read paths fetch via +page.server.ts loaders and pass typed data to
the page.
- JSON REST endpoints + a client cache — more conventional, but
doubled the auth and ownership-check surface. SvelteKit's loader/
action model lets
locals.userflow throughhooks.server.tsto every action without per-route plumbing. - tRPC — keeps types end-to-end but adds a runtime layer that the form actions don't need. The bundle savings of going without tRPC matters on the slower mobile target the PWA aims at.
- Server actions à la Next — SvelteKit's are arguably cleaner because the form falls back to a real POST when JS is unavailable.
Positive
- Every mutation re-renders fresh server state on success — no stale cache to invalidate.
use:enhancekeeps the optimistic feel without needing a client-side store.- Re-auth gate (Phase 1) and rate limit (Phase 1 / earlier) plug in cleanly because they live next to the action body.
Negative
- Some interactions (e.g., live dose-time-since counter) still need client state. We accept that and keep those islands small.
- Action return shapes are TypeScript discriminated unions across
every
fail()call; we worked around the narrowing in templates with a typederrorscast (Phase 2 / Phase 3 fix-types commit).