- Wilderness maneuvers render with compass arrows and cardinal directions
- Network maneuvers prefixed with transport mode (Drive/Walk/Ride)
- Distances under 1 mile show feet with commas
- Pick-from-map mode replaces auto-fill-on-focus (crosshair + toast)
- ESC cancels pick mode
- Place card slides out right during active routing
- Removed debug toasts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add route legend showing wilderness (dashed orange) vs road (solid blue)
- Show place card below directions panel when clicking map during routing
- Clean up error messages to be user-friendly (no offroute text)
- Legend only appears when route has wilderness segments
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
New UX for Get Directions:
- DirectionsPanel component with two stacked input fields
- LocationInput component with autocomplete, coordinate parsing
- Swap button to flip origin/destination
- Travel mode selector (Drive default, Foot, MTB, ATV, 4x4)
- Boundary selector (only visible for non-Drive modes)
- Map click fills active input field with crosshair cursor
- Auto-route when both endpoints are filled
- X button closes directions and returns to search view
Store changes:
- directionsMode state for panel switching
- activeDirectionsField for map click targeting
- startDirections now enters directions mode with destination pre-filled
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Drive (auto) as default route mode, first in travel modes list
- Hide boundary mode selector when Drive mode is active
- Restore Add stop radial menu wedge with stops system integration
- Unify routing through single computeRoute() function in store
- Add coordinate parsing to SearchBar for direct lat/lon input
- Bridge stops system with routeStart/routeEnd for seamless UX
- Support 3+ stops with Valhalla optimization
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
PART 1: Add missing CSS variables to all ui objects
- Add --bg-inset, --bg-muted for component backgrounds
- Add --success, --warning as aliases for --status-success/warning
- Add --warning-muted for warning background states
- Each theme now has 32 CSS variables
PART 2: Per-theme font support
- Move --font-sans and --font-mono from :root to ui objects
- Add fontImports array to theme config (for future custom fonts)
- applyThemeUI() now manages <link> tags for font imports
- Existing themes use empty fontImports (system fonts already loaded)
PART 3: Swatch preview colors
- Add swatch array (3 hex colors) to each theme for visual preview
- light: warm tan, sage green, khaki
- dark: dark brown, sage green, tan
- clean: light gray, Google blue, Google green
- themeList() now returns swatch in result shape
PART 4: Theme picker UI
- New ThemePicker component replaces icon toggle in header
- Palette icon trigger opens popover below
- Shows all themes as circular swatches (conic gradient)
- Active theme has accent ring indicator
- Click swatch to apply theme, closes popover
- Click outside or Escape closes popover
- Styled with current theme CSS variables
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add a plain, utilitarian theme focused on readability and wayfinding:
- White/light gray land (#f5f5f5), soft pastel green parks (#c3ecb2)
- Gentle blue water (#aadaff) with classic road hierarchy
- White minor roads, yellow primary (#fbc02d), orange motorway (#f9a825)
- Pure white UI panels with Google-standard gray text
- All 73 protomaps flavor keys + pois + landcover objects
- Full UI CSS custom properties using Google color palette
- Overlay config for hillshade, contours, public lands, trails
Update theme switcher to cycle through all available themes.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The /outpost.goauthentik.io/sign_out path returns 404 on the
Authentik server - it is not a real endpoint. Changed logout
to redirect directly to auth.echo6.co/if/flow/default-invalidation-flow
with a next= param to return to navi.echo6.co after logout.
B1: Add ResizeObserver to MapView.jsx to handle layout settling.
The map canvas had 0 height at init because layout hadnt settled
when the useEffect fired. ResizeObserver calls map.resize() on
any container size change.
B11: Use native outpost start URL for login initiation:
/outpost.goauthentik.io/start?rd=%2F
This properly triggers auth flow and redirects to / after login.
Removed the Caddy /login handler that wasnt redirecting correctly.
The /api/auth/whoami endpoint returns JSON after auth, leaving
users on a raw JSON page. The new /login endpoint triggers
forward_auth and redirects to / after successful auth.
- MapView.jsx: extract addBoundaryLayer function, use getComputedStyle
for accent color (MapLibre rejects CSS vars in paint properties)
- PlaceCard.jsx: gate fetchNearbyContacts on auth.authenticated
- PlaceDetail.jsx: gate fetchNearbyContacts on auth.authenticated
- api.js: replace invalid timeout option with AbortSignal.timeout()
- RadialMenu.jsx: remove user-select from SVG style (Firefox rejects)
- Panel.jsx: add Cancel button for pending directions state
- Add /api/auth/whoami endpoint check on app load
- Store auth state in Zustand (authenticated, username, loaded)
- Hide Contacts tab when unauthenticated
- Gate fetchNearbyContacts calls on auth.authenticated
- Replace Save button with Log in affordance when unauthenticated
- Add Login/Logout buttons to panel header
- Prevent any /api/contacts/* requests from firing when unauthenticated
Public functionality (search, routing, place details) remains
fully functional for unauthenticated users.
Symptom: clicking Get Directions with GPS denied closed the place card
and showed empty map. User had no UI to pick an origin.
Root cause: commit 9db8cec correctly stopped adding destination to
stops (fixing duplicate), but Panel.jsx only rendered StopList when
panelState was ROUTING/CALCULATED. With pendingDestination set but
stops empty, panelState was IDLE and StopList never mounted.
Fix: showRouteSection is now true when pendingDestination is truthy,
regardless of panelState. showEmptyState is false when pendingDestination
is set. StopList renders and displays its "Search for a starting point"
prompt.
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.
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.
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
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>
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>
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>