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) <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-04-04 11:33:16 +01:00
parent 4c5aacf223
commit ff93a4cfdd
No known key found for this signature in database
GPG key ID: A32FF691A0F752D9
20 changed files with 138 additions and 107 deletions

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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.

View file

@ -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

View file

@ -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)