Archive public-content-visibility
Final tasks ticked post-merge: - 10.2: verified on prod that journal.routes + journal.activities still default to 'private' NOT NULL, with the only public rows being the 15-each demo-bot seeded content - 10.3: demo-activity-bot already inserts with visibility='public' directly in demo-bot.server.ts Syncs the three delta specs into main: + activity-feed: 2 added, 1 modified + public-profiles: new spec (1 added) + route-management: 2 added, 1 modified Moves change to openspec/changes/archive/2026-04-24-public-content-visibility. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9709a0c150
commit
ba6f09171d
10 changed files with 118 additions and 12 deletions
|
|
@ -1,9 +1,7 @@
|
|||
## Purpose
|
||||
|
||||
Activity creation, chronological feed, detail views, and route linking in the Journal app.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Create activity
|
||||
The Journal SHALL allow authenticated users to create an activity by uploading a GPX trace and adding a description.
|
||||
|
||||
|
|
@ -23,12 +21,28 @@ The Journal SHALL display a chronological feed of the authenticated user's activ
|
|||
- **THEN** they see their activities in reverse chronological order with name, date, distance, and duration
|
||||
|
||||
### Requirement: Activity detail page
|
||||
The Journal SHALL display an activity detail page with map, stats, and description.
|
||||
The Journal SHALL display an activity detail page with map, stats, and description. Access depends on the activity's `visibility`: `public` activities are viewable by anyone including unauthenticated visitors, `unlisted` activities are viewable by anyone who has the URL, and `private` activities are viewable only by the owner.
|
||||
|
||||
#### Scenario: View activity detail
|
||||
- **WHEN** a user navigates to an activity URL
|
||||
#### Scenario: Owner views own activity
|
||||
- **WHEN** a logged-in user navigates to an activity they own at any visibility
|
||||
- **THEN** they see the activity name, description, a map with the GPS trace, distance, duration, and elevation stats
|
||||
|
||||
#### Scenario: Anyone views a public activity
|
||||
- **WHEN** any visitor (including unauthenticated) navigates to a `public` activity's URL
|
||||
- **THEN** they see the full activity detail page as above
|
||||
|
||||
#### Scenario: Anyone with the URL views an unlisted activity
|
||||
- **WHEN** any visitor navigates directly to an `unlisted` activity's URL
|
||||
- **THEN** they see the full activity detail page as above
|
||||
|
||||
#### Scenario: Non-owner is blocked from a private activity
|
||||
- **WHEN** a visitor who is not the owner requests a `private` activity URL
|
||||
- **THEN** the server responds with HTTP 404 (not 403), so the existence of the private activity is not leaked
|
||||
|
||||
#### Scenario: Public and unlisted activity pages emit social-share metadata
|
||||
- **WHEN** a visitor loads a `public` or `unlisted` activity detail page
|
||||
- **THEN** the response emits Open Graph and Twitter Card meta tags (`og:title`, `og:description`, `og:type="article"`, `og:site_name`, `twitter:card="summary"`)
|
||||
|
||||
### Requirement: Link activity to route
|
||||
Users SHALL be able to link an existing activity to a route, or create a route from an activity trace.
|
||||
|
||||
|
|
@ -57,3 +71,26 @@ The Journal SHALL expose a reverse-chronological feed of every activity on the i
|
|||
#### Scenario: Private or unlisted activities stay out of the feed
|
||||
- **WHEN** an activity's visibility is `private` or `unlisted`
|
||||
- **THEN** it does not appear in the instance feed, regardless of who is viewing
|
||||
|
||||
### Requirement: Activity visibility
|
||||
The Journal SHALL persist a `visibility` value on every activity and SHALL allow the owner to change it.
|
||||
|
||||
#### Scenario: New activities default to private
|
||||
- **WHEN** an activity is created without an explicit visibility
|
||||
- **THEN** the activity row is persisted with `visibility = 'private'`
|
||||
|
||||
#### Scenario: Owner changes an activity's visibility
|
||||
- **WHEN** an activity owner selects a different visibility (`private`, `unlisted`, `public`) and saves
|
||||
- **THEN** the stored visibility is updated and subsequent access checks use the new value immediately
|
||||
|
||||
### Requirement: Activity listings respect visibility
|
||||
The Journal's own-activities feed SHALL show the owner everything regardless of visibility, while any cross-user listing SHALL only include activities with `visibility = 'public'`.
|
||||
|
||||
#### Scenario: Own activity feed is unchanged
|
||||
- **WHEN** a logged-in user views their own activity feed
|
||||
- **THEN** the feed includes all of their own activities regardless of visibility
|
||||
|
||||
#### Scenario: Public profile lists only public activities
|
||||
- **WHEN** a visitor loads `/users/:username`
|
||||
- **THEN** the rendered list of activities includes only the user's `public` activities; `unlisted` and `private` activities are omitted
|
||||
|
||||
|
|
|
|||
26
openspec/specs/public-profiles/spec.md
Normal file
26
openspec/specs/public-profiles/spec.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# public-profiles Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change public-content-visibility. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: Public profile page
|
||||
The Journal SHALL serve a public profile page at `/users/:username` that lists the user's public routes and activities in reverse chronological order, viewable without authentication.
|
||||
|
||||
#### Scenario: Logged-out visitor views a profile with public content
|
||||
- **WHEN** an unauthenticated visitor navigates to `/users/:username` for a user with at least one `public` route or activity
|
||||
- **THEN** the page renders that user's display name (falling back to username), the `@username@domain` handle, and a reverse-chronological list of their `public` routes and `public` activities
|
||||
- **AND** items marked `unlisted` or `private` do NOT appear in the list
|
||||
|
||||
#### Scenario: Profile 404 when there is no public content
|
||||
- **WHEN** a visitor navigates to `/users/:username` for a user whose content is all `private` or `unlisted`, or for a username that does not exist
|
||||
- **THEN** the server responds with HTTP 404
|
||||
- **AND** the response does NOT distinguish the two cases, so existence of a private-only account is not leaked
|
||||
|
||||
#### Scenario: Owner sees their own profile
|
||||
- **WHEN** a user navigates to their own `/users/:username` while logged in
|
||||
- **THEN** the page renders exactly the same as for a logged-out visitor, plus a small owner-only control strip linking to settings
|
||||
|
||||
#### Scenario: Profile page emits social-share metadata
|
||||
- **WHEN** any visitor loads a populated `/users/:username`
|
||||
- **THEN** the page emits Open Graph tags (`og:title`, `og:site_name`, `og:type="profile"`) so links shared on social platforms render a meaningful preview
|
||||
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
## Purpose
|
||||
|
||||
Route CRUD operations, GPX import/export, sequential versioning, PostGIS spatial storage, and route metadata in the Journal app.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Create route
|
||||
The Journal SHALL allow authenticated users to create a new route with a name and optional description.
|
||||
|
||||
|
|
@ -12,12 +10,28 @@ The Journal SHALL allow authenticated users to create a new route with a name an
|
|||
- **THEN** a new route record is created in PostgreSQL and the user is redirected to the route detail page
|
||||
|
||||
### Requirement: View route
|
||||
The Journal SHALL display route details including map, metadata, and elevation stats.
|
||||
The Journal SHALL display route details including map, metadata, and elevation stats. Access depends on the route's `visibility`: `public` routes are viewable by anyone including unauthenticated visitors, `unlisted` routes are viewable by anyone who has the URL, and `private` routes are viewable only by the owner.
|
||||
|
||||
#### Scenario: View route detail
|
||||
- **WHEN** a user navigates to a route's URL
|
||||
#### Scenario: Owner views own route
|
||||
- **WHEN** a logged-in user navigates to a route they own at any visibility
|
||||
- **THEN** they see the route name, description, a map with the route polyline, distance, and elevation gain/loss
|
||||
|
||||
#### Scenario: Anyone views a public route
|
||||
- **WHEN** any visitor (including unauthenticated) navigates to a `public` route's URL
|
||||
- **THEN** they see the full route detail page as above
|
||||
|
||||
#### Scenario: Anyone with the URL views an unlisted route
|
||||
- **WHEN** any visitor navigates directly to an `unlisted` route's URL
|
||||
- **THEN** they see the full route detail page as above
|
||||
|
||||
#### Scenario: Non-owner is blocked from a private route
|
||||
- **WHEN** a visitor who is not the owner requests a `private` route URL
|
||||
- **THEN** the server responds with HTTP 404 (not 403), so the existence of the private route is not leaked
|
||||
|
||||
#### Scenario: Public and unlisted route pages emit social-share metadata
|
||||
- **WHEN** a visitor loads a `public` or `unlisted` route detail page
|
||||
- **THEN** the response emits Open Graph and Twitter Card meta tags (`og:title`, `og:description`, `og:type="article"`, `og:site_name`, `twitter:card="summary"`)
|
||||
|
||||
### Requirement: Update route
|
||||
The Journal SHALL allow the route owner to update the route name, description, and GPX.
|
||||
|
||||
|
|
@ -89,3 +103,30 @@ Routes SHALL be stored with a metadata envelope containing computed statistics (
|
|||
#### Scenario: Metadata computed on save
|
||||
- **WHEN** a route GPX is saved
|
||||
- **THEN** distance and elevation statistics are computed from the GPX and stored in the metadata
|
||||
|
||||
### Requirement: Route visibility
|
||||
The Journal SHALL persist a `visibility` value on every route and SHALL allow the owner to change it.
|
||||
|
||||
#### Scenario: New routes default to private
|
||||
- **WHEN** a route is created without an explicit visibility
|
||||
- **THEN** the route row is persisted with `visibility = 'private'`
|
||||
|
||||
#### Scenario: Owner changes a route's visibility
|
||||
- **WHEN** a route owner selects a different visibility (`private`, `unlisted`, `public`) in the edit flow and saves
|
||||
- **THEN** the stored visibility is updated and subsequent access checks use the new value immediately
|
||||
|
||||
#### Scenario: Non-owner cannot change visibility
|
||||
- **WHEN** a request to update visibility arrives from a user who is not the route owner
|
||||
- **THEN** the server rejects it with HTTP 403 or 404 (matching the current update-route behaviour), and the stored value is unchanged
|
||||
|
||||
### Requirement: Route listings respect visibility
|
||||
Any listing that exposes routes beyond the owner's own dashboard SHALL only include routes with `visibility = 'public'`.
|
||||
|
||||
#### Scenario: Public profile lists only public routes
|
||||
- **WHEN** a visitor loads `/users/:username`
|
||||
- **THEN** the rendered list of routes includes only the user's `public` routes; `unlisted` and `private` routes are omitted
|
||||
|
||||
#### Scenario: Owner's own routes list is unchanged
|
||||
- **WHEN** a logged-in user views their own routes list at `/routes`
|
||||
- **THEN** the list includes all of their own routes regardless of visibility
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue