Commit graph

266 commits

Author SHA1 Message Date
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
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
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
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
Ullrich Schäfer
3f0fe859a5
Fix lint: remove unused variable in ElevationChart
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 23:57:27 +02:00
Ullrich Schäfer
78ee4ef3dc
Add elevation chart day dividers and wire days through SessionView
- 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>
2026-04-10 23:54:56 +02:00
Ullrich Schäfer
94016fd4c4
Add sidebar day breakdown and overnight toggle UI
- 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>
2026-04-10 23:53:43 +02:00
Ullrich Schäfer
efdb3c1973
Add multi-day data model, computeDays, and GPX roundtrip
- overnight.ts: Yjs helpers to set/clear/check overnight flag on waypoints
- compute-days.ts: Pure function splitting routes into DayStage[] at day breaks
- use-days.ts: Reactive hook mapping Yjs state into computeDays() input
- GPX generate: emit <type>overnight</type> for isDayBreak waypoints
- GPX parse: recognize <type>overnight</type> and set isDayBreak on waypoints

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 23:51:31 +02:00
Ullrich Schäfer
04efe791cd
Update dependencies and move Sentry to pnpm catalog
Dependency bumps:
- react, react-dom: 19.2.4 → 19.2.5 (catalog)
- @sentry/node, @sentry/react: 10.47.0 → 10.48.0
- @sentry/vite-plugin: 5.1.1 → 5.2.0
- @geoman-io/leaflet-geoman-free: 2.19.2 → 2.19.3
- jsdom: 29.0.1 → 29.0.2
- prettier: 3.8.1 → 3.8.2
- turbo: 2.9.3 → 2.9.6
- typescript-eslint: 8.58.0 → 8.58.1
- vitest: 4.1.2 → 4.1.4

Catalog cleanup:
- Move @sentry/node and @sentry/react to pnpm catalog (used by both apps)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 23:39:55 +02:00
Ullrich Schäfer
99cfbc6c1b
Merge pull request #185 from trails-cool/deps/sentry
Bump @sentry/node and @sentry/react from 10.46.0 to 10.47.0
2026-04-06 22:16:56 +01:00
Ullrich Schäfer
7c3ab0a95c Bump @sentry/node and @sentry/react from 10.46.0 to 10.47.0
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 23:11:40 +02:00
Ullrich Schäfer
2a59fc0538 Bump postgres from 3.4.8 to 3.4.9 and isbot from 5.1.36 to 5.1.37
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 23:10:16 +02:00
Ullrich Schäfer
be13145072 Fix DB connection leak in health check handlers
Both server.ts files called createDb() on every /health request,
creating a new postgres.js connection pool (~10 connections) that was
never closed. Docker healthchecks hit this every 15s, exhausting
max_connections within minutes and causing "too many clients" errors.

Fix: use a fresh postgres.js client (max: 1) per health check that is
properly closed in a finally block. This truly tests whether the DB is
accepting new connections without leaking.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 22:39:42 +02:00
Ullrich Schäfer
743169550e
Add undo/redo to planner
Yjs UndoManager tracks waypoints, no-go areas, and notes. Only
local mutations (origin "local") are undoable — remote changes
from other users are not.

- Keyboard shortcuts: Ctrl/Cmd+Z (undo), Ctrl/Cmd+Shift+Z/Y (redo)
- Shortcuts suppressed in text inputs (browser-native undo there)
- Undo/redo buttons in header with disabled state
- stopCapturing on drag to isolate drag as one undo step
- All mutation sites wrapped with "local" origin
- 5 unit tests for UndoManager behavior
- Archived undo-redo change

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:04:01 +01:00
Ullrich Schäfer
a9f8ee61f0
Add GPX file import to planner + archive change
Two import entry points:
- Home page: "Import GPX" button next to "Start Planning"
- In-session: drag-and-drop GPX onto the map (with confirmation)

Parses GPX client-side, extracts waypoints (Douglas-Peucker) and
no-go areas from extensions. Non-GPX files show error toast.

Also:
- Fix spec drift: non-GPX drop now shows error toast (was silent)
- Add E2E tests for import button, invalid GPX, and session creation
- Archive gpx-import-planner change, sync specs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:31:44 +01:00
Ullrich Schäfer
b6179fbdd2
Initialize no-go areas client-side via URL params
In dev mode, the sessions API and the Yjs WebSocket server are
separate processes (Vite plugin vs React Router action), so
server-side Yjs doc initialization doesn't reach the client.

