Commit graph

87 commits

Author SHA1 Message Date
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
4403e22006
Remove useEffect flicker from ClientDate
Now that both server and client use the same locale from context,
the post-hydration useEffect that re-formatted with the browser's
OS locale was causing a visible flicker. Just render with the
context locale directly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 11:48:01 +02:00
Ullrich Schäfer
38ada3bd4f
Pass full locale from Accept-Language to ClientDate
Add detectLocale() that extracts the full locale tag (e.g. "de-DE")
from the request, not just the language. The journal root loader passes
it via LocaleContext so ClientDate renders with the correct locale on
both server and client — no more hardcoded "en-US" fallback.

Also fixes settings page hydration mismatch by using ClientDate for
passkey creation dates instead of inline toLocaleDateString().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 11:46:21 +02:00
Ullrich Schäfer
79f000fcc5
Upgrade i18next v26 + react-i18next v17 with SSR-safe init
Split initI18n() into separate server/client initialization paths
inspired by remix-i18next's architecture:

- initI18nServer(lng): no LanguageDetector, per-request language from
  Accept-Language header
- initI18nClient(): LanguageDetector reads <html lang> first to match
  server, then localStorage and navigator
- detectLanguage(request): parses Accept-Language for best match

Fixes rehydration errors caused by v26 removing initImmediate option
(replaced with initAsync) and LanguageDetector running on the server
where browser APIs don't exist.

Also makes <html lang> dynamic instead of hardcoded "en".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 11:38:36 +02:00
Ullrich Schäfer
f597ff2f85
Merge branch 'main' into dependabot/npm_and_yarn/production-54355eb8c8 2026-03-29 10:53:17 +02:00
Ullrich Schäfer
4859f5d5e0
Only show add-passkey prompt when user has no passkeys
The prompt now only appears when coming from signup/login with
?add-passkey=1 AND the user has zero passkeys. Passkey count display
removed from home — that info lives in /settings now.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 10:50:30 +02:00
Ullrich Schäfer
57a41501da
Merge branch 'main' into dependabot/npm_and_yarn/production-54355eb8c8 2026-03-29 10:41:28 +02:00
Ullrich Schäfer
d426912945
Merge branch 'main' into chore/static-assets 2026-03-29 10:36:22 +02:00
Ullrich Schäfer
518ee9ab69
Add favicons, apple-touch-icons, and Chrome DevTools JSON
Eliminates 404 noise from:
- /favicon.ico (browsers)
- /apple-touch-icon.png, /apple-touch-icon-precomposed.png (iOS)
- /.well-known/appspecific/com.chrome.devtools.json (Chrome)

Favicon uses the Waypoint Dot logo mark (sage green, SVG + ICO).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 10:35:03 +02:00
Ullrich Schäfer
a766bffad5
Add purpose column to magic_tokens, fix email change race condition
- Add `purpose` column (default: 'login') to distinguish login tokens
  from email-change tokens
- verifyMagicToken only matches purpose='login'
- verifyEmailChange only matches purpose='email-change'
- Re-check email availability at verification time, not just initiation
  — prevents race where someone registers with the new email between
  request and verification

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 10:28:53 +02:00
dependabot[bot]
aa4d491950
Bump the production group with 10 updates
Bumps the production group with 10 updates:

