Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 21 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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
4 changes: 2 additions & 2 deletions src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading