Repository navigation
fix(vaults): gate the last silent private-to-team conversion (#68) - #220
Merged
Merged
Conversation
Settings -> Vaults was the one surface where picking a person silently created a team: `PrivateVaultMembersPanel.handleAdd` called `createTeamVaultFromVault` directly, so key custody moved to the server and the vault stopped opening offline with none of it announced, while the same act from the Members page and the share sheet showed the full cost list first. Picking someone now opens `ConvertToTeamGate` like the other two callers, and `createTeamVaultFromVault` is left with no un-gated caller. The invite that follows the conversion goes through a new shared `inviteUserWithRoles`, which is also what the two role-assigning invite panels now call. Both of them still carried the bug fixed in #179 for the Members page: `addMemberById` creates a pending invitation, not a member row, so a follow-up `assignMemberRole` 404s and the chosen role never reached the invitee. The first role now travels on the invitation and the rest are applied only for someone already a member. Also in `InviteBar`: an empty role selection fell back to `member`; it now falls back to the least-privileged assignable role, matching the invite panel. `settings.vaults.members.adding` has no remaining caller and is dropped from all four locales.
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 the residual that #71 handed to #68: the last private→team conversion that happened without asking.
The un-gated path
ConvertToTeamGatewas already wired intoMembersPageandVaultShareSheet, butPrivateVaultMembersPanel.handleAddinVaultsSection.tsxstill calledcreateTeamVaultFromVaultdirectly. So Settings → Vaults was the one surface where picking a person silently created a team, moved the vault's key custody to the server and ended offline access — while the same act from the other two surfaces showed the full cost list first.Picking someone there now opens the same gate, and the invite runs only after consent.
createTeamVaultFromVaultis left with no un-gated caller.The #179 role bug had two more copies
Both invite handlers in
VaultsSection.tsxstill carried the shape fixed for the Members page in #179:addMemberByIdcreates a pending invitation, not ateam_membersrow, so the follow-upassignMemberRole404s — and both swallowed it with.catch(() => {}). Every invitee from Settings → Vaults silently landed on the default role.All three callers now go through one shared
inviteUserWithRolesinservices/vaultShare.ts: the first chosen role travels on the invitation, the rest are applied only for someone already a member.Two smaller things in the same file, both matching what the Members page already does:
member; it now falls back to the least-privileged assignable role. An empty selection is a lost choice, not a request for more access.inviteRoleswas a third hand-rolled copy ofassignableRoles().settings.vaults.members.addinghas no remaining caller and is dropped from all four locales.Verified against a running server
Driven in the app against an isolated server (self-host mode, three accounts), not only in tests:
/v1/teamsand 0 rows inteams— the old code would already have created the team.Pending invitation created for existing user … role=member.connect-onlyticked, the invitation row carriedrole=connect-only.Final invitation rows:
bob → member,carol → connect-only.Tests
vaultShare.test.tscovers the shared helper (first role on the invitation, extras only foralready_member, unknown or empty selection falling back to least-privileged). The private-panel tests now assert the gate: nothing converts before consent, cancel converts nothing and invites nobody, confirm converts then invites, a failed conversion keeps the gate up, and a failed invite still hands the caller the team the conversion created — otherwise the next attempt would convert an already-converted vault into a second team.tsc clean;
src/components/members,src/components/vault-share,src/components/settings,src/services,src/i18nall pass (158 files, 1322 tests).Noted, not fixed
TeamVaultPanel— the component holdingInviteBar— has no render site in the app; only its own test file imports it. That copy of the bug is therefore not user-reachable today. Deleting the dead component felt out of scope here.Still open on #68: the share sheet / explicit Share verb, and vault invite links.
🤖 Generated with Claude Code
https://claude.ai/code/session_014gY5k7XbeR5zoTXdzsuWxF