Commit graph

30 commits

Author SHA1 Message Date
Ullrich Schäfer
be3e69c10b
Security hardening: headers, scanning, Docker, firewall
- Caddy: HSTS, CSP, X-Frame-Options, nosniff, Referrer-Policy,
  Permissions-Policy on all responses
- Caddy: Block scanner paths (.env, .git, wp-config, etc.) with 403
- CI: Gitleaks secret scanning + pnpm audit for vulnerabilities
- Dependabot: Weekly npm + GitHub Actions + monthly Docker updates
- Docker: Non-root user in journal, planner, and brouter containers
- Server: UFW firewall (22/80/443 only) + fail2ban (8 IPs already banned)
- SECURITY.md: Vulnerability disclosure policy
- Privacy page: Security practices section added

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 09:58:12 +01:00
Ullrich Schäfer
7d20dbb12f
Add observability and security-hardening proposals, gitignore settings.local
Two new OpenSpec changes:
- observability (30 tasks): health endpoints, Prometheus, Grafana+Loki,
  structured logging, dashboards, alerting
- security-hardening (24 tasks): Caddy headers, scanner blocking,
  gitleaks, pnpm audit, dependabot, non-root Docker, fail2ban

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 09:48:08 +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
160079b9be
Add planner landing page, archive change
Replace blank home page with landing page: hero with "Start Planning"
CTA, 5 feature cards, Journal CTA, footer with attribution. All strings
in en + de. Sync planner-session spec and archive change.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 04:05:02 +01:00
Ullrich Schäfer
4ddaad8c5f
Add planner landing page with features and CTAs
Replace blank home page with a landing page explaining collaborative
route planning. Hero section with "Start Planning" CTA, 5 feature cards
(collaboration, routing, elevation, GPX, no account), secondary CTA
linking to Journal, and footer with privacy/source/attribution links.

All strings in en + de via i18n.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 04:03:06 +01:00
Ullrich Schäfer
c7c1c275df
Add 8 proposed changes and old trails analysis
Proposed changes (all with proposal, design, specs, tasks):
- app-navigation (9 tasks) — nav bars for both apps
- planner-landing-page (7 tasks) — standalone landing page
- planner-multiplayer-awareness (13 tasks) — participants, cursors, names
- changelog (13 tasks) — public changelog with "what's new"
- transactional-emails (15 tasks) — magic link + welcome emails
- planner-features (18 tasks) — no-go areas, notes, recovery, rate limits
- komoot-import (23 tasks) — Komoot tour import
- route-features (37 tasks) — sharing, multi-day, spatial, photos

