feat: planning structure + task 1.2 stack rounding-out

Add .planning/ scaffolding:
- ROADMAP.md (4 phases, 8 non-negotiable design rules)
- phase-1-foundation/ README + 9 task files (1.2-1.10)
- phase-2-live-map / phase-3-dogfood-readiness / phase-4-future README placeholders

Task 1.2 — stack rounding-out:
- Tailwind 4 via @tailwindcss/vite + src/styles/globals.css
- shadcn/ui (slate, new-york) primitives in src/ui/primitives/:
  button, input, label, form, card, alert
- TanStack Router 1.169 + Query 5.100 (devtools + plugin in devDeps)
- Zustand 5, @directus/sdk 21, zod 4, react-hook-form 7 + resolvers
- Prettier 3 + eslint-config-prettier + eslint-plugin-prettier
- ESLint override disabling react-refresh/only-export-components for
  src/ui/primitives/** (intentional dual-exports in shadcn primitives)
- Path alias @/* -> ./src/* in tsconfig.json + tsconfig.app.json
  (TS 6 deprecates baseUrl; paths now resolve relative to config file).
  Pulled forward from 1.3 because shadcn add CLI needs it resolvable.
- Scripts: dev, build, preview, lint, typecheck, format, format:check,
  test (placeholder)
- App.tsx Tailwind smoke test (centred card + shadcn Button)
- README.md rewritten with stack/scripts/shadcn-add docs

All four gates green: typecheck, lint, format:check, build (222KB / 70KB gz).
This commit is contained in:
2026-05-02 17:34:34 +02:00
parent c3c83b53f6
commit 26e059fc20
34 changed files with 4868 additions and 127 deletions
@@ -0,0 +1,42 @@
# Phase 3 — Dogfood readiness
**Status:** ⬜ Not started
The set of operational features that turn a working pilot into something safe to put in front of race operators on race day. None of these tasks change correctness; they change the experience when things don't go perfectly.
## Outcome statement
When Phase 3 is done:
- **Error boundaries** wrap each major feature region (map, sidebar, header). A crash in the map widget doesn't blank the whole UI; the operator sees a "Map crashed; reload to retry" panel and can keep using the rest.
- **Connection-state UI** is unmissable but not noisy. WS reconnecting → small banner. WS offline > 30s → larger warning. Per-device "last seen N seconds ago" in the device sidebar.
- **Mobile-responsive baseline.** The SPA doesn't crash on a 375px-wide viewport. Map fills the screen; sidebar collapses to a sheet. Not a separate mobile app — the same app, usable in a marshal's pocket browser.
- **Per-device detail panel.** Click a device on the map → side panel showing recent positions, current vehicle/crew, last-seen, manual notes the marshal has typed. Doesn't add data flows; consumes existing snapshots.
- **Operator-friendly empty / loading states.** No blank screens during the snapshot fetch; "No devices reporting yet" message when the event has subscribed but no positions arrived.
- **Vitest + React Testing Library set up.** Unit tests for the auth store, the rAF coalescer, the position-store reducer. Not a comprehensive suite — enough that future changes don't silently regress critical paths.
## Why this is a separate phase
Phase 2 produces a working live map. Phase 3 makes that map _fielded_ — usable when networks are flaky, browsers are exotic, screens are tiny, and operators are stressed. None of these tasks block the map from working in a controlled demo, but all of them affect whether the dogfood is genuinely useful versus a mostly-working tech demo.
## Tasks (sketched, not detailed)
| # | Task | Notes |
| --- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| 3.1 | Error boundaries + crash-recovery UI | One per major region (map, sidebar, header). Logs to console + optional remote-error endpoint |
| 3.2 | Connection-state UI | Banner / chip in the header; per-device last-seen in the sidebar |
| 3.3 | Mobile-responsive baseline | Tailwind breakpoints; map full-screen on mobile; sidebar → sheet |
| 3.4 | Per-device detail panel | Click → side panel; reads from Directus + position store |
| 3.5 | Empty / loading state polish | "Connecting…", "Waiting for first positions…", "No devices in this event yet" |
| 3.6 | Vitest + React Testing Library setup + targeted tests | Auth store, coalescer, position-store reducer. Not exhaustive |
| 3.7 | Production logging discipline | Console-error captures + optional remote-shipper for stage |
| 3.8 | Visual brand pass | Apply the actual TRM brand if one exists by then. Logo + colour palette |
## Acceptance for the phase as a whole
- [ ] All eight tasks (3.13.8) done.
- [ ] Pulling the network mid-session shows a clear "reconnecting" state; reconnecting works; the operator's selection / event-picker state survives the gap.
- [ ] Loading the SPA on an iPhone 12 (or equivalent) viewport doesn't break layout.
- [ ] Clicking a device opens the detail panel; the panel shows the device's current vehicle/crew (joined from Directus) and recent positions (from the store).
- [ ] Vitest runs in CI; existing tests are green; coverage is documented but not gated on a percentage threshold.
- [ ] The dogfood-day operator sign-off: a marshal can use the SPA on race day without us standing over them. (This is the real acceptance gate; the rest is observable proxy.)