- Integration test DB check now hits Planner API (not Journal)
so it correctly skips when no PostgreSQL in CI
- Playwright uploads HTML report artifact on all runs (not just failures)
- Install Chromium with --with-deps for CI compatibility
- Report retained for 30 days
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous version checked for specific error messages that
didn't match Drizzle's actual error text ("Failed query").
Now any error from a withDb-wrapped handler returns 503.
React Router Response objects are still re-thrown.
Verified: DB down → 503 JSON, DB up → 201, home page always 200.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shared withDb() wrapper in @trails-cool/db catches database
connection errors and throws a 503 Response instead of crashing
the server. Re-throws React Router responses (redirects, data()).
Applied to Planner session routes — replaces manual try/catch.
Any route can use withDb(() => ...) for automatic 503 on DB failure.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wrap session API and loader in try/catch to return 503 instead
of crashing the server when PostgreSQL is unavailable. This
prevents E2E test failures in CI where no DB is running.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Planner session tests and integration tests now check for DB
availability before running. Skip gracefully in CI where no
PostgreSQL or BRouter is available.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
- 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>
- 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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>