Skip to content

feat(desktop): add Sign Out to Settings to reset and relaunch#1842

Open
wpfleger96 wants to merge 5 commits into
mainfrom
duncan/signout-wipe
Open

feat(desktop): add Sign Out to Settings to reset and relaunch#1842
wpfleger96 wants to merge 5 commits into
mainfrom
duncan/signout-wipe

Conversation

@wpfleger96

Copy link
Copy Markdown
Collaborator

Summary

Adds a Sign Out action to Settings → Profile that wipes all local Buzz state and relaunches into first-run onboarding — replacing a manual process that required deleting keychain entries, ~/Library/Application Support directories, WebKit localStorage, ~/.buzz, and ~/.config/buzz-agent.

  • Rust (commands/identity.rs): sign_out Tauri command stops all managed agents, deletes keychain blobs for buzz-desktop and buzz-desktop-dev (via SecretStore::delete_all()), removes all App Support and WebKit directories matching xyz.block.buzz.app* and xyz.block.sprout.app* prefixes (covers dev-worktree variants), removes the agent nest, legacy Sprout nest, OAuth token cache, and CLI symlinks, then relaunches via exec + exit(0). Guarded by is_shared_identity() — no-op in harness/CI mode.
  • secret_store.rs: delete_all() removes the entire keychain blob under the interprocess advisory lock and clears the in-memory cache.
  • Frontend (ProfileSettingsCard.tsx): Sign Out section at the bottom of Profile settings with an AlertDialog confirmation (warns to back up nsec first), spinner + disabled state while in-flight, toast on the error path. The command never resolves on success — it restarts the process.
  • tauriIdentity.ts: signOut() binding.
  • check-file-sizes.mjs: Updated overrides for secret_store.rs (1148) and ProfileSettingsCard.tsx (1005) with load-bearing justifications.

Mathieu and Kalvin reported that switching Nostr identities required
manually deleting keychain entries, ~/Library/Application Support dirs,
WebKit localStorage, ~/.buzz, and ~/.config/buzz-agent — an opaque and
error-prone process that blocked routine identity switching.

Implements a two-step Sign Out action in Settings → Profile:

Rust (commands/identity.rs):
- `sign_out` command stops all managed agents, deletes keychain blobs
  for both buzz-desktop and buzz-desktop-dev services, removes all
  App Support and WebKit dirs matching xyz.block.buzz.app* and
  xyz.block.sprout.app* prefixes (covers dev-worktree variants), removes
  the nest (~/.buzz / ~/.buzz-dev), legacy ~/.sprout, OAuth token cache,
  and CLI symlinks, then relaunches via exec+exit(0).
- `SecretStore::delete_all()` removes the entire blob entry under the
  interprocess advisory file lock, bypassing the per-key delete path.
- Guarded by `is_shared_identity()` — no-op in harness/CI mode where
  the key comes from BUZZ_PRIVATE_KEY and keychain wipe is meaningless.

Frontend (ProfileSettingsCard.tsx):
- Sign Out section below the Identity card with an AlertDialog
  confirmation before the irreversible wipe.
- Spinner + disabled state while the wipe is in flight; toast on the
  error path (sign_out never resolves on success — it restarts the
  process).

Also removes a stale biome-ignore suppression comment in
useIndependentThreadPanel.ts and replaces non-null assertions with
optional chaining in e2eBridge.ts (pre-existing lint issues that were
blocking the pre-commit hook).
@wpfleger96 wpfleger96 requested a review from a team as a code owner July 14, 2026 02:12
wpfleger96 added a commit that referenced this pull request Jul 14, 2026
@wpfleger96

Copy link
Copy Markdown
Collaborator Author

Sign Out UI

Settings — Sign out section

The destructive-styled Sign Out card in Settings → Profile, showing the button and description.

signout-section

Confirmation dialog

The AlertDialog shown before the wipe runs, with the nsec-backup warning and Cancel / Sign Out actions.

signout-dialog

Captures the Settings → Profile sign-out section and the confirmation
AlertDialog, exercising the testids added in the sign_out feature.
Replace the inline sign-out wipe (which left a crash window between keychain
delete and app-data delete) with a sentinel-based two-phase design:

Phase 1 — sign_out: writes a durable `.bundle-id.reset-pending` sentinel
outside all wiped paths, then relaunches. No destruction happens in the command
itself, so a crash between write and relaunch is safe — the sentinel persists
and the wipe completes on the next open.

Phase 2 — boot-time hook at the top of setup(): if the sentinel is present,
perform the full wipe before migrations and identity resolution:
- Rename app-data dir atomically (.trash-<pid>), same for WebKit dir
- Remove nest, ~/.sprout, ~/.config/buzz-agent, this build's CLI symlink
- Keychain: delete_all_with_legacy_cleanup() which reads the blob to collect
  all key names and deletes per-key legacy DPK/keyring entries, preventing
  old identity resurrection through migrate_legacy_key()
- Verify: keychain empty + app-data absent + nest absent
- On success: delete sentinel, set completed flag, fall into clean onboarding
- On failure: keep sentinel (retry next boot), set reset_failed flag

Additional correctness fixes:
- Wipe is scoped to the running build only (not prod+dev simultaneously)
- reset_completed suppresses migrate_legacy_nest() and migrate_dev_nest() so
  dev-build reset cannot re-import ~/.buzz into ~/.buzz-dev
- reset_failed flag surfaces a recovery screen in the frontend

Tests: 7 unit tests in reset.rs via injected ResetKeychain trait (fake).
… test

Bump ProfileSettingsCard.tsx px-text override from :672 → :684 to
account for sign-out section line drift. Add a real-keychain integration
test that seeds a legacy per-key `identity` entry + blob, calls
`delete_all_with_legacy_cleanup()`, and proves probe/load cannot
resurrect the old identity — closes the CRITICAL-adjacent gap the
FakeKeychain tests cannot cover.
F1 (CRITICAL): The wipe left the legacy Sprout App Support dir untouched,
so `migrate_legacy_app_data_dir` could restore the old identity on next
boot. Expose `legacy_app_data_dir` and `is_dev_data_dir_name` as
`pub(crate)` from `migration.rs`, add `legacy_app_data_dir` and
`nest_dir` fields to `ResetContext`, compute both in `run_boot_reset`,
and rename-then-delete the legacy dir atomically in Step 1b. Verify its
absence in Step 6.

F2 (CRITICAL): DPK blob and per-key DPK deletes in
`delete_all_with_legacy_cleanup` were `let _ =` — real errors were
silently swallowed. Replace with match arms that propagate real errors
(not-found and dpk-unavailable are treated as success). Add
`verify_fully_wiped` to `SecretStore` and `ResetKeychain` trait that
checks all three keychain shapes the migration path can consume (main
blob, DPK blob, per-key "identity"). Replace the `probe_empty`
verification call with `verify_fully_wiped`.

F3 (IMPORTANT): Tests 6 and 7 only asserted sentinel path strings. Replace
them with behavioral tests that create both `.buzz` and `.buzz-dev` dirs,
inject `nest_dir` into `ResetContext`, run the wipe, and assert the
target nest is gone while the opposite nest survives. Add tests for
`legacy_app_data_dir` removal and that `completed` is `true` on success.

F4 (MINOR): Use `crate::migration::is_dev_data_dir_name` (the
authoritative discriminator) instead of an ad-hoc `starts_with` in
`run_boot_reset` and `lib.rs`. Use `keyring_service()` from `app_state`
instead of duplicating the inline service string.
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