Commit graph

1 commit

Author SHA1 Message Date
Till JS
ca2809da89 docs(plans): visibility-system — unified 4-tier privacy model (planning)
Write up the design for a repo-wide visibility layer before building. Today
the state is fragmented: 7 modules carry ad-hoc isPublic booleans (picture,
cards, presi, memoro, times, broadcast.audience, uload.tags) with
inconsistent semantics and mostly no UI; the majority of modules (library,
calendar, todo, places, events, recipes, goals, habits, quiz, wardrobe,
invoices-clients, …) have nothing. Spaces only carry member permissions,
no public tier. The existing encryption layer (27 encrypted tables) is not
a blocker — embeds.ts already demonstrates "decrypt client-side, inline
plaintext into the publish snapshot".

Design:
- @mana/shared-privacy package with `VisibilityLevel = 'private' | 'space'
  | 'unlisted' | 'public'`, a `<VisibilityPicker>` svelte component, and
  predicate helpers (canEmbedOnWebsite, isVisibleToSpaceMember, …)
- Per-record `visibility text not null default 'private'` on public-capable
  tables only; `unlistedToken`, `visibilityChangedAt`, `visibilityChangedBy`
  alongside. Field stays plaintext so RLS + publish resolvers can read it
  without the user's master key
- Default-per-space-type: personal → private, team/club → space. Never
  public/unlisted by default
- Embed resolvers gate hard on `canEmbedOnWebsite`; user filters (tags,
  status, date window) stack on top, never replace
- RLS predicate extended: `space_member OR visibility='public' OR
  (visibility='unlisted' AND unlisted_token matches header)`

Rollout (soft-first / hard-follow-up per existing migration convention):
M1 shared package · M2 library (pilot) · M3 picture (replaces isPublic) ·
M4 calendar + todo + goals · M5 places/events/recipes/habits/quiz/wardrobe
/invoices · M6 legacy-flag consolidation · M7 /settings/privacy overview
+ kill-switch · M8 (optional) unlisted share links.

Out of scope: per-user sharing, field-level visibility, visibility
cascading, time-boxed public, search-indexing by default. Documented
explicitly so the first implementer doesn't reopen these.

No code yet — waiting on user go-ahead before starting M1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 23:50:17 +02:00