Federated sharing UX: separate it from the share dialog and design its own flow #529
Labels
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: Cordy/Cairn#529
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
From the #389 real-phone pass (Nikola, 2026-09-14): the share dialog currently carries an "or send to a federated address" section alongside the link controls. Decision: sharing and sharing-with-federations should be separated from the initial interaction — clicking Share should not suggest federating.
We got Cairn Peering's UX nailed down (#105/#131/#134/#136) but never gave the OCM/federation send the same treatment; the current section is cramped (recipient field, permission select and Send button competing for one row — visibly broken at phone width) and conceptually muddles two different acts: minting a link vs addressing another instance's user.
Interim (shipped separately): the federated section is hidden in the share dialog; the OCM plumbing and the Federated view stay untouched.
Deep dive to do here (later):
feat.ocm? inside the Federated view? both?/api/v1/ocm/contactsalready exists — plus permission choice and clear failure surface for the allow-list error).Scope grows: the sharing model changes here, not just where federated send lives (Nikola, 2026-09-16).
Context for why this landed on this issue. While auditing the #550 Groups tab against its mockup, the "Used by" column could not count shares, and the reason was structural: Cairn has no group-addressed sharing. A share link points at a path and is never addressed to anyone; group access happens only through spaces, which are pairs of IdP groups. So an app-owned group can never be "used by" a share.
Decision: do not keep that divergence. Diverging from what a large userbase already knows creates confusion and a bad launch. Sharing should sit adjacent to how OpenCloud does it, and links get subdivided.
Target model
/s/surface: unauthenticated, optional password and expiry, the thing you send outside the organisation. OpenCloud's external sharing is the reference.Why this is worth the work beyond parity
Nextcloud's own documentation makes the argument for us: link shares are useful for one-time sharing but poor for collaboration, because everyone acting through a link is identified as "guest", whereas a user or group share keeps identity attached to every action in the activity view. Cairn's audit log is one of its selling points, and link-only sharing quietly undermines it: today, anything shared outside a space is attributable to a link, not a person.
Knock-on effects to design here
share.Storegains an addressee (user or group) alongside path-addressed links; authorisation then resolves throughUser.Groups, which since #550 already merges app-owned and directory groups — so app groups become share targets for free once the addressee exists.Mockup first, per house rules, covering: the reworked share dialog (people/groups + the two link kinds), where federated send lives, and the empty/permission states. i18n ×4.
Scope addition: the group "Used by" column depends on this rework
Carried over from #552 / #550. Decision taken 2026-09-16: keep the column, ship it thin, let this issue fill it in. No work in #552; the fix is a consequence of the model change described in the previous comment.
What the column is
#admin/users/groupsgives every group a Used by cell whose job is to answer one question before a destructive edit: what breaks if I rename or delete this group? Cairn already refuses a delete that would strand a reference (409 with the referencing names). The column is the preview; the 409 is the backstop. A refusal you could have seen coming is a worse experience than one you saw coming.Why it's currently thin
It lists peering refs, admin-group grants and space membership — but no share count, because Cairn's shares point at paths and are never addressed to a group. There is nothing to count. The #550 mockup promised a share count; the shipped column can't deliver one until sharing can address a group, which is exactly what this issue introduces.
So this is not a Used-by bug. It is the sharing model showing through.
What this issue must do about it
Once
share.Storegains an addressee (previous comment), the column gets wired:Findings — why the column is worth keeping at all
Nikola asked whether this is a gain or invented work. Researched 2026-09-16. No competitor surfaces this in the UI.
oc_sharetable directly. That is the documented workaround, not a feature.Conclusion: this is differentiator work, not parity work — one of the few places where we are not catching up but shipping something three competitors' users have asked for and not received. It also sits squarely in Cairn's positioning: the instance admin is accountable for what is on their own hardware, and "I can see what this group touches before I break it" is that accountability made visible.
The caveat to hold honestly: the column is only as valuable as the model beneath it. Until this issue lands it shows two real reference types and no shares, which is honest but thin. Do not market it before the count is real.
Sources
Mockup rev 1 + the constraint that shaped it
sharing-rework-529-mockup.htmldelivered for review. Two decisions were taken before drawing, because the second one changes what the mockup can even depict.The constraint: in Cairn the path decides who can decrypt, not the share
My earlier comment said "a group share must resolve to per-recipient keys the same way space membership does — worth checking against the custody model before committing to a design." Checked. The answer is that space membership works because the content lives in the space's path, and that does not generalise.
internal/storage/encrypt/group.goderives the key scope from the backend path: anything under/spaces/<name>/is encrypted to that space's group recipient, everything else to the owning user. Its own comment on cross-scope moves is explicit — a rename or copy across the boundary must re-encrypt, "otherwise the moved ciphertext keeps its old key and the destination's readers cannot open it".So adding an addressee to
share.Storegrants authorisation and changes nothing about decryption. Bob would be permitted to fetch bytes he cannot read. This is a real difference from OpenCloud and Nextcloud, where a share is a grant attached to a file wherever it sits.Decision 1 — grant authorises, path keys, server re-wraps on read
Three options were on the table: re-encrypt on every share (collides with legal holds #138, versioning #541 and quota, and rewrites content on every membership change); keep sharing as space membership only (the divergence already rejected); or let the grant carry authorisation while reads go through the owner's key scope — the same mechanism the public-link handler already uses for unauthenticated callers.
Chosen: the third. No re-encryption, no data movement, membership changes take effect immediately, and it is consistent with ARCHITECTURE §4.3, which states plainly that Cairn is server-side transparent encryption and not client-side E2E.
The consequence, stated rather than buried: an internal share means the server opens the owner's file on the recipient's behalf. That is already true of Cairn today, but "per-user encryption" invites a stronger reading, so the handbook gains a sentence saying what the encryption does and does not protect against. The same wording should serve #238, which is deciding this at the peering boundary.
Decision 2 — #529 lands before #538
#538's Shares panel renders whatever the sharing model is. Building it first means building it against a model that is about to change. #530's build order becomes #529 → #538 → #539/#540 → #541.
What the mockup covers
Today's dialog versus the reworked panel (people and groups first, then links split into internal — resolves for people who already have access, grants nothing — and public, today's
/s/surface); three role labels mapped onto primitives that already exist (read,write,AllowUpload) rather than a new permission system; federated send relocated to its own act with one entry that branches by transport; the handbook wording; and the states that decide whether it feels finished — nothing shared yet, public links disabled by policy, an addressee that no longer resolves (fail-closed, says so rather than vanishing), and the recipient picker on an instance with no directory.Four decisions I did not take
share.Store, and the federated-send relocation are separable and could ship in that order.Nothing implemented. Review and mark up as usual.
Mockup approved — split into three, this becomes the tracking issue
All four open decisions answered (Nikola, 2026-09-17):
Sub-issues
Build order #570 → #571, with #572 whenever convenient. Then #538 follows, per the earlier decision that its Shares panel must render a settled model.
One correction to my own mockup
The mockup's block 7 proposed splitting as "panel rework, then the addressee, then federated relocation, in that order". That order is wrong and I am not propagating it: the panel has nothing to render without the addressee. Backend first. The issue bodies carry the corrected order; the mockup file does not, so read the issues as authoritative on sequencing.
Decisions recorded for the record
The grant authorises; the path keys. An addressee cannot make a recipient able to decrypt —
encrypt/group.goderives the key scope from the backend path. The read goes through the owner's key scope, the mechanism the public-link handler already uses. Rejected: re-encrypt on every share (collides with holds #138, versioning #541, quota; rewrites on every membership change), and sharing-as-space-membership-only (the divergence this issue exists to close).The consequence is stated, not buried. A share means the server opens the owner's file for the recipient. Already true today, but "per-user encryption" invites a stronger reading, so the handbook gains a sentence — carried in #571, and it should serve #238 at the peering boundary too.
Starting on #570.
Both remaining gates are closed: #572 (the send act, narrowed to peering after the send/share split — the OCM share continues as #603) shipped in v0.6.216, and #598 (Used-by real share counts + fail-closed group delete) shipped in the same release. The full arc of this tracker — grants model (#570), share panel (#571, eyeballed live and closed), send act (#572), and the scope addition (#598) — is built, tested, released, and live on both dogfoods. Closing the tracker.