Commit graph

134 commits

Author SHA1 Message Date
9db8cec0f6 fix: duplicate stop when GPS denied and selecting origin via search
Bug: clicking Get Directions with GPS denied would add the destination
to stops AND set pendingDestination. Then when user selected an origin
via search, selectResult would add the origin AND re-add the pending
destination, resulting in 3 stops (A=dest, B=origin, C=dest again).

Fix: in startDirections GPS-denied path, only set pendingDestination
without adding to stops. The SearchBar selectResult handler already
adds both origin and pending destination when pending exists.
2026-04-26 23:07:59 +00:00
721bc2c9f5 fix: usePanelState returns string, preview decoupled from route
Fixes React error #185 (infinite re-render loop) caused by returning
object from Zustand selector without shallow comparison.

Changed usePanelState to return string states that encode both preview
and route status:
- PREVIEW_CALCULATED: preview + calculated route
- PREVIEW_ROUTING: preview + stops (no route yet)
- PREVIEW: preview only
- ROUTE_CALCULATED: calculated route only
- ROUTING: stops only
- IDLE: nothing

Panel.jsx updated to derive show flags from string states using
startsWith and includes checks.
2026-04-26 23:05:51 +00:00
f5e0b9606e fix: search viewport init, theme-clears-route bug, preview-during-route
Three regressions fixed:

1. mapCenter is now initialized on map 'load' event, not just 'moveend'.
   Searches immediately after page load now correctly include viewport
   bias instead of falling back to default Twin Falls coords.

2. setThemeOverride had stray code from startDirections that wiped
   stops and added undefined place data. Toggling theme cleared the
   active route. Restored setThemeOverride to its correct
   theme-only implementation.

3. usePanelState returned ROUTE_CALCULATED before checking selectedPlace,
   so preview cards could never appear alongside a calculated route.
   Refactored to decouple preview state from route state - preview
   renders whenever selectedPlace exists, independent of route state.
2026-04-26 22:21:23 +00:00
5eb83e9b4b feat(panel): single-panel architecture with UX refinements
Major refactor consolidating two-panel layout (Routes/Contacts + floating
PlaceDetail) into one 400px left column with state-driven content.

Architecture:
- New PlaceCard component for preview and stop cards (collapsible)
- Panel states: IDLE, PREVIEW, ROUTING, PREVIEW_ROUTING, ROUTE_CALCULATED
- usePanelState selector in store.js derives state from selectedPlace/stops/route
- StopList now renders stops as PlaceCard with variant=stop
- PlaceDetail.jsx removed from App.jsx (content moved to PlaceCard)

UX refinements:
- Panel width 400px (was 360px) to fit buttons on one line
- Map zoom padding updated to 420px for wider panel
- Body text bumped to text-sm (14px) for readability
- Get Directions button hidden when 2+ stops (route auto-calculates)
- PlaceCard title prefers feature name (raw.name) over formatted address
- Preview card shows above route during PREVIEW_ROUTING state
- Directions flow no longer shows toast when GPS denied
2026-04-26 21:14:39 +00:00
d0f89c6783 feat(map): polygon boundary, zoom-to-feature, Wikidata link cleanup
Three improvements:

1. When a place has a boundary polygon (from Nominatim), render its
   outline on the map using a dashed accent line. Falls back to the
   existing pulsing ring for places without polygons.

2. Selecting a feature now smoothly zooms the map to fit:
   - With polygon: fitBounds to polygon bbox
   - Without polygon: zoom level based on feature kind (city=11,
     region=7, POI=16, etc.)
   Terrain clicks do not change zoom.

3. Wikidata IDs render as styled 'View on Wikidata' links instead
   of raw 'Wikidata: Qxxxxx' strings.
2026-04-26 08:26:56 +00:00
b354fd0aa0 feat: Consolidated UX improvements for map selection
- Snap selection to feature geometry when clicking labeled places
- Add wikidata enrichment for basemap labels (population, description)
- Differentiate visual feedback: reticle marker vs pulsing highlight
- Clear previous feature highlight when selection changes
- Store selection mode (reticle/feature) and feature info in state

Frontend: MapView click handler, PlaceDetail wikidata fetch, CSS
Backend: /api/place/wikidata/<id> route for Wikidata API lookups

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-26 08:15:09 +00:00
4fcc3d1af4 fix(map): resolve clicks to rendered label features
Click handler now queries rendered features at click point and uses
the highest-priority labeled feature (POI > locality > region) as
the place identifier. Previously, clicks went straight to coord-based
reverse-geocode, causing 'Twin Falls' label clicks to resolve to
whatever feature was nearest (e.g., a radio tower).

Falls back to reverse-geocode when no labeled feature exists at the
click point. Two-click selection model and hover affordance unchanged.
2026-04-26 07:49:08 +00:00
985221e8cd feat(map): add POI and label hover affordance
POIs, city names, and other labeled features now show a pointer cursor
on hover, signaling that they are clickable. Matches the standard
map-app interaction pattern (Google Maps, etc.).

Implemented via MapLibre mouseenter/mouseleave handlers for interactive
layers: pois, places_locality, places_region, places_country,
places_subplace.
2026-04-26 07:28:53 +00:00
37a5eb5b1b feat(map): two-click selection model with precise center dot
Replaces 'every click selects something' with a deliberate two-click
flow:
- First click drops marker (existing circle plus new precise center dot)
  and opens place panel
