79e50289fe
Two CI gaps surfaced on first push: 1. typecheck failed because tsc -b ran before vite build, but src/routeTree.gen.ts is only generated by the Vite plugin during build. tsc has nothing to typecheck against. Fix: install @tanstack/router-cli and chain `tsr generate` before tsc in the typecheck and build scripts. Now any environment that runs typecheck cold (CI, fresh clone) generates the route tree first. Also added a top-level `route-tree` script so the same command is reusable elsewhere if needed. 2. format:check would fail on Windows working trees because git autocrlf (default on Windows) checks files out with CRLF, but .prettierrc pins endOfLine: "lf". Locally the format:check intermittently passed/failed depending on whether files had been recently auto-formatted. Fix: .gitattributes with `* text=auto eol=lf` enforces LF in every working tree. Plus explicit overrides for binary blobs (images) and the route tree file. `git add --renormalize .` brought the index in line with the new policy; no actual file content changed. CI on the next push should now see the same green gates the local working tree shows.
90 lines
8.0 KiB
Markdown
90 lines
8.0 KiB
Markdown
# spa — Roadmap
|
|
|
|
A React + TypeScript single-page application for end-user operators of the TRM platform: race directors, marshals, timekeepers, and (post-Phase 4 of [[directus]]) public-facing participants. Talks to [[directus]] for REST/GraphQL + business-plane WebSocket and to [[processor]] for the live-position WebSocket firehose.
|
|
|
|
This file is the single navigation hub for all SPA implementation planning. Each phase has its own folder with a README and granular task files. Update statuses here as work lands.
|
|
|
|
## Status legend
|
|
|
|
| Symbol | Meaning |
|
|
| ------ | ----------------------------- |
|
|
| ⬜ | Not started |
|
|
| 🟦 | Planned (designed, not coded) |
|
|
| 🟨 | In progress |
|
|
| 🟩 | Done |
|
|
| ⏸ | Paused / blocked |
|
|
| ❄️ | Frozen / future / optional |
|
|
|
|
## Architectural anchors
|
|
|
|
The SPA is specified by the wiki at `../docs/wiki/`. Implementing agents should read these pages before starting any task:
|
|
|
|
- **This service** — `docs/wiki/entities/react-spa.md`
|
|
- **Map subsystem** — `docs/wiki/concepts/maps-architecture.md`, `docs/wiki/sources/traccar-maps-architecture.md`
|
|
- **Live channel** — `docs/wiki/concepts/live-channel-architecture.md`, `docs/wiki/synthesis/processor-ws-contract.md`
|
|
- **Auth + business plane** — `docs/wiki/entities/directus.md`, `docs/wiki/synthesis/directus-schema-draft.md`
|
|
- **System architecture** — `docs/wiki/sources/gps-tracking-architecture.md`, `docs/wiki/concepts/plane-separation.md`
|
|
|
|
## Non-negotiable design rules
|
|
|
|
These rules govern every task. Any deviation must be discussed and documented as a decision before code lands.
|
|
|
|
1. **Singleton MapLibre.** A single `maplibregl.Map` instance lives in a module-level variable, attached to a detached `<div>`. React mounts/unmounts the div via refs across page navigation. WebGL context never recreates. Pattern is documented in `docs/wiki/concepts/maps-architecture.md`.
|
|
2. **Side-effect-only `Map*` components.** Every map-participating component returns `null` and uses `useEffect` for setup + cleanup, with a separate effect for `setData` updates. No DOM marker components, no `react-map-gl`.
|
|
3. **rAF coalescer at the WS boundary.** Position messages buffer per-device; one `requestAnimationFrame` tick flushes the latest snapshot to the Zustand store. Per-message dispatch is the failure mode `traccar-web` exhibits — we don't replicate it.
|
|
4. **Same-origin everything.** SPA bundle, [[directus]] REST/WS, and [[processor]] WS all served under one origin via reverse proxy. Vite dev proxy in dev; Traefik in stage/prod. Cross-origin breaks the cookie auth flow.
|
|
5. **In-memory access token, httpOnly refresh cookie.** Access token never touches `localStorage`. Refresh cookie is `httpOnly + Secure + SameSite=Lax`. Refresh on 401 via Directus's `/auth/refresh`.
|
|
6. **Role-aware UI shape from day one.** Even though everyone's admin until [[directus]] Phase 4, the route guards and conditional rendering check `user.role` rather than hard-coding "everyone sees everything." Retrofitting is painful; baking it in costs nothing.
|
|
7. **Runtime config, not build-time.** Per-environment values (Directus URL, processor WS URL, optional Google Maps key) come from `/config.json` served by the proxy. One image, multiple environments. No rebuild to switch envs.
|
|
8. **Native PostGIS GeoJSON.** Geometry round-trips through `ST_AsGeoJSON(geometry)` on the server, not WKT. The SPA never imports `wellknown` or runs WKT parsing in the client.
|
|
|
|
## Phases
|
|
|
|
### Phase 1 — Foundation
|
|
|
|
**Status:** 🟩 Done
|
|
**Outcome:** A deployable empty-shell SPA: scaffold + stack rounded out + Directus cookie auth flow + protected routes + Gitea CI + compose deploy block. End state: an operator can browse to `https://stage.trmtracking.org`, log in with a Directus credential, see a placeholder home page, log out. No live map yet — that's Phase 2.
|
|
|
|
[**See `phase-1-foundation/README.md`**](./phase-1-foundation/README.md)
|
|
|
|
| # | Task | Status | Landed in |
|
|
| ---- | ------------------------------------------------------------------------------------------------------------ | ------ | -------------------- |
|
|
| 1.1 | Project scaffold (Vite + React + TS) | 🟩 | (manual) |
|
|
| 1.2 | [Stack rounding-out (Tailwind + shadcn/ui + deps + Prettier)](./phase-1-foundation/02-stack-rounding-out.md) | 🟩 | `9918418` |
|
|
| 1.3 | [Vite dev proxy + path aliases + tsconfig hardening](./phase-1-foundation/03-vite-dev-proxy.md) | 🟩 | `39b60c9` |
|
|
| 1.4 | [Runtime config endpoint](./phase-1-foundation/04-runtime-config.md) | 🟩 | `8e2151a` |
|
|
| 1.5 | [Directus auth client (cookie mode + refresh)](./phase-1-foundation/05-directus-auth-client.md) | 🟩 | `38fe2e3` |
|
|
| 1.6 | [Login page](./phase-1-foundation/06-login-page.md) | 🟩 | `7215cb5` |
|
|
| 1.7 | [Routing skeleton (TanStack Router + role-aware guards)](./phase-1-foundation/07-routing-skeleton.md) | 🟩 | `f4a5e5b` |
|
|
| 1.8 | [Logout flow](./phase-1-foundation/08-logout-flow.md) | 🟩 | `1ee339c` |
|
|
| 1.9 | [Gitea CI + Dockerfile + nginx static serve](./phase-1-foundation/09-gitea-ci-and-dockerfile.md) | 🟩 | `9bd3b84` |
|
|
| 1.10 | [Compose service block in trm/deploy](./phase-1-foundation/10-deploy-compose-block.md) | 🟩 | trm/deploy `68ab08f` |
|
|
|
|
### Phase 2 — Live monitoring map
|
|
|
|
**Status:** ⬜ Not started — depends on [[processor]] Phase 1.5 landing
|
|
**Outcome:** The dogfood-day deliverable. MapLibre singleton + tile-source switcher + sprite preload + WS client with rAF coalescer + Zustand position store + `MapPositions` (clustered + selected) + `MapTrails` (bounded ring buffer) + event picker + camera control trio + connection-status indicator. Operators on race day open one page, pick the active event, and watch their field move in real time.
|
|
|
|
[**See `phase-2-live-map/README.md`**](./phase-2-live-map/README.md)
|
|
|
|
### Phase 3 — Dogfood readiness
|
|
|
|
**Status:** ⬜ Not started
|
|
**Outcome:** Operational polish a closed pilot needs but a demo doesn't: error boundaries that don't blank-screen the map, connection-state UI that tells the operator "WS reconnecting", mobile-responsive baseline (doesn't crash on a phone in the field), per-device detail panel, operator-friendly empty/loading states.
|
|
|
|
[**See `phase-3-dogfood-readiness/README.md`**](./phase-3-dogfood-readiness/README.md)
|
|
|
|
### Phase 4 — Future / optional
|
|
|
|
**Status:** ❄️ Not committed
|
|
[**See `phase-4-future/README.md`**](./phase-4-future/README.md)
|
|
|
|
Ideas on radar: geometry editor (depends on [[directus]] Phase 2 collections), replay mode, heatmaps / deck.gl, i18n (Albanian), dark mode, E2E tests.
|
|
|
|
## Operating model
|
|
|
|
- **Implementation agent contract.** Each task file is self-sufficient: goal, deliverables, specification, acceptance criteria. An agent should be able to complete one task without reading the whole wiki — but should skim the wiki references at the top of the task before starting.
|
|
- **Sequence within a phase.** Task numbering reflects intended order. Soft dependencies are explicit in each task's "Depends on" field. Tasks with no dependencies on each other can be done in parallel.
|
|
- **Status updates.** When a task is started, change its row in this ROADMAP to 🟨 and the task file's status badge accordingly. When done, 🟩 + a one-line note in the task file's "Done" section pointing at the merging commit/PR.
|
|
- **Drift control.** If implementation diverges from a task's spec, update the task file _before_ the diverging code lands, with a note explaining why. Do not let plans rot.
|