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>
4 KiB
4 KiB
Why
trails.cool currently has no way to show its content to anyone who isn't logged in. Route and activity detail pages require auth, there is no public-facing profile page, and shared URLs have no Open Graph metadata — so a link pasted in Slack or on a social platform looks like a blank trails.cool card.
Two immediate reasons to fix this:
- Demos + acquisition. The next phase of the project is onboarding more users and contributors. The pitch lands better when you can send a URL that opens directly to "look at this route / this weekend's ride" without making the visitor register first.
- Prerequisite for any
demo-activity-botwork. A synthetic-activity generator is pointless if the generated content isn't publicly viewable. This change unblocks that follow-up.
This is also the smallest possible step into the "social" direction the Journal was always described as heading for. It does not introduce follow/follower, cross-user feeds, reactions, or ActivityPub — those remain future work.
What Changes
- New
visibilitycolumn on routes and activities with valuesprivate(default for existing rows),unlisted(reachable only by direct URL, excluded from listings),public(visible everywhere). - Logged-out visitors can view public route and activity detail pages. Private / nonexistent routes return 404, just like logged-in requests to other users' private content. Unlisted renders for anyone with the URL but does not appear in listings.
- New public profile page at
/users/:usernamethat lists that user's public routes and activities. Returns 404 if the user has no public content (keeps the existence of private-only accounts itself private). - Route detail page gains a visibility selector for owners (the existing edit page is the natural home).
- Open Graph / Twitter Card meta tags on public route and activity detail pages so shared URLs preview nicely (title, description, a small route preview image for future, the instance name as site).
- BREAKING for spec-readers only: several existing requirements in
route-managementandactivity-feedare modified to reflect the new "auth-or-public" access rule. The default in migration isprivate, so behaviour for existing users does not change — their routes stay inaccessible to logged-out visitors until they explicitly make one public.
Capabilities
New Capabilities
public-profiles: Public-facing user pages at/users/:usernamelisting a user's public routes and activities, with nothing shown for users who have no public content.
Modified Capabilities
route-management: adds visibility field + modified access rules so public routes are viewable without auth; owners can change visibility.activity-feed: adds visibility field; public activities render to logged-out visitors.
Impact
- Code: schema (
visibilitycolumn onroutes+activities), loader auth changes onroutes.$id.tsx/activities.$id.tsx, new routeusers.$username.tsx(already exists but is auth-gated today), a visibility selector somewhere in the route edit flow, ametaexport on public detail pages for OG tags. - Data: default
visibility = privatefor all existing rows — three users, zero activities, two empty routes — so migration is effectively a no-op for behaviour. No backfill needed. - Privacy: this change explicitly reduces the default privacy posture only if a user opts in by marking content public. A short sentence in
/legal/privacynotes public content is world-visible. No new third-party surface. - Search engines: public pages are not actively promoted (no sitemap).
robots: noindexstays on legal pages and settings;routesandactivitiespublic pages get a permissive default (search-indexable) so demo URLs work well. - Non-goals (explicit): follow/follower, a cross-user feed, reactions/comments, federation, route privacy per-section. Keeping scope tight.
- Rollback: flipping the default back to
privateeverywhere is a single UPDATE. Pages gating on visibility degrade to "auth-only" if we remove the public path.