Now no-go areas flow the same way as waypoints:
API response → URL params → client-side Yjs initialization.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:47:40 +01:00
Ullrich Schäfer
fdfacd2997
Save full planning state to journal (waypoints + no-go areas)
Save to Journal now includes waypoints and no-go areas alongside
the track — the full planning state needed for round-tripping.
Export Route remains the clean track-only option for other apps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:41:35 +01:00
Ullrich Schäfer
45d1360be5
Include no-go areas when saving to journal
Save to Journal now includes no-go areas in GPX extensions so they
round-trip correctly: Planner → Journal → Edit in Planner preserves
the no-go polygons.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:32:05 +01:00
Ullrich Schäfer
e12479293d
Add descriptions to export dropdown, fix z-index and alignment
- Add description text below each export option explaining the difference
- Fix z-index (z-[1001]) so dropdown renders above the map
- Align dropdown left instead of right to avoid overflow
- Widen dropdown (w-56) to fit descriptions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 11:06:58 +01:00
Ullrich Schäfer
87251ea366
Fix dropdown race: use mousedown for outside-click handler
The document click handler registered on open could catch the same
click event that opened the dropdown, immediately closing it.
Using mousedown avoids the race since it fires before click.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 10:56:22 +01:00
Ullrich Schäfer
721ec9e5e5
Fix export chevron: wider padding + stop click propagation
The chevron was too narrow (px-1.5 → px-2.5) and clicks were
caught by the outside-click handler before toggling the dropdown.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 10:53:18 +01:00
Ullrich Schäfer
4b711abaec
Add split export: Export Route vs Export Plan
Split button with default "Export GPX" (track only) and dropdown:
- Export Route: clean track for use in any app
- Export Plan: track + waypoints + no-go areas in GPX extensions
  (trails:planning namespace) for reimporting into the planner

Also:
- Add no-go area serialization to generateGpx (GPX extensions)
- Parse no-go areas from GPX extensions on import
- Initialize no-go areas in Yjs session from imported GPX
- Save to Journal now exports track only (consistent with Export Route)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 10:52:39 +01:00
Ullrich Schäfer
16568ffa45
Fix no-go button click creating a waypoint
The button was rendered as a React component, not a Leaflet control,
so clicks propagated to the map. Use L.DomEvent.disableClickPropagation
on the container — same approach Leaflet's built-in controls use.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 10:37:43 +01:00
Ullrich Schäfer
3abea9d5a8
Pass no-go area polygons natively to BRouter
BRouter supports a `polygons` parameter with full vertex coordinates.
We were approximating polygons as circles (centroid + max radius),
which poorly represented elongated or concave shapes.

Now passes polygon vertices directly — no approximation, exact
no-go boundaries.

Also removes unused haversineMeters helper and updates the spec.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 10:31:43 +01:00
Ullrich Schäfer
d97992e967
Guard all metric registrations against re-evaluation
Vite's dev server can re-evaluate modules, causing prom-client
"already registered" errors for all metrics, not just default ones.
Use getOrCreate pattern to reuse existing metrics from the registry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 10:02:21 +01:00
Ullrich Schäfer
a3ad073d96
Guard collectDefaultMetrics against duplicate registration
Importing metrics.server.ts from yjs-server.ts caused
collectDefaultMetrics() to re-register during HMR, crashing
the planner dev server. Check if metrics already exist first.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 09:16:31 +01:00
Ullrich Schäfer
e083ee134d
Wire planner metrics gauges to actual session/client counts
plannerActiveSessions and plannerConnectedClients were registered
but never updated — they always reported 0. Now incremented on
WebSocket connect and decremented on close.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 09:16:31 +01:00
Ullrich Schäfer
cc90c32a0e
Extract shared extractWaypoints into @trails-cool/gpx
Both new.tsx and api.sessions.ts had duplicated track-segment
waypoint extraction logic. Moved to packages/gpx as
extractWaypoints(gpxData) — uses <wpt> elements when present,
falls back to start of each track segment + end of last.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 20:47:17 +01:00
Ullrich Schäfer
e0f7c5b806
Apply track-segment waypoint extraction to sessions API
Same fix as new.tsx — the journal→planner handoff also only used
<wpt> elements from GPX. Now both code paths extract waypoints
from track segments when no explicit waypoints exist.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 20:45:05 +01:00