Repository navigation
Sharing: restructure the share UI to invite-first, with "Make public" secondary #59
Description
Activity
- addedparityWeb/API feature-parity workWeb/API feature-parity workP2DepthDeptharea:sharingArea: sharingArea: sharing
on Sep 15, 2026 - added a parent issue
on Sep 15, 2026 ⚠️ The role vocabulary in this issue is wrong — the API does not accept Read-only / Edit / AdminI wrote these issues from
/help/documents, which describes three permission levels as Read-only, Edit and Admin. Those are display names. The API rejects them. Verified live 2026-09-16 on a throwaway list:role=read-only -> 400 {"error":"Invalid role. Must be watcher, collaborator, or manager"} role=edit -> 400 {"error":"Invalid role. Must be watcher, collaborator, or manager"} role=admin -> 400 {"error":"Invalid role. Must be watcher, collaborator, or manager"} role=watcher -> 201 {"email":"…","role":"watcher","expiresAt":null,"url":"…/lists/invite/OG72S9Ee…"} role=collaborator -> 201 {"email":"…","role":"collaborator","expiresAt":null,"url":"…"} role=manager -> 201 {"email":"…","role":"manager","expiresAt":null,"url":"…"}So the wire values are
watcher/collaborator/manager, and any UI needs an explicit mapping to the documented labels:Wire value Help-doc label Can watcherRead-only view content only collaboratorEdit change content, but not rename, change public/private, or move managerAdmin also rename, switch visibility, move, delete Don't send the display names, and don't show the wire values to users.
Other live findings from #55/#56 (PRs #168/#169) that affect these issues
The invite envelopes are asymmetric, which forces a read-after-write:
POST …/invites -> 201 {email, role, expiresAt, url} ← url, NO token GET …/invites -> 200 {invites:[{email, role, expiresAt, accepted, createdAt, token}]} ← token, NO url DELETE …/invites/{token} -> 200 {"revoked":true}Revoke is keyed by token, which the create response doesn't return — so creating an invite must be followed by a
GETto obtain a revocable handle. Copy-link rebuilds the URL from the token.roleworks despite the published OpenAPI request schema omitting it — that schema lists onlyemail+expiresAt. Another case of the spec being thinner than reality;/help/api/sharingis right and the spec is wrong.Error shapes: invalid email →
400 "A valid email address is required"; unknown token →404 "Invite not found or access denied"(note: not-found and not-permitted are deliberately indistinguishable).Ownership envelopes differ between domains — lists nest the object under
data, documents underdocument; both carryuserId, which neitherListSummarynorDocumentSummarymodels.customerStatusvalues arefree/subscriber/subscriber:monthly/subscriber:annual— any non-freevalue grants access, so don't compare against the literal"subscriber".An undocumented
notify:falseon invite create is accepted (201) but not echoed back. It was deliberately not exposed in the UI because there's no way to confirm it actually suppresses the email — which is exactly what #58's "Email this person" toggle needs. Confirming that flag is the blocker for #58's notify toggle, and it needs a mailbox you control to verify.Still unverified for #57
The claim/accept side (
/api/{lists|documents}/invite/{token}) is cookie-session-only per the docs and needs a second verified account, so it is unimplemented. That's the substance of #57.
Product change to match
Per
/help/documents: "You no longer set Public with a separate toggle in the editor. Whether a document is public is now controlled entirely inside the Share window." The window leads with Invite people and offers Make public below, with a copy-link row appearing once public.The app currently exposes
isPublicon the document/list editor and treats share links as the primary sharing mechanism — the inverse of the product's current model.Acceptance criteria