# 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.1–3.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.)