Commit graph

77 commits

Author SHA1 Message Date
Ullrich Schäfer
79e6ae6ea2
Add E2E and integration tests (Group 11)
Journal tests (6):
- Home page, auth pages render correctly
- No password fields on register/login
- Protected routes redirect to login

Planner tests (9):
- Session creation via API
- Map loads in session
- Yjs WebSocket connects (Connected status)
- Profile selector, export button, waypoint sidebar
- Session with initial GPX waypoints
- Expired session returns 404

Integration tests (5):
- GPX import returns parsed waypoints
- BRouter computes Berlin routes
- Routes pass through all waypoints (segment by segment)
- Rate limit headers present
- Rejects < 2 waypoints

Total: 32 unit tests + 20 E2E tests, all passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 23:20:04 +01:00
Ullrich Schäfer
a9c99844c6
Implement Journal activity feed (Group 10)
Activity CRUD:
- Activity list page (/activities) with date, distance, elevation
- Create activity with GPX upload, description, optional route link
- Activity detail with stats and date
- "Link to Route" action (select from existing routes)
- "Create Route from Activity" action (creates route from GPX)

Server logic in activities.server.ts with async GPX parsing.
Uses ClientDate component for SSR-safe date rendering.
All 6 Group 10 tasks complete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 23:20:04 +01:00
Ullrich Schäfer
a7c4a0759b
Merge pull request #27 from trails-cool/update-claude-md-automerge-warning
Update claude md automerge warning
2026-03-24 23:14:19 +01:00
Ullrich Schäfer
21e0d3633a
Merge pull request #26 from trails-cool/planner-journal-handoff
Implement Planner-Journal handoff (Group 9)
2026-03-24 21:25:20 +01:00
Ullrich Schäfer
545469eae0 Fix SSR hydration mismatch for dates
Created ClientDate component that renders dates client-only via
useEffect to prevent server/client locale mismatches. Server
renders with de-DE as default, client updates to user's locale.

Applied to route detail (version dates) and route list (updated dates).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 20:22:58 +00: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
2e2dbe05c1 Fix GPX export: use API route with direct download link
React Router page actions wrap responses, preventing raw file
downloads. Moved GPX export to GET /api/routes/:id/gpx and
changed the button to a plain <a download> link.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 20:22:58 +00:00
Ullrich Schäfer
155d36e94c Fix Edit in Planner: create session via API, avoid URL size limit
GPX data was being encoded in URL params, causing HTTP 431 (Request
Header Fields Too Large). Now the Journal creates a Planner session
via POST to /api/sessions with GPX in the request body, then
redirects to the session URL (154 chars vs 100KB+).

Also moved edit-in-planner logic to dedicated API route for proper
JSON response handling.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 20:22:58 +00:00
Ullrich Schäfer
8559b89be6 Fix Edit in Planner: use client-side redirect for cross-origin
React Router's redirect() can't navigate to a different origin
(localhost:3001). Return the Planner URL as data and redirect
via window.location.href on the client.

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
f47eca565c
Merge pull request #25 from trails-cool/journal-routes
Implement Journal route management (Group 8)
2026-03-24 21:22:44 +01:00
Ullrich Schäfer
edd714fbb1
Implement Journal route management (Group 8)
Route CRUD:
- Create route with name, description, optional GPX upload
- Route detail page with stats (distance, elevation), version history
- Edit route (name, description, upload new GPX → creates new version)
- Delete route with confirmation dialog
- Route list page sorted by last updated

GPX handling:
- Import: parse GPX, compute distance/elevation stats on save
- Export: download route GPX as file
- Version history: new version created on each GPX update

Server logic in routes.server.ts with full Drizzle ORM queries.
All 10 Group 8 tasks complete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 22:25:10 +01:00
Ullrich Schäfer
9ce90eb550
Merge pull request #24 from trails-cool/journal-auth
Implement Journal auth: passkeys + magic links
2026-03-23 22:20:56 +01:00
Ullrich Schäfer
cc308bc17f
Complete Journal auth: add passkey prompt, dev magic link, fix validation
- Add passkey prompt after magic link login (/?add-passkey=1)
- addPasskeyStart/addPasskeyFinish for existing users
- Dev mode: magic link auto-redirects (returns devLink in response)
- Fix username validation: strip invalid chars instead of HTML pattern
- Separate API routes from page routes for JSON fetch compatibility

