Fix all OpenSpec validation failures (36/36 passing)

- Add ## Purpose sections and convert delta headers to ## Requirements
  on all 25 specs
- Add SHALL keywords to requirements missing them (gpx-import,
  planner-session, planner-journal-handoff)
- Convert prose GPX format section to proper scenarios (no-go-areas)
- Create specs/ delta files for 7 changes that were missing them
  (activity-photos, local-dev-stack, multi-day-routes, route-discovery,
  route-sharing, visual-redesign, waypoint-notes)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-04-06 22:17:52 +02:00
parent c6949e9490
commit 0b9b99984e
32 changed files with 414 additions and 35 deletions

View file

@ -0,0 +1,52 @@
## ADDED Requirements
### Requirement: Photo upload via presigned URLs
Users SHALL be able to upload photos to an activity using presigned PUT URLs, with client-side image processing before upload.
#### Scenario: Upload photo
- **WHEN** a user selects a photo on the activity detail page
- **THEN** the client resizes the image (max 2048px long edge), strips EXIF data, converts to WebP
- **AND** requests a presigned PUT URL from `POST /api/activities/:id/photos`
- **AND** uploads directly to Garage (S3-compatible storage)
- **AND** confirms the upload via `PATCH /api/activities/:id/photos/:photoId`
#### Scenario: Size limit enforced
- **WHEN** a processed photo exceeds 5 MB
- **THEN** the presigned URL rejects the upload via content-length condition
#### Scenario: Per-activity photo limit
- **WHEN** an activity already has 20 photos
- **THEN** the server rejects new presigned URL requests
### Requirement: Photo gallery display
The activity detail page SHALL display uploaded photos in a responsive grid with lightbox viewing.
#### Scenario: Gallery grid
- **WHEN** a user views an activity with photos
- **THEN** photos are displayed in a responsive grid (1 column mobile, 2 tablet, 3 desktop) with lazy loading
#### Scenario: Lightbox view
- **WHEN** a user clicks a photo in the gallery
- **THEN** a full-size lightbox overlay opens with left/right navigation and close on escape
### Requirement: Photo deletion
Activity owners SHALL be able to delete photos with soft delete and background S3 cleanup.
#### Scenario: Delete photo
- **WHEN** a user clicks the delete icon on a photo
- **THEN** the photo status is set to 'deleted' and it disappears from the UI
- **AND** the S3 object is cleaned up on next page load or periodic cleanup
### Requirement: Photo storage infrastructure
Photos SHALL be stored in Garage (S3-compatible, self-hosted) running as a Docker container.
#### Scenario: Garage container
- **WHEN** docker compose starts
- **THEN** a Garage container is running with an `activity-photos` bucket
### Requirement: Photo privacy disclosure
Photo storage and EXIF handling SHALL be documented in the privacy manifest.
#### Scenario: Privacy page updated
- **WHEN** a user visits the privacy page
- **THEN** it documents photo storage location, EXIF stripping, and deletion behavior

View file

@ -0,0 +1,29 @@
## MODIFIED Requirements
### Requirement: Optional local monitoring stack
The dev environment SHALL support an optional monitoring profile matching the production stack.
#### Scenario: Start with monitoring
- **WHEN** a developer runs `pnpm dev:full` with `--profile monitoring`
- **THEN** Prometheus, Grafana, and Loki start alongside the app services
### Requirement: Production-aligned PostgreSQL config
The dev PostgreSQL SHALL match production configuration including pg_stat_statements.
#### Scenario: pg_stat_statements available
- **WHEN** the dev PostgreSQL container starts
- **THEN** pg_stat_statements is enabled via initialization scripts
### Requirement: Database seed script
The dev environment SHALL provide a seed script for consistent test data.
#### Scenario: Seed database
- **WHEN** a developer runs the seed script
- **THEN** test users, routes, and activities are created in the local database
### Requirement: Dev environment reset
The dev environment SHALL provide a command to tear down and recreate the local stack.
#### Scenario: Reset dev environment
- **WHEN** a developer runs `pnpm dev:reset`
- **THEN** all Docker volumes are removed, containers are recreated, and the database is re-seeded

