- 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.
Bug 1: hard-loading / while unauthenticated stayed stuck on "Loading..."
forever. Cause: the _authed layout short-circuits to the spinner when
status is anything other than 'authenticated', which means child routes
never mount. The redirect-on-anonymous useEffect lived only in the
home page (_authed/index.tsx), so it never fired — the layout's spinner
was the last thing rendered.
Fix: add a useEffect to the _authed layout component itself that
navigates to /login on the 'anonymous' transition. The layout's gate
is now: beforeLoad redirects on cold-known-anonymous, useEffect
redirects on post-mount transitions to anonymous (e.g. boot probe
resolves to anonymous after the route already rendered).
Bug 2: console warning that @tanstack/router-devtools moved to
@tanstack/react-router-devtools. Same package, renamed.
Fix: pnpm remove @tanstack/router-devtools && pnpm add -D
@tanstack/react-router-devtools; updated the lazy import in __root.tsx
to point at the new package name.
Plus: TRM_Design_System-handoff/ added to .prettierignore — those files
are immutable source material from claude.ai/design and shouldn't be
reformatted.
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.