Commit graph

93 commits

Author SHA1 Message Date
Ullrich Schäfer
2ac4014521
Standardize monorepo pipeline: test, lint, typecheck across all workspaces
Previously only 2-3 workspaces participated in each turbo task. This
expands test, lint, and typecheck to cover all 11 workspaces with
parallel execution and caching.

Test pipeline:
- Move from single root vitest to per-workspace test scripts via turbo
- Shared vitest config (vitest.shared.ts) with passWithNoTests
- Per-workspace configs re-export the shared base
- Mobile uses Jest (jest-expo + React Native Testing Library)
- Add node-environment GPX tests verifying linkedom fallback
- Add i18n mobile init tests
- Exclude apps/mobile from root vitest (uses Jest separately)

Lint pipeline:
- Add eslint lint script to all 9 previously unlinted workspaces
- Standardize all scripts to "eslint ." with shared root config
- Add .expo/ to global ESLint ignores
- Fix lint errors: unused imports in api/types, export parseGpx

Typecheck pipeline:
- Add "typecheck": "tsc" to all 8 packages
- Add @types/node (catalog) to gpx and db packages
- Fix mobile app.config.ts: remove deprecated experiments.monorepo
  and newArchEnabled (both default in Expo SDK 55)
- Add allowImportingTsExtensions to mobile tsconfig

Shared package compatibility (mobile-app Phase 1.3):
- Add linkedom as explicit dependency to @trails-cool/gpx
- Add initI18nMobile() export to @trails-cool/i18n
- Confirm @trails-cool/types is pure interfaces (no DOM deps)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 00:00:43 +02:00
Ullrich Schäfer
08070cdd90
Sync notes through GPX, Journal, and Planner roundtrip
Full notes lifecycle:
- GPX: description field in GpxData, <metadata><desc> in generate/parse
- Export: Plan GPX and Save to Journal include notes as description
- Journal: updateRoute extracts description from GPX, stores on route
- Reimport: Edit in Planner passes notes via URL params → Yjs Y.Text
- Drop import: GPX with <desc> restores notes in session

Spec updated: session-notes gains GPX export, Journal sync, and
reimport requirements.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 03:35:40 +02:00
Ullrich Schäfer
7e89de0f0e
Reduce map fly animation to 200ms
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:48:41 +02:00
Ullrich Schäfer
b010cd8c59
Fly map to highlighted day segment on hover
When hovering a day row in the route detail breakdown, the map smoothly
flies to fit that segment's bounds (0.5s animation). On mouse leave it
flies back to the full route bounds. Refactored geometry splitting into
shared helpers used by both DayColoredRoute and FlyToSegment.

Rotation not implemented — Leaflet doesn't support native bearing
rotation. Would require leaflet-rotate plugin or MapLibre GL.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:46:46 +02:00
Ullrich Schäfer
7029c85dd0
Add route detail day segment highlighting and per-day GPX export
- Hovering a day row dims other segments (opacity 0.3) and thickens
  the hovered day's segment (weight 5) for clear visual focus
- Each day row has a GPX download button that exports just that day's
  track segment via /api/routes/:id/gpx?day=N
- GPX endpoint uses computeDays to extract the correct track slice

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:44:11 +02:00
Ullrich Schäfer
5604fe1c82
Add per-day map coloring and remaining unit tests
- RouteMapThumbnail: Color route segments per day with alternating colors
  when dayBreaks are present, pass through from route detail page
- overnight.test.ts: 6 tests for isOvernight/setOvernight with Y.Doc
- daybreaks-extraction.test.ts: 3 tests for GPX → dayBreaks index extraction

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:12:27 +02:00
Ullrich Schäfer
b496cd9b02
Add Journal day breakdown and elevation chart dividers
- routes.server.ts: Extract dayBreaks from parsed GPX waypoints on save
- routes.$id.tsx: Compute per-day stats in loader, render day breakdown
  section with distance/ascent/descent per day
- ElevationChart: Dashed vertical lines at day boundaries
- i18n: Add Journal day breakdown keys (en + de)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 23:56:57 +02:00
Ullrich Schäfer
04efe791cd
Update dependencies and move Sentry to pnpm catalog
Dependency bumps:
- react, react-dom: 19.2.4 → 19.2.5 (catalog)
- @sentry/node, @sentry/react: 10.47.0 → 10.48.0
- @sentry/vite-plugin: 5.1.1 → 5.2.0
- @geoman-io/leaflet-geoman-free: 2.19.2 → 2.19.3
- jsdom: 29.0.1 → 29.0.2
- prettier: 3.8.1 → 3.8.2
- turbo: 2.9.3 → 2.9.6
- typescript-eslint: 8.58.0 → 8.58.1
- vitest: 4.1.2 → 4.1.4

Catalog cleanup:
- Move @sentry/node and @sentry/react to pnpm catalog (used by both apps)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 23:39:55 +02:00
dependabot[bot]
3b49aa7a5b
Bump nodemailer from 8.0.4 to 8.0.5
Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 8.0.4 to 8.0.5.
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/nodemailer/compare/v8.0.4...v8.0.5)

---
updated-dependencies:
- dependency-name: nodemailer
  dependency-version: 8.0.5
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-10 19:01:58 +00:00
Ullrich Schäfer
99cfbc6c1b
Merge pull request #185 from trails-cool/deps/sentry
Bump @sentry/node and @sentry/react from 10.46.0 to 10.47.0
2026-04-06 22:16:56 +01:00
Ullrich Schäfer
7c3ab0a95c Bump @sentry/node and @sentry/react from 10.46.0 to 10.47.0
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 23:11:40 +02:00
Ullrich Schäfer
2a59fc0538 Bump postgres from 3.4.8 to 3.4.9 and isbot from 5.1.36 to 5.1.37
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 23:10:16 +02:00
Ullrich Schäfer
9b2adebc4d Add ClientMap wrapper to avoid lazy() SSR resolution
.client.tsx modules return undefined on the server, but lazy() still
tries to resolve them during SSR causing "Element type is invalid".
Replace lazy()+Suspense with a ClientMap component that dynamically
imports the map only in useEffect (client-side only).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 22:52:57 +02:00
Ullrich Schäfer
81eb38a24d Fix Leaflet SSR error: rename to .client.tsx
Leaflet accesses `window` on import, crashing during SSR. Renamed
RouteMapThumbnail to .client.tsx so React Router excludes it from
the server bundle entirely. Updated all lazy() imports to match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 22:52:57 +02:00
Ullrich Schäfer
f80ed10a8b Remove unused journal health route file
api.health.ts was never registered in routes.ts — the server.ts
handler intercepts /api/health before React Router sees it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 22:40:24 +02:00
Ullrich Schäfer
be13145072 Fix DB connection leak in health check handlers
Both server.ts files called createDb() on every /health request,
creating a new postgres.js connection pool (~10 connections) that was
never closed. Docker healthchecks hit this every 15s, exhausting
max_connections within minutes and causing "too many clients" errors.

Fix: use a fresh postgres.js client (max: 1) per health check that is
properly closed in a finally block. This truly tests whether the DB is
accepting new connections without leaking.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 22:39:42 +02:00
Ullrich Schäfer
e358c432e7
Fix lint: duration is never reassigned, use const
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 20:04:10 +02:00
Ullrich Schäfer
80291ccf6e
Pass workout stats (distance, duration, startedAt) through on import
ActivityInput now accepts optional distance, duration, and startedAt.
GPX-derived stats take precedence when available, but provider stats
are used as fallback for workouts without GPS data.

The import form and Import All now pass these fields from the Wahoo
workout data so activities show correct metadata even without a FIT file.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 19:59:54 +02:00
Ullrich Schäfer
20b91ef511
Fix Wahoo import bugs, add Import All, update spec
Bugs fixed:
- FIT parser returns timestamps as Date objects, not strings — convert
  to ISO 8601 before passing to generateGpx (caused str.replace crash)
- FIT parser already converts semicircles to degrees — remove redundant
  conversion that produced near-zero coordinates
- Wahoo CDN URLs are pre-signed S3 URLs — remove Bearer auth header
  from download requests (caused 400 "Unsupported Authorization Type")
- Filter out third-party workouts (fitness_app_id >= 1000) since Wahoo
  does not share their data via the API

UX improvements:
- Individual imports use fetcher (no page refresh), button shows
  "Importing..." inline
- "Import all" button imports all unimported workouts on the page
  sequentially with progress indicator
- Add @vitejs/plugin-basic-ssl for local HTTPS dev (opt-in via HTTPS=1)

Also adds unit test for FIT-to-GPX conversion with real fixture file,
and updates wahoo-import spec to reflect all behaviors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 17:47:48 +02:00
Ullrich Schäfer
fdd193cbf0
Add activity import source badge, delete action, and No GPS indicator
- Activity detail: show "Imported from wahoo" badge when activity was
  imported from an external provider
- Activity detail: add delete button with confirmation, cleans up
  sync_imports so the workout can be reimported
- Import page: show "No GPS" badge with tooltip on workouts that have
  no file URL from the provider

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 16:49:42 +02:00
Ullrich Schäfer
8631c8ff14
Fix journal Dockerfile: copy app/lib for custom server imports
The custom server.ts imports logger and metrics from app/lib/ which
are TypeScript source files run via --experimental-strip-types. The
runtime image needs these source files copied (same as planner).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 16:30:17 +02:00
Ullrich Schäfer
7994ea79f4
Fix overview error rate panel, add request metrics to journal
Dashboard: switch error rate panel from app-level http_request_duration
(never observed) to Caddy's caddy_http_response_duration_seconds_count.

Journal: add custom server.ts (matching planner pattern) that observes
http_request_duration_seconds on every request. Replaces react-router-serve
with a custom Node HTTP server that handles /api/health, /api/metrics,
static assets, and request timing. Removes redundant React Router routes
for health and metrics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 16:20:18 +02:00
dependabot[bot]
17a8e82217
Bump @types/nodemailer from 7.0.11 to 8.0.0 in the development group
Bumps the development group with 1 update: [@types/nodemailer](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/nodemailer).


Updates `@types/nodemailer` from 7.0.11 to 8.0.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/nodemailer)

---
updated-dependencies:
- dependency-name: "@types/nodemailer"
  dependency-version: 8.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-05 08:28:22 +00:00
Ullrich Schäfer
9aaa17c898
Fix Wahoo import crash for workouts without FIT files
Some Wahoo workouts (indoor, manual entries) don't have a FIT file URL.
Both the manual import action and webhook handler called downloadFile
unconditionally, throwing "No file URL for workout". Now skips the
download and creates the activity without GPX when no file is available.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 11:41:28 +01:00
Ullrich Schäfer
b6711d23d6
Add Wahoo activity sync with provider-agnostic framework
Provider-agnostic sync framework + Wahoo as first implementation:

Framework (apps/journal/app/lib/sync/):
- SyncProvider interface for OAuth2, webhooks, import, conversion
- Provider registry for settings UI iteration
- Generic sync_connections + sync_imports tables
- Token storage with auto-refresh

Wahoo provider:
- OAuth2 with workouts_read, user_read, offline_data scopes
- Webhook-based auto-import (workout_summary events)
- Manual import page with pagination
- FIT→GPX conversion via fit-file-parser
- Webhook token verification

Routes:
- /api/sync/connect/:provider — OAuth redirect
- /api/sync/callback/:provider — OAuth callback
- /api/sync/disconnect/:provider — remove connection
- /api/sync/webhook/:provider — webhook receiver
- /sync/import/:provider — manual import page

Settings: Connected Services section with per-provider connect/disconnect

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 11:22:02 +01:00
Ullrich Schäfer
6aac8bd885
Add map previews to journal route and activity pages
- Route/activity list pages: map thumbnails with route drawn on OSM tiles
- Route/activity detail pages: interactive Leaflet map with zoom controls
- Server: expose GeoJSON from PostGIS via ST_AsGeoJSON (simplified for lists)
- RouteMapThumbnail component: shared, supports thumbnail and interactive modes
- Placeholder shown for routes/activities without geometry
- Archive journal-route-previews change, sync specs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 09:42:07 +01:00
Ullrich Schäfer
b6179fbdd2
Initialize no-go areas client-side via URL params
In dev mode, the sessions API and the Yjs WebSocket server are
separate processes (Vite plugin vs React Router action), so
server-side Yjs doc initialization doesn't reach the client.

Now no-go areas flow the same way as waypoints:
API response → URL params → client-side Yjs initialization.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:47:40 +01:00
Ullrich Schäfer
d5bcecb345
Fix distance calculation for GPX files without elevation data
Distance was only stored in the elevation profile array, which was
empty when track points had no <ele> elements. Now computed via
haversine independently and exposed as gpxData.distance.

Tested: berlin-dresden-radweg-2021.gpx (5660 points, no elevation)
now correctly reports 248.8 km.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 20:32:47 +01:00
Ullrich Schäfer
6ed828ef9c
Fix PostGIS geom population: use parseGpxAsync + raw SQL
The initial approach (#150) didn't work because:
1. routes.server.ts used parseGpx (sync, needs browser DOMParser)
   instead of parseGpxAsync (uses linkedom on Node.js) — GPX parsing
   silently failed, so stats AND geom were never populated
2. Drizzle's customType doesn't pass SQL expressions through toDriver

Fixes:
- Switch to parseGpxAsync for all server-side GPX parsing
- Use db.execute() with raw ST_GeomFromGeoJSON SQL after insert
- Remove unused lineStringFromCoords helper from schema
- Share setGeomFromGpx between routes and activities
- Add unit tests for GPX→GeoJSON coordinate extraction

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 17:32:07 +01:00
Ullrich Schäfer
b4c3f97296
Populate PostGIS geom column from GPX track data
Extract coordinates from parsed GPX tracks and store as PostGIS
LineString geometry via ST_GeomFromGeoJSON. Applied to:
- createRoute / updateRoute (routes.server.ts)
- createActivity / createRouteFromActivity (activities.server.ts)

Adds lineStringFromCoords() helper to the journal schema that
builds the SQL expression from [lon, lat] coordinate pairs.

Unblocks spatial queries (e.g. route discovery via ST_Intersects).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 17:05:02 +01: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
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