- Second click INSIDE the marker circle opens the radial menu
- Second click OUTSIDE the circle deselects without selecting the new
  spot — requires another click to select

The 4px filled center dot at exact click coordinates gives precise
visual feedback for GPS-coord readout. The existing circle's radius
defines the same-spot tolerance, visually showing the radial-trigger
hit area.

Right-click radial unchanged. Search-dropdown selection drops a marker
for consistency.
2026-04-26 07:17:33 +00:00
a11fc13b33 style(radial): suppress hover highlight on auth-required wedges
Grayed-out wedges shouldn't show the same hover feedback as enabled
wedges — the highlight contradicts the grayed-out 'not available'
signal. Auth-required wedges now stay visually muted on hover while
remaining clickable.
2026-04-26 07:04:02 +00:00
536c0bfe7e style(radial): gray out auth-required wedges instead of lock icon
Lock icon overlay was visually busy and partially obscured the wedge
label. Replaced with a grayed-out treatment using reduced opacity for
auth-required wedges. Cleaner read, instantly recognizable as 'not
fully available'.
2026-04-26 07:01:21 +00:00
19b8c6e23b feat(radial): remove redundant 'What's here' wedge
Left-click on the map already reverse-geocodes and opens the place
panel, making the 'What's here' wedge redundant. Radial drops from
6 to 5 wedges (72° each).

Remaining wedges (all stubs except they show toast):
- Drop pin
- Directions to here
- Save place (auth-gated)
- Add as stop
- Directions from here
2026-04-26 06:44:12 +00:00
15e6267022 style(radial): match Navi color palette in light and dark themes 2026-04-26 06:17:48 +00:00
741d760760 fix(radial): use backdrop element for click-outside dismiss
Replace unreliable window event listener with transparent full-screen
backdrop element. Clicking anywhere outside the radial menu now properly
dismisses it. Also handles right-click on backdrop for dismiss.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-26 06:12:46 +00:00
2e975ea59e feat(map): add radial context menu with reverse-geocode action
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.
2026-04-26 05:42:48 +00:00
b12ebe672e feat(search): add viewport bias for location-aware geocoding
- 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>
2026-04-26 04:03:52 +00:00
ec6f4254b9 fix: zoom indicator now updates live via dedicated useEffect
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-26 00:20:31 +00:00
6db09fd3d3 fix: add z-index to zoom indicator for visibility
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-26 00:07:54 +00:00
3f461dd9a8 feat: add zoom level indicator to map UI
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>
2026-04-26 00:03:37 +00:00
f6cbf5f2cc feat: add contours layer with tier-aware zoom visibility
- 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>
2026-04-24 00:44:20 +00:00
cdd11dc043 Add Public Lands vector tile overlay layer (PAD-US)
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>
2026-04-22 18:52:53 +00:00
771d7eb3b3 Add public land classification to PlaceDetail
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>
2026-04-22 15:36:44 +00:00
3ce860c1e8 Add contacts/phone book UI with search integration
New components:
- ContactModal.jsx: Save/edit overlay with form fields and soft delete
- ContactList.jsx: Contacts tab with filter, create, and tap-to-navigate

Modified:
- store.js: Add contacts slice (contacts, activeTab, editingContact)
- api.js: Add contacts API functions (fetch, create, update, delete, nearby)
- config.js: Add has_contacts fallback flag
- Panel.jsx: Routes/Contacts tab bar (only when has_contacts enabled)
- PlaceDetail.jsx: Save button opens ContactModal, proximity annotation
- SearchBar.jsx: Prepend matching contacts before Photon results
- App.jsx: Render ContactModal at top level
- index.css: Modal overlay, tab bar, contact list item styles

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-22 05:30:19 +00:00
03e9780834 fix: mobile UX — GPS permission flow, traffic toggle, overflow
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>
2026-04-22 03:27:21 +00:00
4020d5ae0a Add hillshade and traffic overlay layers with layer control UI
- 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>
2026-04-21 00:52:20 +00:00
edc5a9788d feat(navi): config-driven tile source, defaults, and feature flags
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>
2026-04-20 23:36:02 +00:00
Ubuntu
a819458865 feat(navi): copy popover + map-click pin drop with reverse geocode
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>
2026-04-20 21:27:07 +00:00
Ubuntu
02f2b25db3 feat(navi): GPS origin + place detail panel + basic actions
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>
2026-04-20 20:59:18 +00:00
Ubuntu
6983e2655b feat: switch to NA tileset + browser geolocation
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>
2026-04-20 18:43:36 +00:00
Ubuntu
e7b08a7dc9 feat: search, multi-stop routing, and route display
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>
2026-04-20 16:50:53 +00:00
Ubuntu
ce32014896 fix: use correct protomaps-themes-base v4 API for layer generation
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>
2026-04-20 06:12:55 +00:00
Ubuntu
ecdfedb98d chore: add deploy.sh for build+rsync
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-20 04:47:55 +00:00
Ubuntu
3f2d33ffbe feat: initial scaffold — Vite + React + Tailwind + MapLibre
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>
2026-04-20 04:47:44 +00:00
c2866a885d Initial commit 2026-04-20 06:42:56 +02:00