Skip to content

feat(settings): read-only Key backup row in Settings → Identity - #1821

Open
matt2e wants to merge 1 commit into
onboarding/07-welcome-first-runfrom
onboarding/08-settings-key-backup-row
Open

matt2e wants to merge 1 commit into
onboarding/07-welcome-first-runfrom
onboarding/08-settings-key-backup-row

Conversation

@matt2e

@matt2e matt2e commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Onboarding stack (merge in order, top to bottom):

  1. fix(navigation): run the visit deadline only while a presenter is attached #1814 navigation: visit deadline only while a presenter is attached
  2. refactor(communities): extract the join owner and add the pre-join relay lookup #1815 communities: join owner and pre-join relay lookup
  3. feat(identity): observable nsec backup status with pass tickets, explicit lock release, and refused-recovery copy #1816 identity: native nsec backup status
  4. feat(builderlab): tenant table — a login origin per hosted relay, granted natively and accepted by the binding signer #1817 builderlab: tenant table and granted native origins
  5. feat(identity): first-run Builderlab account client — sign-in without a plugin session, kgoose reads, link/unlink, backup, saved communities — and its dev seed #1818 identity: first-run Builderlab account client and dev seed
  6. feat(identity): first-run setup store, step table and pages (composition follows in the next PR) #1819 identity: first-run setup store, step table and pages
  7. feat(identity): the first-run Welcome flow replaces the key-setup gate (FOUNDATION: App.tsx) #1820 identity: Welcome flow replaces the key-setup gate
  8. feat(settings): read-only Key backup row in Settings → Identity #1821 settings: read-only Key backup row ← this PR
  9. [DO NOT MERGE] chore(identity): DO NOT MERGE — isolated debug Keychain slot (full stack for native testing) #1822 DO NOT MERGE: isolated debug Keychain slot (full stack for native testing)

Depends on: PR 03 (backup.ts types, backup-failure.ts, identity.backup) and PR 05 (backupOfferedFor) only. It is based on 07 to keep the stack linear; it does not use 06/07 and can be reviewed first.

Diff: production +72/−14; tests +141/−3; docs +10/−0

What this layer is

BackupRow reads identity.backup via useSyncExternalStore, one sentence per status (first run's BACKUP_FAILURES copy, with a "Buzz tries again when you next sign in to Builderlab." tail for causes a new sign-in clears), hidden unless backupOfferedFor(backup) holds. No consent or retry control.

Review focus

  • One copy table: the failure sentences come from backup-failure.ts, the same entries first run shows; only the Settings tail differs. This layer adds the table's cause field (the sentence without first run's instruction) that BackupRow reads, since it is the first caller.
  • Visibility rule shared with first run (backupOfferedFor): Settings passes no origin because its sign-in is always at the build portal; any error hides the row.
  • Known UI points, documented and not fixed here: the row appears after the first status read ("Sign out of Buzz" shifts down once), retryable messages end in "Try again." though the row has no button, and the dev seed makes first run offer backup while the row follows the real rule.

Checks run on this branch's tree

  • Deferred to CI at the author's request (the build machine was under a deliberate stress load from another session, load avg 58–110, so local vitest/cargo runs were timing out in untouched files). Pushed with --no-verify; the hosted checks on this PR are the verification. The plan's checks for this layer were: vitest run src/app, tsc --noEmit, biome check, pnpm design:check, then the whole-suite vitest run at the top of the stack.
  • Tree check ✓: git rev-parse onboarding/08-settings-key-backup-row^{tree} equals the tree of merge --squash 19df106d5 onto 45601246a with the DO-NOT-MERGE commit 8791f32c3 reverted (7ca73468b…).

Native evidence

None on this tree. The native first-run recordings and the hands-on checks on the branch were made on the full tree, which only the DO-NOT-MERGE PR 09 reproduces (it adds the isolated debug Keychain slot). Treat every layer below 09 as unit/integration-tested only.

🤖 Generated with Claude Code