All 10 Group 7 tasks complete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 22:19:17 +01:00
Ullrich Schäfer
6950fd7de5
Fix username validation: strip invalid chars instead of pattern error
Browser's native pattern validation fired before preventDefault,
showing "The string did not match the expected pattern" error.
Replace HTML pattern with onChange that strips invalid characters.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:51:34 +01:00
Ullrich Schäfer
2215099501
Fix auth: separate API routes, verify registration with real DB
- Move auth logic from page actions to dedicated API routes
  (/api/auth/register, /api/auth/login) — React Router page
  actions don't handle JSON fetch() properly
- Remove server imports from page components
- Verify registration start works end-to-end with PostgreSQL:
  WebAuthn challenge generated, user ID created, RP set to localhost

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 17:55:49 +01:00
Ullrich Schäfer
b65b61cbc5
Implement Journal auth: passkeys + magic links, no passwords
Auth flows:
- Passkey registration: email + username → WebAuthn create → account + session
- Passkey login: WebAuthn get → session (instant, no form)
- Magic link fallback: email → token → verify link → session
- Logout via POST /auth/logout

Infrastructure:
- auth.server.ts: WebAuthn (SimpleWebAuthn), magic tokens, cookie sessions
- DB schema: credentials (WebAuthn), magic_tokens tables, no password_hash
- Session middleware via getSessionUser()

Pages:
- /auth/register — email + username + passkey creation
- /auth/login — passkey button + magic link fallback
- /auth/verify — magic link token verification
- /users/:username — public profile page
- Home page shows auth state (register/signin or welcome)

Dynamic imports for @simplewebauthn/browser to avoid SSR issues.
Magic links logged to console in dev (email integration later).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 17:38:46 +01:00
Ullrich Schäfer
3604a2d066
Update auth spec: passkeys + magic links, no passwords
- Passkey (WebAuthn) as primary auth for registration and login
- Magic link email as fallback for new devices
- Removed password_hash from users table
- Added credentials (WebAuthn) and magic_tokens tables
- Updated tasks (7.1-7.10)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 17:24:27 +01:00
Ullrich Schäfer
afb413b026
Merge pull request #23 from trails-cool/planner-ui-fixes
Fix waypoint icons, add elevation chart scrubbing
2026-03-23 12:03:54 +01:00
Ullrich Schäfer
288e9c07f2
Fix cross-window Yjs sync: broadcast doc updates to all connections
The Vite Yjs plugin was only sending sync responses back to the
sender, not broadcasting updates to other WebSocket connections
in the same room. This caused cross-window sync to only work via
BroadcastChannel (same browser context), not across separate
browser windows/incognito.

Fix: Add doc.on('update') listener that broadcasts updates to all
connections in the room except the origin sender.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 08:25:25 +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
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
github-actions[bot]
ce0cd84562
Merge pull request #22 from trails-cool/fix-automerge-triggers 2026-03-23 00:20:28 +00:00
Ullrich Schäfer
1e8e01f8e3
Add all recommended triggers to automerge action
Use the full set of triggers from pascalgn/automerge-action docs:
pull_request (labeled, synchronize, opened, etc), pull_request_review,
check_suite, and status events.

Replaces workflow_run trigger which wasn't firing reliably after
the label was added before CI completed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 01:18:54 +01:00
Ullrich Schäfer
526f721547
Merge pull request #21 from trails-cool/finish-planner-ui
Complete Planner UI: profile selector, elevation chart, GPX export
2026-03-23 01:18:16 +01:00
Ullrich Schäfer
f795406e77
Revert "Add all recommended triggers to automerge action"
This reverts commit 5f353f6320.
2026-03-23 01:18:04 +01:00
Ullrich Schäfer
5f353f6320
Add all recommended triggers to automerge action
Use the full set of triggers from pascalgn/automerge-action docs:
pull_request (labeled, synchronize, opened, etc), pull_request_review,
check_suite, and status events.

