Skip to content

feat(desktop): locally stored NIP-49 encrypted key backup - #2937

Open
tlongwell-block wants to merge 24 commits into
mainfrom
eva/nip49-local-backup
Open

feat(desktop): locally stored NIP-49 encrypted key backup#2937
tlongwell-block wants to merge 24 commits into
mainfrom
eva/nip49-local-backup

Conversation

@tlongwell-block

@tlongwell-block tlongwell-block commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a locally stored NIP-49 encrypted key backup (ncryptsec) to the desktop app, per the plan reviewed in buzz-development (Rev 3, approved 9/10 by Wren; implementation also reviewed and approved 9/10).

Two-artifact design — canonical bytes originate entirely in Rust:

  • create_ncryptsec_backup runs under the identity_mutation lock: encrypt → decrypt-verify against the live pubkey → atomic 0o600 write to {app_data_dir}/identity.ncryptsec → reread/byte-compare → return the exact persisted bytes. The frontend never re-derives or re-encrypts.
  • save_ncryptsec_copy writes a portable copy via the save dialog (parse-gated, secret-file semantics) and never mutates canonical state.
  • generate_backup_passphrase: 6 words from the EFF short wordlist via OsRng (custom passphrases min 12 chars).
  • Import accepts ncryptsec1 with optional password; the raw-nsec path is untouched. Different-pubkey import and sign-out wipe the app-managed backup (post-commit, best-effort — a failed import can never destroy the still-live identity's backup; regression-tested).

Never-relay guarantee (egress guard + tripwires):

  • egress_guard.rs fail-closed at all 8 /events submission boundaries (relay submit funnel, 3× relay.rs, huddle STT, both engram submitters, native WS choke point), rejecting ncryptsec1/NCRYPTSEC1 in text and binary frames. Scope is deliberately ncryptsec-only: pairing intentionally carries raw nsec inside its encrypted session.
  • Site-granular /events inventory tripwire: per-file (/events count, guard-call count) pairs; unlisted files expect zero. Mutation-style tests prove a ninth site in an existing file, a removed guard, and a new unlisted file all fail the scan.
  • ncryptsec source-allowlist scans in both trees (Rust + TS).

Frontend: onboarding BackupStep is encrypted-by-default — the default path never invokes get_nsec (e2e asserts the command log). Raw-nsec export stays behind an explicit click with prior semantics. Shared EncryptedBackupCreator powers onboarding + a new settings row; the import form auto-switches to encrypted mode on ncryptsec1 paste (case-insensitive HRP).

Open product call for @tlongwell-block: onboarding default is encrypted in this PR; flipping to raw-default is a small change either way (documented in the plan).

Review history: plan Rev 3 and the implementation were both iterated with Wren to 9/10 (two blockers from round 1 — import ordering, inventory granularity — plus an uppercase-bech32 hardening gap, all fixed in dde37183e). Thread: buzz-development.

Related issue

Follow-up to the direction explored in #385 (NIP-PB, closed) — this ships local NIP-49 (the standard) instead of a new NIP. No open duplicate found.

Testing

All at exactly dde37183e (same shell, HEAD verified):

  • cargo test — 1680 passed / 0 failed / 14 ignored (includes a deliberate ~70s log_n-18 NIP-49 round trip, spec vector, wrong-password, NFKC, uppercase-vector decrypt, injection test per egress boundary, inventory mutation tests, import-ordering regression tests)
  • cargo clippy --all-targets -- -D warnings — clean; cargo fmt --check — clean
  • pnpm typecheck — clean; JS unit suite 3529/3529; biome (repo-pinned 2.4.16) clean
  • Playwright onboarding-backup / onboarding / onboarding-agent-defaults / profile-nsec-reveal — 86 passed, 1 known avatar-reservation flake (passed on rerun; untouched by this diff). passThroughBackupStep now exercises the encrypted default, so every downstream onboarding spec covers the new path.
  • Note: browser e2e fakes the crypto via the mock bridge (fixed spec-vector blob); decryption correctness is proven in the Rust tests.

Latest onboarding integration

The current head adds an additive IdentityInfo.storage field (ephemeral, system-keyring, local-file, or environment) so onboarding can accurately explain where the active identity is protected. It surfaces storage metadata only—never key material—and leaves the existing lost/keyring-locked recovery behavior intact.

npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d and others added 3 commits July 25, 2026 22:44
Local-only ncryptsec backup of the identity key (plan:
PLANS/NIP49_LOCAL_BACKUP_PLAN.md Rev 3, approved 9/10 by Wren).

- key_backup.rs: NIP-49 codec (nostr nip49, log_n 18), one-artifact-per-
  action create with decrypt-verify against the live pubkey, atomic 0o600
  write + reread/byte-compare, EFF-wordlist passphrase generation,
  ncryptsec import recovery, stale-backup cleanup on identity change.
- commands/identity.rs: create_ncryptsec_backup (whole body under
  identity_mutation; webview can never supply canonical blob bytes),
  save_ncryptsec_copy (dialog selection only + secret-file write, never
  mutates app state), generate_backup_passphrase, import_identity now
  accepts ncryptsec1 with a passphrase (raw-nsec path byte-for-byte
  unchanged).
- egress_guard.rs: the backup must NEVER be transmitted to a relay —
  fail-closed runtime guard rejecting ncryptsec1 at all 8 relay egress
  boundaries (submit funnel, 3x relay.rs, huddle STT, both engram
  submitters, native websocket send loop). Scope is ncryptsec1 only:
  pairing intentionally carries the raw nsec inside its encrypted
  NIP-AB session.
- Tests: injection test per boundary (8), /events inventory-completeness
  tripwire, ncryptsec source-allowlist scan, spec vector, NFKC cross-form,
  0600/atomicity/lifecycle, recovery-mode gating, concurrent identity
  swap vs backup, boot-reset wipes the backup.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Frontend half of PLANS/NIP49_LOCAL_BACKUP_PLAN.md Rev 3 (D3):

- BackupStep: encrypted by default. The default path never invokes
  get_nsec — the webview only ever sees the finished ncryptsec1 blob
  returned by create_ncryptsec_backup. The raw key path survives behind
  an explicit 'Show raw key instead' click with its previous
  loading/error/skip semantics.
- EncryptedBackupCreator (shared by onboarding + settings): generated
  6-word passphrase default with cannot-be-recovered copy, 'choose my
  own' = min 12 chars + confirmation, create -> masked display + copy +
  'Save a copy…' via the native dialog.
- encryptedBackup.ts: pure reducer/validation model, unit-tested without
  a DOM; keyImportInput.ts: HRP classification + submit gating.
- NsecMaskedDisplay: kind='nsec'|'ncryptsec' drives labels/aria/testids;
  existing nsec call sites unchanged.
- NostrKeyImportForm: ncryptsec1 paste switches to encrypted mode
  (passphrase field, no npub preview possible), decrypt errors surface;
  raw nsec flow untouched. import_identity plumbs the optional password
  through OnboardingFlow/MachineOnboardingFlow/KeyringLockedScreen.
- ProfileSettingsCard: 'Encrypted backup' row alongside the raw reveal.
- ncryptsecSourceScan.test.mjs: TS-side source-allowlist tripwire
  mirroring the Rust scan (defense-in-depth per plan D4).
- e2e: mock bridge learns the three backup commands + ncryptsec import;
  onboarding-backup.spec.ts covers the encrypted happy path (asserting
  get_nsec is never called), custom-passphrase validation, raw fallback,
  and error/retry; onboarding.spec.ts adds encrypted-import happy path
  including a wrong-passphrase rejection.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
…r inventory, uppercase bech32

Blocker 1: import_identity persisted-before-cleanup ordering. New
commit_imported_identity helper runs durable persistence FIRST; a failed
different-key import now leaves both the old in-memory identity and its
valid canonical identity.ncryptsec intact. Stale-backup cleanup runs after
the durable commit and is deliberately best-effort (logged, not surfaced
as a half-applied-import error). Regression tests cover the failure path
and prove cleanup cannot precede persist.

Blocker 2: the /events inventory tripwire is now site-granular. Each
inventoried file pairs an expected non-comment /events occurrence count
with an expected egress-guard call count, so an unguarded ninth site in an
already-listed file (or a deleted guard call) fails the scan. The scan
core is pure over (path, content) pairs, with mutation-style tests
demonstrating all three drift classes.

Hardening: bech32 permits an all-uppercase encoding, so the egress guard
now rejects NCRYPTSEC1... too (mixed case stays unblocked - it cannot
decode), and both import classifiers (Rust recover_keys_from_input, TS
classifyKeyImportInput/isPlausibleNcryptsec) route uppercase-valid blobs
to the encrypted path consistently.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
@tlongwell-block
tlongwell-block requested a review from a team as a code owner July 26, 2026 04:00
npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d and others added 2 commits July 28, 2026 09:30
* origin/main: (70 commits)
  feat(relay): make Postgres pool size configurable, default 50 (#3191)
  Publish symbol-bearing debug relay images (#3250)
  feat(tracing): add datastore tracing plumbing (#2760)
  fix(buzz-acp): accept id-keyed config options when resolving model switch (#2795)
  fix(desktop): probe legacy Goose install dir on Windows (#3248)
  refactor(desktop): extract install command execution into install_exec (#3251)
  Polish composer activity layout and transitions (#3151)
  feat(invites): add use-limited invite links (#3141)
  fix(node): bump Buzz-supplied Node runtimes past OpenClaw's >=24.15.0 floor (#3218)
  fix(desktop): preserve thread anchor through layout reflow (#3212)
  feat(search): parse from:/in:/after:/before: and pass them in the filter (#2871)
  fix(desktop): fetch join policies through native networking (#2862)
  fix(desktop): republish agent identity records when a persona rename propagates (#2607)
  fix(desktop): keep project Inbox previews compact (#3193)
  Inbox refactor (#2045)
  Fix composer selection formatting and drop overlay (#3172)
  Refine pending message status (#3153)
  feat(admin): show reported message content in report detail (#3149)
  fix(desktop): recover full local storage on startup (#3182)
  Replace mobile reconnect banners with skeleton shimmer (#3143)
  ...

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Tyler's spec names the settings entry "Password Backup". Rename the
row title and align its description (password, not passphrase) plus
the two doc comments that referenced the old row name. No behavior
change; test ids unchanged.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
@tlongwell-block

Copy link
Copy Markdown
Collaborator Author

Freshened vs main + spec naming pass (channel buzz-nip49-password-backup):

  • 7a6a2cda5 — merge main into the branch (70 commits behind; conflicts none, lockfile/onboarding churn absorbed cleanly).
  • 51ea1b18f — settings row renamed "Password Backup" per Tyler's spec; description now says "password" instead of "passphrase"; two doc comments updated. No behavior change, test ids unchanged.

Per Wren's ruling in-channel: no artifact-model reshape — the flow (type password → encrypt → OS save dialog via save_ncryptsec_copy) already satisfies the spec; the app-managed canonical copy stays until Tyler explicitly rules one-artifact.

Verified at exact 51ea1b18f (same shell, git rev-parse HEAD confirmed):

  • desktop TS: 3735/3735 pass; pnpm typecheck + biome pnpm check clean
  • desktop tauri: 1858 pass / 0 fail / 14 ignored (pre-existing compiled-flag ignores; NIP-49 suite fully in the run incl. round_trip_at_production_cost at log_n 18)
  • just test-unit (buzz-core, buzz-auth, buzz-db --lib, buzz-conformance, buzz-push-gateway): all pass
  • pre-push lefthook full matrix green (branch-skew, desktop-check/test, mobile-test, rust-tests, desktop-tauri-test)

Max's independent clean-room battery + live manual pass runs next at this SHA.

Max's clean-room pass found the wipe -> import-from-file leg unreachable:
the file input accepted only .key,text/plain (macOS greys out the
identity.ncryptsec our own save dialog emits), and the first-launch
spotlight variant rendered no file input at all, leaving paste-only
recovery exactly where a returning user holds a backup file.

Accept .ncryptsec in the shared hidden file input, hoist it out of the
default-variant branch, and give the spotlight variant an 'Import from
a file' button wired to the same picker. Drop-zone behavior in the
default variant is unchanged. E2E covers the spotlight file path end to
end (accept attribute asserted explicitly, since setInputFiles bypasses
it) through decrypt-and-continue.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
@tlongwell-block

Copy link
Copy Markdown
Collaborator Author

Blocker fix: saved .ncryptsec backups are now importable from file (found by Max's clean-room pass at 51ea1b18f):

  • The file input accepted only .key,text/plain — macOS greyed out the identity.ncryptsec this PR's own save dialog emits.
  • The first-launch/wiped spotlight variant rendered no file input at all, so the exact wipe → import-from-file recovery path was paste-only.

Fix (72f00afa8): accept .ncryptsec in the shared hidden file input, hoist it out of the default-variant branch, add an "Import from a file" button to the spotlight variant wired to the same picker. Default-variant drop zone unchanged. New e2e covers the spotlight file path end to end (file → encrypted badge → passphrase → decrypt-and-continue), asserting the accept attribute explicitly since setInputFiles bypasses it.

Verified at exact 72f00afa8 (same shell): desktop TS 3735/3735, typecheck + biome clean, Playwright integration run for onboarding/identity-lost/backup/key-import specs 75 passed (1 flaky retry in an unrelated avatar test, passes on retry), pre-push lefthook lanes green. No Rust surface touched.

npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d and others added 2 commits July 28, 2026 11:00
…kup step

Merging main (7a6a2cd) brought in two smoke specs written against the
old raw-key backup page, which this branch replaces with an
encrypted-by-default step: Next stays disabled until a backup exists,
and the raw nsec (nsec-reveal-toggle) sits behind an explicit 'Show raw
key instead' click. Both specs timed out in CI at 72f00af.

- harness-management.spec.ts (More-harnesses nav, from #3093): route
  through the existing passThroughBackupStep helper instead of clicking
  a disabled Next.
- onboarding-docked-cta-screenshots.spec.ts: click backup-show-raw-key
  before the reveal toggle; the raw-key card is the surface the
  screenshot exists to capture.

Test-only change; no product code touched.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
* origin/main:
  chore(release): release Buzz Desktop version 0.5.0 (#3213)

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
@tlongwell-block

Copy link
Copy Markdown
Collaborator Author

CI fix receipt — head 855d25ef3a138a8b28403d82c0bcda9ff3529392

The two red Desktop Smoke shards at 72f00afa8 (run 30366893036) were a real semantic merge conflict, not flake: the main merge brought in two smoke specs written against the old raw-key backup step, while this branch makes onboarding encrypted-by-default (Next gated on backup creation; raw nsec behind an explicit backup-show-raw-key click).

  • harness-management.spec.ts:676 (More-harnesses nav, from feat(desktop): redesign agent runtime settings #3093) clicked Next on the backup page without creating a backup → now routes through the existing passThroughBackupStep helper (commit d7e52ea1a, test-only).
  • onboarding-docked-cta-screenshots.spec.ts:63 clicked nsec-reveal-toggle without first showing the raw key → now clicks backup-show-raw-key first, which is also the raw-key card the screenshot exists to capture.

Also merged origin/main (one commit: 0.5.0 release version bump, 855d25ef3's merge parent 4a977c588) — the pre-push branch-skew guard correctly refused to push a tree CI would never test.

Verified locally at exact head 855d25ef3 in the same shell: both touched smoke specs 18/18, desktop unit 3735/3735, typecheck + Biome clean, full lefthook pre-push matrix green (incl. desktop-tauri-test).

tellaho and others added 16 commits July 29, 2026 17:42
**Category:** improvement
**User Impact:** Users can create a password-protected key backup, save
it locally, and prove that it works before finishing onboarding or
signing out.

**Problem:** Tyler's NIP-49 foundation made encrypted local backup
possible, but the end-to-end ceremony still needed clearer progression,
trustworthy verification, and a Settings treatment consistent with the
rest of Identity. **Solution:** This stack adds a guided
create/download/test flow, verifies the actual saved backup through Rust
without returning secret key material, minimizes password lifetime in
the webview, and restores warm, plain-language presentation across
onboarding and Settings.

> [!NOTE]
> This PR is intentionally stacked on #2937 (`eva/nip49-local-backup`).
Review only the 19 commits in this stack.

<details>
<summary>File changes</summary>

**desktop/src-tauri/src/commands/identity.rs**  
Adds off-thread verification of NIP-49 backups and returns public
identity details only.

**desktop/src-tauri/src/commands/identity_key_backup_tests.rs**  
Covers verification, identity matching, and failure behavior at the
command boundary.

**desktop/src-tauri/src/egress_guard_tests.rs**  
Keeps the NIP-49 source inventory aligned with the new verification
path.

**desktop/src-tauri/src/key_backup.rs**  
Supports real backup decryption for verification while zeroizing
submitted passwords.

**desktop/src-tauri/src/key_backup_tests.rs**  
Exercises valid, wrong-password, damaged, and different-identity
backups.

**desktop/src-tauri/src/lib.rs**  
Registers the verification command with the desktop runtime.

**desktop/src/features/communities/ui/WelcomeSetup.tsx**  
Routes community onboarding through the revised backup ceremony.

**desktop/src/features/onboarding/lib/encryptedBackup.test.mjs**  
Covers the hardened backup state model and password-clearing behavior.

**desktop/src/features/onboarding/lib/encryptedBackup.ts**  
Models creation and verification with opaque request correlation and
short-lived passwords.

**desktop/src/features/onboarding/ui/BackupStep.tsx**  
Presents the backup choice clearly and advances into the dedicated
download step.

**desktop/src/features/onboarding/ui/BackupTestFlow.tsx**  
Adds the polished select-file, enter-password, verification, error, and
success experience.

**desktop/src/features/onboarding/ui/CommunityOnboardingFlow.tsx**  
Connects community onboarding to the updated backup steps.

**desktop/src/features/onboarding/ui/DownloadKeyStep.tsx**  
Adds the dedicated encrypted-backup download step and pre-creation skip
escape hatch.

**desktop/src/features/onboarding/ui/EncryptedBackupCreator.tsx**  
Guides password generation, backup creation, native saving, safe retry,
and re-download.

**desktop/src/features/onboarding/ui/MachineOnboardingFlow.tsx**  
Sequences chooser, download, exact-file test, and setup progression.

**desktop/src/features/onboarding/ui/NostrKeyImportForm.tsx**  
Aligns encrypted-key import behavior with the backup flow.

**desktop/src/features/onboarding/ui/NsecMaskedDisplay.tsx**  
Improves masked/revealed key presentation and safely wraps long private
keys.

**desktop/src/features/onboarding/ui/OnboardingChrome.tsx**  
Supports the revised onboarding layout and transitions.

**desktop/src/features/onboarding/ui/SetupStep.tsx**  
Integrates the completed backup ceremony with final setup.

**desktop/src/features/onboarding/ui/onboardingFlowSteps.test.mjs**  
Updates flow-level assertions for the new step sequence.

**desktop/src/features/settings/ui/EncryptedBackupRow.tsx**  
Adds sibling Create and Test rows that match the surrounding Identity
settings rhythm.

**desktop/src/features/settings/ui/ProfileSettingsCard.tsx**  
Places password-backup controls in the Identity card.

**desktop/src/features/settings/ui/SignOutSection.tsx**  
Uses explicit backup self-attestation plus the existing wipe phrase
without requiring a raw-key reveal first.

**desktop/src/shared/api/tauriIdentity.ts**  
Exposes typed backup verification to the frontend.

**desktop/src/shared/lib/ncryptsecSourceScan.test.mjs**  
Updates the frontend source allowlist for the verification path.

**desktop/src/testing/e2eBridge.ts**  
Models creation, saving, and verification for browser coverage.

**desktop/tests/e2e/onboarding-backup.spec.ts**  
Covers backup creation, exact-file testing, errors, retries, and
completion.

**desktop/tests/e2e/onboarding-docked-cta-screenshots.spec.ts**  
Updates docked CTA visual coverage for the revised progression.

**desktop/tests/e2e/onboarding.spec.ts**  
Keeps broader onboarding coverage aligned with the backup ceremony.

**desktop/tests/e2e/profile-nsec-reveal.spec.ts**  
Covers Settings create/test behavior and updated success copy.

**desktop/tests/e2e/signout-confirmation.spec.ts**  
Covers backup self-attestation, wipe phrase confirmation, and reset
behavior.

**desktop/tests/helpers/fileDrag.ts**  
Adds reusable file-drop support for backup test coverage.

**desktop/tests/helpers/onboarding.ts**  
Routes tests through chooser, download, and skip behavior consistently.

</details>

## Reproduction steps

1. Start fresh onboarding and choose the password-protected backup path.
2. Continue to **Backup your key**, create a password, save the
`.ncryptsec` file, and confirm that **Next** remains gated until testing
succeeds.
3. Select that exact file, enter its password, and verify the **Your
backup works!** success state.
4. Open **Settings → Identity** and confirm that **Create password
backup** and **Test password backup** appear as sibling rows.
5. Test a valid backup, then test wrong-password and different-identity
cases to confirm clear, non-secret-bearing outcomes.
6. Open sign out and confirm that backup self-attestation plus typing
`wipe all my data` remain required.

## Screenshots

### Onboarding flow

| 1. Creating your key | 2. Key created | 3. Create backup password |
|---|---|---|
| <img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/fe5ebf60-02ad-4584-8e64-ff137bafac02"
/> | <img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/0b8c7c6c-2ae5-494e-bb81-0a7b47ea1b66"
/> | <img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/9fa8f6c0-832a-46ec-9af8-55dcae0adbb8"
/> |
| **4. Select the saved backup** | **5. Enter its password** | **6.
Verification succeeds** |
| <img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/7ed1528c-6a1f-4411-bd3e-c57413320cdf"
/> | <img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/088ec3ed-2735-4b9a-a0c5-6d176b3f06c5"
/> | <img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/7081ce32-9371-46b2-9692-23ff77c39d35"
/> |

### Settings flow

| Create and Test tools | Tested backup success |
|---|---|
| <img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/978ab672-5510-46d8-9116-6df4f1bda34c"
/> | <img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/56a1764d-3c3e-40e2-9f4b-962c5e96a36c"
/> |

## Verification

- Desktop typecheck
- Full desktop JS: 3,733/3,733
- `pnpm check`
- Rust desktop lib: 1,862 passed, 14 ignored; diagnostic: 3/3
- Focused Playwright: 16/16 with one worker

Native save/cancel remains the residual smoke-test risk: browser E2E
mocks the KDF/native save-dialog boundary, while real decrypt and
identity matching are covered in Rust.

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz>
Conflict resolutions:
- relay.rs: took main's submit refactor (submit_signed_event_at_with_keys
  funnel replaces the removed submit_signed_event); the persona flush loop
  on main already publishes through the guarded funnel.
- egress_guard boundary 3 retargeted to the pre-signed funnel entry, and
  the /events inventory rows updated for main's relay.rs / sharing.rs
  layout (sharing.rs site is its in-file mock-relay test fixture; its
  production publish goes through the guarded boundary-1 funnel).
- personas/snapshot/import.rs + e2eBridge.ts: kept both sides' additions.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
- Replace the standalone password-backup step with copy and encrypted-file options inside the identity-key stage.
- Add a midnight security theme, return-to-onboarding control, and downward transition back into the core yellow flow.
- Restore seven-page onboarding pagination and keep backup progress available when users revisit the security view.
- Update onboarding E2E coverage and screenshots for the inline options, direct setup path, and password-backup return behavior.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Replace the password-backup surface's onboarding Skip and Next actions with a dedicated Back control.
- Keep Backup key and Back in the existing bottom-docked CTA location while preserving inline settings behavior.
- Route Back to Backup options with the correct backward transition instead of advancing onboarding.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Track whether the active identity is stored in the system keyring, local fallback file, environment, or ephemeral memory.
- Expose storage metadata through the Tauri identity response and shared TypeScript identity model.
- Add a responsive first information card beside the two backup actions without revealing boot-time identity creation.
- Show keychain password education only when secure storage succeeded and accurate neutral copy for fallback states.
- Extend identity persistence tests and split storage types into focused modules to preserve file-size limits.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Add generated compact light and dark noisy surfaces with reusable tone and size options for cards, popovers, and confirmation dialogs.
- Keep the dark backup option and password panels unboxed, reduce their padding, and top-align parallel card titles for a clearer visual hierarchy.
- Explain identity keys and password-protected backup files in plain language while preserving distinct device, password-manager, and portable-backup choices.
- Normalize onboarding primary, secondary, and icon controls and add reduced-motion-safe transitions between backup creation and verification stages.
- Update focused onboarding E2E coverage for current navigation, compact texture contracts, title alignment, and narrow viewport geometry.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Use the resolved identity storage backend to name the system keychain in the initial backup guidance.
- Keep local-file and unknown storage states accurate without exposing boot-time identity creation.
- Explain in the backup options card that the computer may request the user's password when Buzz needs to read the key.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Give the spotlight password field a white background, subtle border, rounded corners, and dark password and placeholder text.
- Keep the saved-password mask legible against the light input surface.
- Render password generator controls in the standard popover surface instead of the dark textured security treatment.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Increase contrast for the spotlight password field border and focus ring against its white surface.
- Apply dark default and hover colors to the password generator and visibility controls.
- Remove the obsolete dark-surface icon treatment from the light password field.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Add a key-to-password-to-lock timeline to the onboarding backup form.
- Animate four connector dots in a downward pulse with reduced-motion support.
- Preserve the compact settings variant and hide decoration in short windows.
- Keep balanced spacing and semantic security-theme colors across the illustration.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Give shared in-step primary actions a white background with dark labels.
- Add coordinated hover colors that preserve contrast across onboarding surfaces.
- Update the shared CTA documentation to describe the inverted treatment.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Restore the standard primary CTA treatment for general onboarding steps.
- Add a dedicated inverted CTA style for dark backup-security surfaces.
- Apply the white button treatment only to backup creation, file selection, verification, and password-reset actions.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Keep eager NIP-49 encryption silent without changing the password form or advancing the backup flow.
- Commit the encrypted payload for saving only after the user clicks the download button.
- Preserve queued downloads during encryption and show the rotating progress ticker until the payload is ready.
- Update reducer coverage for silent background encryption while leaving end-to-end tests unchanged.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Present backup verification as an optional post-download step with contextual headings and a Skip for now action.
- Match the verification password field to the backup password input and move its primary action into the onboarding footer.
- Add an upward-pulsing file-to-password illustration with reduced-motion support and a stronger selected-file affordance.
- Remove saved filesystem paths from the verification view to avoid exposing local details.
- Reset encrypted-backup state whenever users re-enter the creation flow.
- Confirm before leaving only when an unfinished pre-download password or encryption would be discarded.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Focus the backup password input on entry and submit downloads with Enter, including queued encryption.
- Remove the pre-download discard confirmation so Back immediately resets and exits the optional flow.
- Give successful verification dedicated page copy and a primary Finish action.
- Add an explicit visibility toggle for the unlocked private key while keeping key material masked until requested.
- Preserve the existing verification success message with a minimal, lightly blurred key treatment.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Reconcile focused backup assertions with Tyler’s post-main behavior: default generator surfaces, optional verification copy, hidden saved paths, and fresh re-entry state.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

triage-ready Appropriate for agentic review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants