Commit graph

1066 commits

Author SHA1 Message Date
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
github-actions[bot]
bdb72ae141
Merge pull request #42 from trails-cool/ci-e2e-production-mode 2026-03-25 01:06:59 +00:00
Ullrich Schäfer
537d515df9 Run E2E tests against production build in CI
In CI, Playwright now starts apps with production commands (react-router-
serve, node --experimental-strip-types) instead of dev servers. This
catches runtime issues like missing exports and import resolution errors
before deploy. Locally, dev servers are still used for fast iteration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 01:04:38 +00:00
github-actions[bot]
ae8fc4560a
Merge pull request #43 from trails-cool/fix-planner-static-assets 2026-03-25 01:04:05 +00:00
Ullrich Schäfer
394daca38e
Serve static assets in planner's custom server
The custom server.ts only had the React Router request handler — static
assets in build/client/ were never served, causing 404s for all JS/CSS
in production. Add a static file handler with path traversal protection
and immutable caching for hashed assets.

Verified locally: HTML 200, assets 200, Cache-Control: immutable on
/assets/*.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 02:01:27 +01:00
github-actions[bot]
6afba179f3
Merge pull request #41 from trails-cool/fix-automerge-base-branch 2026-03-25 00:55:47 +00:00
Ullrich Schäfer
3a4119e00e Restrict automerge to PRs targeting main
Prevents stacked PRs from being auto-merged into feature branches.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 00:53:33 +00:00
github-actions[bot]
0bab72dcd7
Merge pull request #39 from trails-cool/fix-planner-production-server 2026-03-25 00:53:00 +00:00
Ullrich Schäfer
62b5fb998e
Fix planner production server + use .ts extensions everywhere
- Fix createRequestHandler → createRequestListener (@react-router/node
  removed the old API)
- Move noEmit + allowImportingTsExtensions to tsconfig.base.json so all
  packages and apps inherit them consistently
- Add explicit .ts extensions to all remaining relative imports across
  packages and apps

Verified locally: node --experimental-strip-types server.ts starts and
responds 200.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 01:41:33 +01:00
github-actions[bot]
655dc46ec8
Merge pull request #38 from trails-cool/fix-ts-extensions 2026-03-25 00:37:02 +00:00
Ullrich Schäfer
4d73de4e0d
Use explicit .ts extensions for Node-compatible imports
Node's --experimental-strip-types requires explicit .ts extensions on
relative imports. Add allowImportingTsExtensions to both app tsconfigs
and update server-side imports in planner and journal for consistency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 01:34:16 +01:00
Ullrich Schäfer
1abcc5c804
Merge pull request #37 from trails-cool/fix-production-deploy
Fix production deploy: TS support + DB schema
2026-03-25 01:27:01 +01:00
Ullrich Schäfer
afeefbf19d
Fix production deploy: TypeScript support + DB schema push
Planner crashed because Node.js couldn't handle .ts imports — add
--experimental-strip-types to the CMD. Journal crashed because the
database had no schemas — add drizzle-kit push to the deploy script.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 01:26:16 +01:00
Ullrich Schäfer
0fdcbf77d6
Merge pull request #36 from trails-cool/fix-cd-trigger
Fix CD: trigger after automerge + manual dispatch
2026-03-25 01:18:56 +01:00
Ullrich Schäfer
46c880973d
Fix CD: trigger on automerge + manual dispatch
GITHUB_TOKEN merges (from automerge action) don't trigger push
events on other workflows. Added:
- workflow_run trigger after Automerge completes
- workflow_dispatch for manual gh workflow run cd.yml

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 01:18:12 +01:00
github-actions[bot]
14656b6527
Merge pull request #35 from trails-cool/fix-docker-builds 2026-03-25 00:12:43 +00:00
Ullrich Schäfer
0a09df27f4
Fix Docker builds: missing deps + bump actions
Dockerfiles:
- Add db package.json to deps stage
- Copy full node_modules (including app-level) to runtime
- Copy packages/ dir for workspace imports at runtime

CD workflow:
- docker/login-action v3 → v4
- docker/build-push-action v6 → v7

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 01:10:01 +01:00
Ullrich Schäfer
21eb943393
Merge pull request #34 from trails-cool/deploy-to-hetzner
Enable CD deployment to Hetzner
2026-03-25 01:02:45 +01:00
Ullrich Schäfer
8e2f183288
Enable CD deployment to Hetzner (task 11.8)
CD workflow:
- Uncommented deploy step
- Copy docker-compose.yml + Caddyfile to server via SCP
- Login to ghcr.io with DEPLOY_GHCR_TOKEN (classic PAT, read:packages)
- Download Germany BRouter segments on first deploy (~750MB)
- Pull images, restart services, verify

Infrastructure:
- BRouter segments as bind mount (./segments) not Docker volume
- Added ORIGIN, PLANNER_URL, SESSION_SECRET, NODE_ENV to Journal
- Added NODE_ENV to Planner
- Disabled Garage for now (no media storage yet)

Required secrets: DEPLOY_HOST, DEPLOY_SSH_KEY, DEPLOY_GHCR_TOKEN

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 01:00:00 +01:00
github-actions[bot]
970f591a60
Merge pull request #33 from trails-cool/fix-playwright-summary 2026-03-24 23:48:55 +00:00
Ullrich Schäfer
6d8e570afc
Fix Playwright job summary: handle nested suite structure
Playwright JSON has file → describe → specs nesting.
Use stats.expected/unexpected for counts, iterate nested suites.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 00:46:21 +01:00
github-actions[bot]
a09f41e03d
Merge pull request #32 from trails-cool/playwright-job-summary 2026-03-24 23:41:02 +00:00
Ullrich Schäfer
074bcff4e5
Add Playwright job summary to CI
Generates a markdown summary table in GitHub Actions showing
passed/failed tests with timing per test. Uses the JSON reporter
to output results, then a Node script to format as markdown
and append to $GITHUB_STEP_SUMMARY.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 00:38:38 +01:00
Ullrich Schäfer
19ce2d1f1e
Merge pull request #31 from trails-cool/fix-automerge-workflow-run
Fix automerge: trigger after CI completes
2026-03-25 00:35:24 +01:00
Ullrich Schäfer
052ff6ac20
Fix automerge: add workflow_run trigger after CI completes
check_suite and status events don't reliably trigger for workflows
on the same commit (GitHub prevents infinite loops). Replaced with
workflow_run on CI completion, which fires after all CI jobs finish.

Added comments explaining why each trigger group exists.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 00:32:22 +01:00
Ullrich Schäfer
e4d4c790b3
Merge pull request #30 from trails-cool/ci-e2e-services
Add PostgreSQL + BRouter to CI E2E tests
2026-03-25 00:29:26 +01:00
Ullrich Schäfer
70d9d71a5c
Fix CI: wait for PostGIS extension before schema push
pg_isready reports true before PostGIS extension is initialized.
Added a second wait loop that queries PostGIS_Version() to ensure
the extension is fully loaded before running drizzle-kit push.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 00:21:25 +01:00
Ullrich Schäfer
1d45c57ae7
Cache PostGIS Docker image in CI
Replace service container with manual docker run + cached image tar.
Service containers pull the image before steps run (uncacheable).
Manual approach: cache the image as a tar, load on cache hit.

PostGIS image is ~400MB — first run pulls and saves, subsequent
runs load from cache in ~2s instead of ~19s.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 00:17:30 +01:00
Ullrich Schäfer
f2f5c6a83e
Cache Playwright browsers in CI
Playwright browser download (25s) is now cached by pnpm-lock hash.
On cache hit, only install system deps (fast). On miss, full install.

Current E2E timings (first run):
- Postgres container: 19s
- BRouter JAR: cached
- Berlin segment: 6s (will be cached next run)
- Playwright install: 25s → cached after first run
- BRouter startup: 9s
- Tests: 27s

Expected after caching: ~60s total (down from ~107s).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 00:15:26 +01:00
Ullrich Schäfer
f0afa8a4a7
Fix withDb: detect DataWithResponseInit from data() throws
React Router's data() throw creates a DataWithResponseInit object
(type + data + init), not a Response or ErrorResponseImpl.
Check for type === "DataWithResponseInit" to re-throw correctly.

Verified: nonexistent session → 404, DB down → 503.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 00:11:48 +01:00
Ullrich Schäfer
dee6f2806f
Add root ErrorBoundary to both apps, simplify withDb
Both apps now have proper ErrorBoundary exports in root.tsx:
- 404: "Page not found"
- 503: "Service temporarily unavailable"
- Other: shows error message

Simplified withDb re-throw check: anything with a "status" property
is treated as a React Router response (covers Response and
ErrorResponseImpl). DB errors throw a plain Response(503) that
the error boundary renders nicely.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 00:06:10 +01:00
Ullrich Schäfer
83009c2b9b
Fix withDb: re-throw React Router ErrorResponseImpl
React Router's data() throws ErrorResponseImpl, not Response.
Check for objects with status + data properties to catch both
Response and ErrorResponseImpl, preventing withDb from
swallowing 404s as 503s.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 00:03:15 +01:00
Ullrich Schäfer
b71117e4ca
Add PostgreSQL + BRouter to CI E2E tests
CI now runs the full stack for E2E tests:
- PostgreSQL as a service container + schema push via drizzle
- BRouter JAR + Berlin segment (E10_N50) with caching
- Both cached across runs (BRouter JAR + segment)

Removed all skip/workaround logic from tests — all 20 E2E tests
now run in CI with real services.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 23:58:01 +01:00
Ullrich Schäfer
cd6f6a6f18
Merge pull request #29 from trails-cool/planner-journal-handoff
Groups 10-11: Activity feed and E2E tests
2026-03-24 23:51:13 +01:00
Ullrich Schäfer
7d273b73c0
Fix CI E2E: check Planner DB, add Playwright HTML report
- 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>
2026-03-24 23:47:43 +01:00
Ullrich Schäfer
2d0a4ac2c7
Fix withDb: catch all non-Response errors as DB failures
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>
2026-03-24 23:43:10 +01:00
Ullrich Schäfer
8fb7771223
Add withDb() helper for graceful DB error handling
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>
2026-03-24 23:39:33 +01:00
Ullrich Schäfer
cadcf753a7
Gracefully handle missing DB in Planner routes
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>
2026-03-24 23:37:05 +01:00
Ullrich Schäfer
e86ff5a8a3
Fix E2E tests in CI: skip DB-dependent tests when no PostgreSQL
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>
2026-03-24 23:33:03 +01:00
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