| Package | From | To |
| --- | --- | --- |
| [@react-router/dev](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dev) | `7.13.1` | `7.13.2` |
| [@react-router/node](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-node) | `7.13.1` | `7.13.2` |
| [@react-router/serve](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-serve) | `7.13.1` | `7.13.2` |
| [drizzle-orm](https://github.com/drizzle-team/drizzle-orm) | `0.45.1` | `0.45.2` |
| [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) | `7.13.1` | `7.13.2` |
| [turbo](https://github.com/vercel/turborepo) | `2.8.20` | `2.8.21` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.57.1` | `8.57.2` |
| [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `4.1.0` | `4.1.2` |
| [@sentry/node](https://github.com/getsentry/sentry-javascript) | `10.45.0` | `10.46.0` |
| [@sentry/react](https://github.com/getsentry/sentry-javascript) | `10.45.0` | `10.46.0` |


Updates `@react-router/dev` from 7.13.1 to 7.13.2
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dev/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/@react-router/dev@7.13.2/packages/react-router-dev)

Updates `@react-router/node` from 7.13.1 to 7.13.2
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-node/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/@react-router/node@7.13.2/packages/react-router-node)

Updates `@react-router/serve` from 7.13.1 to 7.13.2
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-serve/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/@react-router/serve@7.13.2/packages/react-router-serve)

Updates `drizzle-orm` from 0.45.1 to 0.45.2
- [Release notes](https://github.com/drizzle-team/drizzle-orm/releases)
- [Commits](https://github.com/drizzle-team/drizzle-orm/compare/0.45.1...0.45.2)

Updates `react-router` from 7.13.1 to 7.13.2
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.13.2/packages/react-router)

Updates `turbo` from 2.8.20 to 2.8.21
- [Release notes](https://github.com/vercel/turborepo/releases)
- [Changelog](https://github.com/vercel/turborepo/blob/main/RELEASE.md)
- [Commits](https://github.com/vercel/turborepo/compare/v2.8.20...v2.8.21)

Updates `typescript-eslint` from 8.57.1 to 8.57.2
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.57.2/packages/typescript-eslint)

Updates `vitest` from 4.1.0 to 4.1.2
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.2/packages/vitest)

Updates `@sentry/node` from 10.45.0 to 10.46.0
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/10.45.0...10.46.0)

Updates `@sentry/react` from 10.45.0 to 10.46.0
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/10.45.0...10.46.0)

---
updated-dependencies:
- dependency-name: "@react-router/dev"
  dependency-version: 7.13.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@react-router/node"
  dependency-version: 7.13.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@react-router/serve"
  dependency-version: 7.13.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: drizzle-orm
  dependency-version: 0.45.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: react-router
  dependency-version: 7.13.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: turbo
  dependency-version: 2.8.21
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: typescript-eslint
  dependency-version: 8.57.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: vitest
  dependency-version: 4.1.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@sentry/node"
  dependency-version: 10.46.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@sentry/react"
  dependency-version: 10.46.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-29 08:26:40 +00:00
Ullrich Schäfer
715b272395
Fix i18n: use common:key namespace syntax for cross-namespace keys
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 10:19:11 +02:00
Ullrich Schäfer
070a316164
Fix missing common.delete i18n key in settings page
Use both journal and common namespaces so t("common.delete") resolves.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 10:16:41 +02:00
Ullrich Schäfer
be5766fa50
Add account settings page with passkey management
- /settings with profile, security, and account sections
- Profile: edit display name and bio
- Security: list passkeys with device labels, add/delete with
  last-passkey warning
- Account: email change with magic link verification, account
  deletion with username confirmation
- Settings link in nav for authenticated users
- E2E tests: auth guard, profile editing, passkey add/delete,
  last-passkey warning, account deletion, nav visibility
- i18n: full en + de translations for all settings UI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 10:09:06 +02:00
Ullrich Schäfer
9adf9b977a
Passkey browser compatibility: detect support, magic link fallback
- Login: hide passkey button when browser lacks WebAuthn, default to
  magic link mode, hide "back to passkey" link
- Register: detect passkey support and offer magic link registration as
  fallback. New server-side registerWithMagicLink() creates account
  without credential and sends verification email.
- Home: show passkey count for logged-in users, show amber unsupported
  message instead of hiding the add-passkey prompt entirely
- i18n: add passkeyNotSupported, passkeyNotSupportedRegister,
  registerWithMagicLink, passkeyStatus keys (en + de)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 09:54:37 +02:00
Ullrich Schäfer
d95d142122
Fix passkey credential ID encoding and improve error handling
Bug: @simplewebauthn v13 changed credential.id from Uint8Array to
base64url string. Buffer.from(string) without encoding stored the
ASCII text instead of decoded bytes. Authentication then failed to
match because it correctly decoded with "base64url".

Fix: Buffer.from(credential.id, "base64url") in both registration
and add-passkey flows.

Also: catch WebAuthn "not allowed" errors and show a friendly message
instead of the raw browser error.

Existing passkeys must be re-registered after deploy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 21:18:14 +01:00
Ullrich Schäfer
7fdbd4cddf
Fix Planner health check: use createDb instead of non-existent db export
@trails-cool/db doesn't export a db instance — only createDb and withDb.
The health check was importing a non-existent export, always failing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:58:17 +01:00
Ullrich Schäfer
9187c3ce2b
Disable Sentry session replays
Removes replayIntegration and replay sample rates from both apps.
Quota was already exhausted.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:52:10 +01:00
Ullrich Schäfer
0ff9052ae8
Add version (git SHA) to health endpoints
Returns SENTRY_RELEASE (set at build time) as the version field,
making it easy to verify which commit is deployed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:48:27 +01:00
Ullrich Schäfer
3c5fb517dc
Add health and metrics routes to Journal route config
The route files existed but weren't registered in the explicit routes.ts,
so React Router never compiled them into the build.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:42:10 +01:00
Ullrich Schäfer
48fa94e51b
Install ca-certificates in Docker base image for Sentry uploads
node:25-slim doesn't include CA certificates, causing sentry-cli
SSL errors: "unable to get local issuer certificate". Also removes
debug logging from previous troubleshooting.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 18:35:46 +01:00
Ullrich Schäfer
318f0c93c1
Strip trailing newline from Sentry auth token
The token file may have a trailing newline that causes sentry-cli
API requests to fail with "API request failed".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 18:23:20 +01:00
Ullrich Schäfer
5d7b8c82e8
Fix Sentry build: disable telemetry, fix release version passing
- Disable Sentry Vite plugin telemetry (telemetry: false)
- Pass SENTRY_RELEASE as build-arg (not secret) since it's just the git sha
- Keep only SENTRY_AUTH_TOKEN as Docker secret

The release version was showing as *** because Docker secrets get masked
by GitHub Actions. Git sha is not sensitive — safe as a build arg.

The sourcemap warning from react-router is a known upstream issue (ships
without sourcemaps) and is harmless.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 18:18:00 +01:00
Ullrich Schäfer
6b147d04ec
Use pnpm.onlyBuiltDependencies for @sentry/cli and esbuild
pnpm approve-builds in Dockerfile ran after install, so the approval
wasn't picked up. Use onlyBuiltDependencies in package.json instead —
this is read during install and allows build scripts to run.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 18:07:25 +01:00
Ullrich Schäfer
5e200c8b62
Fix Sentry source map upload in Docker builds
- Approve @sentry/cli and esbuild build scripts so postinstall runs
- Use --mount=type=secret instead of ARG for SENTRY_AUTH_TOKEN
  (fixes Docker warning about secrets in build args)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 18:01:54 +01:00
Ullrich Schäfer
49aadd04a9
Add observability: health endpoints, structured logging, metrics, Grafana stack
Health endpoints:
- /api/health (Journal) and /health (Planner) with DB connectivity check
- Docker healthchecks updated to use app health endpoints

Structured logging:
- Pino with JSON output in production, pretty-print in dev
- Request logging middleware in Planner (method, path, status, duration)
- Replaced console.log/error with structured logger in email and auth flows

Prometheus metrics:
- prom-client with default Node.js metrics + custom histograms/gauges
- /metrics endpoints on both apps
- http_request_duration, planner_active_sessions, brouter_request_duration

Monitoring stack:
- Prometheus, Loki, Grafana containers in docker-compose
- Grafana provisioned with datasources, dashboards, and alert rules
- Caddy access logging (JSON to stdout for Loki)
- grafana.trails.cool with basic auth via Caddy

Dashboards and alerting:
- Overview: request rate, error rate, latency p50/p95/p99
- Planner: active sessions, connected clients, BRouter latency
- Infrastructure: memory, CPU, event loop lag
- Alerts: disk >80%, app down 2min, error rate >5%

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:59:44 +01: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
3f7499ff06 Fix i18n: move init to root.tsx and downgrade to stable versions
Move initI18n() from entry.client.tsx to root.tsx in both Planner and
Journal so i18n is initialized before any component renders. Downgrade
react-i18next to 16.6.1 and i18next to 25.10.4 to avoid hydration
mismatch issues introduced in newer versions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 23:18:15 +00:00
Ullrich Schäfer
d9ef69c0a0
Fix i18n: init in entry.client.tsx before hydration
With custom entry.client.tsx, initI18n() in root.tsx runs too late —
the client hydrates before i18n is initialized, showing raw translation
keys. Move initI18n() to entry.client.tsx where it runs before
startTransition/hydrateRoot.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:52:20 +01:00
Ullrich Schäfer
871ed81869
Fix Docker builds: corepack removed in Node 25
Node 25 no longer ships corepack. Replace corepack enable/prepare with
npm install -g pnpm.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:42:54 +01:00
Ullrich Schäfer
a5248872c7
Merge pull request #64 from trails-cool/dependabot/docker/apps/planner/node-25-slim
Bump node from 24-slim to 25-slim in /apps/planner
2026-03-25 13:21:32 +01:00
dependabot[bot]
2df9172161 Bump node from 24-slim to 25-slim in /apps/journal
Bumps node from 24-slim to 25-slim.

---
updated-dependencies:
- dependency-name: node
  dependency-version: 25-slim
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-25 12:32:34 +01:00
dependabot[bot]
c5508fc293 Bump node from 24-slim to 25-slim in /apps/planner
Bumps node from 24-slim to 25-slim.

---
updated-dependencies:
- dependency-name: node
  dependency-version: 25-slim
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-25 12:32:33 +01:00
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
cb2a202a0f Filter 404s from Sentry on client and server
Client ErrorBoundary: skip captureException for HTTP errors < 500.
Server beforeSend: drop events where __serialized__.status is 404.

Resolves JOURNAL-2 and JOURNAL-8 (scanner bot noise).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 08:02:26 +00: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
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
4a721fc586
Add privacy route to journal route config
Route was missing from the explicit routes.ts, so typegen didn't
generate types for it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 03:15:38 +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
4291fa6c3f Set all Sentry sample rates to 1.0
No users yet — capture everything. Traces, session replays, and error
replays all at 100%. Easy to dial back when traffic grows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 01:54:01 +00:00
Ullrich Schäfer
119dc25f5a Disable Sentry in CI, tag environment correctly
Client: VITE_SENTRY_ENVIRONMENT=ci baked in during CI build, disables
Sentry and sets 0 sample rates. Server: checks process.env.CI to
disable and tag as "ci" environment.

Production remains enabled with "production" environment tag.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 01:54:01 +00: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
091fc0a4bd
Add Sentry source maps and release tracking
- @sentry/vite-plugin uploads source maps during Docker build
- Release tagged with git SHA (SENTRY_RELEASE) on both client and server
- Environment set to production/development for filtering
- CD passes SENTRY_AUTH_TOKEN + SENTRY_RELEASE as Docker build args
- docker-compose passes SENTRY_RELEASE to runtime containers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 02:43:28 +01:00
Ullrich Schäfer
ef07915f38
Add Sentry error tracking to both apps
Planner (client + custom server) and Journal (client + entry.server):
- Client: @sentry/react with browser tracing + replay on error
- Server: @sentry/node for unhandled exceptions
- ErrorBoundary captures React errors via Sentry.captureException
- Disabled in dev, 10% trace sample rate in production

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 02:35:16 +01:00
Ullrich Schäfer
cc041d3235
Fix Yjs WebSocket server to use sync protocol
The production server was sending raw Yjs updates but the client
(y-websocket's WebsocketProvider) expects the y-protocols sync protocol
with message type headers. This caused "Unexpected end of array" errors
on every connection.

Rewrite to match the Vite dev plugin: use y-protocols/sync for document
sync, y-protocols/awareness for presence, and lib0 for encoding.

Verified locally: server starts, WebSocket connects, sync messages
exchange correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 02:24:13 +01:00
Ullrich Schäfer
a5d682d671 Fix CORS on Planner→Journal callback endpoint
The Save to Journal button makes a cross-origin POST from
planner.trails.cool to trails.cool. The browser's preflight OPTIONS
request was returning HTML (no CORS headers), blocking the save flow.

Add CORS headers to all responses and handle OPTIONS preflight in a
loader. Origin is restricted to PLANNER_URL.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 01:16:32 +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
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