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:
parent
4c5aacf223
commit
ff93a4cfdd
20 changed files with 138 additions and 107 deletions
|
|
@ -1,83 +1,10 @@
|
||||||
## ADDED Requirements
|
## MODIFIED Requirements
|
||||||
|
|
||||||
### Requirement: Settings page access
|
### Requirement: Connected Services section
|
||||||
The Journal SHALL provide an account settings page at `/settings` accessible to authenticated users.
|
The settings page SHALL include a "Connected Services" section for managing external integrations.
|
||||||
|
|
||||||
#### Scenario: Authenticated access
|
#### Scenario: Wahoo connection status
|
||||||
- **WHEN** a logged-in user navigates to `/settings`
|
- **WHEN** a user views the settings page
|
||||||
- **THEN** the settings page is displayed with profile, security, and account sections
|
- **THEN** a "Connected Services" section shows Wahoo as connected or disconnected
|
||||||
|
- **AND** connected state shows "Disconnect" button
|
||||||
#### Scenario: Unauthenticated access
|
- **AND** disconnected state shows "Connect Wahoo" button
|
||||||
- **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`
|
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,9 @@
|
||||||
## MODIFIED Requirements
|
## MODIFIED Requirements
|
||||||
|
|
||||||
### Requirement: Add passkey from new device
|
### Requirement: Store external service tokens
|
||||||
The Journal SHALL allow logged-in users to register additional passkeys from the account settings page or via the post-login prompt.
|
The journal auth system SHALL store OAuth tokens for external services alongside user credentials.
|
||||||
|
|
||||||
#### Scenario: Add passkey after magic link login
|
#### Scenario: Wahoo token storage
|
||||||
- **WHEN** a user logs in via magic link on a device that supports WebAuthn
|
- **WHEN** a user connects their Wahoo account
|
||||||
- **THEN** the system prompts them to register a passkey for that device
|
- **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
|
||||||
#### 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
|
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,14 @@ Other participants' cursors on the map SHALL be clearly visible with proper styl
|
||||||
- **WHEN** cursors are rendered
|
- **WHEN** cursors are rendered
|
||||||
- **THEN** they appear below map controls (zoom, layer switcher) in z-index
|
- **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
|
### Requirement: Map polygon drawing
|
||||||
The Planner map SHALL support drawing and displaying no-go area polygons.
|
The Planner map SHALL support drawing and displaying no-go area polygons.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
- **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
|
- **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
|
### 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.
|
Routes SHALL be stored with a metadata envelope containing computed statistics (distance, elevation gain/loss), routing profile, contributor list, and tags.
|
||||||
|
|
||||||
|
|
|
||||||
34
openspec/specs/route-preview/spec.md
Normal file
34
openspec/specs/route-preview/spec.md
Normal 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
|
||||||
70
openspec/specs/wahoo-import/spec.md
Normal file
70
openspec/specs/wahoo-import/spec.md
Normal 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)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue