From ff93a4cfddaaaeeb1e6a20907b01b80e8141dd7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Sat, 4 Apr 2026 11:33:16 +0100 Subject: [PATCH] Archive wahoo-import and journal-route-previews, sync specs Archive completed changes and sync their delta specs to main: - wahoo-import: new wahoo-import spec, updated journal-auth and account-settings - journal-route-previews: new route-preview spec, updated map-display and route-management Co-Authored-By: Claude Opus 4.6 (1M context) --- .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/map-display/spec.md | 0 .../specs/route-management/spec.md | 0 .../specs/route-preview/spec.md | 0 .../tasks.md | 0 .../2026-04-04-wahoo-import}/.openspec.yaml | 0 .../2026-04-04-wahoo-import}/design.md | 0 .../2026-04-04-wahoo-import}/proposal.md | 0 .../specs/account-settings/spec.md | 0 .../specs/journal-auth/spec.md | 0 .../specs/wahoo-import/spec.md | 0 .../2026-04-04-wahoo-import}/tasks.md | 0 openspec/specs/account-settings/spec.md | 89 ++----------------- openspec/specs/journal-auth/spec.md | 32 ++----- openspec/specs/map-display/spec.md | 8 ++ openspec/specs/route-management/spec.md | 12 +++ openspec/specs/route-preview/spec.md | 34 +++++++ openspec/specs/wahoo-import/spec.md | 70 +++++++++++++++ 20 files changed, 138 insertions(+), 107 deletions(-) rename openspec/changes/{journal-route-previews => archive/2026-04-04-journal-route-previews}/.openspec.yaml (100%) rename openspec/changes/{journal-route-previews => archive/2026-04-04-journal-route-previews}/design.md (100%) rename openspec/changes/{journal-route-previews => archive/2026-04-04-journal-route-previews}/proposal.md (100%) rename openspec/changes/{journal-route-previews => archive/2026-04-04-journal-route-previews}/specs/map-display/spec.md (100%) rename openspec/changes/{journal-route-previews => archive/2026-04-04-journal-route-previews}/specs/route-management/spec.md (100%) rename openspec/changes/{journal-route-previews => archive/2026-04-04-journal-route-previews}/specs/route-preview/spec.md (100%) rename openspec/changes/{journal-route-previews => archive/2026-04-04-journal-route-previews}/tasks.md (100%) rename openspec/changes/{wahoo-import => archive/2026-04-04-wahoo-import}/.openspec.yaml (100%) rename openspec/changes/{wahoo-import => archive/2026-04-04-wahoo-import}/design.md (100%) rename openspec/changes/{wahoo-import => archive/2026-04-04-wahoo-import}/proposal.md (100%) rename openspec/changes/{wahoo-import => archive/2026-04-04-wahoo-import}/specs/account-settings/spec.md (100%) rename openspec/changes/{wahoo-import => archive/2026-04-04-wahoo-import}/specs/journal-auth/spec.md (100%) rename openspec/changes/{wahoo-import => archive/2026-04-04-wahoo-import}/specs/wahoo-import/spec.md (100%) rename openspec/changes/{wahoo-import => archive/2026-04-04-wahoo-import}/tasks.md (100%) create mode 100644 openspec/specs/route-preview/spec.md create mode 100644 openspec/specs/wahoo-import/spec.md diff --git a/openspec/changes/journal-route-previews/.openspec.yaml b/openspec/changes/archive/2026-04-04-journal-route-previews/.openspec.yaml similarity index 100% rename from openspec/changes/journal-route-previews/.openspec.yaml rename to openspec/changes/archive/2026-04-04-journal-route-previews/.openspec.yaml diff --git a/openspec/changes/journal-route-previews/design.md b/openspec/changes/archive/2026-04-04-journal-route-previews/design.md similarity index 100% rename from openspec/changes/journal-route-previews/design.md rename to openspec/changes/archive/2026-04-04-journal-route-previews/design.md diff --git a/openspec/changes/journal-route-previews/proposal.md b/openspec/changes/archive/2026-04-04-journal-route-previews/proposal.md similarity index 100% rename from openspec/changes/journal-route-previews/proposal.md rename to openspec/changes/archive/2026-04-04-journal-route-previews/proposal.md diff --git a/openspec/changes/journal-route-previews/specs/map-display/spec.md b/openspec/changes/archive/2026-04-04-journal-route-previews/specs/map-display/spec.md similarity index 100% rename from openspec/changes/journal-route-previews/specs/map-display/spec.md rename to openspec/changes/archive/2026-04-04-journal-route-previews/specs/map-display/spec.md diff --git a/openspec/changes/journal-route-previews/specs/route-management/spec.md b/openspec/changes/archive/2026-04-04-journal-route-previews/specs/route-management/spec.md similarity index 100% rename from openspec/changes/journal-route-previews/specs/route-management/spec.md rename to openspec/changes/archive/2026-04-04-journal-route-previews/specs/route-management/spec.md diff --git a/openspec/changes/journal-route-previews/specs/route-preview/spec.md b/openspec/changes/archive/2026-04-04-journal-route-previews/specs/route-preview/spec.md similarity index 100% rename from openspec/changes/journal-route-previews/specs/route-preview/spec.md rename to openspec/changes/archive/2026-04-04-journal-route-previews/specs/route-preview/spec.md diff --git a/openspec/changes/journal-route-previews/tasks.md b/openspec/changes/archive/2026-04-04-journal-route-previews/tasks.md similarity index 100% rename from openspec/changes/journal-route-previews/tasks.md rename to openspec/changes/archive/2026-04-04-journal-route-previews/tasks.md diff --git a/openspec/changes/wahoo-import/.openspec.yaml b/openspec/changes/archive/2026-04-04-wahoo-import/.openspec.yaml similarity index 100% rename from openspec/changes/wahoo-import/.openspec.yaml rename to openspec/changes/archive/2026-04-04-wahoo-import/.openspec.yaml diff --git a/openspec/changes/wahoo-import/design.md b/openspec/changes/archive/2026-04-04-wahoo-import/design.md similarity index 100% rename from openspec/changes/wahoo-import/design.md rename to openspec/changes/archive/2026-04-04-wahoo-import/design.md diff --git a/openspec/changes/wahoo-import/proposal.md b/openspec/changes/archive/2026-04-04-wahoo-import/proposal.md similarity index 100% rename from openspec/changes/wahoo-import/proposal.md rename to openspec/changes/archive/2026-04-04-wahoo-import/proposal.md diff --git a/openspec/changes/wahoo-import/specs/account-settings/spec.md b/openspec/changes/archive/2026-04-04-wahoo-import/specs/account-settings/spec.md similarity index 100% rename from openspec/changes/wahoo-import/specs/account-settings/spec.md rename to openspec/changes/archive/2026-04-04-wahoo-import/specs/account-settings/spec.md diff --git a/openspec/changes/wahoo-import/specs/journal-auth/spec.md b/openspec/changes/archive/2026-04-04-wahoo-import/specs/journal-auth/spec.md similarity index 100% rename from openspec/changes/wahoo-import/specs/journal-auth/spec.md rename to openspec/changes/archive/2026-04-04-wahoo-import/specs/journal-auth/spec.md diff --git a/openspec/changes/wahoo-import/specs/wahoo-import/spec.md b/openspec/changes/archive/2026-04-04-wahoo-import/specs/wahoo-import/spec.md similarity index 100% rename from openspec/changes/wahoo-import/specs/wahoo-import/spec.md rename to openspec/changes/archive/2026-04-04-wahoo-import/specs/wahoo-import/spec.md diff --git a/openspec/changes/wahoo-import/tasks.md b/openspec/changes/archive/2026-04-04-wahoo-import/tasks.md similarity index 100% rename from openspec/changes/wahoo-import/tasks.md rename to openspec/changes/archive/2026-04-04-wahoo-import/tasks.md diff --git a/openspec/specs/account-settings/spec.md b/openspec/specs/account-settings/spec.md index 67a3e32..e323676 100644 --- a/openspec/specs/account-settings/spec.md +++ b/openspec/specs/account-settings/spec.md @@ -1,83 +1,10 @@ -## ADDED Requirements +## MODIFIED Requirements -### Requirement: Settings page access -The Journal SHALL provide an account settings page at `/settings` accessible to authenticated users. +### Requirement: Connected Services section +The settings page SHALL include a "Connected Services" section for managing external integrations. -#### Scenario: Authenticated access -- **WHEN** a logged-in user navigates to `/settings` -- **THEN** the settings page is displayed with profile, security, and account sections - -#### Scenario: Unauthenticated access -- **WHEN** an unauthenticated user navigates to `/settings` -- **THEN** they are redirected to `/auth/login` - -### Requirement: Profile editing -The settings page SHALL allow users to edit their display name and bio. - -#### Scenario: Update display name -- **WHEN** a user changes their display name and submits -- **THEN** the display name is updated and reflected on their public profile - -#### Scenario: Update bio -- **WHEN** a user enters a bio (max 160 characters) and submits -- **THEN** the bio is saved and visible on their public profile - -#### Scenario: Empty fields -- **WHEN** a user clears their display name -- **THEN** their username is used as the display name fallback - -### Requirement: Passkey management -The settings page SHALL list all registered passkeys and allow adding or deleting them. - -#### Scenario: View passkeys -- **WHEN** a user visits the security section -- **THEN** they see a list of their passkeys with device type, transport label, and registration date - -#### Scenario: Add passkey -- **WHEN** a user clicks "Add passkey" on a browser that supports WebAuthn -- **THEN** the browser passkey creation prompt appears and the new passkey is added to the list - -#### Scenario: Add passkey unsupported browser -- **WHEN** a user visits the security section on a browser without WebAuthn -- **THEN** the "Add passkey" button is disabled with a message explaining the browser limitation - -#### Scenario: Delete passkey -- **WHEN** a user clicks delete on a passkey and confirms -- **THEN** the passkey is removed and can no longer be used for login - -#### Scenario: Delete last passkey -- **WHEN** a user deletes their only passkey -- **THEN** a warning is shown that they will need to use magic links to sign in, and the deletion proceeds after confirmation - -### Requirement: Email change -The settings page SHALL allow users to change their email address with verification. - -#### Scenario: Initiate email change -- **WHEN** a user enters a new email and submits -- **THEN** a verification link is sent to the new email address - -#### Scenario: Verify new email -- **WHEN** the user clicks the verification link -- **THEN** their email is updated to the new address - -#### Scenario: Duplicate email -- **WHEN** a user enters an email already in use by another account -- **THEN** an error is shown indicating the email is taken - -### Requirement: Account deletion -The settings page SHALL allow users to permanently delete their account. - -#### Scenario: Delete account -- **WHEN** a user clicks "Delete account" and types their username to confirm -- **THEN** their account and all associated data are permanently deleted and they are logged out - -#### Scenario: Cancel deletion -- **WHEN** a user opens the delete confirmation but does not confirm -- **THEN** no data is deleted and they remain on the settings page - -### Requirement: Settings navigation -The Journal navigation SHALL include a link to the settings page for authenticated users. - -#### Scenario: Settings link visible -- **WHEN** a logged-in user views the navigation -- **THEN** a "Settings" link is present that navigates to `/settings` +#### Scenario: Wahoo connection status +- **WHEN** a user views the settings page +- **THEN** a "Connected Services" section shows Wahoo as connected or disconnected +- **AND** connected state shows "Disconnect" button +- **AND** disconnected state shows "Connect Wahoo" button diff --git a/openspec/specs/journal-auth/spec.md b/openspec/specs/journal-auth/spec.md index a088b87..4d8a60e 100644 --- a/openspec/specs/journal-auth/spec.md +++ b/openspec/specs/journal-auth/spec.md @@ -1,29 +1,9 @@ ## MODIFIED Requirements -### Requirement: Add passkey from new device -The Journal SHALL allow logged-in users to register additional passkeys from the account settings page or via the post-login prompt. +### Requirement: Store external service tokens +The journal auth system SHALL store OAuth tokens for external services alongside user credentials. -#### Scenario: Add passkey after magic link login -- **WHEN** a user logs in via magic link on a device that supports WebAuthn -- **THEN** the system prompts them to register a passkey for that device - -#### Scenario: Add passkey from settings -- **WHEN** a user clicks "Add passkey" in the security section of account settings -- **THEN** the browser passkey creation prompt appears and the new passkey is stored - -#### Scenario: Add passkey prompt on unsupported browser -- **WHEN** a user logs in via magic link on a device that does not support WebAuthn -- **THEN** the system shows the add-passkey prompt with a message that the browser does not support passkeys - -## ADDED Requirements - -### Requirement: Delete passkey -The Journal SHALL allow users to delete individual passkeys from account settings. - -#### Scenario: Delete passkey -- **WHEN** a user deletes a passkey from account settings -- **THEN** the credential is removed from the database and can no longer be used for authentication - -#### Scenario: Delete last passkey warning -- **WHEN** a user attempts to delete their only remaining passkey -- **THEN** a warning is shown explaining they will need to use magic links, and deletion proceeds after confirmation +#### Scenario: Wahoo token storage +- **WHEN** a user connects their Wahoo account +- **THEN** access token, refresh token, expiry time, and Wahoo user ID are stored in the `wahoo_tokens` table +- **AND** tokens are associated with the journal user ID diff --git a/openspec/specs/map-display/spec.md b/openspec/specs/map-display/spec.md index e7d0d86..f9c7760 100644 --- a/openspec/specs/map-display/spec.md +++ b/openspec/specs/map-display/spec.md @@ -77,6 +77,14 @@ Other participants' cursors on the map SHALL be clearly visible with proper styl - **WHEN** cursors are rendered - **THEN** they appear below map controls (zoom, layer switcher) in z-index +### Requirement: Map components used in journal app +The `@trails-cool/map` package's `MapView` and `RouteLayer` components SHALL be used in the journal app for route previews, in addition to the planner. + +#### Scenario: Journal uses shared map components +- **WHEN** the journal renders a route map preview or detail map +- **THEN** it uses `MapView` and `RouteLayer` from `@trails-cool/map` +- **AND** no map code is duplicated between planner and journal + ### Requirement: Map polygon drawing The Planner map SHALL support drawing and displaying no-go area polygons. diff --git a/openspec/specs/route-management/spec.md b/openspec/specs/route-management/spec.md index c224ae5..b911f1e 100644 --- a/openspec/specs/route-management/spec.md +++ b/openspec/specs/route-management/spec.md @@ -67,6 +67,18 @@ Route geometries SHALL be stored as PostGIS LineString geometries extracted from - **WHEN** a GPX file is imported or a route is saved from the Planner - **THEN** the route geometry is extracted and stored as a PostGIS LineString for future spatial queries +### Requirement: Route data includes geometry for rendering +Route and activity loaders SHALL return GeoJSON geometry when available. + +#### Scenario: Route list returns simplified geometry +- **WHEN** the routes list loader runs +- **THEN** each route includes a `geojson` field containing the geometry as a GeoJSON string +- **AND** the geometry is simplified server-side via `ST_Simplify()` for list page performance + +#### Scenario: Route detail returns full geometry +- **WHEN** the route detail loader runs and the route has geometry +- **THEN** the route includes a `geojson` field with the full-resolution GeoJSON geometry + ### Requirement: Route metadata envelope Routes SHALL be stored with a metadata envelope containing computed statistics (distance, elevation gain/loss), routing profile, contributor list, and tags. diff --git a/openspec/specs/route-preview/spec.md b/openspec/specs/route-preview/spec.md new file mode 100644 index 0000000..6b4a297 --- /dev/null +++ b/openspec/specs/route-preview/spec.md @@ -0,0 +1,34 @@ +## ADDED Requirements + +### Requirement: Route map preview on list pages +Route and activity list pages SHALL show a small map thumbnail for each item that has geometry. + +#### Scenario: Route with geometry +- **WHEN** the routes list page loads and a route has a `geom` column +- **THEN** a small map thumbnail is rendered showing the route path +- **AND** the map auto-fits to the route bounds + +#### Scenario: Route without geometry +- **WHEN** a route has no `geom` (legacy route) +- **THEN** a placeholder is shown instead of a map thumbnail + +#### Scenario: Activity with geometry +- **WHEN** the activities list page loads and an activity has a `geom` column +- **THEN** a small map thumbnail is rendered showing the activity path + +### Requirement: Interactive map on detail pages +Route and activity detail pages SHALL show an interactive read-only map with the route/activity drawn. + +#### Scenario: Route detail with geometry +- **WHEN** a user views a route detail page and the route has geometry +- **THEN** a full-width interactive map is shown with the route path +- **AND** the map has zoom controls and layer switching +- **AND** the map auto-fits to the route bounds + +#### Scenario: Activity detail with geometry +- **WHEN** a user views an activity detail page and the activity has geometry +- **THEN** a full-width interactive map is shown with the activity path + +#### Scenario: Detail page without geometry +- **WHEN** a route or activity has no geometry +- **THEN** no map section is rendered diff --git a/openspec/specs/wahoo-import/spec.md b/openspec/specs/wahoo-import/spec.md new file mode 100644 index 0000000..f3004b9 --- /dev/null +++ b/openspec/specs/wahoo-import/spec.md @@ -0,0 +1,70 @@ +## ADDED Requirements + +### Requirement: Provider-agnostic sync framework +The system SHALL provide a common interface for external activity sync providers. + +#### Scenario: Add new provider +- **WHEN** a developer wants to add a new sync provider (e.g., Garmin) +- **THEN** they implement the `SyncProvider` interface in a single file +- **AND** register it in the provider registry +- **AND** all OAuth, webhook, import, and settings UI works automatically + +### Requirement: Connect Wahoo account +Users SHALL be able to connect their Wahoo account via OAuth2. + +#### Scenario: Connect Wahoo +- **WHEN** a user clicks "Connect Wahoo" in journal settings +- **THEN** they are redirected to Wahoo's OAuth authorization page with scopes `workouts_read`, `user_read`, `offline_data` +- **AND** after granting permission, redirected back to the journal +- **AND** access and refresh tokens are stored in `sync_connections` + +#### Scenario: Disconnect Wahoo +- **WHEN** a user clicks "Disconnect" next to their Wahoo connection +- **THEN** the stored tokens are deleted from `sync_connections` + +#### Scenario: Token refresh +- **WHEN** a Wahoo API call fails with an expired token +- **THEN** the refresh token is used to obtain a new access token automatically + +### Requirement: Webhook-based automatic sync +New Wahoo workouts SHALL be automatically imported when they complete. + +#### Scenario: Webhook receives new workout +- **WHEN** Wahoo sends a `workout_summary` webhook to `/api/sync/webhook/wahoo` +- **THEN** the system identifies the user via `provider_user_id` +- **AND** downloads the FIT file from Wahoo's CDN +- **AND** converts it to GPX +- **AND** creates a journal activity with the GPX, stats, and PostGIS geometry +- **AND** records the import in `sync_imports` to prevent duplicates + +#### Scenario: Duplicate webhook +- **WHEN** a webhook arrives for a workout already imported +- **THEN** the import is skipped silently (idempotent) + +#### Scenario: Unknown user webhook +- **WHEN** a webhook arrives with a `provider_user_id` not matching any connection +- **THEN** the request is ignored with a 200 response (don't reveal user existence) + +### Requirement: Manual import +Users SHALL be able to browse and selectively import older Wahoo workouts. + +#### Scenario: View workout list +- **WHEN** a user visits the Wahoo import page +- **THEN** their Wahoo workouts are listed with date, type, duration, and distance +- **AND** already-imported workouts are marked + +#### Scenario: Import workout +- **WHEN** a user clicks "Import" on a Wahoo workout +- **THEN** the FIT file is downloaded, converted to GPX, and a new activity is created + +### Requirement: FIT to GPX conversion +The system SHALL convert Wahoo's FIT binary files to GPX format. + +#### Scenario: Convert FIT with GPS data +- **WHEN** a FIT file contains GPS track records +- **THEN** track points with lat, lon, elevation, and timestamp are extracted +- **AND** a valid GPX string is produced using `generateGpx` + +#### Scenario: FIT without GPS data +- **WHEN** a FIT file has no GPS records (e.g., indoor trainer workout) +- **THEN** the activity is created without GPX or geometry (stats only)