Skip to content

feat(desktop): guide and verify password-protected backups - #3617

Merged
tlongwell-block merged 21 commits into
eva/nip49-local-backupfrom
tho/nip49-tyler-stack
Jul 29, 2026
Merged

feat(desktop): guide and verify password-protected backups#3617
tlongwell-block merged 21 commits into
eva/nip49-local-backupfrom
tho/nip49-tyler-stack

Conversation

@tellaho

@tellaho tellaho commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

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.

File changes

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.

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
image image image
4. Select the saved backup 5. Enter its password 6. Verification succeeds
image image image

Settings flow

Create and Test tools Tested backup success
image image

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.

npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w and others added 20 commits July 29, 2026 13:14
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
…step

- BackupStep.tsx: replace the static "Account created!" landing with a
  two-phase intro driven by a 1.4s timer (INTRO_HOLD_MS):
  - Loading phase: title "Creating your identity key" with the animated
    FuzzyLogo mark rendered in place of the body content; description,
    Keycase card, and footer CTAs are withheld
  - Finished phase: title swaps to "Your unique identity key has been
    created" with new copy ("Your identity key will be saved to your
    keychain. Back it up somewhere safe so you can restore your account.
    Never share your key."), and the Keycase flow + footer fade in via
    animate-in fade-in (700ms, disabled under prefers-reduced-motion)
- Unify the previously mode-dependent (encrypted vs raw) header copy into
  the single new description string
- Use plain string concat for the h1 classes: cn()'s tailwind-merge
  misreads the custom text-title size token as conflicting with
  text-foreground and drops it
- onboarding-backup.spec.ts: assert the loading -> finished title/logo
  sequence, and wait for the intro logo to unmount before screenshots so
  captures are deterministic

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
…y and download options

