Skip to content

fix(vaults): gate the last silent private-to-team conversion (#68) - #220

Merged
kipavy merged 1 commit into
devfrom
fix/gate-settings-vault-convert
Sep 8, 2026
Merged

kipavy merged 1 commit into
devfrom
fix/gate-settings-vault-convert

Conversation

@kipavy

@kipavy kipavy commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Closes the residual that #71 handed to #68: the last private→team conversion that happened without asking.

The un-gated path

ConvertToTeamGate was already wired into MembersPage and VaultShareSheet, but PrivateVaultMembersPanel.handleAdd in VaultsSection.tsx still called createTeamVaultFromVault directly. 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. createTeamVaultFromVault is left with no un-gated caller.

The #179 role bug had two more copies

Both invite handlers in VaultsSection.tsx still carried the shape fixed for the Members page in #179: addMemberById creates a pending invitation, not a team_members row, so the follow-up assignMemberRole 404s — 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 inviteUserWithRoles in services/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:

  • An empty role selection fell back to member; it now falls back to the least-privileged assignable role. An empty selection is a lost choice, not a request for more access.
  • inviteRoles was a third hand-rolled copy of assignableRoles().

settings.vaults.members.adding has 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:

  • Picking a person in Settings → Vaults showed the consent modal with 0 requests to /v1/teams and 0 rows in teams — the old code would already have created the team.
  • Cancel left both at zero and added nobody.
  • Confirm converted, then logged Pending invitation created for existing user … role=member.
  • From the Members page invite panel with only connect-only ticked, the invitation row carried role=connect-only.
  • Zero 4xx/5xx responses across the whole run — no doomed role assignment on either path.

Final invitation rows: bob → member, carol → connect-only.

Tests

vaultShare.test.ts covers the shared helper (first role on the invitation, extras only for already_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/i18n all pass (158 files, 1322 tests).

Noted, not fixed

TeamVaultPanel — the component holding InviteBar — 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

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.
@kipavy
kipavy merged commit a5e8771 into dev Sep 8, 2026
4 checks passed
@kipavy
kipavy deleted the fix/gate-settings-vault-convert branch September 8, 2026 00:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant