镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

feat(builderlab): offer restoring the account's backed-up key on mismatch - #2458

Draft
kalvinnchau wants to merge 3 commits into
mainfrom
feat/restore-backed-up-key
Draft

kalvinnchau wants to merge 3 commits into
mainfrom
feat/restore-backed-up-key

Conversation

@kalvinnchau

@kalvinnchau kalvinnchau commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Problem

If a Builderlab account is linked to key Y (which is backed up) and this device holds a different key X, there is no way to switch the device to Y. Native recovery refuses while a key is present (identity.require_missing(), called from nsec_restore_begin), and Settings → Identity has no Builderlab session to detect the mismatch.

Change

The Builderlab Hosted communities card already detects the mismatch, with or without backup consent. When Y is in the account's backup list, its mismatch section now offers Open Identity to sign out:

  • communities/native.ts: read-only backups action → /v1/buzz/nsec-backups/status, sent only when nsec_backup_state.builderlab is true (the same gate as first run's backupOfferedFor). Elsewhere it lists no keys.
  • communities/api.ts: backedUpKeyList, first run's all-or-nothing key list parsing, now shared by first run (builderlab-account.ts) and the card.
  • HostedCommunities.tsx: reads the list while mismatched. If it contains the bound key, the section shows copy and a button that calls onRestore. A failed read or malformed list hides the option.
  • index.tsx: on desktop, onRestore opens Settings → Identity. The existing Sign out dialog makes the user reveal or copy X and confirm "I have my key" before signing out.
  • After restart, the user signs in with the same Builderlab account; first run finds it linked to backed-up Y and starts recovery. No native or first-run changes.
  • docs/buzz-help.md: one sentence describing the option.

Non-goals: live key swap without sign-out, auto-opening the Sign out dialog from the plugin, and a browser-host route (restore is desktop-native only).

Validation

  • New tests: the card shows the option (case-insensitive key match) and hides it when only the device key is backed up, the list has a malformed entry, or the read fails. The native backend sends no status request unless builderlab is true, and POSTs {} when it is.
  • vitest run src/bundled/builderlab/communities src/bundled/builderlab/index.test.tsx src/features/identity: 24 files, 640 tests pass. tsc --noEmit and biome check are clean.

Not yet done: the full sign-out → restart → Welcome back → Restore flow in the running app, and a visual check in both themes and at narrow and wide widths.

kalvinnchau and others added 3 commits November 4, 2026 10:16
…atch

when the builderlab account is linked to a different key than this device
and that key is backed up, the identity mismatch section offers restore
backed-up key. it opens settings -> identity; signing out returns to first
run, where welcome back restores the linked key. adds a read-only
nsec-backups/status route to the native backend; a failed read hides the
option.

Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
gate the mismatch restore on the native uploader's builderlab backup flag,
as backupOfferedFor does for first run, and share first run's all-or-nothing
backup key list parsing (backedUpKeyList). rename the action to open identity
to sign out and describe the automatic restore after builderlab sign-in.

Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
capture the session credential before awaiting nsec_backup_state and check it
afterwards, as bind does, so a sign-in that replaced it is never read.

Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
matt2e added a commit that referenced this pull request Nov 9, 2026
…s the next step (#3082)

## Why

Identity sync names Builderlab in two of its sentences: when this key
isn't linked to the account (waiting `not_linked`) and when the account
already holds a different key (failed `other_key_linked`). The next step
for both, **Connect Buzz identity** or **Switch to this device's
identity**, is on Settings → Plugins → Builderlab, but the row had
nothing to click. People had to find that page themselves.

## What

In those two sentences the first "Builderlab" becomes a
`SettingsSectionLink` to `block.builderlab/login`, the shared inline
link from #2122. Settings passes the opener only while the Builderlab
card is registered and reuses `openSection`, so focus moves into the
opened section as it does from Decisions API. Without an opener, or in
any other state, the sentence stays plain text. The status text itself
is unchanged; only the name becomes a link.

Deliberately unlinked:

- `no_session`: Builderlab signs in with the build's NIP-FI session, and
that Organization sign-in is on this same Identity page.
- `no_consent`: its next step is the row's own **Sync with Builderlab**
button; Builderlab's page has nothing for it.
- Retryable failures and `signed_out` clear on a new sign-in. The
remaining failures have no fix on Builderlab's page.

The linked states arise only after the user opted into automatic backup:
`nsec_backup.rs` admits consent (`NoConsent`) before it reads the
account's key (`NotLinked`, `OtherKeyLinked`). The link only navigates.
Connecting on Builderlab's page starts a backup the user already agreed
to.

## How

`BackupRow` takes an optional `openBuilderlab` and splits the existing
feedback sentence around the first "Builderlab" for the two states.
`Settings` derives the opener from `openSection` and the contributed
card list. Two files of production change, about 40 lines, plus tests
and the help guide and identity doc.

## Risk

Low. No native, protocol, or persistence changes. The shared link style
makes the Identity sync row about 2 px taller in the linked states.
Known gap, shared with the existing Sync button: a status change while
the link has focus drops focus to the page.

## Testing

- `pnpm typecheck` and biome pass on `01c105cd5`.
- `IdentitySettings.test.tsx` and `Settings.test.tsx`: 124 tests pass.
With main's `IdentitySettings.tsx` and `Settings.tsx` swapped in, three
fail: the two "links Builderlab from the … sentence to its Settings
page" cases and "opens Builderlab from Identity sync only while its card
is registered". The negative cases (`no_session`, `no_consent`, idle,
running, complete, every other failure kind, and no opener) pass on
both, as intended.
- The Settings test removes the Builderlab card while Identity is open
and asserts the link goes in place with no section switch, as well as
the mount-time gate. A `BackupRow` that reads its opener once (`useRef`)
passes the old test and fails this assertion.
- The before/after recording below exercises the real `Settings`
component in headless Chromium.

Not verified: a native FI build, both themes, narrow widths, enlarged
text, and keyboard navigation in the real app.

## Before and after

The same pointer script runs on origin/main (left) and this branch
(right): click "Builderlab" in the Identity sync sentence, go back to
Identity, switch the backup status from waiting `not_linked` to failed
`other_key_linked`, click "Builderlab" again.


https://github.com/user-attachments/assets/04a67ba8-6374-4226-ae24-d8222287f861

![BEFORE, just after the first click: the cursor is on the plain-text
"Builderlab", and Identity is still highlighted and
shown.](https://github.com/user-attachments/assets/24895d72-57e3-430c-a7d1-b065dc1567f2)

![AFTER, the same moment: Plugins → Builderlab is highlighted in the
side menu and its section shows Connect Buzz
identity.](https://github.com/user-attachments/assets/7dd687f0-b496-467f-90d0-56b8ea16b4b9)

**Real:** the `Settings` component, the side menu, the link, the section
switch and the focus move. **Faked:** the backup status, and a fixture
Builderlab card that renders the real `Login` and `HostedCommunities` on
fake sign-in and account data, since the plugin registers its card only
with a native session or the live development server. Recorded in
headless Chromium with Playwright, not a native build.

## Bigger picture

Overlaps with #3069, which deletes `BackupRow` and moves backup state
into a key-card notice, and rewrites the same paragraphs in
`docs/buzz-help.md` and `docs/identity.md`. Whichever lands second
reconciles. If this lands first, #3069 can either carry `openBuilderlab`
and the card gate into the notice, splitting the period-less
`not_linked` and `other_key_linked` strings, or drop the prop and its
tests on the grounds that its plugin-contributed `BuilderlabIdentityRow`
already links Builderlab from Identity. If #3069 lands first, this
becomes a re-implementation in the notice, and the "only button in the
row is the link" assertion has to allow for **Retry backup**.

#2458 links the other direction: Builderlab's Hosted communities card
opens Settings → Identity when the account's backed-up key differs from
this device's. It is complementary.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: mtoohey <mtoohey@squareup.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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