Skip to content

Epic: rethink sharing and visibility (private/friends/public) #272

Description

@ALeonard9

Story

As a druthers user, I want to choose exactly who sees each of my lists — nobody, my friends, or everybody — and to share any of them in one click, so that druthers is something I can show off without giving up control of it.

This is the tracking epic for a ground-up rethink of sharing and visibility. Individual work lands in the child issues below; this issue holds the decisions they all depend on.

Architecture decisions

Visibility becomes a tier, not a boolean. The eight Boolean columns on DbUser (public_moviespublic_watchlist_games) become eight enum columns valued private | friends | public, plus a ninth for the profile itself. Migration maps True → public, False/NULL → private.

The profile tier must be at least as open as the most-open shelf tier (public > friends > private). If any shelf is public, the profile is public. If the most-open shelf is friends-only, the profile may be friends-only or public. Enforced server-side, surfaced in the settings UI. A handle remains required for anything non-private.

Two distinct social relations, not one:

  • friendships — mutual, request/accept/decline/unfriend, added by exact handle only (no searchable user directory, so the user list can't be enumerated). This is what the friends tier resolves against.
  • follows — asymmetric, no approval, permitted only against profiles that are public. Followers see public content only; following never grants the friends tier.

/v1/public/{handle} becomes viewer-aware. Optional authentication: anonymous callers see public shelves; an authenticated accepted friend additionally sees friends shelves. Unknown handles and fully-private profiles continue to 404 identically, so the endpoint never confirms a private account exists.

Sharing is independent of privacy. The share affordance is present on every shareable page regardless of visibility settings. What changes is the target: the copied/shared link points at the user's own profile when that profile is public, and at the landing page otherwise.

Two rendering paths for social, deliberately. Server-rendered OG images (next/og ImageResponse) so pasted links unfurl on platforms that scrape; the existing canvas-rendered PNG stays for Instagram/Stories, where the user uploads an image by hand.

Countries is removed entirely — all three repos, tables dropped. It was never in the API's SHELVES registry, so it has no visibility story, and it is out of product scope.

Child issues

Phase 0 — clear the deck. Must land first: every later issue touches SHELVES or the pages countries occupies.

Phase 1 — the model (api). Gates all of Phase 2.

Phase 2 — settings & profile (web).

Phase 3 — list depth. Independent of Phase 4; both unblock after Phase 1.

Phase 4 — sharing (web).

Phase 5 — filed, not scheduled.

Channel companions — filed so the web-first gap is tracked, not rediscovered.

Downstream — depends on this epic, not part of it.

Context

Raw capture:

I'd want to be able to share my Top 25 or Watchlist Publicly or with specific users (friends). I'd want to easily click a button from my Top 25 to copy a link to my profile for friends or publicly. I'd also want a clearly labeled other option to share to social media (Facebook/ Instagram/ etc.) Well formatted, optimized for social media. This button should be always accessible and unrelated privacy settings, other than a link should populate to the users profile if it is public. A 9th setting for the public landing page (top 5 for each category).

All pages that have the possibility to be public or shared with friends should have sensible options for how it is presented.

I envision a clear settings page, with Radials that let me enable visibility for each domains top 25 and watchlist for private, public or friends.

I'd want the ability to share a good duel on social media, pitting the two options in a cool visual. All social media shares should link to the landing page by default, optionally link to the users profile.

The ranking page should have different lengths. Default to 25, but should stretch to 100+ items.

Decisions taken during grooming:

  • Friends is a full mutual friend graph, built now, phased across issues — not deferred and not replaced by an unlisted-link tier.
  • Following public profiles is additionally in scope; a friends/follows activity feed is explicitly "down the road" (Phase 5).
  • The ninth setting governs /u/<handle>, which is rescoped to a clone of the signed-in home page minus the activity and schedule boxes. The existing per-shelf sub-pages at /u/<handle>/<category> and .../watchlist are kept and linked from it.
  • List length is viewer-controlled and remembered per signed-in user, and must not break on a 2,000-item shelf.
  • Duel sharing supports both an open "A or B?" matchup card and a decided-verdict card, user's choice. Replies live on the social platform — there is no in-app voting.
  • Countries: full removal across api/web/mcp, tables dropped.

Estimate

  • Recommended model: Opus 4.8 — spans three repos, a schema migration, a new authorization surface, and cross-cutting product logic.
  • Human effort: XL — multiple product and design decision points across five phases.

Notes for Implementation

Phases are ordered by dependency. Phase 0 must land first: every later issue touches SHELVES or the pages countries currently occupies. Phase 1 issues are the gate for all of Phase 2. Phases 3 and 4 are independent of each other and can run in parallel once Phase 1 is done.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:sharingSocial sharing / share cardspriority:p1Do firststoryUser story groomed from the Druthers Backlog (Todoist)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions