diff --git a/CLAUDE.md b/CLAUDE.md index dc376df..f49157a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,15 +6,17 @@ Project-specific guidance for webmap.dev. Global workflow preferences are in `~/ **webmap.dev** is a GPS mapping web app built with Leaflet and ESRI geocoding. Key features: -- Real-time GPS location tracking with centering and breadcrumb trail recording +- Real-time GPS location tracking with three-state locate (off / active-following / passive) +- Turn-by-turn routed navigation via FOSSGIS Valhalla (driving / cycling / walking) - Reverse geocoding (pin drop → address lookup) with clipboard copy - Address search via ESRI Leaflet Geocoder +- Offline tile pre-download + Workbox passive caching (PWA) ## Tech Stack - **Vite** + **TypeScript** (ES2020 target, strict mode, `noUncheckedIndexedAccess`) - **Leaflet** 1.9 + **esri-leaflet** 3 + **esri-leaflet-geocoder** 3 -- No test framework (no `test` script in package.json) +- **vitest** for unit tests (pure-function tests; no DOM/Leaflet integration) ## Key Commands @@ -24,6 +26,8 @@ npm run build # Production build npm run preview # Preview production build npm run type-check # TypeScript check (tsc --noEmit) npm run lint # ESLint +npm test # vitest run +npm run size # size-limit (gzipped JS bundle, capped at 100 kB) ``` ## File Structure @@ -32,21 +36,28 @@ npm run lint # ESLint src/ ├── main.ts # Entry point — wires all modules together ├── types.ts # AppState interface + createInitialState() - ├── map.ts # Leaflet map initialization - ├── controls.ts # UI controls (clipboard, centering, tracking toggles) - ├── geocoding.ts # Address search + reverse geocoding - ├── location.ts # GPS locationfound event handler - ├── timer.ts # GPS polling loop (scheduleUpdateCallback) + ├── map.ts # Leaflet map + tile-layer config + ├── layers-control.ts # Base-map / overlay popover control + ├── controls.ts # Bottom-left thumb cluster (locate, version, attribution) + ├── geocoding.ts # Address search + reverse geocoding + Navigate-here + ├── location.ts # GPS locationfound handler + heading-cone wedge + ├── guidance.ts # Turn-by-turn navigation state machine + pill UI + ├── routing.ts # Valhalla client + polyline6 decoder + ├── geo.ts # haversineDistance, bearingDeg, pointToSegmentMeters + ├── timer.ts # GPS polling refcount loop + ├── consent.ts # First-run privacy consent modal + ├── keepalive.ts # Wake Lock + silent-audio loop for background GPS └── style.css # App styles +docs/adr/ # Architecture Decision Records ``` ## Architecture Pattern -**Single AppState object** (`types.ts`) threaded by reference through all modules — no event bus or state management library. Modules mutate state directly. +**Single AppState object** (`types.ts`) threaded by reference through all modules — no event bus or state management library. Modules mutate state directly. See [ADR-001](docs/adr/ADR-001-single-mutable-state.md). -**GPS polling uses a refcount** (`updateCallback: number`) so centering and tracking can independently request/release the polling loop without stepping on each other (0 = stopped, 1 = centering only, 2 = centering + tracking). +**GPS polling uses a refcount** (`updateCallback: number`) so locate, guidance, and other consumers can independently request/release the watch without stepping on each other. See [ADR-002](docs/adr/ADR-002-refcount-gps-polling.md). ## Notes - `esri-leaflet-geocoder.d.ts` is a local type stub — the package lacks complete TypeScript types -- No automated tests — verify changes with `npm run type-check && npm run lint` and manual browser testing +- Verify changes with `npm run type-check && npm run lint && npm test`; UI changes also require manual browser testing diff --git a/src/style.css b/src/style.css index f2c7029..0ef08ba 100644 --- a/src/style.css +++ b/src/style.css @@ -8,8 +8,8 @@ html { height: 100%; width: 100%; padding: 0; margin: 0; } body { height: 100%; width: 100%; padding: 0; margin: 0; } #map { position: absolute; top: 0; bottom: 0; right: 0; left: 0; } -/* Multiply preserves base-map color in flat areas (hillshade ≈ white there) and only darkens slopes. */ -.hillshade-blend { mix-blend-mode: multiply; } +/* Per-tile multiply — a container-level blend can isolate under .leaflet-tile-container's translate3d. */ +.hillshade-blend img.leaflet-tile { mix-blend-mode: multiply; } /* Icon state classes for GPS accuracy filter visual feedback */ #disabled {