@matt2e
matt2e marked this pull request as ready for review October 28, 2026 13:40
@matt2e
matt2e requested a review from a team as a code owner October 28, 2026 13:40
@matt2e
matt2e force-pushed the onboarding/08-settings-key-backup-row branch from d5a5ef2 to c1e4548 Compare October 28, 2026 14:18
@matt2e
matt2e force-pushed the onboarding/07-welcome-first-run branch from 4823851 to 787e2b9 Compare October 28, 2026 14:18
@matt2e
matt2e force-pushed the onboarding/08-settings-key-backup-row branch from c1e4548 to 954be58 Compare October 28, 2026 14:21
@matt2e
matt2e force-pushed the onboarding/07-welcome-first-run branch from 787e2b9 to 141b42b Compare October 28, 2026 14:21
First run leaves the automatic backup choice on after a failed backup, so a
later Builderlab sign-in may finish the backup in the background; this row
makes that visible. `BackupRow` (`src/app/IdentitySettings.tsx`) reads
`identity.backup` through `useSyncExternalStore`, as Settings reads its
other stores, and sits in the existing rows `SettingsGroup` above "Sign out
of Buzz" as a `PreferenceRow` with no trailing control; the sentence is
wrapped in `role="status"`.

One sentence per status — idle: "Buzz hasn’t checked your key’s backup
since it opened." (what a launch or reload reports, not "no backup");
running: "Backing up your key…"; complete: "Your key is backed up to
Builderlab."; waiting{no_consent}: "Automatic backup is off for this key.";
waiting{no_session}: "Sign in to Builderlab to back up your key.";
waiting{not_linked} and every failed kind: the entry from `BACKUP_FAILURES`
(`backup-failure.ts`), so it is the same copy as first run's failed page,
with the Settings tail "Buzz tries again when you next sign in to
Builderlab." for the causes a new sign-in clears (busy, unreachable,
unconfirmed, storage, signed_out). There is no second table. Read-only: no
consent toggle, no retry.

Hidden unless backup is offered, by the same rule first run uses:
`backupOfferedFor(backup)` (`builderlab-account.ts`) answers false unless a
Builderlab sign-in would be at the build's own portal — Settings passes no
origin because its sign-in always is — and then returns the status's
`builderlab`; any error answers false. Known differences, documented: the
row appears after the first status read, so "Sign out of Buzz" moves down
one row once the IPC answers; retryable messages end in "Try again." though
the row has no button; under the `BUZZ_DEV_FIRST_RUN_ACCOUNT` seed first run
offers backup while the row follows the real rule.

Tests: `IdentitySettings.test.tsx` drives the real `createBackupStatus`
store over a scripted native side with a mocked Tauri `Channel`: the
sentence for complete, running, waiting{no_session}, waiting{not_linked},
failed{unreachable} and the signed_out tail; hidden when the status says
`builderlab: false` and when the build has no Builderlab portal; following
pushes at seq 2, 3 and 4 with a late seq-2 push changing nothing; no button
or switch in the row. `Settings.test.tsx`'s identity case gains an inert
`backup` store. docs/identity.md describes the row.

Stack: PR 08 of the onboarding stack, on 07 to keep the stack linear, but
it depends only on 03 (`backup.ts`, `backup-failure.ts`, `identity.backup`)
and 05 (`backupOfferedFor`) — it can be reviewed before 06/07.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
@matt2e
matt2e force-pushed the onboarding/08-settings-key-backup-row branch from 954be58 to b4e66e5 Compare October 29, 2026 03:36
@matt2e
matt2e force-pushed the onboarding/07-welcome-first-run branch from 141b42b to e2f928f Compare October 29, 2026 03:36

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Star Lord’s automated source review via Wes’s account, integrating Rocket, Mantis and Groot: no production defects found; one low-priority documentation correction is inline. Optional: remove the PR description’s stale claim that retryable Settings messages end in “Try again”; they now describe retrying on the next Builderlab sign-in.

Head b4e66e5944b1b16ba0e1b28baa12eb996a65fa02, base e2f928f8b8d89f9506ba1b1e703d22fd0eba9da2. Source-only: tests, CI, native backup behavior and rendered accessibility were not validated.

Comment thread docs/identity.md
that a failure first run's Try again could clear, and a lapsed sign-in, say
instead that Buzz tries again the next time the user signs in to Builderlab,
since a new sign-in hands the uploader a session and starts another pass. Idle is what a fresh launch or
a window reload reports, not evidence that no backup exists. The row has no

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 [P3] Qualify when a window reload reports idle

NsecBackupHost::document_load in src-tauri/src/nsec_backup.rs:302–312 changes the status to Idle only when it interrupts a Running pass. Settled statuses such as Complete or Failed are retained and returned by the next status read, so reloading Settings does not generally produce the idle sentence described here. Please say that a fresh launch reports idle and a reload does so when it interrupts an active pass, while preserving the explanation that idle does not prove absence of a backup.

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.

3 participants