feat: task 1.4 runtime config endpoint

Three-file config module under src/config/:
- schema.ts: RuntimeConfigSchema (directusUrl, liveWsUrl, businessWsUrl,
  optional googleMapsKey, env enum). Custom UrlOrAbsolutePath validator
  accepts http(s)/ws(s) URLs plus paths starting with /.
- load.ts: loadConfig() fetches /config.json with cache: 'no-store',
  safeParse, throws ConfigValidationError on schema failure.
- context.ts: RuntimeConfigContext + useRuntimeConfig() hook (no JSX).
- provider.tsx: RuntimeConfigProvider — loading / ready / error states.
  Single retry after 500ms on network failure; renders zod issues on
  validation failure for debuggability.

public/config.json: committed dev defaults (relative paths matching the
Vite proxy from 1.3).

src/main.tsx wraps App in <RuntimeConfigProvider>.
README gains a Runtime config section with the schema table.

Deviation: spec sketched provider+hook in one context.tsx. Split into
context.ts (hook only) and provider.tsx (component only) to satisfy
react-refresh/only-export-components without adding an eslint override.
This commit is contained in:
2026-05-02 17:43:26 +02:00
parent 90c35e0ad5
commit 309333b2d3
9 changed files with 232 additions and 3 deletions
+16
View File
@@ -57,6 +57,22 @@ Override per-developer by copying `.env.example` to `.env.local` and editing —
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`.