Also adds docs/old-trails-analysis.md with feature analysis from the
older trails project.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 03:57:20 +01:00
Ullrich Schäfer
9d51fd6b36
Archive sentry-improvements, sync specs
Sync Sentry error tracking requirement (5 scenarios) to main
infrastructure spec. Archive completed change.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 03:13:36 +01: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
3a519ea05e
Remove old phase-1-mvp location (moved to archive)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 02:55:43 +01:00
Ullrich Schäfer
e530492753
Archive phase-1-mvp, sync specs to main
All 85 tasks complete. Archive change to openspec/changes/archive/ and
sync 9 capability specs to openspec/specs/ for future reference.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 02:55:28 +01:00
Ullrich Schäfer
85b8454de7 Mark phase 1 MVP tasks 11.6-11.8 complete
- 11.6: i18n wired up in both apps (PR #44)
- 11.7: Responsive layout breakpoints added (PR #45)
- 11.8: Deployed to Hetzner, both apps live at trails.cool

All 11 task groups in phase 1 MVP are now complete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 01:14:19 +00: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
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
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
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
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
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
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
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
Ullrich Schäfer
6a3e566438
Complete Planner session management (tasks 4.2, 4.6, 4.7) (#9) 2026-03-22 22:18:36 +00:00
Ullrich Schäfer
7908a518b1
Add infrastructure: Terraform, Docker Compose, BRouter, CD pipeline (#2)
Terraform:
- Hetzner CX22 server with Docker, firewall (80/443/22)
- SSH key and output for server IP

Docker Compose:
- Caddy reverse proxy (auto HTTPS)
- Journal and Planner app containers
- BRouter routing engine container
- PostgreSQL + PostGIS
- Garage S3 storage

BRouter:
- Dockerfile based on eclipse-temurin:11-jre
- Segment download script for Germany (4 tiles, ~750MB)

CD Pipeline:
- Build & push Docker images to ghcr.io on main push
- Deploy step commented out (enable when server provisioned)

Scripts:
- PostgreSQL daily backup with 14-day retention
- Dockerfiles for both apps (multi-stage builds)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 13:32:01 +01:00
Ullrich Schäfer
464ba0f64d
Add Planner session management with Yjs WebSocket (#1)
- Yjs WebSocket server at /sync/:sessionId for real-time collaboration
- Session creation API (POST /api/sessions) with optional GPX initialization
- Session join page with Planner layout (header, map area, sidebar)
- In-memory session store (PostgreSQL persistence deferred to infra setup)
- Custom production server entry with WebSocket upgrade handling
- Vite path aliases (~/) for clean imports
- Fix tsconfig for apps: noEmit, exclude build dir

Tasks completed: 4.1, 4.3, 4.4, 4.5
Deferred: 4.2 (PostgreSQL), 4.6-4.8 (need client-side Yjs from Group 6)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 13:26:03 +01:00
Ullrich Schäfer
2cfa5e54e7
Implement shared packages (tasks 2.1-2.6)
- @trails-cool/types: Route, Activity, Waypoint, RouteVersion, RouteMetadata
- @trails-cool/gpx: GPX parser (XML→waypoints/tracks/elevation) and generator
  with round-trip test, haversine distance, elevation gain/loss computation
- @trails-cool/map: MapView (Leaflet + OSM/OpenTopoMap/CyclOSM layer switcher),
  RouteLayer (GeoJSON polyline rendering)
- @trails-cool/ui: Button (primary/secondary/ghost), Input (with label/error),
  Card components with Tailwind styling
- @trails-cool/i18n: react-i18next config with English + German translations,
  browser language detection, fallback to English

All 13 unit tests pass. Both apps build successfully.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 13:09:35 +01:00
Ullrich Schäfer
f58fe99cde
Add GitHub Actions CI, wire ESLint, lock in Caddy
CI pipeline with parallel jobs for fast feedback:
- typecheck, lint, unit tests, build run in parallel
- e2e tests run after build
- Playwright artifacts uploaded on failure

ESLint wired into both apps with sensible defaults:
- underscore-prefixed unused vars allowed
- root package.json marked as type: module

Architecture updated: Caddy as reverse proxy with auto HTTPS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 12:55:12 +01:00
Ullrich Schäfer
30d40de7cf
Complete monorepo toolchain setup (tasks 1.1-1.7)
- Turborepo + pnpm workspaces with catalog for shared dependency versions
- TypeScript strict config (base + per-package extends)
- Tailwind CSS v4 via @tailwindcss/vite plugin
- ESLint + Prettier shared config
- Planner app scaffolded with React Router 7 (port 3001)
- Journal app scaffolded with React Router 7 (port 3000)
- Both apps build and start successfully via turbo

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 12:12:57 +01:00
Ullrich Schäfer
da2f3c04ba
Add OpenSpec phase-1-mvp change with full specifications
OpenSpec initialized with Claude Code integration. Created phase-1-mvp
change with all artifacts:

- proposal.md: 9 new capabilities covering Planner, Journal, and infrastructure
- design.md: 8 technical decisions with alternatives considered
- specs/: 9 capability specs with testable WHEN/THEN scenarios
  - planner-session, brouter-integration, map-display
  - journal-auth, route-management, planner-journal-handoff
  - activity-feed, shared-packages, infrastructure
- tasks.md: 11 task groups, 74 implementation tasks

Ready for implementation via /opsx:apply

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 11:35:21 +01:00