Removes workflow_run trigger which wasn't firing reliably.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 01:17:05 +01:00
Ullrich Schäfer
230094b956
Fix Yjs WebSocket in dev mode
- Add Vite plugin implementing y-protocols sync server for dev mode
- Fix useYjs hook: move provider creation into useEffect to survive
  React StrictMode remounts
- Fix WebSocket URL: use /sync as base (y-websocket appends room name)
- Verified in browser: Connected status, Host badge, awareness working

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 01:12:13 +01:00
Ullrich Schäfer
49e9d589b1
Fix: recompute route on profile change
Added Y.Map observer on routeData that triggers route recomputation
when the 'profile' key changes. Previously profile changes synced
via Yjs but didn't trigger a new BRouter request.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 00:58:31 +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
github-actions[bot]
b83384ab17
Merge pull request #20 from trails-cool/fix-automerge-ready-state 2026-03-22 23:51:37 +00: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
github-actions[bot]
e0cce17b8d
Merge pull request #19 from trails-cool/planner-map-ui 2026-03-22 23:33:17 +00: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
github-actions[bot]
9eee93b27b
Merge pull request #18 from trails-cool/fix-automerge-timing 2026-03-22 23:27:02 +00:00
Ullrich Schäfer
33edeb17fb
Fix automerge: wait for full CI workflow, not individual checks
Changed trigger from check_suite to workflow_run on CI workflow.
This ensures all jobs (typecheck, lint, test, build, e2e) pass
before auto-merging, not just the first check to complete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 00:26:48 +01:00
github-actions[bot]
49202899a8
Merge pull request #17 from trails-cool/fix-esbuild-vuln 2026-03-22 23:25:24 +00:00
Ullrich Schäfer
2fe8c93edb
Fix esbuild security vulnerability (Dependabot #1)
Override esbuild >=0.25.0 to fix CVE in dev server that allows
cross-origin requests. The vulnerable 0.18.20 was pulled in
transitively via drizzle-kit → @esbuild-kit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 00:25:09 +01:00
github-actions[bot]
d1f1bef2eb
Merge pull request #16 from trails-cool/fix-automerge-node24 2026-03-22 23:22:33 +00:00
Ullrich Schäfer
18d8870745
Force automerge action to run on Node.js 24
pascalgn/automerge-action@v0.16.4 hasn't released a Node 24 build yet.
Use FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 env var to suppress the warning.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 00:22:18 +01:00
github-actions[bot]
d868e78247
Merge pull request #15 from trails-cool/disable-squash-merge 2026-03-22 23:20:10 +00:00
Ullrich Schäfer
643dfc8c05
Switch from squash merges to regular merge commits
Squash merges caused orphaned commits when pushing after auto-merge.
Regular merges preserve full commit history and avoid this issue.

- Disabled squash and rebase merge on repo settings
- Updated automerge action: MERGE_METHOD squash → merge
- Updated CLAUDE.md workflow docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 00:19:57 +01:00
Ullrich Schäfer
75c9f6901b
Add reverse DNS (PTR) records to Terraform config (#14) 2026-03-22 23:17:33 +00:00
Ullrich Schäfer
bf4eb81fcb
Add reverse DNS (PTR) records to Terraform config
IPv4 and IPv6 PTR records pointing to trails.cool.
These were applied to the server but missing from the committed config.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 00:15:28 +01:00
Ullrich Schäfer
2f3bc9bd21
Warn against pushing to auto-merged branches in CLAUDE.md (#13) 2026-03-22 23:15:14 +00:00
Ullrich Schäfer
223f75b4c3
Warn against pushing to auto-merged branches in CLAUDE.md
Commits pushed after a PR auto-merges are orphaned from main.
Always check PR state before pushing to an existing branch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 00:14:57 +01:00
Ullrich Schäfer
52b2baaf58
Add local dev setup, fix BRouter Dockerfile, archive change (#12) 2026-03-22 23:11:43 +00:00
Ullrich Schäfer
2d02ae3f9e
Add BRouter proxy, rate limiting, and spec updates (tasks 5.1-5.2) (#11) 2026-03-22 22:43:47 +00:00