Commit graph

35 commits

Author SHA1 Message Date
Ullrich Schäfer
22c5ebd838
Auto-enable POI categories on routing profile change
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>
2026-04-11 02:19:30 +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
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
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
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
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
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
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
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
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
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
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
99a4d7af97
Fix fitBounds running before elevation chart resizes map
invalidateSize() + requestAnimationFrame ensures Leaflet knows
the map's actual dimensions after the elevation chart renders.
Also removes unnecessary filter on already-typed coordinates.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 15:20:06 +02:00
copilot-swe-agent[bot]
1b9683752f
Fit map to route bounds when opening a route in the planner
Agent-Logs-Url: https://github.com/trails-cool/trails/sessions/558ccff8-ed5d-432a-a5d3-aca49c0e531e

Co-authored-by: stigi <13815+stigi@users.noreply.github.com>
2026-03-29 12:31:40 +00:00
Ullrich Schäfer
400189d9c1
Fix waypoint drag creating unwanted split points
The ghost marker (route split indicator) was interfering with waypoint
dragging because it sat above waypoints (zIndexOffset 1000) and stayed
active during drag operations.

Adopt brouter-web's suspension pattern:
- Suspend route interaction on waypoint mouseover (not just dragstart),
  unsuspend on mouseout — ghost marker hides before any click/drag
- Lower ghost marker zIndexOffset to -100 so waypoints always receive
  clicks first
- Keep drag guards as a safety net

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 12:34:09 +02:00
Ullrich Schäfer
b080a15fb1
Add route interactions: click-to-split, drag-to-reshape, colored route rendering
- Enrich BRouter response with per-point 3D coordinates and segment boundary
  tracking (EnrichedRoute interface)
- ColoredRoute component: plain, elevation gradient (green→yellow→red), and
  surface color modes with invisible wide polyline for click targeting
- Click-to-split: click on route polyline inserts waypoint at nearest point,
  mapped to correct segment via boundary indices
- MidpointHandles: draggable CircleMarkers at route segment midpoints for
  reshaping, hidden below zoom 12, opaque on hover
- Color mode toggle (select) synced via Yjs routeData
- i18n keys for color mode labels (en + de)
- Unit tests for segment boundary tracking (13 tests)
- E2E tests for enriched route response and color mode toggle

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:15:55 +01:00
Ullrich Schäfer
0a8dd0b766
Add transactional emails (SMTP) and planner features (no-go areas, notes, crash recovery)
Transactional emails:
- Add nodemailer SMTP email module with dev-mode console logging
- Magic link template and welcome template with HTML + plain text
- Wire sendMagicLink into login flow, sendWelcome into registration
- Update privacy page and deploy docs for SMTP configuration

Planner features:
- No-go areas: draw polygons on map (leaflet-geoman), synced via Yjs,
  passed to BRouter as nogos parameter, route recomputes on change
- Session notes: collaborative Y.Text textarea in sidebar tab
- Crash recovery: periodic localStorage save of Yjs state, restore on reconnect
- Rate limit session creation (10/IP/hour) in /new route

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 01:00:42 +01:00
Ullrich Schäfer
9b4f4c6759
Add multiplayer awareness: participant list, name editing, cursor styling, join/leave toasts
Implement the planner-multiplayer-awareness OpenSpec change:

- Add setUserName to use-yjs.ts that persists to localStorage and syncs via awareness
- Create ParticipantList component showing all session participants with color dots,
  host badge, and inline name editing for the local user
- Replace plain text cursor labels with SVG pointer arrows and styled name tags
  (colored background, drop shadow, rounded corners, z-index below map controls)
- Track awareness add/remove events and display auto-dismissing toasts (3s)
  when participants join or leave the session
- Add i18n keys for participant UI in English and German

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 08:40:35 +01:00
Ullrich Schäfer
55806fc84a
Fix waypoint drag, host election, segment routing, debug panel
Waypoint drag:
- Use observeDeep instead of observe to detect Y.Map property changes

Host election:
- Extracted to host-election.ts with 8 unit tests
- Simple rule: lowest real client ID is always host, deterministic
- Always sets role (host or participant), no missing roles
- Filters server docs (empty awareness state) from election

BRouter routing:
- Route segment-by-segment like bikerouter.de (one request per
  waypoint pair), guarantees route passes through every waypoint
- Merge GeoJSON segments with accumulated stats
- 8 unit tests for segment merging and waypoint splitting

Awareness cleanup:
- Track client IDs per WebSocket connection
- Immediately remove awareness on WebSocket close

Debug panel:
- Visibility + expanded state persisted in localStorage
- Hotkey (Shift+Cmd+Option+T) toggles visibility
- Labels server docs as (server)
- All logic self-contained in YjsDebugPanel.tsx

User identity:
- Color and name persisted in localStorage across reloads

Tests: 32 total (16 existing + 8 host election + 8 routing)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 08:52:52 +01:00
Ullrich Schäfer
6ee07f0570
Fix waypoint icons and add elevation chart scrubbing
Waypoint icons:
- Replace broken Leaflet default icons with numbered blue circles
- Numbers match sidebar order (1, 2, 3...)

Elevation chart:
- Mouse hover shows crosshair, elevation, and distance at cursor
- Red dot appears on the map at the corresponding route position
- Dot disappears when mouse leaves the chart

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 07:04:04 +01:00
Ullrich Schäfer
af70c822ee
Implement Planner map UI with collaborative editing (Groups 5+6)
Core Planner experience:
- Full-screen Leaflet map with OSM/OpenTopoMap/CyclOSM layers
- Click to add waypoints, drag to move, right-click to delete
- Waypoint sidebar with reorder, delete, route stats
- Real-time Yjs sync between participants
- Live cursors showing other users' mouse positions on the map

Routing:
- Host election via Yjs awareness (lowest client ID)
- Auto-failover when host disconnects
- Debounced BRouter route computation (500ms)
- Route polyline display from BRouter GeoJSON
- Route stats (distance, elevation gain/loss)

Client-side architecture:
- useYjs hook: WebSocket connection, awareness, document management
- useRouting hook: host election, route computation, stats parsing
- PlannerMap component: Leaflet + Yjs integration
- WaypointSidebar component: list with reorder/delete
- Lazy-loaded components for code splitting

Remaining: elevation chart, profile selector, GPX export (tasks 6.7-6.9)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 00:32:51 +01:00