Implements RadialMenu component (general-purpose, configurable wedges)
and useContextMenu hook (right-click on desktop, 450ms long-press with
8px movement threshold on touch).
First wired action: "What's here" — reverse-geocodes the trigger
location and opens the place panel for the result. Remaining wedges
(Drop pin, Directions from here, Directions to here, Add as stop,
Save place) render but stub to a toast — wiring deferred to follow-up
sessions.
Per design doc NAVI-DIRECTIONS-REDESIGN.md sections covering Phases a
and b of the implementation sequence.
- Add mapCenter state to store (lat/lon/zoom)
- Track map center on moveend in MapView
- Pass mapCenter to searchGeocode from SearchBar
- Update searchGeocode API call to include viewport params
Search results now prioritize locations near the current map view.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds a persistent zoom indicator pill in the bottom-left corner showing
current zoom level (e.g., "Z 11.4"). Updates live as user pans/zooms.
Also includes contour test layer support (blue color scheme) for
development verification of contour pipeline changes.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Adds contour PMTiles vector source (contours-na.pmtiles)
- Minor/intermediate/index tier rendering at z11+/z8+/z4+
- Elevation labels on index contours at z12+
- Dark theme opacity adjustment
- has_contours feature flag gated
Completes T pipeline integration (Phase 1).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Toggleable vector tile overlay rendering 651k PAD-US protected areas
as colored polygons on the map. Data-driven styling by agency/designation
(USFS green, NPS darker green, BLM tan, wilderness amber, state teal).
Unit name labels at z10+. Feature-flagged via has_public_lands_layer.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Shows PAD-US land ownership data (unit name, agency hierarchy, access
status) when clicking on map areas. Upgrades "Dropped pin" name to
land unit summary on public land. Private land gets a muted indicator.
- api.js: fetchLandclass() for /api/landclass endpoint
- PlaceDetail.jsx: LandclassSection, PrivateLandIndicator components
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
GPS permission:
- Remove silent mount-time getCurrentPosition calls that cause iOS Safari
to cache a "denied" state without ever prompting the user
- LocateButton always retries getCurrentPosition on tap (user gesture)
- Only show "denied" toast on PERMISSION_DENIED (code 1), not timeout
- MapView watchPosition now starts only after confirmed grant, not unconditionally
Traffic toggle:
- Fix isStyleLoaded() race in LayerControl — if style not loaded when
toggle fires, defer to map.once("style.load") instead of silently bailing
- Change outside-click handler from mousedown to pointerdown for mobile
Mobile UX (from prior session):
- Add LocateButton component (crosshair GPS locate/re-center)
- Reposition layer control + locate button to top-right on mobile
(below MapLibre nav controls, above bottom sheet)
- ModeSelector: add min-w-0 to prevent flex overflow at 390px
- StopItem: remove button visible on touch (60% opacity vs hover-only)
- Panel: overflow-x-hidden + safe-area-inset-bottom on mobile sheet
- Body overflow-x guard on mobile viewports
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New LayerControl component with popover toggles for hillshade/traffic
- MapView: add/remove hillshade raster-dem and traffic raster layers
- Overlay layers persist in localStorage, survive theme swaps
- Hillshade defaults ON, traffic defaults OFF when available
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Load deployment config from /api/config on startup:
- src/config.js: loader with 3s timeout + hardcoded fallback
- src/hooks/useConfig.js: useConfig() and useFeature() hooks
- MapView.jsx: tile URL, attribution, center, zoom from config
- main.jsx: loads config before first render
Falls back to home profile defaults if backend unavailable.
No visible behavior change — infrastructure for future features.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces Share button with Copy dropdown (Address / Coordinates).
Map single-click drops preview pin, opens PlaceDetail with
"Dropped pin" placeholder, reverse geocodes via /api/reverse to
fill in address. Stop-pin clicks preserved via flag ref. Escape
key closes PlaceDetail. Double-click zoom unaffected.
Phase 3 Step 5 C1.5 of Navi.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds synthetic "Your location" stop A when GPS granted; place
detail panel slides in on search result click with Directions /
Add stop / Save (stub) / Share actions; elevation via Valhalla
/height; react-hot-toast for feedback; pendingDestination state
for GPS-denied Directions flow.
Phase 3 Step 5 C1 of Navi.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Swap tile source from idaho.pmtiles (427 MB, Idaho-only) to na.pmtiles
(31 GB, CONUS + Canada + Mexico + Alaska). Replace MapLibre GeolocateControl
with browser geolocation API that flies to user location on load and stores
coordinates in Zustand for future use.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Full navigation UI with:
- Search bar with 150ms debounced autocomplete from /api/geocode
- Keyboard navigation (arrow keys, Enter, Escape)
- Exact match badge for verified address results
- Multi-stop list with drag-to-reorder (dnd-kit)
- 10-stop cap with disabled state
- Mode selector (drive/walk/bike)
- Valhalla route display with per-leg color polyline
- Maneuver list with instructions, distance, time remaining
- Click maneuver to fly map to that point
- Optimize stops button (3+ stops, uses /optimized_route)
- Responsive: side panel (desktop ≥768px), bottom sheet (mobile)
- Stop pins: green origin, red destination, blue intermediate
- Pin popup with remove button
- Geolocation permission requested on first route, not on load
- Error handling for unroutable pairs
- nginx proxy for /api/ and /valhalla/ endpoints
Dependencies added: zustand, @dnd-kit/core, @dnd-kit/sortable,
@dnd-kit/utilities
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
layersWithCustomTheme() expects a Theme object as its second argument,
not a string key. Passing "dark" (string) caused all layer paint
properties to be empty objects — rendering tiles invisibly on black
background. Switched to layers(source, namedTheme("dark"), {lang:"en"})
which is the canonical v4 API. Added v4 sprite URL for POI icons.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Minimal Idaho basemap via local PMTiles extract from Protomaps
daily build (20260419). Deployed to /mnt/nav/frontend/ via rsync,
served by nginx on :8440 as navi.echo6.co.
Phase 3 Step 1 of Navi frontend.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>