Commit graph

711 commits

Author SHA1 Message Date
Ullrich Schäfer
0ff04aaec7
Add E2E tests for hillshading overlay and POI markers
- Hillshading: mock tile endpoint, enable via DOM evaluate, verify
  tile requests fired
- POI markers: mock Overpass API response, zoom to threshold, enable
  category, verify marker rendered in marker pane

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 02:14:56 +02:00
Ullrich Schäfer
f414c97887
Update specs with POI-waypoint integration, resilience, z-index
Proposal: Added POI→waypoint, POI snapping, and metadata storage.
Design: Added D10 (POI-waypoint integration), D11 (Overpass fallback),
D12 (z-index layering). Updated risks/trade-offs.
Tasks: Added sections 11 (POI-waypoint, 5 tasks) and 12 (resilience,
5 tasks). Fixed Overpass test to match updated query format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 02:09:44 +02:00
Ullrich Schäfer
14a2bd82fc
Snap route-inserted waypoints to nearby POIs
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>
2026-04-11 02:06:40 +02:00
Ullrich Schäfer
246806cf1a
Increase route rate limit from 60 to 300 requests/hour
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>
2026-04-11 02:05:41 +02:00
Ullrich Schäfer
6d82ebe127
Store POI metadata (osmId, tags) on waypoints for Journal use
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>
2026-04-11 02:02:47 +02:00
Ullrich Schäfer
a71efef1ce
Clear waypoint name when dragged away from a POI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 02:00:44 +02:00
Ullrich Schäfer
1067b49576
Add waypoint snapping to nearby POIs (50m threshold)
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>
2026-04-11 01:59:49 +02:00
Ullrich Schäfer
3af6aee32c
Add "Add as waypoint" button to POI popups
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>
2026-04-11 01:56:10 +02:00
Ullrich Schäfer
8c3fc36d23
Add Overpass fallback endpoint and reduce query size
- 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>
2026-04-11 01:54:03 +02:00
Ullrich Schäfer
729b33a15f
Add Z_WAYPOINT_HIGHLIGHTED (1200) for hovered waypoint markers
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>
2026-04-11 01:51:39 +02:00
Ullrich Schäfer
337df6b527
Fix marker z-ordering: waypoints and highlight above POIs and route
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>
2026-04-11 01:46:55 +02:00
Ullrich Schäfer
f310e0f66f
Lower POI minimum zoom from 12 to 10
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 01:42:56 +02:00
Ullrich Schäfer
b156093167
Fix POIs not fetching on map pan
- 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>
2026-04-11 01:39:33 +02:00
Ullrich Schäfer
787cf23395
Remove picnic sites from camping POI category
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>
2026-04-11 01:38:15 +02:00
Ullrich Schäfer
a62b35bf45
Show more info in POI popups: category, description, address, phone
Popup now shows: name, category label, description, address (street +
housenumber + postcode + city), phone (clickable tel: link), opening
hours, website, and OSM link.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 01:33:31 +02:00
Ullrich Schäfer
65330cf9e2
Switch Overpass endpoint to overpass.kumi.systems
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>
2026-04-11 01:28:03 +02:00
Ullrich Schäfer
d1b8674575
Handle Overpass rate limit returned as 200 with error body
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>
2026-04-11 01:25:59 +02:00
Ullrich Schäfer
c92d4cb4af
Fix Overpass 429: increase debounce, add minimum request interval
- 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>
2026-04-11 01:23:24 +02:00
Ullrich Schäfer
c23c325965
Add i18n keys and unit tests for overlays
- i18n: POI category names, panel UI strings (en + de)
- overpass.test.ts: Query building, response parsing, deduplication
- poi-cache.test.ts: Tile quantization, cache hit/miss, bbox filtering
- poi-categories.test.ts: Profile-to-overlay mapping, category validation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 01:19:27 +02:00
Ullrich Schäfer
6aa2229b39
Add POI overlay panel, markers, and map integration
- 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>
2026-04-11 01:17:41 +02:00
Ullrich Schäfer
375ff2fa13
Add Overpass client, POI categories, cache, and usePois hook
- poi-categories.ts: 9 POI categories with Overpass QL queries, icons,
  colors, and profile-aware defaults
- overpass.ts: Query builder, JSON parser, deduplication, rate limit error
- poi-cache.ts: Tile-based cache (0.1° grid cells, 10min TTL)
- use-pois.ts: React hook with 500ms debounce, AbortController, zoom
  threshold (>=12), exponential backoff on 429

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 01:15:34 +02:00
Ullrich Schäfer
0e399e5174
Add tile overlay definitions and LayersControl entries
- layers.ts: overlayLayers with hillshading, Waymarked Cycling/Hiking/MTB
- PlannerMap: LayersControl.Overlay entries for each overlay tile layer
- Leaflet handles attribution updates natively on toggle

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 01:13:21 +02:00
Ullrich Schäfer
a1b231c54c
Merge pull request #198 from trails-cool/feat/multi-day-routes
Add multi-day routes with overnight waypoints
2026-04-11 01:10:09 +02:00
Ullrich Schäfer
e6c68fb71b
Add BRouter mock for deterministic E2E tests
- brouter-mock.ts: Canned route responses for 2 and 3 waypoint sessions,
  plus latLngToPixel helper for pixel-precise map interactions
- planner.test.ts: Route split, map zoom, and overnight tests now use
  mockBRouter() for instant deterministic responses instead of real
  BRouter — fixes all 3 flaky tests
- Used .first() for sidebar km locator to avoid strict mode violations
  when both header summary and stats footer match