- BackupStep.tsx: replace the single Keycase card with a chooser view
  centered on a full-width masked key display:
  - Masked nsec with same-length monospace mask, inline reveal toggle
    (backup-key-reveal-toggle) and inline copy button (backup-copy-key)
    with spinner/checkmark feedback and an inline error banner
    (backup-copy-error) when the keychain fetch fails
  - Copy guidance ("save it somewhere safe — a password manager is a
    great place for it") moved into a tooltip on the copy icon
  - Security note restored below the key card
  - Encrypted download flow moved behind a footer CTA
    (backup-option-download) between Next and Back; opens a dedicated
    view with an "All backup options" back link (backup-back-to-options)
  - Raw key is fetched from the keychain only on explicit reveal/copy;
    the download path never fetches it
- EncryptedBackupCreator.tsx: de-brand "Keycase" to download/password
  terminology across headings, buttons, errors, and a11y labels
  ("Create Keycase" → "Encrypt and download", "Keycase saved to" →
  "Saved to", etc.)
- NsecMaskedDisplay.tsx: de-brand the ncryptsec kind label to
  "encrypted key"
- onboarding-backup.spec.ts: rewrite for the new UX — chooser
  masked-key/reveal/copy coverage (with clipboard permissions), download
  happy path behind the footer CTA, return-to-chooser navigation, and
  reveal error/retry paths replacing the old raw-key error tests
- onboarding-docked-cta-screenshots.spec.ts: capture the reveal toggle
  on the chooser instead of the removed backup-show-raw-key flow

Settings keeps its own Keycase wording; only the onboarding surface is
de-branded here.

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Add a shared-element transition in the backup step: clicking "Download my
key" scales the textured nsec card down and translates it up into a compact
white square "shell box", framing the metaphor that the user is working on
the secure shell that surrounds their key while the encrypted download view
loads in below.

- BackupStep.tsx: wrap the chooser's nsec Card and the download view's shell
  box in motion.div elements sharing a layoutId, so Motion runs the FLIP
  morph (550ms, same cubic-bezier as the onboarding line-slide) with an
  automatic crossfade; going back via "All backup options" reverses the morph
- Add ShellBracket: black square brackets (6px lines, 2rem outer radius)
  that fade in and clamp inward around the box once the morph settles
  (onLayoutAnimationComplete gates via new shellSettled state); geometry
  keeps the lines 10px clear of the box on all sides, concentric with its
  rounded-2xl corners, forming a 96px square around the 64px box
- Fade/rise the "Download your key" card in concurrently with the morph,
  replacing the old VIEW_ANIMATION_CLASS crossfade
- Respect prefers-reduced-motion via useReducedMotion: morph and bracket
  entrance render their final state instantly
- No e2e changes needed: all existing test ids are preserved and the
  onboarding-backup.spec.ts suite passes unmodified (8/8)

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
…y" step

Split the backup step's in-place download view into its own page in the
machine onboarding flow, with its own progress dot, slide transition, and
footer. The chooser keeps the masked-key card; "Backup your key" (primary
CTA) now navigates forward instead of swapping views, with a delayed
fade-in ghost "Skip for now" beside it and a Settings hint below.

- DownloadKeyStep.tsx (new): "Backup your key" page — title + encryption
  explainer, shell box + closing brackets animating in on mount (replaces
  the card→box layoutId morph, which can't span two pages), textured card
  hosting EncryptedBackupCreator, and a footer whose primary slot holds
  the portaled "Encrypt and download" button until the payload exists,
  then a primary "Next" that continues to setup
- BackupStep.tsx: reduced to the chooser; removed view state, ShellBracket,
  BackToOptions, and the morph; new onDownload prop; footer reworked to a
  centered primary CTA with an absolutely-positioned "Skip for now" that
  fades in after 1s (animation-delay/duration arbitrary properties so the
  hover transition isn't delayed) and a Settings → Profile → Identity hint
- EncryptedBackupCreator.tsx: new opt-in createButtonPortal /
  createButtonClassName / onCreated props so onboarding can relocate the
  create button into its footer (undefined = inline for Settings; null =
  slot not mounted yet, render nothing to avoid an inline flash)
- MachineOnboardingFlow.tsx: new "download" page between backup and setup
  (Back → backup, Next → setup); progress indices shifted (download=3,
  setup=4, config=5)
- OnboardingChrome.tsx: TOTAL_ONBOARDING_PAGES 7 → 8; WelcomeSetup 5→6 and
  CommunityOnboardingFlow 6→7/7→8 dot positions bumped to match
- SetupStep.tsx: same footer treatment — centered Next with a no-background
  "Skip for now" hanging off its right edge, fading in after 1s
- tests: passThroughBackupStep helper now skips from the chooser;
  onboarding-backup.spec.ts covers the new page (create button absent from
  footer until encryption, Back returns to the chooser); 47 e2e + 3778
  unit tests pass

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
…r for key backup

Replace the two-mode (generated vs custom + confirm) encrypted-backup
passphrase UI with a single password input and an inset generator, and make
encryption eager so Download commits instantly in the common case.

- EncryptedBackupCreator.tsx: single password input (eye reveal toggle) with
  a refresh icon inset in the field that anchors a 1Password-style popover
  containing only Words (range slider, 3-10) and Separator (Spaces default,
  Hyphens/Periods/Commas) controls; every generation (open, control change,
  repeat icon clicks) writes straight into the field — no candidate preview
  or Use button. Popover stays open across re-rolls; only click-outside or
  Esc closes it. Validation hint is absolutely positioned below the field
  (text-xs, fade + slide-down) so it never shifts layout.
- encryptedBackup.ts: state model rewritten for eager background encryption —
  the KDF starts (debounced 400ms) as soon as the passphrase is valid,
  results are keyed by passphrase so stale completions never commit, and
  Download either commits a finished result instantly or queues until the
  in-flight encryption lands (downloadPending). New helpers:
  pendingEncryptPassphrase, isEncrypting, downloadDisabled, passphraseIssue.
- DownloadKeyStep.tsx: subtitle merges the password-safety hint and drops the
  encryption-mechanics sentence; Back is locked via onBusyChange while a
  queued download resolves.
- key_backup.rs / commands/identity.rs: generate_passphrase now takes word
  count (clamped 3-10, default 3) and separator (default space); phrases
  under MIN_PASSPHRASE_LEN are re-drawn so 3-word draws always pass the same
  length gate as typed passphrases.
- tauriIdentity.ts: generateBackupPassphrase accepts { words, separator }.
- e2eBridge.ts: mocked generator honors word count and separator from a
  fixed deterministic word pool so specs can assert the popover controls.
- onboarding-backup.spec.ts: happy path drives the generator popover (open,
  slider, separator select, re-roll, Esc) and the custom-password test drops
  the confirm field; encryptedBackup.test.mjs rewritten for the eager
  lifecycle (queued download, stale results, failure retry).

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
…ownload

Polish the queued-download state of the encrypted key backup flow (Download
clicked while the background KDF is still running):

- EncryptedBackupCreator.tsx: the disabled Download button now shows a
  vertical ticker instead of a static label, cycling "Downloading once
  finished" / "Encrypting your password" / "Just a bit longer..." every 2.5s.
  The stacked column slides up inside a one-line clipped viewport (300ms
  ease-out, motion-reduce safe) and ends with a clone of the first message so
  the loop wraps from the same bottom origin, then snaps back to the real
  first row with the transition disabled once the clone settles. All lines
  render at all times, so the button holds the width of the longest message,
  and the viewport is aria-live=polite for screen readers.
- While the download is queued, the password field flips back to masked and
  the reveal (eye) toggle disables, so the passphrase isn't left readable on
  screen while the user waits for the native save dialog (input and generator
  popover were already locked in this state).

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
… move to download step

Soften the backup chooser and shift the opt-out into the download step:

- BackupStep.tsx: replace the "Backup your key" primary CTA
  (backup-option-download) with a plain "Next" button (onboarding-next) that
  leads into the download step via onDownload; remove the "Skip for now"
  button and the now-unused onNext prop. backupNextDisabled() still gates the
  new Next (always actionable).
- MachineOnboardingFlow.tsx: drop the removed onNext prop from the
  <BackupStep> call site.
- DownloadKeyStep.tsx: add a "Skip for now" ghost button (onboarding-skip,
  calls onNext) absolutely positioned off the right edge of the footer's
  create-button slot, so the Download CTA stays centered; it renders only
  until the backup exists, after which the primary slot swaps to Next.
- EncryptedBackupCreator.tsx: rename the idle Download button to "Backup key"
  (the pending-download ticker is unchanged).
- E2E: passThroughBackupStep and the docked-CTA screenshot spec route
  Next -> download page -> Skip; onboarding-backup.spec.ts swaps every
  backup-option-download click for onboarding-next, routes the two
  skip-to-setup paths through onboarding-skip, and asserts the skip button on
  the download page.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
…up flow

- Add BackupTestFlow.tsx: after downloading the Keycase, the step becomes an
  explicit "Test your backup" view — a large dropzone for the downloaded
  file, then a password prompt that succeeds the instant the password is
  typed completely, ending in a spring-in check badge with an emoji burst
  celebration (skipped under reduced motion)
- Verification is instant and KDF-free: the dropped file's bytes are
  compared to the committed ncryptsec blob and the typed password to the
  in-memory passphrase, so no key material beyond what the creator already
  held enters the component
- Wrong files get inline errors (Keycase-but-different vs not-a-Keycase);
  full-length wrong passwords get a mismatch hint; a "Re-download backup"
  button re-opens the native save dialog and "Use a different file" returns
  to the dropzone
- EncryptedBackupCreator.tsx: swap the masked-ncryptsec result view
  (NsecMaskedDisplay + "Save a copy…") for BackupTestFlow
- DownloadKeyStep.tsx: header/subtitle now conditional — "Backup your key
  with a password" while creating, "Test your backup" once created; remove
  the bracketed shell-box visual, the keep-this-file-private hints, and the
  "back up anytime in Settings" footer note
- BackupStep.tsx / MachineOnboardingFlow.tsx: chooser footer "Next" now
  always leads into the download step (skip lives there as "Skip for now")
- Update onboarding-backup.spec.ts happy path to drive the new flow
  (wrong file, right file, wrong password, success, Next) with screenshots;
  align docked-CTA spec and passThroughBackupStep helper with the new
  navigation; allowlist BackupTestFlow.tsx in the ncryptsec source scan

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- BackupTestFlow.tsx: replace the always-on dashed dropzone with an
  oversized onboarding-style primary pill ("Select your backup file",
  h-14, rounded-full, bg-primary with the onboarding CTA label color)
  that opens the file browser; window-level dragenter/dragleave
  listeners (depth-counted, filtered to file drags) morph it back into
  the dashed drop target only while a file is dragged over the window
- BackupTestFlow.tsx: restyle "Re-download backup" as the flow's ghost
  pill secondary (h-12, rounded-full, bg-foreground/10) and move the
  "Saved to <path>" label below the button in a centered column
- EncryptedBackupCreator.tsx: add onVerified prop and forward it to
  BackupTestFlow so parents can observe a passed backup test
- DownloadKeyStep.tsx: track hasVerified; after the backup is created,
  Next renders disabled until the test passes while a ghost "Skip for
  now" button hangs off its right edge as the escape hatch; retitle the
  post-download page to "Now, test your backup"
- onboarding-backup.spec.ts: assert the new heading, the disabled
  Next + visible Skip state after download, and that a passed test
  enables Next and removes Skip (all 8 tests passing)

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Back navigation in the machine onboarding flow no longer destroys
in-progress key-backup work, and the "Now, test your backup" view only
appears once the file is actually on disk.

- EncryptedBackupCreator.tsx: new useEncryptedBackupSession() hook lifts
  everything that must survive step unmounts — the encrypt reducer state
  (entered password, cached KDF result, committed blob), the verified
  flag, the saved path, the save-once guard, and the test-flow
  progress — into a host-owned session. The creator accepts an optional
  session prop and falls back to a private one, so the settings
  "Save a new Keycase" card is unchanged. KDF completions now dispatch
  unguarded so results landing while the step is unmounted still commit.
- EncryptedBackupCreator.tsx: advancing to the test view now requires a
  successful save, not just a committed blob. Canceling the native save
  dialog (or a save failure) rolls the commit back to the password form
  and clears the save-once guard so "Download backup" can be clicked
  again; save errors now render on the form. CTA renamed
  "Backup key" → "Download backup".
- encryptedBackup.ts: new "back-to-password" reducer event discards the
  committed blob while keeping the passphrase and its cached encryption
  result, so returning to the form re-downloads instantly without a
  second KDF run. Covered by a new unit test.
- BackupTestFlow.tsx: drop/password/success progress is host-controlled
  (survives remounts), and the wrong-password hint no longer compares
  attempt length against the real password (which leaked its length) —
  it shows only after a 900ms typing pause or an explicit Enter.
- DownloadKeyStep.tsx: Back from the test view returns to the password
  form (via backupSessionToPasswordEntry) instead of leaving the step;
  the pre-verification footer shows a ghost "Skip for now" instead of a
  disabled Next; the "back up anytime in Settings" hint only renders on
  the password form; hasCreated/hasVerified derive from the session
  (session.created keeps the step outside the ncryptsec confinement
  scan).
- MachineOnboardingFlow.tsx: owns the backup session; the setup
  (providers) step's Back returns fresh-key users to the "Backup your
  key with a password" form rather than the key-created page.
- BackupStep.tsx: footer hint moved to the download step.
- onboarding-backup.spec.ts: pre-verification footer asserts Next is
  absent (not disabled), documents save-gating, and adds a spec covering
  test-view Back → password form with the password intact plus instant
  re-download from the cached encryption.

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
The "Select your backup file" button in BackupTestFlow hardcoded
text-(--buzz-onboarding-cta-label), a CSS variable defined only under
.buzz-onboarding-neutral-theme. In the settings card that variable is
unset, so the label color fell back to the inherited dark foreground on
the dark bg-primary pill — invisible text. The drop-stage buttons also
ignored the existing "boxed" variant and always rendered at onboarding
scale.

- Select button: boxed variant now uses text-primary-foreground with
  settings-scale sizing (h-9 px-6, text-sm label); spotlight keeps the
  onboarding CTA treatment (h-14 px-12, CTA-label color) unchanged
- Re-download button: boxed variant drops from h-12 px-10 text-base to
  h-9 px-6 text-sm to match the other pill buttons in settings cards
- Verified via Playwright screenshot of the settings drop stage; the
  onboarding-backup and profile-nsec-reveal specs both pass

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Replace the morphing select-button dropzone in the "Test your backup"
flow with the same full-surface drag treatment the chat composer uses:
while a file drag is anywhere over the window, a dashed drop overlay
takes over the host surface instead of reshaping the CTA button.

- BackupTestFlow.tsx: the "Select your backup file" button is now a
  static pill; a window-level file drag renders an absolutely
  positioned overlay (inset-2, dashed primary border, backdrop blur,
  centered "Drop your backup file here" pill mirroring the composer's
  DropZoneOverlay) that is itself the drop target, so anywhere on the
  host surface accepts the file. Removed the per-button isDragActive
  tracking; drop handling moved from the button to the overlay.
- EncryptedBackupRow.tsx: extracted from ProfileSettingsCard (file-size
  ratchet) and given `relative` so the overlay covers the whole
  settings row; onboarding needs no host change since the textured
  Card is already relative.
- tests/helpers/fileDrag.ts: new shared helpers to synthesize window
  file-drag enter/end and drop a file onto a testid target.
- onboarding-backup.spec.ts: cover overlay appearing on drag, reverting
  on drag end, and advancing to the password check when the correct
  file is dropped on the overlay (new 04b screenshot).
- profile-nsec-reveal.spec.ts: cover the overlay takeover/dismiss on
  the settings backup row.

The overlay carries its own testid (backup-test-drop-overlay);
backup-test-dropzone stays on the select button, keeping existing
selectors working.

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
…ation

The hardened-verification commit (0ce29b187) replaced the in-memory
backup check with a real Rust NIP-49 decrypt but rewrote the test-flow
UI from scratch, dropping the treatment built across the prior flow
commits (60b7da6..a8929a3db) and reintroducing protocol jargon into
primary copy. This restores the presentation and phrasing around the
new engine; all security behavior is unchanged (Rust decrypt,
request-id correlation, attempt clearing, explicit Verify, exact-file
onboarding gate, saved-password mask, change-password confirmation).

- BackupTestFlow.tsx: bring back the SuccessBurst emoji celebration,
  spring check badge, and staged fade-ins with useReducedMotion guards;
  password autofocus on stage entry; aria-hidden icons; animate-in on
  the accepted-file row; explanatory comments. Variant-aware buttons
  return: spotlight uses h-14 px-12 with the onboarding CTA label
  variable, boxed uses h-9 px-6 text-sm text-primary-foreground
  (preserving the a1e250201 invisible-label fix); Re-download and "Use
  a different file" regain their prior sizes. Copy is plain-language
  again: "Select your backup file", "That's the one. Now enter your
  password to prove you can unlock it.", a "Checking…" verifying state,
  and errors anchored under the input. Success splits by context:
  onboarding celebrates "Your backup works!" with no npub noise, while
  the settings tester reports "This backup works" plus which identity
  it restores, rendered via the canonical PubKey component instead of a
  hand-truncated code pill.
- EncryptedBackupRow.tsx: move NIP-49 out of the row descriptions into
  fine print ("Backups use the standard NIP-49 format…"); restore the
  toggle's aria-label, transition-colors, and focus ring offset.
- EncryptedBackupCreator.tsx: delete the plain centered "Backup
  downloaded" block; a settings save now lands in the same masked
  saved-password form onboarding uses on Back, with "Download backup
  again", the saved path, and a note that the password is not kept.
- profile-nsec-reveal.spec.ts: update the two success-copy assertions;
  all testids stay stable so no other spec changes were needed.

Verified: typecheck clean, desktop unit 3781/3781, pnpm check clean,
focused Playwright (onboarding-backup + profile-nsec-reveal) 12/12,
and fresh hash-distinct screenshots of every changed state.

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
- Rewrite the "Create a new backup password?" AlertDialog description in
  EncryptedBackupCreator.tsx:
  - Drop the "Buzz no longer keeps the password for this backup" sentence —
    an implementation detail that didn't help the user decide
  - Anchor the copy on the "starting over" action to match the
    "Start with a new password" confirm button
  - Replace "copies you already saved will keep working with their original
    password" with plain language: earlier backups still work with the
    password they were created with
- No e2e updates needed: no spec asserts on this dialog's description text

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- SignOutSection: the "I have tested a key backup" checkbox no longer
  requires revealing or copying the nsec first — it is enabled as soon
  as the dialog opens (still disabled while sign-out is pending)
- Remove hasInteractedWithKey state, canConfirmBackup gate, and the
  "Reveal or copy this last-chance private key first." hint text
- NsecMaskedDisplay: drop the now-unused onKeyInteraction prop
- NsecMaskedDisplay boxed variant: render the key in monospace and let
  the revealed nsec break to the next line (font-mono, break-all,
  wrap-anywhere) instead of overflowing the container; also applies to
  the profile settings key reveal
- Update signout-confirmation e2e spec: remove the reveal-unlocks test
  and copy-before-check steps; assert checkbox resets unchecked on
  reopen instead of disabled

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Remove the explicit separator between backup actions so the identity card's existing divide-y styling renders a single border.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
@tellaho
tellaho marked this pull request as ready for review July 29, 2026 20:58
@tellaho
tellaho requested a review from a team as a code owner July 29, 2026 20:58
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
@tlongwell-block
tlongwell-block merged commit 88d08e4 into eva/nip49-local-backup Jul 29, 2026
25 checks passed
@tlongwell-block
tlongwell-block deleted the tho/nip49-tyler-stack branch July 29, 2026 21:42
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.

2 participants