Commit graph

24 commits

Author SHA1 Message Date
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
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
b9a77be020
Add route error toasts with waypoint rollback
- Show error toasts when route calculation fails, with distinct messages:
  - "No route found" for BRouter 4xx (unroutable waypoints, missing tiles)
  - "Route calculation failed" for server errors / network failures
  - "Too many requests" for rate limiting
- Rollback all waypoints to last known good state on route failure
- Guard against restore→recompute loop via restoringRef flag
- Extract reusable useToasts hook from awareness toast logic
- Add BRouterError class to distinguish client vs server errors
- API returns 422 for unroutable requests instead of blanket 502

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 14:09:20 +02: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
bb4f193405 Add app navigation bars to Journal and Planner
Journal gets a nav bar in root.tsx with conditional links: Routes and
Activities for authenticated users, Sign In and Register for guests.
Active route is highlighted via useLocation(). User menu shows username
linking to profile and a logout button.

Planner SessionView header title becomes a home link back to /.

Adds i18n keys (en + de) for all nav labels. Updates E2E tests to use
scoped nav selectors now that links appear in both nav bar and page body.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 03:12:27 +00:00
Ullrich Schäfer
9a904d9f69
Sentry improvements: context, tracing, source maps, privacy manifest
- Set Sentry user context (id, username) in Journal root for all routes
- Tag Planner errors with session_id
- Use reactRouterV7BrowserTracingIntegration for route-aware traces
- Hidden source maps (no sourceMappingURL in bundles, .map deleted after
  upload to Sentry)
- Privacy manifest at /privacy documenting all data collection
- robots.txt blocking all bots on both apps
- Suppress i18next promotional console log

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 03:11:07 +01:00
Ullrich Schäfer
b96b32cb53 Wire up i18n in both apps
Initialize i18n via initI18n() in both root.tsx files and replace all
hardcoded user-facing strings with useTranslation() calls. Adds missing
translation keys for planner (profiles, connection states, save flow)
and journal (auth pages, route management, passkey prompts).

Both English and German translations are complete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 01:47:58 +00:00
Ullrich Schäfer
69f420e879
Add basic responsive layout for tablet and mobile
Planner: hide title on small screens, wrap header items, hide waypoint
sidebar below md breakpoint (map takes full width).
Journal: stack route stats and action buttons on mobile, responsive
header layout on route detail.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 02:11:22 +01:00
Ullrich Schäfer
14b179bb0c Fix waypoint loading in Edit in Planner flow
Three issues fixed:

1. GPX parser used browser DOMParser which doesn't exist in Node/Vite SSR.
   Added async parseGpxAsync() using linkedom for server-side parsing.

2. Server-side session initialization stored waypoints in a Yjs doc
   instance separate from the Vite WebSocket plugin's doc store.
   Moved waypoint initialization to client-side: API returns parsed
   waypoints, client adds them to Yjs after sync.

3. GPX was encoded in URL params causing HTTP 431. Now the Journal
   creates a Planner session via API (POST body), and only passes
   compact waypoint coordinates in URL params.

Verified: Journal route → Edit in Planner → 4 waypoints loaded,
route computed (79.3km), elevation profile, Save to Journal ready.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 20:22:58 +00:00
Ullrich Schäfer
3567dac259 Allow repeated saves to Journal from Planner
Save button stays visible after saving — users can keep editing
and save again. "Saved!" shows as inline confirmation, "Return to
Journal" link appears alongside the save button.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 20:22:58 +00:00
Ullrich Schäfer
40e541fcce Implement Planner-Journal handoff (Group 9)
JWT-based handoff between Journal and Planner:

Journal side:
- JWT token generation scoped to route_id with 7-day expiry (jose)
- "Edit in Planner" button on route detail page — generates JWT,
  redirects to Planner /new with callback URL, token, and GPX
- Callback endpoint (POST /api/routes/:id/callback) validates JWT
  and creates new route version from received GPX

Planner side:
- /new route accepts callback, token, returnUrl, gpx params
- Creates session with callback metadata, initializes with GPX
- "Save to Journal" button POSTs GPX with Bearer token to callback
- "Return to Journal" link shown after successful save

All 6 Group 9 tasks complete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 20:22:58 +00: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
2e5c31d117
Add animated progress bar during route computation
Blue sliding progress bar appears at the top of the map while
BRouter is computing a route. Overlays on top of Leaflet map
with z-index 1000.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 08:34:34 +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
54b6232c5d
Complete Planner UI: profile selector, elevation chart, GPX export
- ProfileSelector: dropdown synced via Y.Map, triggers route recompute
  (Hiking, Cycling fast/safe, Shortest, Car profiles)
- ElevationChart: canvas-based elevation profile extracted from BRouter
  GeoJSON, shows min/max elevation and distance
- ExportButton: generates GPX from current waypoints + route track,
  downloads as route.gpx

All Groups 5 + 6 tasks complete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 00:57:39 +01:00
Ullrich Schäfer
e40b7dc199
Fix SSR and automerge timing
SSR fix:
- ClientOnly wrapper prevents Leaflet/Yjs from running during SSR
- SessionView lazy-loaded to avoid browser API usage on server

Automerge fix:
- Set MERGE_READY_STATE to 'clean' only (was including 'unstable')
- Prevents merging before all CI checks complete

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 00:49:54 +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