fix(share): explicit consent for private→team vault conversion, and make the chosen role reach the invitee - #179
Merged
Merged
Conversation
…e chosen role reach the invitee Adding one person to a private vault used to create a team, link the vault, initialise a vault key and convert the vault to cloud-synced — with no dialog and no explanation. Inviting someone now opens a modal stating the three costs before anything happens: no offline access, members hold a copy of the vault key that removal does not take back, and no way back to private. PrivateVaultInvitePanel is deleted. A private vault goes consent → convert → the existing team invite panel, which removes the second invite panel without changing the Members page layout. Bugs fixed, all found by driving the running app against a live server: - The chosen role never reached the invitee. addMemberById creates a pending invitation, not a member row, so the follow-up assignMemberRole 404s — and an empty catch swallowed it, leaving every invitee on the default role. The role now travels on the invitation; role assignment runs only for an existing member. - Failed invites reported success: the handler caught every error, then cleared the typed query and told its parent an invite had happened. - Seats rendered "? available · ? total" when the subscription load failed. - Text on accent backgrounds used a hardcoded #08151b, unreadable under a dark accent; now uses --t-on-accent, computed from accent luminance. - The vault header's member stack was hover-only with cursor-default, unreachable by keyboard and on touch; it is now a focusable button with aria-haspopup/aria-expanded and Enter/Space. The + affordance is unchanged. - markTeamVaultLoadedAfterLocalActivation was imported from the wrong module; the test mocked the whole path, so it passed while production would have thrown. Refs #68
kipavy
force-pushed
the
feat/vault-share-sheet
branch
from
August 25, 2026 10:55
50551b4 to
8392f60
Compare
…e selected Two live-verified defects in the invite panel: - A transport failure (fetch/reqwest rejected before any HTTP status came back) propagated its raw message straight into the panel's error text, e.g. "error sending request for url (http://host:8080/...)". Both call sites now render the named members.error.inviteFailed message; a new inviteFailureReason() helper in vaultShare.ts collapses any URL-bearing message to a translated "sync server could not be reached" reason, reused by the toast formatter too so it doesn't leak there either. The dropdown now closes when an error is set, since it was rendering on top of the error text. - Unticking every role chip left `selectedRoleIds` empty, and the code silently fell back to "member" — not the least-privileged role. The invite and email-invite actions are now disabled with an inline hint until a role is picked; a new leastPrivilegedRole() in vaultShareModel.ts is kept as a defensive fallback in the invite path, never relied on by the UI.
4 tasks
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.
Closes part of #68.
What this does
Replaces the silent private→team vault conversion with an explicit consent moment, and fixes three real defects found while driving the running app against a live server.
The conversion is no longer a side effect
Adding one person to a private vault used to create a team, link the vault, initialise a vault key and convert the vault to cloud-synced — with no dialog and no explanation. Inviting someone now opens a modal that states the three costs before anything happens:
PrivateVaultInvitePanelis deleted. A private vault now goes consent → convert → the existing team invite panel, which removes the second invite panel without changing the Members page layout.Bugs fixed
The chosen role never reached the invitee.
addMemberByIdcreates a pending invitation, not a member row, so the follow-upassignMemberRole404s — and it was swallowed by an empty catch. Every invitee silently landed on the default role. The role is now passed toaddMemberById, which the server stores on the invitation; role assignment happens only for someone who is already a member. Reproduced against a live server before fixing:Failed invites reported success. The invite handler caught all errors, then unconditionally cleared the typed query and told its parent an invite had happened. On failure the query now survives so the user can retry, and the named error toast is the only report.
Seats rendered
? available · ? totalwhen the subscription load failed. There is now an explicit unavailable state.Smaller fixes
#08151b, which is unreadable under a dark accent. Now uses--t-on-accent, which is computed from accent luminance.cursor-default— unreachable by keyboard and on touch. It is now a real focusable button witharia-haspopup/aria-expandedand Enter/Space handling. The+affordance itself is unchanged.markTeamVaultLoadedAfterLocalActivationwas imported from the wrong module; the test mocked the whole path so it passed while production would have thrown.Not in this PR
Verification
Full suite: 508 files, 3911 tests, exit 0.
tsc --noEmitexit 0.Driven end to end in the headless build against a throwaway server: consent → convert → invite, with the invitation's role confirmed in the database as the role chosen in the UI rather than the default.