- leaflet.markercluster: Dynamic import with fallback to plain layer
group. Clusters POI markers at low zoom, ungroups at zoom 15+.
- Base layer sync: baselayerchange event writes to Yjs, new
participants load the selected base layer on connect.
- Both tile overlays and base layer now persist across participants
and crash recovery.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OverlaySync component listens to Leaflet overlayadd/overlayremove
events and writes enabled overlay IDs to Yjs routeData. On connect,
loads initial overlay state from Yjs. LayersControl.Overlay uses
checked prop to reflect the synced state.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
useYjsPoiSync: Bidirectional sync between local POI state and Yjs
routeData. When a participant enables/disables POI categories, the
change propagates to all connected clients. Initial state loaded from
Yjs on connect. Crash recovery included via existing Yjs localStorage
snapshot.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
POI markers should render below waypoint markers so waypoints are
always easy to click and drag.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
useProfileDefaults hook observes profile changes in Yjs and merges
relevant POI categories into the enabled set. Skips initial load to
respect existing state. Cycling profiles → bike infra, hiking →
shelter + viewpoints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The app is primarily for cycling. Default profile is now "Cycling (fast)"
instead of "Hiking". Profile order: fastbike, safety, shortest, car,
trekking.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When clicking on the route to insert a waypoint, the new point now
snaps to a nearby POI (within 50m) just like click-to-add and drag.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
With multi-waypoint routes (6 BRouter requests per computation) and
debounced re-routing on waypoint changes, 60/hour was too easy to hit
during active planning sessions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a waypoint is created from or snapped to a POI, the Yjs Y.Map
now stores osmId (OSM node ID) and poiTags (phone, website, address,
opening hours, etc.). Dragging away clears this data.
This persists through the Yjs document and will be available when the
route is saved to the Journal, enabling future display of campsite
contact details, opening hours, etc. on route detail pages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When placing or dragging a waypoint within 50m of a visible POI, the
waypoint snaps to the POI's exact coordinates and adopts its name.
Snapping only applies to visible POIs (enabled categories, current
viewport). Explicit name (from "Add as waypoint" button) is preserved
without snapping.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Clicking the button in a POI popup adds the POI's location and name as
a new waypoint at the end of the route. Uses event delegation on the
map container to handle clicks on dynamically created popup buttons.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Try overpass.kumi.systems first, fall back to overpass-api.de
- Reduced max results from 200 to 100, added maxsize:1MB limit
- Shortened timeout from 15s to 10s, added qt (quiet) output mode
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Highlighted waypoints now render above normal waypoints (1000) but
below POI markers (1500) and the elevation highlight dot (2000).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract all Leaflet zIndexOffset values into z-index.ts constants:
POI markers (-1000) < cursors (-1000) < ghost waypoint (-100)
< waypoint markers (1000) < highlight dot (2000)
Replaced CircleMarker highlight with a Marker+DivIcon so it
participates in z-index ordering above waypoint markers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fixed sort() mutating enabledCategories array in place (used spread copy)
- Show loading status immediately when a fetch will happen, not after
the debounce delay
- Reduced debounce to 800ms and min interval to 2s for snappier response
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Picnic sites (a table in a park) are not useful for route planning
alongside actual camp sites and caravan sites. Removed from the
camping query to reduce noise.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
overpass.kumi.systems has higher rate limits than overpass-api.de.
This is the same endpoint brouter-web uses as its default.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Overpass API sometimes returns HTTP 200 with a plain-text error body
containing "rate_limited" instead of a proper 429 status. Now checks
response body for this pattern before attempting JSON parse.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- PoiRefresher: Use ref for refresh callback to avoid re-running effect
on every category change. Only trigger refresh on category changes,
not on mount.
- usePois: Bump debounce from 500ms to 1000ms, add 3s minimum interval
between actual Overpass requests, increase backoff base to 10s.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- PoiPanel: Collapsible panel with category checkboxes, count badges,
loading/error/zoom-too-low states
- PoiMarkers: L.Marker with L.DivIcon per POI, click popup with name,
hours, website, OSM link. z-index below route/waypoints.
- PoiRefresher: Listens to map moveend, refreshes POIs via usePois hook
- Wired into PlannerMap alongside existing controls
Skipped markercluster (7.3) — can add later if density is an issue.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When hovering a day row in the route detail breakdown, the map smoothly
flies to fit that segment's bounds (0.5s animation). On mouse leave it
flies back to the full route bounds. Refactored geometry splitting into
shared helpers used by both DayColoredRoute and FlyToSegment.
Rotation not implemented — Leaflet doesn't support native bearing
rotation. Would require leaflet-rotate plugin or MapLibre GL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Hovering a day row dims other segments (opacity 0.3) and thickens
the hovered day's segment (weight 5) for clear visual focus
- Each day row has a GPX download button that exports just that day's
track segment via /api/routes/:id/gpx?day=N
- GPX endpoint uses computeDays to extract the correct track slice
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The subtle size bump (24→28px) is sufficient visual feedback without
the outline ring.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace position-based CircleMarker highlight with index-based marker
highlighting. Hovered waypoint marker grows from 24px to 30px with an
outline ring in its color. Uses CSS transition for smooth animation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hovering a waypoint row in the sidebar highlights it on the map with
the same red CircleMarker used by the elevation chart hover. Reuses
the existing highlightPosition state — both sidebar and chart feed
into the same mechanism.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use absolute positioning with left:50% + translate(-50%) for proper
horizontal centering of auto-width content. Reduced font to 10px,
padding to 1px 6px, border-radius to 8px.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use iconSize + iconAnchor instead of CSS transform to position the
day label pill above the waypoint marker. The label was overlapping
the marker because translate(-50%, -30px) didn't clear the 24px icon.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ExportButton.getWaypoints() wasn't reading the overnight flag from
Yjs Y.Maps, so exported plan GPX was missing <type>overnight</type>.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three gaps in the Planner → Journal → Planner roundtrip:
1. SaveToJournalButton: wasn't including isDayBreak when building
waypoints for GPX generation
2. PlannerMap GPX import: wasn't setting overnight on Y.Maps when
importing waypoints with isDayBreak
3. use-yjs initial waypoints: wasn't setting overnight from isDayBreak
Also widened the waypoint type throughout the chain (api.sessions,
session.$id, SessionView, extractWaypoints) to carry isDayBreak.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- RouteMapThumbnail: Color route segments per day with alternating colors
when dayBreaks are present, pass through from route detail page
- overnight.test.ts: 6 tests for isOvernight/setOvernight with Y.Doc
- daybreaks-extraction.test.ts: 3 tests for GPX → dayBreaks index extraction
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Overnight waypoints render amber-brown (#8B6D3A) with moon icon
- Day boundary labels (white pill "Day N · X km") at overnight waypoints
- Right-click middle waypoints toggles overnight (first/last still delete)
- Wire days prop through SessionView → PlannerMap
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WaypointSidebar and DayBreakdown were calling useTranslation() without
the "planner" namespace, then using fully qualified keys like
"planner.multiDay.overnight". Fixed to use useTranslation("planner")
and drop the prefix from all t() calls.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- routes.server.ts: Extract dayBreaks from parsed GPX waypoints on save
- routes.$id.tsx: Compute per-day stats in loader, render day breakdown
section with distance/ascent/descent per day
- ElevationChart: Dashed vertical lines at day boundaries
- i18n: Add Journal day breakdown keys (en + de)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ElevationChart: Dashed vertical lines at day boundaries with distance labels
- SessionView: Lift useDays() to top level, pass to both ElevationChart and
SidebarTabs to avoid duplicate hook calls
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- DayBreakdown: Collapsible day sections with per-day stats
- WaypointSidebar: Overnight toggle button (moon icon), day-grouped view
when any waypoint is marked overnight, route summary in header
- SessionView: Wire useDays() hook into sidebar via SidebarTabs
- i18n: Add multiDay keys for en + de (day labels, overnight, stats)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>