View file

@ -0,0 +1,50 @@
## ADDED Requirements
### Requirement: Overnight waypoint markers
Any waypoint SHALL be toggleable as an overnight stop, creating day boundaries in the route.
#### Scenario: Toggle overnight
- **WHEN** a user toggles the overnight flag on a waypoint
- **THEN** the waypoint's `overnight: true` flag is set in the Yjs document
- **AND** the route is visually divided into days at that point
#### Scenario: Implicit day boundaries
- **WHEN** overnight stops are set
- **THEN** the first waypoint is the implicit start of Day 1 and the last waypoint is the implicit end of the final day
### Requirement: Per-day statistics
The Planner SHALL compute and display distance, ascent, and estimated duration for each day.
#### Scenario: Day stats computed
- **WHEN** a route has overnight waypoints
- **THEN** per-day distance, total ascent, and estimated duration are derived from segment boundaries and coordinates
### Requirement: Day-aware sidebar
The sidebar SHALL group waypoints by day with collapsible sections and per-day stats.
#### Scenario: Day breakdown
- **WHEN** a route has multiple days
- **THEN** waypoints are grouped under "Day 1", "Day 2", etc. with collapsible sections
- **AND** each section header shows day distance and ascent
### Requirement: Elevation chart day dividers
The elevation chart SHALL show day boundaries as dashed vertical lines.
#### Scenario: Day dividers on chart
- **WHEN** a route has multiple days
- **THEN** dashed vertical lines with "Day N" labels appear at each overnight waypoint position
### Requirement: Map day labels
The map SHALL display day summary labels at day boundary waypoints.
#### Scenario: Day labels on map
- **WHEN** a route has multiple days
- **THEN** white pill markers at day boundaries show "Day N . X km"
### Requirement: Multi-day GPX export
Day structure SHALL be preserved in GPX exports via waypoint type elements.
#### Scenario: Export multi-day route
- **WHEN** a user exports a plan with overnight waypoints
- **THEN** overnight waypoints include a `<type>overnight</type>` element in the GPX
- **AND** reimporting the GPX restores the day structure

View file

@ -0,0 +1,28 @@
## ADDED Requirements
### Requirement: Map-based route exploration
The Journal SHALL provide an explore page with a full-page map for browsing public routes by location.
#### Scenario: Browse public routes
- **WHEN** a user visits `/routes/explore`
- **THEN** a full-page Leaflet map is displayed
- **AND** public routes within the current viewport are shown as clickable polylines
#### Scenario: Route popup
- **WHEN** a user clicks a route polyline on the explore map
- **THEN** a popup shows the route name, distance, elevation gain, author, and a link to the detail page
### Requirement: Bounding box spatial query
The Journal SHALL provide an API endpoint for querying public routes by map viewport bounds.
#### Scenario: Viewport query
- **WHEN** the map viewport changes
- **THEN** the client requests routes intersecting the current bounds via PostGIS `ST_Intersects`
- **AND** results are limited to 50 routes with simplified geometries
### Requirement: Spatial index
The routes table SHALL have a GiST index on the geometry column for fast bounding box queries.
#### Scenario: Index exists
- **WHEN** the database is set up
- **THEN** a GiST index on `journal.routes.geom` supports efficient spatial queries

View file

@ -0,0 +1,38 @@
## ADDED Requirements
### Requirement: Route visibility
Routes and activities SHALL support private (default) and public visibility levels.
#### Scenario: Set route public
- **WHEN** a route owner toggles visibility to "public"
- **THEN** the route is viewable by anyone with the link
#### Scenario: Default private
- **WHEN** a new route is created
- **THEN** its visibility is "private" (owner-only)
### Requirement: Per-user route sharing
Route owners SHALL be able to share routes with specific users at view or edit permission levels.
#### Scenario: Share with user
- **WHEN** a route owner shares a route with another user at "view" level
- **THEN** the shared user sees the route in their collection and can view it
#### Scenario: Edit permission
- **WHEN** a route owner shares a route with "edit" permission
- **THEN** the shared user can update the route name, description, and GPX
### Requirement: Route forking
Logged-in users SHALL be able to fork any public route into their own collection.
#### Scenario: Fork public route
- **WHEN** a user clicks "Fork" on a public route
- **THEN** a copy of the route metadata and latest GPX is created in their collection
- **AND** a `forkedFromId` link references the original route
### Requirement: Contributor tracking
Route versions SHALL record the identity of the contributor who saved them.
#### Scenario: Planner callback contributor
- **WHEN** the Planner saves back to the Journal via JWT callback
- **THEN** the contributor's identity is recorded on the new route version

