Proposal + design + specs + tasks for the social layer. Adds a `follows` relation (local + federated via ActivityPub), Follow buttons on public profiles, and a `/feed` route showing public activities from followed users. Capabilities: - New: social-follows (the graph + /feed) - Modified: public-profiles (Follow button + counts) - Modified: journal-landing (Feed link on signed-in dashboard) Design picks: - Pull-based feed (no fan-out-on-write) — fine at trails.cool scale - follows keyed by actor IRI, local denorm FK for join perf - Fedify wires Follow/Accept/Undo; we handle the DB side - Remote activity ingestion via polling (not push) — tolerates our downtime without losing activities Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 KiB
ADDED Requirements
Requirement: Profile visibility setting
Every user SHALL have an explicit profile_visibility of public or private. New accounts SHALL default to public. Users SHALL be able to change their profile visibility from account settings at any time.
Scenario: Default for a new account
- WHEN a user registers
- THEN their
profile_visibilityispublic
Scenario: Existing user backfilled to public
- WHEN the migration that introduces
profile_visibilityruns against pre-existing rows - THEN every existing user's
profile_visibilityis set topublic, preserving current effective behavior
Scenario: User toggles profile to private
- WHEN a user changes their profile visibility to
privatein settings and saves - THEN subsequent requests to
/users/:usernamereturn HTTP 404 (regardless of how much public content they have), and they become unfollowable (existing follow rows are unaffected, but no new follows can be created)
Scenario: User toggles profile back to public
- WHEN a previously-private user switches
profile_visibilitytopublicand saves - THEN their
/users/:usernamebecomes reachable again (subject to the existing "has public content" gate) and Follow buttons reappear for visitors
MODIFIED 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. The page SHALL render only when the user's profile_visibility is public AND they have at least one public route or activity. For signed-in viewers other than the owner, the page SHALL display a Follow / Unfollow toggle that mirrors the current follow relation (see social-follows spec). The page SHALL also display follower and following counts with links to the respective collections.
Scenario: Logged-out visitor views a public profile with public content
- WHEN an unauthenticated visitor navigates to
/users/:usernamefor a user whoseprofile_visibilityispublicand who has at least onepublicroute or activity - THEN the page renders that user's display name (falling back to username), the
@username@domainhandle, follower and following counts, and a reverse-chronological list of theirpublicroutes andpublicactivities - AND items marked
unlistedorprivatedo NOT appear in the list
Scenario: Profile 404 cases are indistinguishable
- WHEN a visitor navigates to
/users/:usernamefor any of: a user withprofile_visibility = 'private', a user withprofile_visibility = 'public'but zero public items, a user whose content is allprivateorunlisted, or a username that does not exist - THEN the server responds with HTTP 404
- AND the response does NOT distinguish the cases, so existence of a private account is not leaked
Scenario: Owner sees their own profile
- WHEN a user navigates to their own
/users/:usernamewhile logged in - THEN if their
profile_visibility = 'public'and they have at least one public item, the page renders exactly the same as for a logged-out visitor, plus a small owner-only control strip linking to settings - AND if their profile would 404 for visitors (private or no public content), they are redirected to settings or shown an owner-only "your profile isn't public yet" view (implementation detail)
- AND no Follow button is shown (users cannot follow themselves)
Scenario: Signed-in viewer sees a Follow control on a public profile
- WHEN a signed-in user other than the owner loads a profile that returns 200
- THEN the page renders a Follow button if no follow row exists for them against this user, and an Unfollow button if one does
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