- 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>