feat(dialog): multiple recipients with per-recipient permissions - #258
Merged
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #258 +/- ##
==========================================
+ Coverage 73.93% 79.06% +5.12%
==========================================
Files 23 27 +4
Lines 564 726 +162
Branches 168 202 +34
==========================================
+ Hits 417 574 +157
- Misses 126 133 +7
+ Partials 21 19 -2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The dialog now accepts multiple recipients: the search field is an add-only picker and selected recipients render as a list, each with its own permission preset selector and fine-grained toggles. The share-level permission acts as the maximum and the default for newly added recipients. A recipient's available presets and toggles are capped at the permissions the share grants; toggles beyond the maximum are disabled. The backend enforces the real cap (the sharer's own permissions on a reshare, or the admin default). Adds per-recipient permission methods to the Share class and API client, and extends SharingRecipient with permission_preset + permissions. Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
Rework the per-recipient row to match the sidebar: avatar + name + a compact preset dropdown, and a three-dot menu that opens a small modal for full permission editing (preset + custom toggles) plus removal. Extract the preset selector and fine-grained toggles into a reusable PermissionEditor component, used both for the share-level default/maximum and inside the per-recipient modal. The editor shows an info notice when some permissions are capped: a reshare names the owner and the permissions granted, otherwise it states the share's maximum. Toggles above the maximum are disabled. A reshare is detected per recipient (its initiator differs from the share owner). Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
…ient row
- Guard recipient.permissions (the backend does not yet return per-recipient
permissions), which previously threw on an undefined .map().
- Recipient row: drop the inline preset select; show a static preset label
("Can view" / "Custom permissions"), and move preset shortcuts + custom
permissions (opens the modal) + remove into the three-dot menu.
- Hide the Invited/Anyone tab bar once a recipient (or the link) exists.
- Constrain the "Add people" field width.
Assisted-by: ClaudeCode:claude-opus-4-8
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
Set up Playwright component testing (@playwright/experimental-ct-vue) and cover the recipient/permission UI in a real browser: - PermissionEditor: notice visibility, per-permission toggles, over-cap toggles disabled, toggles hidden outside custom mode. - RecipientRow (via an in-browser fixture, since a Share with methods cannot be passed as a serialized prop): preset subtitle, remove from the menu, and the custom-permissions modal opening. - RecipientList: one row per recipient, excluding the link (token). Specs are named *.ct.ts so Vitest (which globs *.spec/*.test) ignores them. Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
- Move the Playwright component tests to tests/ct/ so they are outside the library's rootDir (they import fixtures outside lib, which broke the build and ts:check). - Add Share.setRecipientPermission / selectRecipientPreset unit tests. - Add SharePanel tab-bar tests (hidden once a recipient exists). - Rename the recipient remove action to "Remove participant" and add left padding to the recipient list. Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
…m Vitest Follow the repo convention (*.spec.ts) for the Playwright component tests and exclude tests/ct/ from Vitest so the two runners do not collide. Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
NcSelectUsers defaults to min-width: 260px and its focus box-shadow can spill past the dialog padding, making the "Add people" control wider than the dialog. Pin it to the container width (min-width: 0, max-width: 100%, border-box) like the preset select. Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
@nextcloud/vue's multiple select (.select.vs--multiple) renders ~2px too wide and overflows its container. Inset it by 1px on each side as a workaround until it is fixed upstream in @nextcloud/vue. Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
The multiple-select width is now fixed at the source in @nextcloud/vue (NcSelect), so the local margin-inline workaround is no longer needed. Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
9.11.0 includes the NcSelect multiple-variant width fix, so the recipient picker no longer overflows the dialog. Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
…ople The Invited/Anyone tabs disappeared as soon as a recipient existed, including the link's own token recipient, so they vanished right after picking "Anyone". Always show the tabs instead. Switching to "Anyone" now asks for confirmation when invited people are present, and removes them, since a public link cannot keep them. Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
nextcloud/server#63854 landed per-recipient permissions with a different shape than assumed, so align with it: - SharingRecipient exposes `permissions` only; there is no per-recipient preset field, so the selected preset is derived from the enabled permissions. - updateShareRecipientPermission sends recipientClass / recipientValue / recipientInstance / permissionClass / enabled. - There is no per-recipient preset endpoint: drop it, and apply a preset by toggling each permission to match it (skipping any beyond the share maximum). Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
@nextcloud/vue 9.11.0 insets the multiple select with margin-inline, which combined with an explicit width: 100% made the control 2px wider than the dialog and introduced a horizontal scrollbar. Letting the width stay auto lets the margins shrink it correctly. Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
Once a share has been sent its type is committed, so hide the Invited/Anyone tabs for an active share and keep them (with the confirmation when dropping invited people) while it is still a draft. Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
A recipient's permissions are a sparse override map: the share's permissions are the base (and the maximum), and a recipient entry overrides one of them. A freshly added recipient has no overrides at all. The dialog listed only the recipient's own entries, so a new participant showed no permissions and picking a preset had nothing to toggle (it did nothing). Overlay the overrides on the share's permissions instead, so recipients start from the share's permissions and presets apply correctly. Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
…modal The modal passed through the panel's "custom preset selected" flag, so the permission toggles were hidden whenever the recipient's permissions matched a preset — and disappeared mid-edit as soon as the toggles happened to match one. The modal is the custom-permission editor, so its toggles are always shown. Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
skjnldsv
force-pushed
the
feature/dialog-per-recipient-permissions
branch
from
September 2, 2026 12:32
8c445df to
e3e8a40
Compare
…ttings - Keep the copy/send actions pinned to the bottom of the scrolling form. - Add a (confirmed) delete action at the bottom of the settings view, pinned the same way; deleting closes the dialog. - Give the form a min-height so the dialog stops jumping around as its content changes. Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
- Give the sticky action bars an opaque background plus a top border so the scrolling content no longer bleeds into them. - Settings: the delete action is compact and aligned to the end instead of spanning the dialog. - Confirmation: use the success text colour so the checkmark stays readable in dark mode, and align "Done" to the end. - Rename the custom permission entry from "Can…" to "Custom permissions", as it is called elsewhere. - Mark the share's own preset as "(default)" in the per-recipient options, so hand-changed recipients are easy to spot. - Scroll the fine-grained toggles into view when the custom entry is selected. - Drop the tune icon from the recipient menu's custom permissions entry. Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
The dialog always started on the invited-people view. Since that view syncs the token recipient, opening an existing link share removed its token and so destroyed the public link. Derive the initial view from the share instead, and restrict the mount-time sync to adding a token so a mismatch can never delete one. Signed-off-by: Barthelemy Briand <barthelemy.briand@nextcloud.com> Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
…pient rows The confirmation dialog was mocked to always confirm, so nothing asserted that declining keeps the share and its recipients. Make the mocked answer settable and cover both directions, plus the removal a recipient row performs, which was only asserted through its event and not by the recipient it acts on. Signed-off-by: Barthelemy Briand <barthelemy.briand@nextcloud.com> Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
skjnldsv
marked this pull request as ready for review
September 2, 2026 14:37
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds multiple recipients to the sharing dialog and lets each recipient carry its own permissions.
The share-level permissions stay the default and the maximum: a new recipient inherits them, and a recipient can never be elevated above what the share grants. The backend enforces the real cap (the sharer's own permissions on a reshare, or the admin default); the dialog reflects it and explains it with a notice when something is not grantable.
What changed
(default).Testing
npm test— Vitest, including new coverage for the destructive paths: declining a confirmation must not delete the share or drop recipients, a failed deletion must not report success, and a recipient row must remove the recipient it renders.npm run test:ct— Playwright component tests for the recipient rows and the permission editor.sharing.unified_api_enableset: add several recipients, change one recipient's permissions, confirm the share max caps them, open an existing link share from the sidebar and confirm its link survives.AI disclosure
This pull request was written with AI assistance (Claude Code); every commit carries an
Assisted-bytrailer. The code was reviewed by me before submission.