- 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.
- src/data/events.ts: useUserEvents() TanStack Query (5-min stale,
sort -starts_at). EventSummary type is a Pick of EventRow.
- src/live/active-event.ts: useActiveEventOrchestration() returns the
swap fn — unsubscribe previous + clearForEvent + subscribe new +
applySnapshot on success + persist to localStorage. Out-of-order
safety via per-call version counter. Plus readSavedActiveEventId().
- src/ui/components/event-picker.tsx: <EventPicker> dropdown.
useState + click-outside; rows show name + date + discipline.
- src/live/index.ts: re-exports active-event helpers.
- src/routes/_authed/monitor.tsx: auto-select effect (one-shot via
initializedRef, gated on events loaded + WS connected); renders
<EventPicker> wired to setActiveEvent.
Deviations:
1. Vanilla div + useState dropdown instead of shadcn Popover —
no new shadcn primitive add; easy to swap later for keyboard nav.
2. Auto-select gated on connectionStatus === 'connected' so the
subscribe call gets the snapshot path (not 'not-connected').
3. Logout-clears-saved-event-id deferred to a small Phase 1.8
follow-up; documented in task risks.
Bundle: 395KB / 120KB gz (~1KB up from 2.6).
- src/auth/logout.ts: performLogout({ queryClient, navigate? })
orchestration. Calls store.logout(), clears query cache, navigates
to /login. Server-call failures swallowed; local state still flips
to anonymous.
- src/auth/cross-tab-sync.ts: startCrossTabSync(). Subscribes to the
auth store and bumps trm-auth-version in localStorage on status
transitions; listens for storage events and re-runs initialize()
when another tab bumps. Idempotent via module-level guard. No-ops
if localStorage is unavailable.
- src/ui/components/logout-button.tsx: <LogoutButton> wraps shadcn
Button. Pass-through props but locks onClick / disabled / children.
Local isLoggingOut for "Signing out..." indicator + double-click
guard.
- src/auth/bootstrap.tsx: <AuthBootstrap> now calls startCrossTabSync()
alongside the existing init steps. Single mount point.
- src/auth/index.ts: re-export performLogout + startCrossTabSync.
- src/routes/_authed/index.tsx: replaced the inline sign-out button
with <LogoutButton />. The existing useEffect on 'anonymous' status
handles navigation when cross-tab sync fires.
Bonus: tsconfig.app.json now has "strict": true. TanStack Router emits
a conditional-type error ("strictNullChecks must be enabled") in
editors when strict is off; tsc -b was lenient. Caught in 1.7's
review. Typecheck remained green after enabling.