All 38 E2E tests pass in 9.7s (was 34s+ with real BRouter).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 01:07:39 +02:00
Ullrich Schäfer
f90571fc2c
Fix overnight E2E test: use nearby waypoints, scope locators to sidebar
The test used waypoints 300km apart causing BRouter timeouts. Switched
to nearby Berlin waypoints for fast route computation. Scoped all
locators to the sidebar to avoid strict mode violations from map
elements matching the same text.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 01:02:02 +02:00
Ullrich Schäfer
b6bca0229b
Fix E2E strict mode violations in multi-day tests
- "km" locator matched both header summary and stats footer — narrowed
  to match "km ·" pattern unique to the header
- "Day 1" locator matched both map label pill and sidebar header —
  scoped to sidebar with page.locator("aside")

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:59:03 +02:00
Ullrich Schäfer
adc5b44739
Archive multi-day-routes change, sync spec to main
Synced delta spec (6 requirements) to openspec/specs/multi-day-routes/.
Archived change to openspec/changes/archive/2026-04-11-multi-day-routes/.

All 4 artifacts complete. All 35 tasks complete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:54:05 +02:00
Ullrich Schäfer
d325127a06
Update specs with waypoint hover, day interaction, per-day export
Proposal: Added waypoint highlighting, Journal day interaction, and
per-day GPX export to What Changes and Impact sections.

Design: Added D13 (sidebar waypoint hover → map marker scale), D14
(Journal day segment hover with fly-to-bounds), D15 (per-day GPX
export endpoint with ?day=N query param).

Tasks: Added sections 8 (Planner waypoint hover, 2 tasks), 9 (Journal
day interaction, 5 tasks). Renumbered testing to section 10.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:52:40 +02:00
Ullrich Schäfer
7e89de0f0e
Reduce map fly animation to 200ms
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:48:41 +02:00
Ullrich Schäfer
b010cd8c59
Fly map to highlighted day segment on hover
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>
2026-04-11 00:46:46 +02:00
Ullrich Schäfer
7029c85dd0
Add route detail day segment highlighting and per-day GPX export
- 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>
2026-04-11 00:44:11 +02:00
Ullrich Schäfer
e2fc682a95
Adjust waypoint highlight animation to 0.2s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:39:41 +02:00
Ullrich Schäfer
8a3a374aa1
Double waypoint highlight animation duration to 0.3s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:39:22 +02:00
Ullrich Schäfer
18c9d12c48
Use CSS scale(1.17) for waypoint highlight instead of sizing individually
Single transform handles both the marker and text scaling uniformly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:38:49 +02:00
Ullrich Schäfer
fe64aa0103
Scale waypoint marker text on highlight (12→13px)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:38:00 +02:00
Ullrich Schäfer
c78a6f8bc9
Remove outline ring from waypoint highlight permanently
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>
2026-04-11 00:37:24 +02:00
Ullrich Schäfer
4853db27fc
Temporarily disable outline ring on highlighted waypoint markers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:37:03 +02:00
Ullrich Schäfer
eebf694d1a
Tone down waypoint highlight: 24→28px, keep font size unchanged
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:36:38 +02:00
Ullrich Schäfer
705252a1c6
Highlight waypoint marker on sidebar hover instead of red dot
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>
2026-04-11 00:35:42 +02:00
Ullrich Schäfer
13c2925c18
Add waypoint highlighting on sidebar hover
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>
2026-04-11 00:33:44 +02:00
Ullrich Schäfer
4a429c8c59
Shrink day label pill and fix centering
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>
2026-04-11 00:30:49 +02:00
Ullrich Schäfer
83a416b6c9
Fix day label positioning above overnight marker
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>
2026-04-11 00:30:15 +02:00
Ullrich Schäfer
cd180bc13b
Fix exported plan GPX missing overnight waypoint metadata
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>
2026-04-11 00:28:51 +02:00
Ullrich Schäfer
857100ef94
Fix overnight flag lost on Journal roundtrip
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>
2026-04-11 00:27:09 +02:00
Ullrich Schäfer
2970a460f8
Add E2E tests, Journal map coloring, and remaining unit tests
- planner.test.ts: E2E test for overnight toggle → day breakdown in sidebar,
  and GPX import with overnight metadata
- integration.test.ts: E2E test for isDayBreak preservation through GPX import
- RouteMapThumbnail: Per-day route coloring with alternating colors
- overnight.test.ts: Y.Doc-aware tests for isOvernight/setOvernight
- daybreaks-extraction.test.ts: GPX → dayBreaks index extraction tests

All 28 tasks complete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:14:08 +02:00
Ullrich Schäfer
5604fe1c82
Add per-day map coloring and remaining unit tests
- 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>
2026-04-11 00:12:27 +02:00
Ullrich Schäfer
77174f192d
Add unit tests for computeDays and GPX overnight roundtrip
- compute-days.test.ts: 8 tests covering single day, two days, three days,
  ascent/descent, edge cases (first/last overnight, single waypoint, empty)
- generate.test.ts: 4 new tests for isDayBreak emission, GPX roundtrip with
  isDayBreak preserved, and splitByDay multi-track output

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:05:54 +02:00
Ullrich Schäfer
774e2b1ae3
Add multi-track GPX export option (splitByDay)
generateGpx() gains a splitByDay option that splits the track into
separate <trk> elements per day, each named "Day N: Start - End".
Track points are split at the closest points to overnight waypoints.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:04:48 +02:00
Ullrich Schäfer
dd8d72d7b3
Add map overnight markers, day labels, and context menu
- 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>
2026-04-11 00:03:56 +02:00
Ullrich Schäfer
a21bf16e73
Fix i18n: use planner namespace in sidebar components
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>
2026-04-10 23:59:27 +02:00