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>