Team vault: unlock when the key lands, name the owner in the waiting copy, role-aware landing - #186
Merged
Merged
Conversation
This was referenced Aug 26, 2026
Open
kipavy
force-pushed
the
feat/vault-join-first-access
branch
from
August 26, 2026 11:53
c147246 to
de18fc1
Compare
…e owner Closes the parts of #70 that survived review. The keyless mechanism and the benign waiting panel shipped with #41; what was missing was that the wait never ended on its own, and that the copy could not say who it was waiting for. - `put_vault_keys` notifies every key recipient with `membership_changed`, but the joiner is already a member by then, so the membership delta is zero, `onTeamAdded` never fires, and nothing re-read the key that had just landed. The honest waiting state was therefore also a permanent one until the user hit Retry or restarted. Any team still in `awaiting_key` is now re-fetched on that event. Foreground fetch on purpose: `{ background: true }` suppresses every status write, including the "loaded" a team with no blob yet reaches, which unlocks the vault in memory while the panel keeps saying "waiting". - The waiting copy names the owner — "Waiting for @handle to grant access — they need to come online once" — and falls back to the existing generic sentence while the roster is unresolved rather than flashing a blank name. The panel loads the roster itself, since it stands in for the pages that otherwise would. - A member landing in a team vault for the first time gets the surface their role can use, chosen by permission bits rather than role name: a connect-only invitee lands on connections, not a keychain of redacted rows. #70 also asked for a welcome card on first access. Deliberately not built: the waiting panel already promises this exact outcome, so the vault opening is the promise being kept rather than an unexplained event, and every fact such a card would carry — vault name, sharer, role, contents — is already in the vault header, the Members tab and the list behind it.
kipavy
force-pushed
the
feat/vault-join-first-access
branch
from
August 26, 2026 12:44
de18fc1 to
b2865db
Compare
Contributor
Author
|
Rebased onto Two accounts, two app instances, isolated server + DB.
Full suite on the rebased branch: 512 files / 3941 passed, 2 skipped. The one failing suite was |
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 parts of #70 that survived review. Rebased-in-place: an earlier revision of this PR carried a first-access welcome card; it was cut after review, see the bottom.
What was already built
#41 shipped the mechanism and the first cut of the keyless UX, and none of it was rebuilt here: the
awaiting_keystatus, the clock-icon sidebar badge, the dedicated waiting panel with its retry button,reconcileAwaitingKeys,reconcileTeamVaultKeysand theteam_members:<id>SSE handler all already worked.The bug this fixes
put_vault_keys(server/src/routes/team_sync.rs) notifies every recipient of a freshly wrapped key withmembership_changed. The joiner is already a member when that fires, sohandleMembershipChangedEventcomputes a zero delta,onTeamAddednever runs, and nothing re-read the key that had just landed.So the benign, self-healing waiting state was in practice a permanent one: the vault stayed locked until the user hit Retry or restarted the app.
refreshAwaitingKeyTeams()now re-fetches any team still inawaiting_keyon that event.It is a foreground fetch on purpose.
{ background: true }suppresses every status write — including theloadedthat a team with no blob yet reaches — so the vault unlocked in memory while the panel kept saying "waiting". That was observed live before it was fixed, which is the only reason it is called out here.The other two changes
The waiting copy names the owner.
waitingForAccessBodyNamed: "Waiting for @handle to grant access — they need to come online once." The panel loads the roster itself, because it stands in for the pages that otherwise would, and falls back to the existing generic sentence while the handle is unresolved rather than flashing a blank name.Role-aware landing.
firstViewNavreads permission bits, not role names, so custom roles are covered too:CONNECTlands on connections,VIEW_SECRETSon the keychain,MANAGE_MEMBERSon members. Applied when a joined vault loads, and when a vault unlocks under the waiting panel the user is actually looking at — never from a background event for a vault that is off screen, which would yank the nav out from under them.selectedTeamIdwas extracted out ofMainPanel's hook so the "which team is on screen" rule has one definition rather than two.Verification
Two accounts, two app instances, an isolated server + DB (
testing-voltius-team-features-e2e), screenshotted from the running app at each step:GET .../vault-key → 404.Vault keys upserted key_count=2→ Bob's window, untouched, flips to the loaded vault. No manual retry. This is the path that did not work before.Full suite green: 511 files / 3934 tests.
tsc --noEmitclean.Copy I cannot personally review
One new string per locale,
layout.mainPanel.teamVault.waitingForAccessBodyNamed, in fr / ru / zh. fr and ru deliberately avoid a gendered verb for the owner ("une seule connexion de sa part suffit" / "достаточно один раз выйти в сеть").What #70 asked for and this deliberately does not build
A welcome card on first access. The waiting panel already promises this exact outcome ("this happens automatically the next time an owner is online"), so the vault opening is the promise being kept, not an unexplained event that needs narrating. And every fact the card would carry is already on screen: vault name, "team" and member count in the header, role in the Members tab, contents in the list the card would have been covering. It is not worth a persisted store, an entry in the account-park list and 28 locale strings. Recorded on #70 as deliberately-not-built.
Still open on #70
GET vault-keyrequiresPERM_VIEW_SECRETS, whichconnect-onlylacks, so an empty team vault 403s and the client renders "Access revoked" to someone never revoked.