feat: task 2.9 connection status + per-device staleness fade — Phase 2 done

- src/live/last-seen.ts: formatLastSeen(ts, now) — "now" / "Ns ago" /
  "Nm ago" / "HH:MM".
- src/live/use-staleness.ts: useStalenessTick(intervalMs=1000) hook
  re-renders subscribers every interval with the current epoch ms.
- src/ui/components/connection-chip.tsx: <ConnectionChip /> bottom-left,
  three states (connected / connecting+reconnecting / disconnected),
  aria-live polite + role status.
- src/map/layers/map-positions.tsx: every FeatureProps now carries
  staleSec; both symbol layers interpolate icon-opacity (and text-opacity
  on the non-selected layer) on staleSec — 0-60s full opacity,
  5min faded, 30min very faded. Update effect rebuilds features at the
  staleness tick rate (1Hz).
- src/routes/_authed/monitor.tsx: renders <ConnectionChip />.
- src/live/index.ts: re-exports formatLastSeen + useStalenessTick.

Strategy A (faded marker via interpolation) chosen over Strategy B
(separate warning-badge layer) per the task's open-question
resolution; simpler and easier to extend later.

Deviations:
1. stalenessTick is its own hook in src/live/use-staleness.ts rather
   than a property on the position store — keeps the store clean of
   UI-driven re-render concerns; the hook is reusable.
2. <DeviceLastSeen deviceId> standalone component skipped — the SPA
   doesn't have a sidebar yet (Phase 3.4); the per-marker fade IS the
   indicator for v1.

Bundle: main 396KB / 121KB gz — small bump from 2.8.

🎉 Phase 2 — Live monitoring map — complete. All 9 tasks shipped.
End-to-end: login -> /monitor -> event auto-selects -> snapshot
positions render -> live updates flow via WS through the rAF
coalescer -> staleness fades stale markers -> connection chip
surfaces WS state. Dogfood-blocking work for Rally Albania 2026 done.
This commit is contained in:
2026-05-03 00:17:09 +02:00
parent 18d893f47a
commit ed88f1767d
9 changed files with 198 additions and 7 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
# Phase 2 — Live monitoring map
**Status:** ⬜ Not started — depends on [[processor]] Phase 1.5 landing (already shipped).
**Status:** 🟩 Done — all 9 tasks shipped.
The dogfood-day deliverable. After Phase 2, an operator opens the SPA, picks the active event, and watches the field move on a real-time map. Inherits the architecture documented in `docs/wiki/concepts/maps-architecture.md` from `docs/wiki/sources/traccar-maps-architecture.md`, with the deliberate divergences (rAF coalescer, Zustand, longer trail default, racing sprite set, native PostGIS GeoJSON) baked in from day one.
@@ -56,7 +56,7 @@ When Phase 2 is done:
| 2.6 | [MapTrails (bounded ring buffer, polyline rendering)](./06-map-trails.md) | 🟩 |
| 2.7 | [Event picker (subscription driver)](./07-event-picker.md) | 🟩 |
| 2.8 | [Camera control trio](./08-camera-trio.md) | 🟩 |
| 2.9 | [Connection status + per-device last-seen indicators](./09-connection-status.md) | |
| 2.9 | [Connection status + per-device last-seen indicators](./09-connection-status.md) | 🟩 |
## Files modified