A public profile (`profile_visibility = 'public'`) with zero public routes or activities now returns 200 and renders the empty profile shell instead of 404. The earlier change kept the legacy "AND has public content" gate to preserve a no-existence-leak guarantee, but that conflated the implicit behavior with the new explicit setting. With profile_visibility now a deliberate toggle, the cleaner contract is: visibility = public means the profile renders, period. Followability already only depends on profile_visibility, so this brings the public-page contract in line with that. The `private` toggle remains the way to hide a profile from visitors. Updates social-feed change spec + design to match (drops the "AND has at least one public" predicate and the matching 404 scenario; adds an explicit "empty public profile renders an empty shell" scenario). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4.2 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 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 whenever the user's profile_visibility is public, even if they have no public routes or activities yet — the empty case shows the profile shell with empty section copy. 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
- WHEN an unauthenticated visitor navigates to
/users/:usernamefor a user whoseprofile_visibilityispublic - 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: Empty public profile renders an empty shell
- WHEN an unauthenticated visitor navigates to
/users/:usernamefor a user withprofile_visibility = 'public'but nopublicroutes or activities - THEN the page returns HTTP 200 and renders the profile header (display name, handle, counts) plus empty-state copy in the routes and activities sections
- AND the user is followable: the Follow button (for signed-in viewers other than the owner) is rendered
Scenario: Profile 404 cases
- WHEN a visitor navigates to
/users/:usernamefor a user withprofile_visibility = 'private'OR 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 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', 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_visibility = 'private', the page renders for them with an amber explainer banner noting that visitors see a 404 - 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