julian 4ac5ed4eca fix(auth): switch directus sdk to 'session' mode
'cookie' mode keeps the session alive via an in-memory access token
that's refreshed from a refresh cookie when the SDK is alive. After a
hard reload the SDK has no access token in memory, so /users/me 401s
before autoRefresh can kick in (or the refresh cookie's surface doesn't
cover plain reads cleanly). Net effect: every reload bounces back to
login even when the cookie is still valid.

'session' mode puts the actual session in the cookie. Browser sends it
automatically on every request, the SDK doesn't need to manage tokens,
and reload survives cleanly because /users/me with the session cookie
just works.

Reordered .with() calls to match the working pattern from a prior
project: rest() before authentication() (cosmetic; SDK accepts either
order).
2026-05-02 18:45:31 +02:00
2026-05-02 16:53:12 +02:00
2026-05-02 16:56:15 +02:00
2026-05-02 16:53:12 +02:00

trm/spa

End-user single-page app for the TRM platform: race directors, marshals, timekeepers, and (post-Phase 4 of trm/directus) public-facing participants. Talks to trm/directus for REST/GraphQL + business-plane WebSocket and to trm/processor for the live-position WebSocket firehose.

Architectural anchors live in trm/docs/wiki/:

  • wiki/entities/react-spa.md — this service.
  • wiki/concepts/maps-architecture.md — map subsystem (Phase 2).
  • wiki/synthesis/processor-ws-contract.md — live-position wire spec (Phase 2).
  • wiki/synthesis/directus-schema-draft.md — schema this consumes.

Implementation planning lives in .planning/ — see .planning/ROADMAP.md.

Stack

  • Vite 8 + React 19 + TypeScript 6 — SPA build, no SSR.
  • Tailwind CSS 4 via @tailwindcss/vite.
  • shadcn/ui primitives (slate base, new-york style) — copy-in components owned by us, in src/ui/primitives/.
  • TanStack Router + Query — file-based routes, server-state cache.
  • Zustand — high-frequency client state (auth, live positions in Phase 2).
  • @directus/sdk — typed Directus client (REST + WS).
  • zod — runtime validation (config, forms, WS protocol).
  • react-hook-form + @hookform/resolvers — forms.

Common scripts

Command Purpose
pnpm dev Start the Vite dev server on :5173.
pnpm build Type-check + production build into dist/.
pnpm preview Serve the built dist/ for smoke testing.
pnpm typecheck Type-check only (no build).
pnpm lint ESLint over the repo.
pnpm format Prettier auto-format.
pnpm format:check Prettier check only (CI gate).
pnpm test Test runner (Phase 3 wires Vitest).

Adding a shadcn primitive

pnpm dlx shadcn@latest add <component>

Components land in src/ui/primitives/ per components.json's alias config. Edit them freely — they're our code from the moment they land.

Local dev

The Vite dev server (pnpm dev on :5173) proxies three path namespaces to local services so everything runs same-origin — the same topology stage/prod gets via Traefik, no CORS workarounds needed.

Path Forwards to Notes
/api/* ${VITE_DEV_DIRECTUS_URL}/* Directus REST + GraphQL. Default http://localhost:8055.
/ws-business ${VITE_DEV_DIRECTUS_URL}/websocket Directus business-plane WebSocket (auto-derived ws:// scheme).
/ws-live ${VITE_DEV_PROCESSOR_WS_URL}/ Processor live-position WebSocket. Default ws://localhost:8081 (Phase 1.5).

Override per-developer by copying .env.example to .env.local and editing — Vite auto-loads .env.local in any mode and the values flow into vite.config.ts via loadEnv.

If the dev port 5173 collides with another Vite app, run with VITE_PORT=5174 pnpm dev (or pin a different port in vite.config.ts).

Runtime config

/config.json is fetched at app boot, validated with zod, and held in a React context (useRuntimeConfig() hook). One image, multiple environments — the deploy stack overrides the file via a Docker volume mount; the SPA never rebuilds for an env-only change.

Field Type Notes
directusUrl URL/path Directus REST + GraphQL base. Same-origin path or absolute URL.
liveWsUrl URL/path Processor live-position WebSocket URL.
businessWsUrl URL/path Directus business-plane WebSocket URL.
googleMapsKey string? Optional. If absent, Google tile sources are hidden in the UI.
env enum dev / stage / prod — used in log labels and feature-flag conditionals.

URLs may be absolute (http(s)://, ws(s)://) or absolute paths starting with /. Relative paths work because the SPA is always same-origin to its backends. The committed public/config.json uses path-only defaults that match the dev proxy.

In stage/prod the deploy stack mounts an override file at /usr/share/nginx/html/config.json; see trm/deploy/README.md (lands in Phase 1.10).

CI

.gitea/workflows/build.yml (lands in Phase 1.9) runs the four gates — typecheck, lint, format:check, build — and publishes a Docker image on push to main.

S
Description
No description provided
Readme 701 KiB
Languages
TypeScript 49.1%
HTML 24.7%
JavaScript 20.3%
CSS 5.6%
Dockerfile 0.3%