Commit Graph

5 Commits

Author SHA1 Message Date
julian 0b54f87860 feat: task 2.3 sprite preload (racing categories)
- src/map/assets/icons/: 9 placeholder SVGs (background plate, direction
  arrow, 7 categories: rally-car / quad / ssv / motorcycle / runner /
  hiker / default). Hand-authored simple silhouettes; replaced in
  Phase 3.8 with the branded set.
- src/map/core/categories.ts: SpriteCategory/SpriteColor types,
  mapCategoryToSprite() normaliser, inferColor() helper.
- src/map/core/sprite-preload.ts: idempotent preloadSprites() with
  memoised promise, whenSpritesReady() alias, getSpriteRegistry()
  read-only access, installSprites(map) for the mapReady flow.
  Composition pipeline draws the plate + composites a tinted icon
  centred on it (category sprites) or tints the arrow alone (direction
  sprites). Tint via canvas globalCompositeOperation: 'source-in'.
- src/main.tsx: void preloadSprites() fired at boot; the promise is
  memoised so mapReady flow awaits the same instance.
- src/map/core/map-view.tsx: onStyleData() awaits whenSpritesReady()
  AND _map.loaded() before installing sprites and flipping mapReady
  true. Sprites reinstall on every style swap.

Registry: 7 categories x 4 colours + 4 direction-only entries = 32
total. ~160KB in memory.

Deviations:
1. Direction sprites have no plate (it's a separate symbol layer in
   2.5 overlaid on the device sprite; double-plate would look wrong).
2. Hardcoded the design-system palette (#2E8C4A / #E8412B / #0E0E0C /
   #2563C8) directly. When 3.8 lands, these rebind to TRM tokens via
   CSS variables.
2026-05-03 09:30:02 +02:00
julian 1c4a3b9d32 docs: mark task 2.2 done in ROADMAP 2026-05-03 09:29:48 +02:00
julian 87a738313e feat: task 2.1 MapView singleton + mapReady gate
- pnpm add maplibre-gl + -D @types/geojson.
- src/map/core/styles.ts: defaultStyle (OSM raster bootstrap; 2.2
  replaces with the basemap-switcher descriptor table).
- src/map/core/map-view.tsx: module-level Map singleton lazily created
  on first <MapView> mount, attached to a class="trm-map-host" detached
  <div> that React refs append/remove on mount/unmount. Style-data
  lifecycle flips mapReady false on every styledata event, polls
  loaded() at 33ms intervals, flips ready true once the style is
  loaded — the canonical MapLibre style-swap dance.
- Exports getMap()/getMapReady()/subscribeMapReady()/useMapReady (via
  useSyncExternalStore for SSR-safe + concurrent-safe reads). getMap()
  throws if called pre-mount; the explicit failure mode beats a
  null-able top-level export.
- src/routes/_authed/monitor.tsx: new /monitor route, full-viewport
  <MapView /> for 2.1 (no children — subsequent tasks plug in here).
- src/routes/_authed/index.tsx: home-page card now links to /monitor.
- eslint.config.js: override for src/map/** + src/live/** disables
  react-refresh/only-export-components. Same pattern as the existing
  overrides for shadcn primitives and route files.

Deviation: spec sketched a top-level `map` constant export; implemented
as `getMap(): MapLibreMap` (a function) so the singleton stays lazy
until <MapView> mounts. Top-level constant would either force eager
init (breaks SSR/tests) or be nullable (footgun). The function form
throws a clear error if called pre-mount.

Bundle: /monitor lazy chunk is 1MB raw / 274KB gzipped (MapLibre + CSS).
Other routes unaffected. Vite chunk-size warning is harmless.
2026-05-03 09:28:38 +02:00
julian 05543529e4 docs(planning): file Phase 2 task specs (live monitoring map)
Nine task files matching Phase 1's shape (Goal / Deliverables / Spec /
Acceptance / Risks / Done). README updated with full sequencing diagram,
files-modified outline, tech stack additions, design rules, and phase
acceptance.

| #   | Task                                                                  |
| --- | --------------------------------------------------------------------- |
| 2.1 | MapView singleton + mapReady gate                                     |
| 2.2 | Tile-source switcher (Esri / OpenTopoMap / OSM / optional Google)     |
| 2.3 | Sprite preload — 7 racing categories x 4 colour variants              |
| 2.4 | WS client + rAF coalescer + Zustand position store + connection store |
| 2.5 | MapPositions — clustered + selected sources                           |
| 2.6 | MapTrails — bounded ring buffer, polyline rendering                   |
| 2.7 | Event picker — TanStack Query + WS subscription orchestration         |
| 2.8 | Camera control trio — default-fit / selected-follow / one-shot        |
| 2.9 | Connection status + per-device last-seen indicators                   |

Sequencing: 2.1 and 2.4 are parallel foundations (singleton vs data
pipeline). Once both land, 2.5 / 2.6 / 2.7 / 2.9 fan out independently.
2.2 / 2.3 only need 2.1. 2.8 sits at the end on top of 2.1 + 2.5.

Each task documents its deliverables down to file paths + interface
shapes, includes concrete code sketches in the Specification, lists
explicit out-of-scope items, and surfaces risks for the implementer
to think about. An agent (or future me) can pick up any single task
and ship it without re-deriving the design from the wiki.

Resolved Phase 2 design decisions baked into the task files:
- Trails: flat-colour-per-device for v1, defer speed-coloured segments
  to a Phase 3 polish task.
- Cluster params: 14/50 (traccar default); tune after seeing real data.
- Event picker placement: top-left dropdown.
- Multi-event: out — single-select, one event at a time.
- Stale-position visual: fade icon opacity; defer warning badges.
2026-05-03 09:28:16 +02:00
julian 26e059fc20 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).
2026-05-02 18:41:54 +02:00