View file

@ -0,0 +1,40 @@
## ADDED Requirements
### Requirement: Design token system
The Planner SHALL use CSS custom properties for colors, typography, and spacing with Tailwind config extensions.
#### Scenario: Consistent styling
- **WHEN** a component is styled
- **THEN** it uses project design tokens (warm off-whites, sage green accent, earthy tones) via Tailwind classes
#### Scenario: Typography
- **WHEN** text is rendered
- **THEN** body text uses Outfit font and statistics use Geist Mono
### Requirement: Redesigned topbar
The Planner topbar SHALL include a logo, segmented color mode toggle, participant avatars, and export controls.
#### Scenario: Topbar layout
- **WHEN** a user is in a planning session
- **THEN** the topbar shows: logo with mountain mark, color mode toggle, participant avatars with names, and Export GPX button
### Requirement: Redesigned sidebar
The Planner sidebar SHALL display a route summary header with stats, collapsible day breakdown, and waypoints nested inside days.
#### Scenario: Route summary
- **WHEN** a route is computed
- **THEN** the sidebar header shows route name, distance, ascent, and estimated duration
### Requirement: Redesigned map markers
Map waypoint markers SHALL use olive/dark numbered circles with overnight badges on stop waypoints.
#### Scenario: Marker styling
- **WHEN** waypoints are displayed on the map
- **THEN** they appear as dark numbered circles instead of default blue markers
### Requirement: Mobile responsive layout
The Planner SHALL use a bottom sheet pattern on mobile instead of a hidden sidebar.
#### Scenario: Mobile view
- **WHEN** the Planner is viewed on a mobile device
- **THEN** a bottom sheet replaces the sidebar with swipeable tabs (Days/Waypoints/Notes)

View file

@ -0,0 +1,46 @@
## ADDED Requirements
### Requirement: Per-waypoint text notes
Each waypoint SHALL support an optional plain-text note synced via Yjs.
#### Scenario: Add note to waypoint
- **WHEN** a user clicks the note area under a waypoint in the sidebar and types text
- **THEN** the note is stored in the waypoint's Y.Map as a `note` string field
- **AND** auto-saves on blur
#### Scenario: Note syncs to participants
- **WHEN** a user adds or edits a waypoint note
- **THEN** all other participants see the update in real-time via Yjs
### Requirement: Map note indicators
Waypoint markers with notes SHALL show a visual indicator on the map.
#### Scenario: Note icon on marker
- **WHEN** a waypoint has a note
- **THEN** its map marker shows a small note icon
#### Scenario: Note tooltip
- **WHEN** a user hovers or taps a marker with a note
- **THEN** the note text appears in a tooltip
### Requirement: Notes in GPX export
Waypoint notes SHALL be exported as `<desc>` elements in GPX output.
#### Scenario: Export notes
- **WHEN** a user exports a plan with waypoint notes
- **THEN** each waypoint's note appears as a `<desc>` element in the GPX file
### Requirement: Nearby POI display
When a waypoint is selected, nearby POIs from OpenStreetMap SHALL be shown on the map and in the sidebar.
#### Scenario: POI lookup
- **WHEN** a user selects a waypoint
- **THEN** nearby POIs are fetched from the Overpass API and displayed as small markers on the map and as a list in the sidebar
### Requirement: Snap waypoint to POI
Users SHALL be able to move a waypoint to a nearby POI's exact coordinates.
#### Scenario: Snap to POI
- **WHEN** a user clicks a nearby POI
- **THEN** the waypoint moves to the POI's coordinates
- **AND** the POI's name and type are added as a note prefix (e.g., "Campsite - Waldcamp Fichtelberg")