Commit graph

154 commits

Author SHA1 Message Date
Ullrich Schäfer
3a1c34317d
route-surface-breakdown (Phase 1): Planner-path surface/waytype bars
Synchronous path + rendering for the surface/waytype breakdown:
- map-core `computeSurfaceBreakdown(coords, surfaces, highways)` → distance-
  weighted metres per surface + waytype category (unit-tested);
- `SurfaceBreakdownSchema` in @trails-cool/api;
- nullable `surfaceBreakdown` jsonb on routes + activities;
- Planner `SaveToJournalButton` computes it from the BRouter waytags already in
  routeData and sends it; `api.save-to-journal` forwards it; the journal route
  callback validates + persists (journal is the authoritative validator);
- `SurfaceBreakdown` component (stacked bars per dimension, map-core palettes,
  legend category · % · km largest-first, unknown → "other", hidden when empty)
  on route + activity detail; journal gains a @trails-cool/map-core dep;
- i18n journal.surface.* (en + de).

Phase 2 (async Overpass backfill + SSE for imports/uploads, and the e2e that
seeds a breakdown) follows in a separate PR.

Tests: computeSurfaceBreakdown unit (map-core 36); SurfaceBreakdown component
(jsdom, journal 326). typecheck + lint green; verified in the browser.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 18:46:49 +02:00
dependabot[bot]
ccca2c8aae
build(deps): bump the production group across 1 directory with 6 updates
Bumps the production group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [eslint](https://github.com/eslint/eslint) | `10.4.1` | `10.5.0` |
| [@logtape/logtape](https://github.com/dahlia/logtape/tree/HEAD/packages/logtape) | `2.1.3` | `2.1.4` |
| [pg-boss](https://github.com/timgit/pg-boss) | `12.18.3` | `12.19.1` |
| [@sentry/react](https://github.com/getsentry/sentry-javascript) | `10.53.1` | `10.57.0` |
| [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) | `4.3.0` | `4.3.1` |
| [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.3.0` | `4.3.1` |



Updates `eslint` from 10.4.1 to 10.5.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.4.1...v10.5.0)

Updates `@logtape/logtape` from 2.1.3 to 2.1.4
- [Changelog](https://github.com/dahlia/logtape/blob/main/CHANGES.md)
- [Commits](https://github.com/dahlia/logtape/commits/2.1.4/packages/logtape)

Updates `pg-boss` from 12.18.3 to 12.19.1
- [Release notes](https://github.com/timgit/pg-boss/releases)
- [Commits](https://github.com/timgit/pg-boss/compare/12.18.3...12.19.1)

Updates `@sentry/react` from 10.53.1 to 10.57.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.53.1...10.57.0)

Updates `@tailwindcss/vite` from 4.3.0 to 4.3.1
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.1/packages/@tailwindcss-vite)

Updates `tailwindcss` from 4.3.0 to 4.3.1
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.1/packages/tailwindcss)

---
updated-dependencies:
- dependency-name: "@logtape/logtape"
  dependency-version: 2.1.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@sentry/react"
  dependency-version: 10.57.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@tailwindcss/vite"
  dependency-version: 4.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: eslint
  dependency-version: 10.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: pg-boss
  dependency-version: 12.19.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: tailwindcss
  dependency-version: 4.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-14 14:30:31 +00:00
Ullrich Schäfer
4754c229b9
profile-weekly-distance: make the chart legible (axis, grid, tracks, hover)
Addresses feedback that a lone bar conveyed nothing. Rewrite WeeklyDistanceChart
as a proper SVG chart:
- y-scale topped at the busiest week with 0 / half / peak gridlines + km labels;
- faint per-week track columns so the 12-week axis is always visible (empty
  weeks read as gaps, not nothing — no more floating bar);
- oldest→newest date bounds on the x-axis;
- a hover readout naming the week + its distance ("Week of {{date}} · X km").

i18n adds profileStats.weekOf. Component test updated for the SVG structure
(bar per non-zero week, peak-topped scale, hover readout). typecheck + lint +
unit (journal 322) green; verified in the browser.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 08:50:01 +02:00
Ullrich Schäfer
ceda9f1877
profile-weekly-distance: weekly distance bar chart on the profile
Implements the profile-weekly-distance change (specs/profile-weekly-distance):

- getWeeklyDistance(ownerId, { publicOnly, weeks=12 }) in activities.server.ts:
  one query that gap-fills in SQL — generate_series of week-starts LEFT JOINed
  to activities — so it returns exactly 12 contiguous { weekStart, distance }
  rows with matching Postgres week boundaries, viewer-scoped, no cache, no
  schema change.
- WeeklyDistanceChart: SVG bars normalized to the busiest week (empty weeks keep
  their slot as zero-height bars), per-bar title distance, localized label;
  renders nothing when there's no distance in the window. Mounted under the
  ProfileStats header.
- i18n journal.profileStats.weeklyDistance (en + de).

Tests: WeeklyDistanceChart component (jsdom: bar count incl. zero weeks, empty
→ hidden, normalization); e2e creates an activity with distance and asserts the
chart renders. typecheck + lint + unit (journal 321) green; verified in the
browser.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 07:46:06 +02:00
Ullrich Schäfer
3d3c56aaf4
profile-stats: lifetime roll-up header on the profile
Implements the profile-stats change (specs/profile-stats):

- getActivityStats(ownerId, { publicOnly }) in activities.server.ts: one indexed
  aggregate over stored columns (count, sum distance/ascent/elapsed duration) +
  a rolling last-4-weeks count. No cache table, no schema change, no GPX parsing
  (design §D1).
- Profile loader computes it scoped to the viewer (public-only for visitors,
  full totals for the owner); ProfileStats header renders count · distance ·
  ascent · time + "N in the last 4 weeks" via the shared StatRow + stats.ts
  formatters; hidden when there are no visible activities.
- i18n journal.profileStats.* in en + de.

Tests: ProfileStats component (jsdom: totals, empty, last-4-weeks toggle);
e2e asserts the owner roll-up counts their activities. typecheck + lint + unit
(journal 318) green; verified in the browser.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 07:25:06 +02:00
Ullrich Schäfer
536a8f98b9
journal-elevation-profile: elevation chart + map↔chart sync
Implements the journal-elevation-profile change (specs/journal-elevation-profile):

- gpx: `elevationSeries(tracks)` → { d, e, lat, lng }[] with cumulative distance,
  downsampled (keeps first/last), empty when <2 points carry elevation.
- ElevationProfile: read-only SVG area chart (vertical gradient fill), highest/
  lowest summary + a hover readout. Reports hovered index (onActive) and clicked
  index (onSeek); draws a marker at the active index. Renders nothing for an
  empty series.
- RouteMapThumbnail: ActiveMarker (CircleMarker at the chart's active point),
  HoverTracker (route hover → nearest sample → onHoverIndex), Recenter (panTo on
  chart click). Props forwarded through ClientMap.
- Wired into the activity + route detail pages via a shared activeIndex/centerOn
  state; loaders expose the series (activity reuses the moving-time parse).
- i18n journal.elevation.{highest,lowest} in en + de.

Ascent/descent stay in the stat row (#532); the chart summary shows highest/
lowest to avoid duplication.

Tests: elevationSeries unit; ElevationProfile component (jsdom); e2e creates an
activity from an elevation GPX and asserts the chart renders. typecheck + lint +
unit (gpx 67, journal 315) green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 15:34:45 +02:00
Ullrich Schäfer
0325d01bca
activity-stats: shared StatRow + canonical metrics across surfaces
Implements the activity-stats change (specs/activity-stats):

- gpx: `movingTime(tracks)` — moving seconds from trackpoint timestamps,
  excluding stationary spans + long gaps; null when no timestamps.
- stats.ts: pure formatters (distance/elevation/duration/speed/pace),
  sport-aware `deriveRate` (pace for foot sports, speed otherwise, speed
  default), and `activityStatItems` encoding the canonical order
  (distance · time · [moving] · pace/speed · ascent · descent; compact subset).
- StatRow: one shared presentational component (size sm/lg), adopted on the
  activity detail (full set; loader derives moving time), route detail
  (distance/ascent/descent), feed card + profile list (compact).
- i18n: journal.stats.* in en + de.

Tests: movingTime (stationary/gap exclusion, moving ≤ elapsed), deriveRate,
formatters, activityStatItems ordering + compact, StatRow render (jsdom).
typecheck + lint + unit (gpx 63, journal 311) green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 14:48:36 +02:00
Ullrich Schäfer
2cb32cd2d3
activity-sport-type: schema, contract, write/read paths, federation
Implements the activity-sport-type change (specs/activity-sport-type):

- db: nullable `sport_type` column on journal.activities + SportType /
  SPORT_TYPES (text().$type<> convention).
- api: optional sportType on the activity read + create schemas (mirrored
  SPORT_TYPES; @trails-cool/api stays zod-only).
- write: ActivityInput + createActivity persist it; mapSportType() normalizes
  provider strings (Komoot bulk import passes tour.sport; Garmin unset);
  threaded through the unified importActivity.
- read/display: sportType added to the detail/feed/profile loaders and the v1
  REST endpoints; shared SportBadge (glyph + i18n label) on detail, feed, and
  profile; sport-aware feed verb; create-form <select>.
- i18n: journal.activities.sport.* (labels + verbs) in en + de.
- federation: `sport` PropertyValue on the Note when set.

Tests: mapSportType unit table; federation asserts the sport attachment is
present when set and omitted when unset. typecheck + lint + unit all green.
E2E (create→badge) still to add.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 14:11:41 +02:00
dependabot[bot]
19160bbe82
build(deps): Bump the production group across 1 directory with 21 updates
Bumps the production group with 21 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [turbo](https://github.com/vercel/turborepo) | `2.9.17` | `2.9.18` |
| [@logtape/logtape](https://github.com/dahlia/logtape/tree/HEAD/packages/logtape) | `2.1.1` | `2.1.3` |
| [isbot](https://github.com/omrilotan/isbot) | `5.1.41` | `5.1.42` |
| [nodemailer](https://github.com/nodemailer/nodemailer) | `8.0.10` | `8.0.11` |
| [@types/nodemailer](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/nodemailer) | `8.0.0` | `8.0.1` |
| [@expo/metro-runtime](https://github.com/expo/expo) | `56.0.14` | `56.0.15` |
| [@maplibre/maplibre-react-native](https://github.com/maplibre/maplibre-react-native) | `11.3.3` | `11.3.4` |
| [expo](https://github.com/expo/expo/tree/HEAD/packages/expo) | `56.0.9` | `56.0.11` |
| [expo-constants](https://github.com/expo/expo/tree/HEAD/packages/expo-constants) | `56.0.17` | `56.0.18` |
| [expo-dev-client](https://github.com/expo/expo/tree/HEAD/packages/expo-dev-client) | `56.0.19` | `56.0.20` |
| [expo-file-system](https://github.com/expo/expo/tree/HEAD/packages/expo-file-system) | `56.0.7` | `56.0.8` |
| [expo-linking](https://github.com/expo/expo/tree/HEAD/packages/expo-linking) | `56.0.13` | `56.0.14` |
| [expo-location](https://github.com/expo/expo/tree/HEAD/packages/expo-location) | `56.0.16` | `56.0.17` |
| [expo-notifications](https://github.com/expo/expo/tree/HEAD/packages/expo-notifications) | `56.0.16` | `56.0.17` |
| [expo-router](https://github.com/expo/expo/tree/HEAD/packages/expo-router) | `56.2.9` | `56.2.10` |
| [expo-sqlite](https://github.com/expo/expo/tree/HEAD/packages/expo-sqlite) | `56.0.4` | `56.0.5` |
| [@codemirror/view](https://github.com/codemirror/view) | `6.43.0` | `6.43.1` |
| [pg-boss](https://github.com/timgit/pg-boss) | `12.18.2` | `12.18.3` |
| [@sentry/node](https://github.com/getsentry/sentry-javascript) | `10.56.0` | `10.57.0` |
| [@sentry/react](https://github.com/getsentry/sentry-javascript) | `10.53.1` | `10.57.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `22.19.20` | `22.19.21` |



Updates `turbo` from 2.9.17 to 2.9.18
- [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.9.17...v2.9.18)

Updates `@logtape/logtape` from 2.1.1 to 2.1.3
- [Changelog](https://github.com/dahlia/logtape/blob/main/CHANGES.md)
- [Commits](https://github.com/dahlia/logtape/commits/2.1.3/packages/logtape)

Updates `isbot` from 5.1.41 to 5.1.42
- [Changelog](https://github.com/omrilotan/isbot/blob/main/CHANGELOG.md)
- [Commits](https://github.com/omrilotan/isbot/compare/v5.1.41...v5.1.42)

Updates `nodemailer` from 8.0.10 to 8.0.11
- [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.10...v8.0.11)

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

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

Updates `@expo/metro-runtime` from 56.0.14 to 56.0.15
- [Changelog](https://github.com/expo/expo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits)

Updates `@maplibre/maplibre-react-native` from 11.3.3 to 11.3.4
- [Release notes](https://github.com/maplibre/maplibre-react-native/releases)
- [Changelog](https://github.com/maplibre/maplibre-react-native/blob/main/CHANGELOG.md)
- [Commits](https://github.com/maplibre/maplibre-react-native/compare/v11.3.3...v11.3.4)

Updates `expo` from 56.0.9 to 56.0.11
- [Changelog](https://github.com/expo/expo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo)

Updates `expo-constants` from 56.0.17 to 56.0.18
- [Changelog](https://github.com/expo/expo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-constants)

Updates `expo-dev-client` from 56.0.19 to 56.0.20
- [Changelog](https://github.com/expo/expo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-dev-client)

Updates `expo-file-system` from 56.0.7 to 56.0.8
- [Changelog](https://github.com/expo/expo/blob/main/packages/expo-file-system/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-file-system)

Updates `expo-linking` from 56.0.13 to 56.0.14
- [Changelog](https://github.com/expo/expo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-linking)

Updates `expo-location` from 56.0.16 to 56.0.17
- [Changelog](https://github.com/expo/expo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-location)

Updates `expo-notifications` from 56.0.16 to 56.0.17
- [Changelog](https://github.com/expo/expo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-notifications)

Updates `expo-router` from 56.2.9 to 56.2.10
- [Changelog](https://github.com/expo/expo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-router)

Updates `expo-sqlite` from 56.0.4 to 56.0.5
- [Changelog](https://github.com/expo/expo/blob/main/packages/expo-sqlite/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-sqlite)

Updates `@codemirror/view` from 6.43.0 to 6.43.1
- [Changelog](https://github.com/codemirror/view/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/view/commits)

Updates `pg-boss` from 12.18.2 to 12.18.3
- [Release notes](https://github.com/timgit/pg-boss/releases)
- [Commits](https://github.com/timgit/pg-boss/compare/12.18.2...12.18.3)

Updates `@sentry/node` from 10.56.0 to 10.57.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.56.0...10.57.0)

Updates `@sentry/react` from 10.53.1 to 10.57.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.53.1...10.57.0)

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

---
updated-dependencies:
- dependency-name: "@codemirror/view"
  dependency-version: 6.43.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@expo/metro-runtime"
  dependency-version: 56.0.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@logtape/logtape"
  dependency-version: 2.1.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@maplibre/maplibre-react-native"
  dependency-version: 11.3.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@sentry/node"
  dependency-version: 10.57.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@sentry/react"
  dependency-version: 10.57.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@types/node"
  dependency-version: 22.19.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@types/nodemailer"
  dependency-version: 8.0.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@types/nodemailer"
  dependency-version: 8.0.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo
  dependency-version: 56.0.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-constants
  dependency-version: 56.0.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-dev-client
  dependency-version: 56.0.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-file-system
  dependency-version: 56.0.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-linking
  dependency-version: 56.0.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-location
  dependency-version: 56.0.17
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-notifications
  dependency-version: 56.0.17
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-router
  dependency-version: 56.2.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-sqlite
  dependency-version: 56.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: isbot
  dependency-version: 5.1.42
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: nodemailer
  dependency-version: 8.0.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: pg-boss
  dependency-version: 12.18.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: turbo
  dependency-version: 2.9.18
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-11 19:22:35 +00:00
Ullrich Schäfer
01d4832edb
security: authorize and validate presigned upload requests
POST /api/v1/uploads minted an upload key from caller input with no
ownership check, no content-type allowlist, and the raw filename
interpolated into the S3 key.

- Ownership: a caller may only mint upload URLs for a route/activity
  they own, enforced via the branded loadOwnedRoute/loadOwnedActivity
  (404 on miss, no existence leak). Closes writing into another
  user's resource key-space.
- Content type: the request schema now constrains contentType to an
  image/gpx allowlist, so active content (HTML/SVG/JS) that would
  execute if served inline is rejected at the request boundary.
- Filename: sanitizeUploadFilename() reduces the client filename to a
  safe basename (charset-restricted, no path components, no leading
  dots, length-bounded) before it becomes part of the key.

Tests: schema allow/deny + filename sanitization in @trails-cool/api;
handler tests covering owned-success, not-owner 404, disallowed
content-type, and the route-vs-activity ownership branch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 22:23:14 +02:00
Ullrich Schäfer
f684272eb5
Merge branch 'main' into package-shims 2026-06-10 07:50:17 +02:00
Ullrich Schäfer
765c9f49a8
remove the map and ui shim packages
Both failed the deletion test in the telling direction:

- @trails-cool/ui: Button/Input/Card had zero consumers — both apps
  roll their own elements inline. The only live part was a 6-line
  styles.css (the Tailwind entry + one keyframe), which now lives in
  each app as app/styles.css.
- @trails-cool/map: MapView and RouteLayer had zero consumers; the
  package was otherwise a re-export of two map-core constants, and the
  two import sites now use @trails-cool/map-core directly. The
  "map components go in @trails-cool/map" convention had drifted long
  ago — the real map components live in apps/planner/app/components.

CLAUDE.md's repository structure and conventions updated to match
reality (including pointing shared-type guidance at the post-#515
sources: db row types, api contracts, Waypoint in types). Dockerfiles
no longer COPY the deleted package manifests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 07:47:59 +02:00
Ullrich Schäfer
61a2d0085b
one source of truth for Route/Activity shapes; enforce api contracts
Route and Activity existed three times: hand-written interfaces in
packages/types, Zod contracts in packages/api, and Drizzle columns in
packages/db — each with different fields and nullability. The
hand-written ones had drifted so far they had zero importers; the Zod
contracts were advisory because v1 handlers hand-rolled Response.json
shapes nothing validated.

- packages/types keeps only what both apps actually share (Waypoint,
  WaypointPoiTags) and documents where row types and wire contracts
  live; the dead Route/RouteMetadata/RouteVersion/Activity interfaces
  are gone
- packages/db exports canonical inferred row types (RouteRow,
  ActivityRow, RouteVersionRow, UserRow)
- packages/api contracts are reconciled with the real wire format
  (RouteVersionSchema gains the id and createdBy fields the endpoint
  has always returned) and gain Create*ResponseSchemas
- apiJson(schema, payload) in api-guard parses every v1 response
  through its contract: drift is now a thrown ZodError in tests/CI,
  unknown keys are stripped, and payloads are compile-checked as
  z.input of the schema

Enforcement immediately caught two real drifts: nullable DB
descriptions could ship null where the contract promises string (now
coalesced at the boundary), and GET /api/v1/activities/:id was missing
the routeName and photos fields its contract declares.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 07:39:50 +02:00
Ullrich Schäfer
855244747c
journal: typed job seam + Komoot credentials through the manager
Two related fixes from the architecture review:

Typed job seam. Job payloads were `unknown` end-to-end: every handler
opened with `job.data as SomePayload`, every enqueue site passed a bare
string queue name and an unchecked object, and a typo meant a runtime
failure in a background worker. packages/jobs gains defineJob(), which
keeps the payload typed inside the handler and performs the
contravariance cast once inside the package. The journal declares all
14 queues and their payload shapes in app/jobs/payloads.ts; enqueue()/
enqueueOptional() and defineJournalJob() key off that map, so enqueue
sites and handlers cannot drift and queue names are compile-checked.

Komoot credential bypass. The bulk-import route enqueued the raw
credentials JSONB in the pg-boss payload, skipping withFreshCredentials
entirely: credentials sat at rest in the job table, were never
refreshed if stale, and markNeedsRelink never fired. The payload now
carries only the serviceId; the handler resolves fresh credentials
through the ConnectedServiceManager at execution time, and marks the
import batch failed (instead of leaving it pending forever) when
credential resolution itself fails.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 02:05:24 +02:00
Ullrich Schäfer
8eeb775f54
Merge branch 'main' into dependabot/npm_and_yarn/production-e9efbc881e 2026-06-10 01:28:40 +02:00
Ullrich Schäfer
0360757ae8 feat(journal): Garmin activity import — provider, webhook pipeline, backfill (§1–5)
Garmin Connect as the third connected-services provider (spec:
garmin-import). The interesting parts:

- Push-first ingestion: Garmin has no list endpoint. The webhook
  normalizes ping (callbackURL) and push (inline) notification batches
  into events; the slow work (authorized FIT download, FIT→GPX via the
  shared converter, activity creation) runs in a garmin-import-activity
  pg-boss job so the webhook answers fast. Callback URLs are validated
  against Garmin's API host before any fetch (SSRF guard).
- History via backfill requests: /sync/import/garmin is a date-range
  requester with honest async progress (no pick list — the concept
  doesn't exist in a push model). Ranges chunk to Garmin's 90-day cap;
  overlaps are free via sync_imports dedupe. Requests persist in
  import_batches via two new nullable columns (range_start/range_end).
- OAuth2 + PKCE on the existing oauth credential kind. Design
  correction from apply: the verifier rides a short-lived httpOnly
  cookie scoped to the callback path — the state param is visible in
  redirect URLs and must never carry it. Manifests opt in via pkce:true.
- Deregistration notifications flip the connection to 'revoked'
  (row kept for audit, imports retained, re-connect prompt shown).
- Framework evolutions, all additive: parseWebhook returns
  WebhookEvent[] (Garmin batches; Wahoo adapted), manifest gains
  configured()/importUrl/pkce, importActivity accepts summary stats
  for FIT-less imports, manager gains markRevoked.
- Env-gated: no GARMIN_CLIENT_ID → provider hidden on
  /settings/connections. Privacy manifest entry (DE+EN). i18n en+de.

Rollout (§6) stays gated on the Garmin Developer Program application
(submitted 2026-06-07). Fixtures are doc-shaped; the staging soak
swaps in recorded payloads if shapes differ.

Gate: typecheck ✓ lint ✓ unit+integration ✓ e2e 70/72 + both known
flakes green isolated ✓ openspec validate ✓

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 17:47:22 +02:00
Ullrich Schäfer
b96bef91a9 feat(journal): outbox-poll ingestion of remote trails activities (§7)
Tasks 7.1–7.3, 7.5 (+7.4 pacing; Retry-After-duration backoff noted as
remaining). Resolves the activities.owner_id open question.

Schema (design decision from the open question):
- activities.owner_id nullable + check constraint enforcing exactly
  one of (owner_id, remote_actor_iri) — the follows pattern again.
- remote_published_at carries the origin's publish time for the §8
  feed sort; index on remote_actor_iri for the feed join.
- Compiler-audited fallout: notification fan-out, the Note object
  dispatcher, and the activity detail loader explicitly skip/404
  remote rows (their canonical page is the origin; feed links there).
  Every other surface joins users on owner_id and excludes remote rows
  structurally.

Ingestion:
- federation-ingest.server.ts: parseOutboxItem targets exactly our own
  outgoing Create(Note) shape (PropertyValue stats, first-paragraph
  name, audience from to/cc); unknown items skipped, never fatal.
- ingestRemoteActivities: replay-safe via unique remote_origin_iri,
  conflict-streak early exit (outboxes are newest-first).
- pollRemoteActor: signed fetch (Authorized Fetch via a local
  follower's key), outbox resolution via remote_actors cache with
  actor-doc fallback (which refreshes the cache), 1 req/5 s per-host
  pacing, last_polled_at stamping. Network + pacing injectable.

Jobs: poll-remote-actor (per-actor; the §4 Accept listener already
enqueues it as the first poll) + poll-remote-outboxes (5-min cron
sweep over accepted remote follows not polled within the hour).

Tests: parse-shape units; integration suite for ingestion provenance,
audience→visibility mirroring, replay safety, the DB author invariant,
poll flow (actor-doc → collection → page), signer requirement, and
due-polling selection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 12:27:10 +02:00
Ullrich Schäfer
f4d2cf027c docs(journal): federation privacy manifest + runbook + honest home blurb (§10)
10.1 Privacy manifest (legal/privacy):
- German legal half: Föderation entry under Empfänger/Drittanbieter —
  what a public profile exposes, what accepted followers' servers
  receive, the loss-of-control over delivered copies, Art. 6(1)(a)
  basis, private profiles don't federate.
- English manifest half: dedicated Federation (ActivityPub) section —
  actor object contents, push delivery + retraction semantics (incl.
  the tombstone caveat), encrypted-at-rest signing keys, remote actor
  cache, ingested remote content with the followers-only viewer gate,
  inbox logging/rate limits. PRIVACY_LAST_UPDATED bumped.

10.2 docs/deployment.md federation runbook: enabling per environment,
key rotation posture, abuse monitoring, and the troubleshooting
checklist distilled from the 2026-06-06/07 soak (IP families first,
no outbox backfill, tombstones, the 10s delivery timeout, actor
re-fetch).

10.3 Home marketing blurb (en+de) aligned to what actually ships:
Mastodon can follow you + trails-to-trails outbound — no more 'follow
friends anywhere' overstatement.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 12:21:51 +02:00
Ullrich Schäfer
62b40a25f3 feat(journal): outbound trails-to-trails follows (social-federation §6)
Tasks 6.1–6.6. A local user can follow a user on another trails
instance: WebFinger-resolve the handle, verify the target runs
trails.cool via NodeInfo — checked against the ACTOR IRI's host, never
the handle's domain (split-domain constraint) — record a Pending
follow row, deliver a signed Follow. The §4 inbox listeners already
settle (Accept) or drop (Reject) the row.

- federation-outbound.server.ts: followRemoteActor / cancelRemoteFollow
  (Undo delivery) / listOutgoingRemoteFollows. Network steps (lookup,
  NodeInfo, delivery) injectable for offline integration tests.
  Software allowlist: trails-cool.
- /follows/outgoing: follow-by-handle form + pending/accepted list +
  cancel; linked from the feed empty state; i18n en+de. Remote actors
  have no local profile page, so the remote Pending state lives here
  (the profile Pending button from locked accounts covers local).
- /.well-known/trails-cool now publishes software: trails-cool (6.2).
- Clear 4xx codes: invalid_handle, local_handle, remote_resolve_failed,
  not_trails (6.3).
- Tests: handle-parser + allowlist units; integration suite for the
  Pending lifecycle (create/idempotent/refuse-non-trails/refuse-
  unresolvable/own-host/cancel+Undo) with injected network deps; e2e
  anonymous-redirect guard for the new page.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 11:50:35 +02:00
dependabot[bot]
a4582654e1
chore(deps): bump the production group across 1 directory with 30 updates
Bumps the production group with 29 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [react](https://github.com/facebook/react/tree/HEAD/packages/react) | `19.2.6` | `19.2.7` |
| [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `19.2.15` | `19.2.17` |
| [@expo/fingerprint](https://github.com/expo/expo/tree/HEAD/packages/@expo/fingerprint) | `0.19.3` | `0.19.4` |
| [@fission-ai/openspec](https://github.com/Fission-AI/OpenSpec) | `1.3.1` | `1.4.1` |
| [fit-file-parser](https://github.com/jimmykane/fit-parser) | `3.0.1` | `3.0.2` |
| [i18next](https://github.com/i18next/i18next) | `26.3.0` | `26.3.1` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.60.0` | `8.60.1` |
| [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `4.1.7` | `4.1.8` |
| [isbot](https://github.com/omrilotan/isbot) | `5.1.40` | `5.1.41` |
| [@expo/metro-runtime](https://github.com/expo/expo) | `56.0.13` | `56.0.14` |
| [@maplibre/maplibre-react-native](https://github.com/maplibre/maplibre-react-native) | `11.3.0` | `11.3.3` |
| [@sentry/cli](https://github.com/getsentry/sentry-cli) | `3.4.3` | `3.5.0` |
| [expo-constants](https://github.com/expo/expo/tree/HEAD/packages/expo-constants) | `56.0.16` | `56.0.17` |
| [expo-dev-client](https://github.com/expo/expo/tree/HEAD/packages/expo-dev-client) | `56.0.18` | `56.0.19` |
| [expo-location](https://github.com/expo/expo/tree/HEAD/packages/expo-location) | `56.0.15` | `56.0.16` |
| [expo-notifications](https://github.com/expo/expo/tree/HEAD/packages/expo-notifications) | `56.0.15` | `56.0.16` |
| [expo-router](https://github.com/expo/expo/tree/HEAD/packages/expo-router) | `56.2.8` | `56.2.9` |
| [react-native-reanimated](https://github.com/software-mansion/react-native-reanimated/tree/HEAD/packages/react-native-reanimated) | `4.4.0` | `4.4.1` |
| [react-test-renderer](https://github.com/facebook/react/tree/HEAD/packages/react-test-renderer) | `19.2.6` | `19.2.7` |
| [@vitest/browser](https://github.com/vitest-dev/vitest/tree/HEAD/packages/browser) | `4.1.7` | `4.1.8` |
| [@vitest/browser-playwright](https://github.com/vitest-dev/vitest/tree/HEAD/packages/browser-playwright) | `4.1.7` | `4.1.8` |
| [@react-router/dev](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dev) | `7.16.0` | `7.17.0` |
| [@react-router/node](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-node) | `7.16.0` | `7.17.0` |
| [@react-router/serve](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-serve) | `7.16.0` | `7.17.0` |
| [@sentry/node](https://github.com/getsentry/sentry-javascript) | `10.55.0` | `10.56.0` |
| [@sentry/react](https://github.com/getsentry/sentry-javascript) | `10.53.1` | `10.56.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `22.19.19` | `22.19.20` |
| [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) | `19.2.6` | `19.2.7` |
| [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) | `7.16.0` | `7.17.0` |



Updates `react` from 19.2.6 to 19.2.7
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.7/packages/react)

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

Updates `@expo/fingerprint` from 0.19.3 to 0.19.4
- [Changelog](https://github.com/expo/expo/blob/main/packages/@expo/fingerprint/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/@expo/fingerprint)

Updates `@fission-ai/openspec` from 1.3.1 to 1.4.1
- [Release notes](https://github.com/Fission-AI/OpenSpec/releases)
- [Changelog](https://github.com/Fission-AI/OpenSpec/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Fission-AI/OpenSpec/compare/v1.3.1...v1.4.1)

Updates `fit-file-parser` from 3.0.1 to 3.0.2
- [Changelog](https://github.com/jimmykane/fit-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jimmykane/fit-parser/commits)

Updates `i18next` from 26.3.0 to 26.3.1
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v26.3.0...v26.3.1)

Updates `typescript-eslint` from 8.60.0 to 8.60.1
- [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.60.1/packages/typescript-eslint)

Updates `vitest` from 4.1.7 to 4.1.8
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.8/packages/vitest)

Updates `isbot` from 5.1.40 to 5.1.41
- [Changelog](https://github.com/omrilotan/isbot/blob/main/CHANGELOG.md)
- [Commits](https://github.com/omrilotan/isbot/compare/v5.1.40...v5.1.41)

Updates `@expo/metro-runtime` from 56.0.13 to 56.0.14
- [Changelog](https://github.com/expo/expo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits)

Updates `@maplibre/maplibre-react-native` from 11.3.0 to 11.3.3
- [Release notes](https://github.com/maplibre/maplibre-react-native/releases)
- [Changelog](https://github.com/maplibre/maplibre-react-native/blob/main/CHANGELOG.md)
- [Commits](https://github.com/maplibre/maplibre-react-native/compare/v11.3.0...v11.3.3)

Updates `@sentry/cli` from 3.4.3 to 3.5.0
- [Release notes](https://github.com/getsentry/sentry-cli/releases)
- [Changelog](https://github.com/getsentry/sentry-cli/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-cli/compare/3.4.3...3.5.0)

Updates `expo-constants` from 56.0.16 to 56.0.17
- [Changelog](https://github.com/expo/expo/blob/main/packages/expo-constants/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-constants)

Updates `expo-dev-client` from 56.0.18 to 56.0.19
- [Changelog](https://github.com/expo/expo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-dev-client)

Updates `expo-location` from 56.0.15 to 56.0.16
- [Changelog](https://github.com/expo/expo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-location)

Updates `expo-notifications` from 56.0.15 to 56.0.16
- [Changelog](https://github.com/expo/expo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-notifications)

Updates `expo-router` from 56.2.8 to 56.2.9
- [Changelog](https://github.com/expo/expo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-router)

Updates `react-native-reanimated` from 4.4.0 to 4.4.1
- [Release notes](https://github.com/software-mansion/react-native-reanimated/releases)
- [Changelog](https://github.com/software-mansion/react-native-reanimated/blob/main/packages/react-native-reanimated/RELEASE.md)
- [Commits](https://github.com/software-mansion/react-native-reanimated/commits/4.4.1/packages/react-native-reanimated)

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

Updates `react-test-renderer` from 19.2.6 to 19.2.7
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.7/packages/react-test-renderer)

Updates `@vitest/browser` from 4.1.7 to 4.1.8
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.8/packages/browser)

Updates `@vitest/browser-playwright` from 4.1.7 to 4.1.8
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.8/packages/browser-playwright)

Updates `@react-router/dev` from 7.16.0 to 7.17.0
- [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.17.0/packages/react-router-dev)

Updates `@react-router/node` from 7.16.0 to 7.17.0
- [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.17.0/packages/react-router-node)

Updates `@react-router/serve` from 7.16.0 to 7.17.0
- [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.17.0/packages/react-router-serve)

Updates `@sentry/node` from 10.55.0 to 10.56.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.55.0...10.56.0)

Updates `@sentry/react` from 10.53.1 to 10.56.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.53.1...10.56.0)

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

Updates `react-dom` from 19.2.6 to 19.2.7
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.7/packages/react-dom)

Updates `react-router` from 7.16.0 to 7.17.0
- [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.17.0/packages/react-router)

Updates `vite` from 7.3.3 to 7.3.5
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.3.5/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.3.5/packages/vite)

---
updated-dependencies:
- dependency-name: react
  dependency-version: 19.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@types/react"
  dependency-version: 19.2.17
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@expo/fingerprint"
  dependency-version: 0.19.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@fission-ai/openspec"
  dependency-version: 1.4.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: fit-file-parser
  dependency-version: 3.0.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: i18next
  dependency-version: 26.3.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: typescript-eslint
  dependency-version: 8.60.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: vitest
  dependency-version: 4.1.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: isbot
  dependency-version: 5.1.41
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@expo/metro-runtime"
  dependency-version: 56.0.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@maplibre/maplibre-react-native"
  dependency-version: 11.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@sentry/cli"
  dependency-version: 3.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: expo-constants
  dependency-version: 56.0.17
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-dev-client
  dependency-version: 56.0.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-location
  dependency-version: 56.0.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-notifications
  dependency-version: 56.0.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-router
  dependency-version: 56.2.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: react-native-reanimated
  dependency-version: 4.4.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@types/react"
  dependency-version: 19.2.17
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: react-test-renderer
  dependency-version: 19.2.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@vitest/browser"
  dependency-version: 4.1.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@vitest/browser-playwright"
  dependency-version: 4.1.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@react-router/dev"
  dependency-version: 7.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@react-router/node"
  dependency-version: 7.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@react-router/serve"
  dependency-version: 7.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@sentry/node"
  dependency-version: 10.56.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@sentry/react"
  dependency-version: 10.56.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@types/node"
  dependency-version: 22.19.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: react-dom
  dependency-version: 19.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: react-router
  dependency-version: 7.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: vite
  dependency-version: 7.3.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-07 09:48:14 +00:00
Ullrich Schäfer
5bf9358dc6 fix(db): allow 'public' credential_kind in connected_services check
The 0002 migration's allowed list predates the Komoot public-profile
connection mode (api.sync.komoot.verify.ts writes credentialKind
'public'). Against production data — which has such a connection —
the drop-then-add CHECK failed at ATRewriteTable and blocked every
cd-apps deploy since 2026-06-07 06:25 UTC.

Also documents 'public' in the schema comment with a pointer to keep
the two lists in sync.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 08:48:27 +02:00
Ullrich Schäfer
b17685d58c feat(journal): federation inbox — Mastodon follows land here
social-federation tasks 3.1–3.4, 4.1–4.8. With this, a Mastodon user
can follow a public trails user: WebFinger → actor fetch → signed
Follow → recorded + Accept(Follow) pushed back.

Identity surface (section 3):
- Actor objects now carry the user's public key (publicKey +
  assertionMethods via Fedify key pairs dispatcher; keys generated
  lazily as a fallback to the backfill) and an inbox IRI; url uses
  localActorIri (3.1).
- Software discovery shipped as standard NodeInfo
  (/.well-known/nodeinfo + /nodeinfo/2.1, software.name trails-cool)
  instead of the originally-sketched custom AS actor field — Fedify's
  typed vocab can't emit arbitrary actor props and NodeInfo is what
  the fediverse reads. Artifacts updated accordingly (3.4).

Inbox (section 4):
- /users/:username/inbox resource route; HTTP Signatures verified by
  Fedify before any listener runs (4.1). Rate-limited 60 req/5 min per
  source instance (host from Signature keyId) BEFORE verification so
  hostile instances can't burn CPU on key fetches (4.8).
- Listeners: Follow → auto-accept for public profiles + Accept pushed
  back (4.2); Undo(Follow) → row removed (4.3); Accept(Follow) →
  Pending settled + first outbox poll enqueued (4.4); Reject(Follow) →
  Pending dropped (4.5; UI notice deferred to 6.6). Unhandled types
  are acknowledged + dropped by Fedify (4.6).
- Replay protection via Fedify's KvStore, now Postgres-backed
  (journal.federation_kv + daily sweep job) so dedupe survives
  restarts (4.7).

Schema (discovered requirement):
- follows.follower_id relaxed to nullable + follows.follower_actor_iri
  for inbound remote followers — the proposal's 'follows is already
  federation-ready' only held for outbound. Check constraint enforces
  exactly one follower identity; partial unique index dedupes remote
  follows. Notification fan-out + approve flow now filter local
  followers explicitly. Design/proposal updated.

Tests: 7 inbox integration tests (accept/refuse/idempotence/undo/
settle/reject/check-constraint), 6 KvStore integration tests, 2 unit
tests for source-host extraction. All against real Postgres, gated on
FEDERATION_INTEGRATION=1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 14:33:43 +02:00
Ullrich Schäfer
9a90b6db55 feat(journal): federation schema + per-user signing keypairs
social-federation tasks 2.1–2.5:

- users.public_key / users.private_key_encrypted (TEXT NULL): RSA 2048
  keypairs as JWK JSON; private key AES-256-GCM encrypted at rest with
  FEDERATION_KEY_ENCRYPTION_KEY.
- remote_actors table: cache of remote AP actors (display fields,
  inbox/outbox URLs, public key, software discovery field, poll cursor).
- activities.remote_origin_iri (UNIQUE) / remote_actor_iri / audience:
  provenance + audience tagging for rows ingested from remote outboxes.
  Replay-safe ingestion keys off the unique origin IRI.
- crypto.server.ts: generalized into createAesCipher(envVar, salt)
  factory; existing INTEGRATION_SECRET surface unchanged.
- federation-keys.server.ts: generate/ensure/load keypairs.
  RSASSA-PKCS1-v1_5 because that's what Mastodon interops on.
- backfill-user-keypairs pg-boss job: enqueued once per server startup
  when FEDERATION_ENABLED=true; only touches users with NULL keys, so
  re-runs are no-ops. New users get keys at registration (both passkey
  and magic-link paths), best-effort with the backfill as safety net.
- design.md: noted open question — activities.owner_id is NOT NULL but
  remote-ingested rows have no local owner; decide in task 7.2.

Schema is additive; zero behavior change while FEDERATION_ENABLED is
off. db:push verified clean against local Postgres.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 14:15:51 +02:00
dependabot[bot]
2e6a83b063
chore(deps): bump the production group with 28 updates
Bumps the production group with 28 updates:

| Package | From | To |
| --- | --- | --- |
| [@expo/fingerprint](https://github.com/expo/expo/tree/HEAD/packages/@expo/fingerprint) | `0.19.2` | `0.19.3` |
| [eslint](https://github.com/eslint/eslint) | `10.4.0` | `10.4.1` |
| [fit-file-parser](https://github.com/jimmykane/fit-parser) | `3.0.0` | `3.0.1` |
| [i18next](https://github.com/i18next/i18next) | `26.2.0` | `26.3.0` |
| [turbo](https://github.com/vercel/turborepo) | `2.9.14` | `2.9.16` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.59.4` | `8.60.0` |
| [@simplewebauthn/server](https://github.com/MasterKale/SimpleWebAuthn/tree/HEAD/packages/server) | `13.3.0` | `13.3.1` |
| [nodemailer](https://github.com/nodemailer/nodemailer) | `8.0.8` | `8.0.10` |
| [@expo/metro-runtime](https://github.com/expo/expo) | `56.0.12` | `56.0.13` |
| [@maplibre/maplibre-react-native](https://github.com/maplibre/maplibre-react-native) | `11.2.1` | `11.3.0` |
| [@sentry/react-native](https://github.com/getsentry/sentry-react-native) | `8.12.0` | `8.13.0` |
| [expo-constants](https://github.com/expo/expo/tree/HEAD/packages/expo-constants) | `56.0.15` | `56.0.16` |
| [expo-crypto](https://github.com/expo/expo/tree/HEAD/packages/expo-crypto) | `56.0.3` | `56.0.4` |
| [expo-dev-client](https://github.com/expo/expo/tree/HEAD/packages/expo-dev-client) | `56.0.15` | `56.0.18` |
| [expo-linking](https://github.com/expo/expo/tree/HEAD/packages/expo-linking) | `56.0.11` | `56.0.13` |
| [expo-location](https://github.com/expo/expo/tree/HEAD/packages/expo-location) | `56.0.13` | `56.0.15` |
| [expo-notifications](https://github.com/expo/expo/tree/HEAD/packages/expo-notifications) | `56.0.13` | `56.0.15` |
| [expo-router](https://github.com/expo/expo/tree/HEAD/packages/expo-router) | `56.2.6` | `56.2.8` |
| [react-native-reanimated](https://github.com/software-mansion/react-native-reanimated/tree/HEAD/packages/react-native-reanimated) | `4.3.1` | `4.4.0` |
| [react-native-safe-area-context](https://github.com/AppAndFlow/react-native-safe-area-context) | `5.7.0` | `5.8.0` |
| [react-native-worklets](https://github.com/software-mansion/react-native-reanimated/tree/HEAD/packages/react-native-worklets) | `0.8.3` | `0.9.1` |
| [yjs](https://github.com/yjs/yjs) | `13.6.30` | `13.6.31` |
| [@react-router/dev](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dev) | `7.15.1` | `7.16.0` |
| [@react-router/node](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-node) | `7.15.1` | `7.16.0` |
| [@react-router/serve](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-serve) | `7.15.1` | `7.16.0` |
| [@sentry/node](https://github.com/getsentry/sentry-javascript) | `10.53.1` | `10.55.0` |
| [@sentry/react](https://github.com/getsentry/sentry-javascript) | `10.53.1` | `10.55.0` |
| [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) | `7.15.1` | `7.16.0` |


Updates `@expo/fingerprint` from 0.19.2 to 0.19.3
- [Changelog](https://github.com/expo/expo/blob/main/packages/@expo/fingerprint/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/@expo/fingerprint)

Updates `eslint` from 10.4.0 to 10.4.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.4.0...v10.4.1)

Updates `fit-file-parser` from 3.0.0 to 3.0.1
- [Changelog](https://github.com/jimmykane/fit-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jimmykane/fit-parser/commits)

Updates `i18next` from 26.2.0 to 26.3.0
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v26.2.0...v26.3.0)

Updates `turbo` from 2.9.14 to 2.9.16
- [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.9.14...v2.9.16)

Updates `typescript-eslint` from 8.59.4 to 8.60.0
- [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.60.0/packages/typescript-eslint)

Updates `@simplewebauthn/server` from 13.3.0 to 13.3.1
- [Release notes](https://github.com/MasterKale/SimpleWebAuthn/releases)
- [Changelog](https://github.com/MasterKale/SimpleWebAuthn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/MasterKale/SimpleWebAuthn/commits/v13.3.1/packages/server)

Updates `nodemailer` from 8.0.8 to 8.0.10
- [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.8...v8.0.10)

Updates `@expo/metro-runtime` from 56.0.12 to 56.0.13
- [Changelog](https://github.com/expo/expo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits)

Updates `@maplibre/maplibre-react-native` from 11.2.1 to 11.3.0
- [Release notes](https://github.com/maplibre/maplibre-react-native/releases)
- [Changelog](https://github.com/maplibre/maplibre-react-native/blob/main/CHANGELOG.md)
- [Commits](https://github.com/maplibre/maplibre-react-native/compare/v11.2.1...v11.3.0)

Updates `@sentry/react-native` from 8.12.0 to 8.13.0
- [Release notes](https://github.com/getsentry/sentry-react-native/releases)
- [Changelog](https://github.com/getsentry/sentry-react-native/blob/main/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-react-native/compare/8.12.0...8.13.0)

Updates `expo-constants` from 56.0.15 to 56.0.16
- [Changelog](https://github.com/expo/expo/blob/main/packages/expo-constants/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-constants)

Updates `expo-crypto` from 56.0.3 to 56.0.4
- [Changelog](https://github.com/expo/expo/blob/main/packages/expo-crypto/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-crypto)

Updates `expo-dev-client` from 56.0.15 to 56.0.18
- [Changelog](https://github.com/expo/expo/blob/main/packages/expo-dev-client/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-dev-client)

Updates `expo-linking` from 56.0.11 to 56.0.13
- [Changelog](https://github.com/expo/expo/blob/main/packages/expo-linking/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-linking)

Updates `expo-location` from 56.0.13 to 56.0.15
- [Changelog](https://github.com/expo/expo/blob/main/packages/expo-location/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-location)

Updates `expo-notifications` from 56.0.13 to 56.0.15
- [Changelog](https://github.com/expo/expo/blob/main/packages/expo-notifications/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-notifications)

Updates `expo-router` from 56.2.6 to 56.2.8
- [Changelog](https://github.com/expo/expo/blob/main/packages/expo-router/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-router)

Updates `react-native-reanimated` from 4.3.1 to 4.4.0
- [Release notes](https://github.com/software-mansion/react-native-reanimated/releases)
- [Changelog](https://github.com/software-mansion/react-native-reanimated/blob/main/packages/react-native-reanimated/RELEASE.md)
- [Commits](https://github.com/software-mansion/react-native-reanimated/commits/4.4.0/packages/react-native-reanimated)

Updates `react-native-safe-area-context` from 5.7.0 to 5.8.0
- [Release notes](https://github.com/AppAndFlow/react-native-safe-area-context/releases)
- [Commits](https://github.com/AppAndFlow/react-native-safe-area-context/compare/v5.7.0...v5.8.0)

Updates `react-native-worklets` from 0.8.3 to 0.9.1
- [Release notes](https://github.com/software-mansion/react-native-reanimated/releases)
- [Commits](https://github.com/software-mansion/react-native-reanimated/commits/worklets-0.9.1/packages/react-native-worklets)

Updates `yjs` from 13.6.30 to 13.6.31
- [Release notes](https://github.com/yjs/yjs/releases)
- [Commits](https://github.com/yjs/yjs/compare/v13.6.30...v13.6.31)

Updates `@react-router/dev` from 7.15.1 to 7.16.0
- [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.16.0/packages/react-router-dev)

Updates `@react-router/node` from 7.15.1 to 7.16.0
- [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.16.0/packages/react-router-node)

Updates `@react-router/serve` from 7.15.1 to 7.16.0
- [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.16.0/packages/react-router-serve)

Updates `@sentry/node` from 10.53.1 to 10.55.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.53.1...10.55.0)

Updates `@sentry/react` from 10.53.1 to 10.55.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.53.1...10.55.0)

Updates `react-router` from 7.15.1 to 7.16.0
- [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.16.0/packages/react-router)

---
updated-dependencies:
- dependency-name: "@expo/fingerprint"
  dependency-version: 0.19.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: eslint
  dependency-version: 10.4.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: fit-file-parser
  dependency-version: 3.0.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: i18next
  dependency-version: 26.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: turbo
  dependency-version: 2.9.16
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: typescript-eslint
  dependency-version: 8.60.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@simplewebauthn/server"
  dependency-version: 13.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: nodemailer
  dependency-version: 8.0.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@expo/metro-runtime"
  dependency-version: 56.0.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@maplibre/maplibre-react-native"
  dependency-version: 11.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@sentry/react-native"
  dependency-version: 8.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: expo-constants
  dependency-version: 56.0.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-crypto
  dependency-version: 56.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-dev-client
  dependency-version: 56.0.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-linking
  dependency-version: 56.0.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-location
  dependency-version: 56.0.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-notifications
  dependency-version: 56.0.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-router
  dependency-version: 56.2.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: react-native-reanimated
  dependency-version: 4.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: react-native-safe-area-context
  dependency-version: 5.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: react-native-worklets
  dependency-version: 0.9.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: yjs
  dependency-version: 13.6.31
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@react-router/dev"
  dependency-version: 7.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@react-router/node"
  dependency-version: 7.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@react-router/serve"
  dependency-version: 7.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@sentry/node"
  dependency-version: 10.55.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@sentry/react"
  dependency-version: 10.55.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: react-router
  dependency-version: 7.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-31 08:37:04 +00:00
Ullrich Schäfer
10deae88c9
fix: extensionless server-side imports + lint rule to catch them
## Symptom

Grafana's \`app-crash-log\` alert fires on every deploy. The Loki query
that backs it (\`ERR_|FATAL|uncaughtException|…\`) matches:

  Error [ERR_MODULE_NOT_FOUND]: Cannot find module
  '/app/apps/journal/app/lib/logger.server' imported from
  '/app/apps/journal/app/lib/email.server.ts'

A real bug, not a false positive: \`email.server.ts\` line 2 was
\`import { logger } from \"./logger.server\"\` — no extension.

## Why typecheck didn't catch it

\`tsconfig.base.json\` sets \`moduleResolution: \"bundler\"\`. The bundler
resolver accepts extensionless relative imports because Vite / esbuild
/ webpack resolve them at build time. TypeScript was happy.

Production runs \`node --experimental-strip-types server.ts\`, which
uses Node's NodeNext ESM resolver — strict about extensions. The two
resolvers disagree silently for files Node loads directly (server.ts,
\`.server.ts\` modules dynamically imported from it, and jobs).

## Fix

1. **Added \`.ts\` extensions to the 4 broken imports** I could find:
   - \`apps/journal/app/lib/email.server.ts\` → \`./logger.server.ts\`
     (the actual deploy-blocker)
   - \`apps/planner/app/lib/brouter.ts\` → \`./route-merge.ts\`, \`./http.server.ts\`
   - \`apps/planner/app/lib/use-nearby-pois.ts\` → \`./overpass.ts\`
   - \`packages/map/src/MapView.tsx\` → \`./layers.ts\` (caught by the rule)

2. **Added \`eslint-plugin-import-x\` with \`import-x/extensions: always\`**,
   scoped to files Node actually executes raw:
   - \`apps/*/server.ts\`
   - \`apps/*/app/lib/**/*.server.ts\`
   - \`apps/*/app/jobs/**/*.ts\`
   - \`packages/*/src/**/*.{ts,tsx}\`

   Ignored: route-scoped \`*.server.ts\` files (bundled by Vite into the
   React Router build, never loaded by Node), and test files (Vitest's
   own resolver).

## What's still possible

- Non-\`.server.ts\` files in \`app/lib\` (e.g. \`legal.ts\`, \`actor-iri.ts\`)
  could still ship extensionless imports. They're not in the lint
  scope. If one breaks at runtime we can extend the glob — for now
  they tend to be tiny utility modules that don't import other
  relatives.
- The deeper fix would be \`moduleResolution: nodenext\` for server-side
  tsconfig, or bundling the server code so Node never sees raw \`.ts\`.
  Bigger surgery; the lint rule covers the failure mode for now.

Full repo: pnpm typecheck / lint / test all green after \`pnpm install\`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 08:08:19 +02:00
Ullrich Schäfer
7918ba052a
chore(ts): drop the last 2 \eslint-disable\ comments in prod code
After this, \`grep -rn 'eslint-disable' apps/ packages/\` returns 0
(excluding tests and node_modules).

**\`apps/planner/app/lib/brouter.ts\`** — \`while (true)\` in
\`readBodyWithCap\` tripped \`no-constant-condition\`. Replaced with
\`for (;;)\` which the rule explicitly allows. No behavior change.

**\`packages/gpx/src/parse.ts\`** — the sync \`parseGpx\` exported a
fallback path that did \`require(\"linkedom\")\` for non-browser sync
use, with an \`eslint-disable\` for \`no-require-imports\`. It was dead
code: \`packages/gpx/src/index.ts\` only re-exports \`parseGpxAsync\`,
and grepping the monorepo finds no caller of the sync version. Deleted
the function entirely; \`getDOMParser\` (the async helper) still serves
the async path with a clean ESM \`await import(\"linkedom\")\`.

Full repo: pnpm typecheck / lint / test all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 07:34:09 +02:00
Ullrich Schäfer
b0b58d36fb
chore(ts): eliminate the remaining 4 \as any\ casts in production code
Followup to #447. The audit ran on \`as unknown as\` first; this PR
closes out \`as any\` separately. After this, \`grep -rn ' as any\\b'
apps/ packages/\` returns 0 (excluding tests and node_modules).

## Sites fixed

**\`apps/journal/server.ts\`** + **\`packages/jobs/src/types.ts\`** —
\`komootBulkImportJob as any\`. The job had a typed payload
(\`JobDefinition<KomootBulkImportData>\`) but the worker's
\`JobDefinition[]\` array forced a contravariance cast at every site
that mixed typed and untyped jobs. Dropped the generic from
\`JobDefinition\` entirely; handlers narrow their own \`job.data\`. Only
one job (komoot-bulk-import) used the generic, so the surface is tiny.

**\`apps/journal/app/lib/connected-services/fit.ts\`** + same pattern in
\`routes/sync.import.\$provider.server.ts\` — \`parser.parse(buffer as any,
(err: unknown, d: any) => ...)\`. fit-file-parser's TypeScript types
require \`Buffer<ArrayBuffer>\` specifically; a generic Node \`Buffer\`
is structurally \`Buffer<ArrayBufferLike>\` (which includes
SharedArrayBuffer). The runtime accepts either, so narrowed the cast
to \`as Buffer<ArrayBuffer>\` — still a cast, but precise about what
we're asserting and why. Removed the \`(err, d: any) => …\` ad-hoc
callback typings; the library exports a proper \`FitParserCallback\`.

**\`apps/mobile/lib/editor/RouteMap.tsx\`** — \`onLongPress\` handler
took \`(event: any)\`. maplibre-react-native v11 generates the event
type via React Native codegen but doesn't re-export it as a public
TypeScript type. Replaced with a local structural slice of the parts
we actually read.

Net: 4 \`as any\` sites in prod code → 0. \`pnpm typecheck\` / \`lint\` /
\`test\` all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 07:11:02 +02:00
Ullrich Schäfer
b4c64a40e7
fix(journal): single-use JWT enforcement for route callback tokens (#2 Phase B)
After Phase A (#442) moved the journal callback token off the browser,
the token was still replayable on the wire until \`exp\` (7 days). This
PR makes each token strictly single-use.

Changes:

- **\`journal.consumed_jwt_jti\` table** — \`jti TEXT PRIMARY KEY,
  consumed_at TIMESTAMPTZ, expires_at TIMESTAMPTZ\`. Picked up by
  drizzle-kit push on deploy.
- **\`createRouteToken\` now sets a \`jti\` claim** (\`randomUUID()\`).
- **\`verifyRouteToken\` atomically consumes the jti** via
  \`INSERT … ON CONFLICT DO NOTHING RETURNING jti\`. Postgres
  serializes the insert, so exactly one concurrent caller wins; the
  rest see an empty result and throw \`TokenAlreadyConsumedError\`.
  Tokens without a \`jti\` claim (i.e. minted before this PR) are
  also rejected — the right call: any in-flight legacy token sitting
  in a planner session is replayable, and we'd rather fail-loud than
  silently grandfather them in.
- **\`consumed-jti-sweep\` job** — daily 03:45 UTC cron that
  \`DELETE WHERE expires_at < now()\`. Keeps the table tiny; offset
  from the other purge jobs to spread load.
- **e2e replay test** — \`integration.test.ts\` now exercises a
  same-token double-submit and asserts the second returns 401 with
  \`/consumed|already/i\`.

UX implication worth flagging: a user who clicks \"Save\" twice (or whose
network retries a failed POST) sees an error on the second attempt.
They go back to the journal for a fresh \"Edit in Planner\" link.

Full repo: pnpm typecheck / lint / test all green (177 + 31 integration).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 00:38:08 +02:00
Ullrich Schäfer
d2b28a1164
fix(planner): add index on sessions.last_activity for the expire job
The hourly `expireSessions()` cron runs `DELETE FROM planner.sessions
WHERE last_activity < cutoff`. Without an index on `last_activity`,
that's a full table scan growing linearly with the total sessions
ever created (planner sessions are never user-deleted; expiry is the
only churn path).

`drizzle-kit push` picks this up on next deploy.
2026-05-25 23:59:10 +02:00
copilot-swe-agent[bot]
e2460374de
fix(i18n): normalize html language tags in client detection
Agent-Logs-Url: https://github.com/trails-cool/trails/sessions/7546ba18-5628-4f50-bc8b-104fa7ba6afe

Co-authored-by: stigi <13815+stigi@users.noreply.github.com>
2026-05-25 13:24:11 +00:00
Ullrich Schäfer
ebedfa257b
fix: add E2E opt-out for fail-loud secret/DB-URL guards
Playwright runs the server via `react-router serve` with
NODE_ENV=production but against a local dev Postgres and local cookie
secrets. The guards added in 5a7bb76 refused to start under that
configuration. `E2E=true` (already set by the CI E2E job) is now the
explicit opt-out: in real production this env var is never set, so the
guard still bites.
2026-05-24 11:45:09 +02:00
Ullrich Schäfer
5a7bb76ff1
fix(journal): fail loud in production when secrets are unset
Adds `requireSecret(name, devFallback)` in lib/config.server.ts and
`getDatabaseUrl()` in @trails-cool/db. Both:
- return the env var when set,
- fall back to the dev default in non-production,
- throw at boot in production if the env var is missing OR matches the
  known dev fallback (which would otherwise silently ship a public
  secret / point at localhost).

Applied to:
- JWT_SECRET (lib/jwt.server.ts) — was `?? "dev-jwt-secret-change-in-production"`
- SESSION_SECRET (lib/auth/session.server.ts) — was `?? "dev-secret-change-in-production"`
- DATABASE_URL (server.ts health + boss; packages/db migrate-data) — was
  `?? "postgres://trails:trails@localhost:5432/trails"`

Why: these strings are in the repo and known to attackers. A
misconfigured prod deploy that forgot to set them would either run with
guessable signing keys (full session/JWT forgery) or connect to a
non-existent localhost DB. Better to refuse to start than to silently
operate insecurely.

Tests:
- packages/db/src/get-database-url.test.ts (5 cases)
- lib/config.server.test.ts gains `requireSecret` cases (4 new)

Full repo: pnpm typecheck, pnpm lint, pnpm test all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 11:37:20 +02:00
Ullrich Schäfer
6186ffa062
Merge pull request #407 from trails-cool/dependabot/npm_and_yarn/production-05de8b6350
Bump the production group with 14 updates
2026-05-24 11:26:03 +02:00
dependabot[bot]
01d8f433f1
Bump the production group with 14 updates
Bumps the production group with 14 updates:

| Package | From | To |
| --- | --- | --- |
| [@expo/fingerprint](https://github.com/expo/expo/tree/HEAD/packages/@expo/fingerprint) | `0.16.7` | `0.19.2` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.59.3` | `8.59.4` |
| [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `4.1.6` | `4.1.7` |
| [nodemailer](https://github.com/nodemailer/nodemailer) | `8.0.7` | `8.0.8` |
| [@sentry/cli](https://github.com/getsentry/sentry-cli) | `3.4.2` | `3.4.3` |
| [@sentry/react-native](https://github.com/getsentry/sentry-react-native) | `8.11.1` | `8.12.0` |
| [react-native-safe-area-context](https://github.com/AppAndFlow/react-native-safe-area-context) | `5.7.0` | `5.8.0` |
| [react-native-screens](https://github.com/software-mansion/react-native-screens) | `4.25.0` | `4.25.2` |
| [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `19.2.14` | `19.2.15` |
| [ws](https://github.com/websockets/ws) | `8.20.1` | `8.21.0` |
| [@vitest/browser](https://github.com/vitest-dev/vitest/tree/HEAD/packages/browser) | `4.1.6` | `4.1.7` |
| [@vitest/browser-playwright](https://github.com/vitest-dev/vitest/tree/HEAD/packages/browser-playwright) | `4.1.6` | `4.1.7` |
| [@garmin/fitsdk](https://github.com/garmin/fit-javascript-sdk) | `21.202.0` | `21.205.0` |
| [@sentry/react](https://github.com/getsentry/sentry-javascript) | `10.51.0` | `10.53.1` |


Updates `@expo/fingerprint` from 0.16.7 to 0.19.2
- [Changelog](https://github.com/expo/expo/blob/main/packages/@expo/fingerprint/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/@expo/fingerprint)

Updates `typescript-eslint` from 8.59.3 to 8.59.4
- [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.59.4/packages/typescript-eslint)

Updates `vitest` from 4.1.6 to 4.1.7
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.7/packages/vitest)

Updates `nodemailer` from 8.0.7 to 8.0.8
- [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.7...v8.0.8)

Updates `@sentry/cli` from 3.4.2 to 3.4.3
- [Release notes](https://github.com/getsentry/sentry-cli/releases)
- [Changelog](https://github.com/getsentry/sentry-cli/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-cli/compare/3.4.2...3.4.3)

Updates `@sentry/react-native` from 8.11.1 to 8.12.0
- [Release notes](https://github.com/getsentry/sentry-react-native/releases)
- [Changelog](https://github.com/getsentry/sentry-react-native/blob/main/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-react-native/compare/8.11.1...8.12.0)

Updates `react-native-safe-area-context` from 5.7.0 to 5.8.0
- [Release notes](https://github.com/AppAndFlow/react-native-safe-area-context/releases)
- [Commits](https://github.com/AppAndFlow/react-native-safe-area-context/compare/v5.7.0...v5.8.0)

Updates `react-native-screens` from 4.25.0 to 4.25.2
- [Release notes](https://github.com/software-mansion/react-native-screens/releases)
- [Commits](https://github.com/software-mansion/react-native-screens/compare/4.25.0...4.25.2)

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

Updates `ws` from 8.20.1 to 8.21.0
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.20.1...8.21.0)

Updates `@vitest/browser` from 4.1.6 to 4.1.7
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.7/packages/browser)

Updates `@vitest/browser-playwright` from 4.1.6 to 4.1.7
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.7/packages/browser-playwright)

Updates `@garmin/fitsdk` from 21.202.0 to 21.205.0
- [Release notes](https://github.com/garmin/fit-javascript-sdk/releases)
- [Commits](https://github.com/garmin/fit-javascript-sdk/compare/21.202.0...21.205.0)

Updates `@sentry/react` from 10.51.0 to 10.53.1
- [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.51.0...10.53.1)

---
updated-dependencies:
- dependency-name: "@expo/fingerprint"
  dependency-version: 0.19.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: typescript-eslint
  dependency-version: 8.59.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: vitest
  dependency-version: 4.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: nodemailer
  dependency-version: 8.0.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@sentry/cli"
  dependency-version: 3.4.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@sentry/react-native"
  dependency-version: 8.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: react-native-safe-area-context
  dependency-version: 5.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: react-native-screens
  dependency-version: 4.25.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@types/react"
  dependency-version: 19.2.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: ws
  dependency-version: 8.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@vitest/browser"
  dependency-version: 4.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@vitest/browser-playwright"
  dependency-version: 4.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@garmin/fitsdk"
  dependency-version: 21.205.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@sentry/react"
  dependency-version: 10.53.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-24 08:31:11 +00:00
Ullrich Schäfer
4de6c86d41
fix(journal): architectural audit omnibus
Addresses 8 issues from the Journal architecture audit:

1. DB indexes on routes.ownerId + activities.ownerId. Listing queries on
   these tables were full table scans; adds composite indexes matching
   the order-by columns (updatedAt/startedAt/createdAt).
2. Zod validation on /api/auth/register body. Previously the action
   destructured request.json() with zero schema validation.
3. N+1 GeoJSON batch fetch collapsed to a single ANY($1::text[]) query
   in both routes.server and activities.server.
4. Webhook envelope validation in /api/sync/webhook/:provider.
5. AbortSignal.timeout(30s) on all external fetches (Komoot, Wahoo) via
   a new fetchWithTimeout helper in lib/http.server.ts.
6. .limit(100) on listPublicRoutesForOwner / listPublicActivitiesForOwner.
9. Welcome email moved off fire-and-forget onto a pg-boss job with
   retryLimit: 3 (send-welcome-email).
10. process.env.ORIGIN ?? "http://localhost:3000" centralized into
    lib/config.server.ts::getOrigin() across 14 call sites.

Issues 7 (centralized apiError/auth guards across 60+ route files) and
8 (split .server.ts boundaries across 20+ route files) intentionally
deferred — both are pure refactors that would balloon this PR past
reviewability and warrant their own focused PRs.

Tests added:
- lib/config.server.test.ts (2 cases)
- lib/http.server.test.ts (3 cases — timeout abort, success passthrough,
  caller-signal composition)
- routes/api.sync.webhook.$provider.test.ts (6 cases)
- routes/api.auth.register.test.ts (7 cases — schema rejection paths +
  the new welcome-email enqueue assertion)

Full repo: pnpm typecheck, pnpm lint, pnpm test all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 10:28:33 +02:00
Ullrich Schäfer
5ff7af8a81
Add background bulk import for Komoot
Replace the per-tour import UI with a fire-and-forget background job:

- Add `import_batches` DB table tracking status, found/imported/duplicate counts
- Add `runKomootBulkImport` server function that pages all Komoot tours,
  fetches GPX, creates activities, and deduplicates via sync_imports
- Add `komoot-bulk-import` pg-boss job registered at server startup
- Add POST /api/sync/komoot/import to enqueue the job
- Add GET /api/sync/komoot/import-status to return the latest batch
- Replace the Komoot import page with a progress UI that polls every 2s
  while a batch is running and shows found/imported/skipped counts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 18:19:10 +02:00
Ullrich Schäfer
8641b0ad90
Add activity sort toggle on user profile page
Default sort is by activity date (startedAt); users can switch to
"Date added" (createdAt) via a URL query param (?sort=addedAt).
Activities without a startedAt fall to the bottom when sorted by date.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 18:06:06 +02:00
Ullrich Schäfer
03304c354b
Add Komoot import with public (bio verification) and authenticated modes
Two-mode import: public mode verifies Komoot account ownership by checking
that the user's trails.cool profile URL appears in their Komoot bio — no
credentials stored. Authenticated mode uses email + password (AES-256-GCM
encrypted) to import private tours as well.

Includes unit tests for crypto/komoot client and E2E tests for the full
connect + import flow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 10:18:46 +02:00
Ullrich Schäfer
aef67aab7a
Include waypoint note in GPX export and extractWaypoints type
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 07:38:55 +02:00
Ullrich Schäfer
c2abb64ee0
Add per-waypoint notes and nearby POI snap to Planner
- `note` field on Waypoint type, stored in Yjs Y.Map and exported as
  `<desc>` inside `<wpt>` in GPX
- Inline textarea note editing in WaypointSidebar with auto-resize,
  character counter (500 max), save-on-blur, Escape cancel
- Note indicator dot on map markers; note tooltip on hover
- `useNearbyPois` hook: fetches POIs within 500m of selected waypoint
  via Overpass proxy, 500ms debounce, AbortController, 60s rate-limit
  suppression
- NearbyPoiMarkers component: renders POI markers on map for selected
  waypoint with snap-to-POI on click
- Nearby section in WaypointSidebar: list with snap buttons, spinner,
  empty/rate-limited states, "Show more" toggle (5 → all)
- Unit tests for fetchNearbyPois bbox geometry and snap-to-POI Yjs
  transaction behaviour

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 23:58:21 +02:00
Ullrich Schäfer
861701e881
Show POI details (phone, website, opening hours) on Journal route detail page
Waypoints snapped to OSM POIs in the Planner now carry their metadata all
the way through to the Journal:

- Extend Waypoint type with osmId and poiTags fields
- Extract osmId/poiTags from Yjs Y.Map in ExportButton and SaveToJournalButton
- Encode POI metadata as <trails:poi> extensions in GPX <wpt> elements
- Parse <trails:poi> extensions back in the GPX parser
- Display phone, website, opening hours, address on Journal route detail
- E2E test for the full roundtrip; seed endpoint now defaults to public visibility

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 23:32:38 +02:00
dependabot[bot]
9c5cd7d6e8
Bump fit-file-parser in the development group across 1 directory
Bumps the development group with 1 update in the / directory: [fit-file-parser](https://github.com/jimmykane/fit-parser).


Updates `fit-file-parser` from 2.3.3 to 3.0.0
- [Changelog](https://github.com/jimmykane/fit-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jimmykane/fit-parser/commits)

---
updated-dependencies:
- dependency-name: fit-file-parser
  dependency-version: 3.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-10 11:09:33 +00:00
dependabot[bot]
9cc08a725a
Bump the production group with 41 updates
Bumps the production group with 41 updates:

| Package | From | To |
| --- | --- | --- |
| [expo](https://github.com/expo/expo/tree/HEAD/packages/expo) | `55.0.19` | `55.0.23` |
| [react](https://github.com/facebook/react/tree/HEAD/packages/react) | `19.2.5` | `19.2.0` |
| [@expo/fingerprint](https://github.com/expo/expo/tree/HEAD/packages/@expo/fingerprint) | `0.16.6` | `0.16.7` |
| [i18next](https://github.com/i18next/i18next) | `26.0.8` | `26.0.10` |
| [react-i18next](https://github.com/i18next/react-i18next) | `17.0.6` | `17.0.7` |
| [turbo](https://github.com/vercel/turborepo) | `2.9.8` | `2.9.12` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.59.1` | `8.59.2` |
| [isbot](https://github.com/omrilotan/isbot) | `5.1.39` | `5.1.40` |
| [zod](https://github.com/colinhacks/zod) | `4.4.2` | `4.4.3` |
| [@expo/metro-runtime](https://github.com/expo/expo) | `55.0.10` | `55.0.11` |
| [@gorhom/bottom-sheet](https://github.com/gorhom/react-native-bottom-sheet) | `5.2.13` | `5.2.14` |
| [@maplibre/maplibre-react-native](https://github.com/maplibre/maplibre-react-native) | `11.0.2` | `11.1.1` |
| [@sentry/react-native](https://github.com/getsentry/sentry-react-native) | `8.10.0` | `8.11.0` |
| [expo-constants](https://github.com/expo/expo/tree/HEAD/packages/expo-constants) | `55.0.15` | `55.0.16` |
| [expo-dev-client](https://github.com/expo/expo/tree/HEAD/packages/expo-dev-client) | `55.0.30` | `55.0.32` |
| [expo-dev-menu](https://github.com/expo/expo/tree/HEAD/packages/expo-dev-menu) | `55.0.26` | `55.0.27` |
| [expo-device](https://github.com/expo/expo/tree/HEAD/packages/expo-device) | `55.0.15` | `55.0.16` |
| [expo-file-system](https://github.com/expo/expo/tree/HEAD/packages/expo-file-system) | `55.0.17` | `55.0.19` |
| [expo-linking](https://github.com/expo/expo/tree/HEAD/packages/expo-linking) | `55.0.14` | `55.0.15` |
| [expo-location](https://github.com/expo/expo/tree/HEAD/packages/expo-location) | `55.1.8` | `55.1.9` |
| [expo-router](https://github.com/expo/expo/tree/HEAD/packages/expo-router) | `55.0.13` | `55.0.14` |
| [expo-splash-screen](https://github.com/expo/expo/tree/HEAD/packages/expo-splash-screen) | `55.0.19` | `55.0.20` |
| [expo-status-bar](https://github.com/expo/expo/tree/HEAD/packages/expo-status-bar) | `55.0.5` | `55.0.6` |
| [expo-system-ui](https://github.com/expo/expo/tree/HEAD/packages/expo-system-ui) | `55.0.16` | `55.0.17` |
| [expo-web-browser](https://github.com/expo/expo/tree/HEAD/packages/expo-web-browser) | `55.0.14` | `55.0.15` |
| [react-native-gesture-handler](https://github.com/software-mansion/react-native-gesture-handler) | `2.31.1` | `2.31.2` |
| [react-native-reanimated](https://github.com/software-mansion/react-native-reanimated/tree/HEAD/packages/react-native-reanimated) | `4.3.0` | `4.3.1` |
| [jest-expo](https://github.com/expo/expo/tree/HEAD/packages/jest-expo) | `55.0.16` | `55.0.17` |
| [react-test-renderer](https://github.com/facebook/react/tree/HEAD/packages/react-test-renderer) | `19.2.5` | `19.2.6` |
| [@codemirror/view](https://github.com/codemirror/view) | `6.41.1` | `6.42.1` |
| [@react-router/dev](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dev) | `7.14.2` | `7.15.0` |
| [@react-router/node](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-node) | `7.14.2` | `7.15.0` |
| [@react-router/serve](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-serve) | `7.14.2` | `7.15.0` |
| [@sentry/node](https://github.com/getsentry/sentry-javascript) | `10.51.0` | `10.52.0` |
| [@sentry/react](https://github.com/getsentry/sentry-javascript) | `10.51.0` | `10.52.0` |
| [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) | `4.2.4` | `4.3.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `22.19.17` | `22.19.18` |
| [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) | `19.2.5` | `19.2.6` |
| [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) | `7.14.2` | `7.15.0` |
| [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.2.4` | `4.3.0` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `7.3.2` | `7.3.3` |


Updates `expo` from 55.0.19 to 55.0.23
- [Changelog](https://github.com/expo/expo/blob/main/packages/expo/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo)

Updates `react` from 19.2.5 to 19.2.0
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.0/packages/react)

Updates `@expo/fingerprint` from 0.16.6 to 0.16.7
- [Changelog](https://github.com/expo/expo/blob/main/packages/@expo/fingerprint/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/@expo/fingerprint)

Updates `i18next` from 26.0.8 to 26.0.10
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v26.0.8...v26.0.10)

Updates `react-i18next` from 17.0.6 to 17.0.7
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v17.0.6...v17.0.7)

Updates `turbo` from 2.9.8 to 2.9.12
- [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.9.8...v2.9.12)

Updates `typescript-eslint` from 8.59.1 to 8.59.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.59.2/packages/typescript-eslint)

Updates `isbot` from 5.1.39 to 5.1.40
- [Changelog](https://github.com/omrilotan/isbot/blob/main/CHANGELOG.md)
- [Commits](https://github.com/omrilotan/isbot/compare/v5.1.39...v5.1.40)

Updates `zod` from 4.4.2 to 4.4.3
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v4.4.2...v4.4.3)

Updates `@expo/metro-runtime` from 55.0.10 to 55.0.11
- [Changelog](https://github.com/expo/expo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits)

Updates `@gorhom/bottom-sheet` from 5.2.13 to 5.2.14
- [Release notes](https://github.com/gorhom/react-native-bottom-sheet/releases)
- [Changelog](https://github.com/gorhom/react-native-bottom-sheet/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gorhom/react-native-bottom-sheet/compare/v5.2.13...v5.2.14)

Updates `@maplibre/maplibre-react-native` from 11.0.2 to 11.1.1
- [Release notes](https://github.com/maplibre/maplibre-react-native/releases)
- [Changelog](https://github.com/maplibre/maplibre-react-native/blob/main/CHANGELOG.md)
- [Commits](https://github.com/maplibre/maplibre-react-native/compare/v11.0.2...v11.1.1)

Updates `@sentry/react-native` from 8.10.0 to 8.11.0
- [Release notes](https://github.com/getsentry/sentry-react-native/releases)
- [Changelog](https://github.com/getsentry/sentry-react-native/blob/main/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-react-native/compare/8.10.0...8.11.0)

Updates `expo-constants` from 55.0.15 to 55.0.16
- [Changelog](https://github.com/expo/expo/blob/main/packages/expo-constants/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-constants)

Updates `expo-dev-client` from 55.0.30 to 55.0.32
- [Changelog](https://github.com/expo/expo/blob/main/packages/expo-dev-client/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-dev-client)

Updates `expo-dev-menu` from 55.0.26 to 55.0.27
- [Changelog](https://github.com/expo/expo/blob/main/packages/expo-dev-menu/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-dev-menu)

Updates `expo-device` from 55.0.15 to 55.0.16
- [Changelog](https://github.com/expo/expo/blob/main/packages/expo-device/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-device)

Updates `expo-file-system` from 55.0.17 to 55.0.19
- [Changelog](https://github.com/expo/expo/blob/main/packages/expo-file-system/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-file-system)

Updates `expo-linking` from 55.0.14 to 55.0.15
- [Changelog](https://github.com/expo/expo/blob/main/packages/expo-linking/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-linking)

Updates `expo-location` from 55.1.8 to 55.1.9
- [Changelog](https://github.com/expo/expo/blob/main/packages/expo-location/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-location)

Updates `expo-router` from 55.0.13 to 55.0.14
- [Changelog](https://github.com/expo/expo/blob/main/packages/expo-router/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-router)

Updates `expo-splash-screen` from 55.0.19 to 55.0.20
- [Changelog](https://github.com/expo/expo/blob/main/packages/expo-splash-screen/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-splash-screen)

Updates `expo-status-bar` from 55.0.5 to 55.0.6
- [Changelog](https://github.com/expo/expo/blob/main/packages/expo-status-bar/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-status-bar)

Updates `expo-system-ui` from 55.0.16 to 55.0.17
- [Changelog](https://github.com/expo/expo/blob/main/packages/expo-system-ui/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-system-ui)

Updates `expo-web-browser` from 55.0.14 to 55.0.15
- [Changelog](https://github.com/expo/expo/blob/main/packages/expo-web-browser/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-web-browser)

Updates `react-native-gesture-handler` from 2.31.1 to 2.31.2
- [Release notes](https://github.com/software-mansion/react-native-gesture-handler/releases)
- [Commits](https://github.com/software-mansion/react-native-gesture-handler/compare/v2.31.1...v2.31.2)

Updates `react-native-reanimated` from 4.3.0 to 4.3.1
- [Release notes](https://github.com/software-mansion/react-native-reanimated/releases)
- [Changelog](https://github.com/software-mansion/react-native-reanimated/blob/main/packages/react-native-reanimated/RELEASE.md)
- [Commits](https://github.com/software-mansion/react-native-reanimated/commits/4.3.1/packages/react-native-reanimated)

Updates `jest-expo` from 55.0.16 to 55.0.17
- [Changelog](https://github.com/expo/expo/blob/main/packages/jest-expo/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/jest-expo)

Updates `react-test-renderer` from 19.2.5 to 19.2.6
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.6/packages/react-test-renderer)

Updates `@codemirror/view` from 6.41.1 to 6.42.1
- [Changelog](https://github.com/codemirror/view/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/view/commits)

Updates `@react-router/dev` from 7.14.2 to 7.15.0
- [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.15.0/packages/react-router-dev)

Updates `@react-router/node` from 7.14.2 to 7.15.0
- [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.15.0/packages/react-router-node)

Updates `@react-router/serve` from 7.14.2 to 7.15.0
- [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.15.0/packages/react-router-serve)

Updates `@sentry/node` from 10.51.0 to 10.52.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.51.0...10.52.0)

Updates `@sentry/react` from 10.51.0 to 10.52.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.51.0...10.52.0)

Updates `@tailwindcss/vite` from 4.2.4 to 4.3.0
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/packages/@tailwindcss-vite)

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

Updates `react-dom` from 19.2.5 to 19.2.6
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.6/packages/react-dom)

Updates `react-router` from 7.14.2 to 7.15.0
- [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.15.0/packages/react-router)

Updates `tailwindcss` from 4.2.4 to 4.3.0
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/packages/tailwindcss)

Updates `vite` from 7.3.2 to 7.3.3
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.3.3/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.3.3/packages/vite)

---
updated-dependencies:
- dependency-name: expo
  dependency-version: 55.0.23
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: react
  dependency-version: 19.2.0
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@expo/fingerprint"
  dependency-version: 0.16.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: i18next
  dependency-version: 26.0.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: react-i18next
  dependency-version: 17.0.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: turbo
  dependency-version: 2.9.12
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: typescript-eslint
  dependency-version: 8.59.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: isbot
  dependency-version: 5.1.40
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: zod
  dependency-version: 4.4.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@expo/metro-runtime"
  dependency-version: 55.0.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@gorhom/bottom-sheet"
  dependency-version: 5.2.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@maplibre/maplibre-react-native"
  dependency-version: 11.1.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@sentry/react-native"
  dependency-version: 8.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: expo-constants
  dependency-version: 55.0.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-dev-client
  dependency-version: 55.0.32
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-dev-menu
  dependency-version: 55.0.27
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-device
  dependency-version: 55.0.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-file-system
  dependency-version: 55.0.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-linking
  dependency-version: 55.0.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-location
  dependency-version: 55.1.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-router
  dependency-version: 55.0.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-splash-screen
  dependency-version: 55.0.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-status-bar
  dependency-version: 55.0.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-system-ui
  dependency-version: 55.0.17
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-web-browser
  dependency-version: 55.0.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: react-native-gesture-handler
  dependency-version: 2.31.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: react-native-reanimated
  dependency-version: 4.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: jest-expo
  dependency-version: 55.0.17
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: react-test-renderer
  dependency-version: 19.2.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@codemirror/view"
  dependency-version: 6.42.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@react-router/dev"
  dependency-version: 7.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@react-router/node"
  dependency-version: 7.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@react-router/serve"
  dependency-version: 7.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@sentry/node"
  dependency-version: 10.52.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@sentry/react"
  dependency-version: 10.52.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@tailwindcss/vite"
  dependency-version: 4.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@types/node"
  dependency-version: 22.19.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: react-dom
  dependency-version: 19.2.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: react-router
  dependency-version: 7.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: tailwindcss
  dependency-version: 4.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: vite
  dependency-version: 7.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-10 08:42:40 +00:00
Ullrich Schäfer
067e2ebd0b
fix: enforce Terms gate on bearer-token API requests
Mobile API requests authenticated via OAuth2 bearer tokens bypassed the
Terms gate that the root loader applies to web cookie sessions. Extend
requireApiUser to compare the user's termsVersion with TERMS_VERSION
and return a structured 403 { code: "TERMS_OUTDATED", currentTermsVersion }
on mismatch so mobile clients can surface their own re-acceptance UI.

Spec delta on journal-auth captures the new requirement.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 01:59:28 +02:00
Ullrich Schäfer
6de516718d
Schema rename + ConnectedServiceManager foundation (groups 1-2)
Implements tasks 1.1-1.4 and 2.1-2.5 of deepen-connected-services:

DB:
- Rename journal.sync_connections -> journal.connected_services.
- Add credential_kind discriminator (oauth | web-login | device) and
  credentials JSONB (shape per kind), status column, and a unique index
  on (user_id, provider) lifting the previously app-only invariant
  into the DB.
- Idempotent backfill in 0002_connected_services.sql moves existing
  Wahoo rows' tokens into the JSONB blob with credential_kind='oauth'.

Code:
- New apps/journal/app/lib/connected-services/ module:
  - types.ts: ConnectedService, CredentialKind, OAuthCredentials,
    NeedsRelinkError, CredentialAdapter, ProviderOAuthConfig, etc.
  - credential-adapters/oauth.ts: standard OAuth2 refresh_token flow,
    revoke endpoint, 4xx -> NeedsRelinkError / 5xx -> transient.
  - manager.ts: ConnectedServiceManager (link, unlink, withFreshCredentials,
    markNeedsRelink). Centralizes credential lifecycle in one chokepoint.
  - registry.ts: ProviderManifest type + capability seam interfaces
    (Importer, RoutePusher, WebhookReceiver). Manifests register
    themselves at import time.

Tests:
- manager.test.ts (8 tests): refresh-on-expired, refresh-fail->needs_relink,
  ConnectionNotActiveError, link/unlink, revoke is best-effort.
- credential-adapters/oauth.test.ts (10 tests): refresh contract,
  refresh_token retention, 4xx vs 5xx behaviour, revoke.
- All 18 new tests pass.

Compatibility:
- apps/journal/app/lib/sync/connections.server.ts is now a thin shim
  translating the legacy TokenSet API onto the JSONB-shaped table so
  existing callers (routes, pushes.server.ts) keep working until tasks
  5.x migrate them to the manager. To be deleted in task 5.6.

Pre-existing journal test failures (12) are unrelated to this change:
they pre-date this PR and stem from a workspace resolution issue with
@trails-cool/fit (verified by running tests against main).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 01:14:38 +02:00
dependabot[bot]
eec904971d
Bump the production group with 19 updates
Bumps the production group with 19 updates:

| Package | From | To |
| --- | --- | --- |
| [expo](https://github.com/expo/expo/tree/HEAD/packages/expo) | `55.0.17` | `55.0.19` |
| [@sentry/vite-plugin](https://github.com/getsentry/sentry-javascript-bundler-plugins) | `5.2.0` | `5.2.1` |
| [eslint](https://github.com/eslint/eslint) | `10.2.1` | `10.3.0` |
| [jsdom](https://github.com/jsdom/jsdom) | `29.0.2` | `29.1.1` |
| [react-i18next](https://github.com/i18next/react-i18next) | `17.0.4` | `17.0.6` |
| [turbo](https://github.com/vercel/turborepo) | `2.9.6` | `2.9.8` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.59.0` | `8.59.1` |
| [jose](https://github.com/panva/jose) | `6.2.2` | `6.2.3` |
| [nodemailer](https://github.com/nodemailer/nodemailer) | `8.0.6` | `8.0.7` |
| [zod](https://github.com/colinhacks/zod) | `4.3.6` | `4.4.2` |
| [@gorhom/bottom-sheet](https://github.com/gorhom/react-native-bottom-sheet) | `5.2.10` | `5.2.13` |
| [@sentry/cli](https://github.com/getsentry/sentry-cli) | `3.4.0` | `3.4.1` |
| [@sentry/react-native](https://github.com/getsentry/sentry-react-native) | `8.9.1` | `8.10.0` |
| [expo-dev-client](https://github.com/expo/expo/tree/HEAD/packages/expo-dev-client) | `55.0.28` | `55.0.30` |
| [expo-dev-menu](https://github.com/expo/expo/tree/HEAD/packages/expo-dev-menu) | `55.0.24` | `55.0.26` |
| [expo-notifications](https://github.com/expo/expo/tree/HEAD/packages/expo-notifications) | `55.0.20` | `55.0.22` |
| [pg-boss](https://github.com/timgit/pg-boss) | `12.18.0` | `12.18.2` |
| [@sentry/node](https://github.com/getsentry/sentry-javascript) | `10.50.0` | `10.51.0` |
| [@sentry/react](https://github.com/getsentry/sentry-javascript) | `10.49.0` | `10.51.0` |


Updates `expo` from 55.0.17 to 55.0.19
- [Changelog](https://github.com/expo/expo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo)

Updates `@sentry/vite-plugin` from 5.2.0 to 5.2.1
- [Release notes](https://github.com/getsentry/sentry-javascript-bundler-plugins/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/main/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript-bundler-plugins/compare/5.2.0...5.2.1)

Updates `eslint` from 10.2.1 to 10.3.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.2.1...v10.3.0)

Updates `jsdom` from 29.0.2 to 29.1.1
- [Release notes](https://github.com/jsdom/jsdom/releases)
- [Commits](https://github.com/jsdom/jsdom/compare/v29.0.2...v29.1.1)

Updates `react-i18next` from 17.0.4 to 17.0.6
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v17.0.4...v17.0.6)

Updates `turbo` from 2.9.6 to 2.9.8
- [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.9.6...v2.9.8)

Updates `typescript-eslint` from 8.59.0 to 8.59.1
- [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.59.1/packages/typescript-eslint)

Updates `jose` from 6.2.2 to 6.2.3
- [Release notes](https://github.com/panva/jose/releases)
- [Changelog](https://github.com/panva/jose/blob/main/CHANGELOG.md)
- [Commits](https://github.com/panva/jose/compare/v6.2.2...v6.2.3)

Updates `nodemailer` from 8.0.6 to 8.0.7
- [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.6...v8.0.7)

Updates `zod` from 4.3.6 to 4.4.2
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v4.3.6...v4.4.2)

Updates `@gorhom/bottom-sheet` from 5.2.10 to 5.2.13
- [Release notes](https://github.com/gorhom/react-native-bottom-sheet/releases)
- [Changelog](https://github.com/gorhom/react-native-bottom-sheet/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gorhom/react-native-bottom-sheet/compare/v5.2.10...v5.2.13)

Updates `@sentry/cli` from 3.4.0 to 3.4.1
- [Release notes](https://github.com/getsentry/sentry-cli/releases)
- [Changelog](https://github.com/getsentry/sentry-cli/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-cli/compare/3.4.0...3.4.1)

Updates `@sentry/react-native` from 8.9.1 to 8.10.0
- [Release notes](https://github.com/getsentry/sentry-react-native/releases)
- [Changelog](https://github.com/getsentry/sentry-react-native/blob/main/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-react-native/compare/8.9.1...8.10.0)

Updates `expo-dev-client` from 55.0.28 to 55.0.30
- [Changelog](https://github.com/expo/expo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-dev-client)

Updates `expo-dev-menu` from 55.0.24 to 55.0.26
- [Changelog](https://github.com/expo/expo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-dev-menu)

Updates `expo-notifications` from 55.0.20 to 55.0.22
- [Changelog](https://github.com/expo/expo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-notifications)

Updates `pg-boss` from 12.18.0 to 12.18.2
- [Release notes](https://github.com/timgit/pg-boss/releases)
- [Commits](https://github.com/timgit/pg-boss/compare/12.18.0...12.18.2)

Updates `@sentry/node` from 10.50.0 to 10.51.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.50.0...10.51.0)

Updates `@sentry/react` from 10.49.0 to 10.51.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.49.0...10.51.0)

---
updated-dependencies:
- dependency-name: expo
  dependency-version: 55.0.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@sentry/vite-plugin"
  dependency-version: 5.2.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: eslint
  dependency-version: 10.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: jsdom
  dependency-version: 29.1.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: react-i18next
  dependency-version: 17.0.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: turbo
  dependency-version: 2.9.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: typescript-eslint
  dependency-version: 8.59.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: jose
  dependency-version: 6.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: nodemailer
  dependency-version: 8.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: zod
  dependency-version: 4.4.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@gorhom/bottom-sheet"
  dependency-version: 5.2.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@sentry/cli"
  dependency-version: 3.4.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@sentry/react-native"
  dependency-version: 8.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: expo-dev-client
  dependency-version: 55.0.30
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-dev-menu
  dependency-version: 55.0.26
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: expo-notifications
  dependency-version: 55.0.22
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: pg-boss
  dependency-version: 12.18.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: "@sentry/node"
  dependency-version: 10.51.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: "@sentry/react"
  dependency-version: 10.51.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-03 08:33:39 +00:00
Ullrich Schäfer
db3eeed60f
Apply wahoo-route-update: PUT on re-push instead of duplicate POST
Re-pushing an edited route to Wahoo now updates the existing remote
route via PUT against the stored remote_id instead of POSTing a new
copy. external_id drops the version suffix and identifies the logical
route. sync_pushes is keyed by (user, route, provider) and tracks
last_pushed_version for the "local newer" UI state.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 12:08:16 +02:00
Ullrich Schäfer
dbba1c1607
Fix FIT Course capabilities and lap duration
The Course message advertised capabilities=0x04 (time only), telling
consumers the course had no position data. Wahoo accepted the route
(distance/elevation come from request fields) but rendered an empty
map. Set capabilities to valid|distance|position (0x1A).

Also write a real lap totalElapsedTime/totalTimerTime derived from the
1Hz record timestamps; a zero-duration lap can be rejected as malformed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 10:24:50 +02:00
Ullrich Schäfer
b60cd96736
Revoke Wahoo tokens on disconnect; surface OAuth errors
Disconnecting a sync provider now calls the provider's revoke endpoint
(DELETE /v1/permissions for Wahoo) before dropping the local row, so
tokens don't accumulate against Wahoo's per-(app,user) cap. The token
exchange now classifies Wahoo's "Too many unrevoked access tokens" 400
as a distinct OAuthError code, and the connections settings page shows
a localized banner for that and other connect failures instead of a
silent ?error=sync_failed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 10:03:50 +02:00
Ullrich Schäfer
76c3e49de2
Add Send to Wahoo UI, i18n, and privacy disclosure
Route detail page now renders one of three states for the owner of a
route with a Wahoo connection:

- "Send to Wahoo" button + privacy tooltip when no successful push
  exists for the current version, plus the inline last-error blurb
  if the previous attempt failed
- "Sent to Wahoo on <date>" pill when sync_pushes has a pushedAt
  for the current version
- Banner row at the top mapping the ?push= and ?code= query params
  the action route appends to user-facing copy

i18n strings cover all 8 banner states (success, needs_permission,
no_connection, no_geometry, validation, rate_limit, token_expired,
generic) in English and German.

Privacy disclosure lives at /legal/privacy (apps/journal/app/routes/
legal.privacy.tsx), the in-app surface this repo uses instead of a
docs/privacy.md manifest. The new bullet declares that route
geometry, name, and description are transmitted to Wahoo on opt-in
via the Send to Wahoo button.

E2E tests for the push and re-auth flows (tasks 9.4/9.5) are
deferred — they need a server-side Wahoo mock harness that doesn't
exist yet. Slice-4 unit tests cover the pipeline against the
provider mock.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 22:44:50 +02:00