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

Account-first onboarding and sign-in recovery - #2579

Merged
morgmart merged 3 commits into
mainfrom
morganm/onboarding-audit
Nov 6, 2026
Merged

morgmart merged 3 commits into
mainfrom
morganm/onboarding-audit

Conversation

@morgmart

@morgmart morgmart commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Onboarding audit for desktop first run: account-first Builderlab sign-in, recovery for a stalled sign-in, and clearer community setup.

  • Sign-in recovery. A sign-in that times out waiting on the browser shows "Finish in your browser" with Continue, not "Sign-in did not finish". Open Buzz (buzz://sign-in) restarts a waiting or timed-out sign-in, including one started from a community address.
  • Connect to a community. It's a join-or-create choice, and it only offers "create your own" when Create is available. A refused create replaces "available" and holds Create until the name changes.
  • Dark-mode hover fill. Hover fills stay light on light surfaces inside dark mode, so "Other options" is no longer dark text on a dark fill.
  • Clear glass button. Rebuilt on the shared primary glass material.

Account details (name, birthday, country) are not in this PR. The in-app form was reverted in the last commit. Public desktop sign-in now goes through the hosted setup page mobile uses: ext-builderbot-ui #403, then cash-server #146925.

Overlaps #2576 in federated_identity/mod.rs and oauth_callback, so it rebases onto #2576 once that lands.

Checks at the head commit: tsc clean, biome clean, full vitest run 17,857 passed / 22 skipped.

@baxen baxen changed the title Account-first onboarding with in-app account setup Account-first onboarding and sign-in recovery Nov 5, 2026
@matt2e
matt2e force-pushed the morganm/onboarding-audit branch from 991fff2 to f545ab4 Compare November 5, 2026 12:36
@morgmart
morgmart marked this pull request as ready for review November 5, 2026 22:40
@morgmart
morgmart requested review from a team as code owners November 5, 2026 22:40

@kalvinnchau kalvinnchau left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Two actionable findings: occupied-device recovery can unlink the account key without replacing the device key, and existing-key adoption bypasses account/backup confirmation.

Comment thread src/features/identity/Welcome.tsx Outdated
if (onDevice === checkedKey)
return { next: [...finish(checkedKey), ...open] };
// Another key here is never replaced unasked: Welcome back.
if (onDevice || !checkedBackups?.includes(checkedKey)) return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 [P1] Guard recovery when the device already holds another key

On relaunch with device key A and account key B, Saved key → paste B → Continue never calls identity_import: service.ts:179 rejects operations while identity is ready, but setup.saved accepts A as success. The start-over variant likewise never calls identity_create; it unlinks B and then attempts to bind retained A. A pubkey_already_bound refusal leaves B unlinked and setup failed. Both paths were reproduced through the actual app in Chromium and WebKit with mocked native/account boundaries; no real account was modified. Handle the occupied-device mismatch before exposing missing-key recovery, and verify the expected replacement key before unlinking. Extend the relaunch test through import and start-over.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 At 83a8fc7, import and Start over now refuse before import/create/unlink; the destructive behavior above is repaired.

[P2] The backed-up recovery branch still hangs: with device key A and account key B whose backup is available, Saved key calls beginRecovery (Welcome.tsx:880–888), but service.ts:97 returns while identity is ready. No nsec_restore_begin occurs, and the undefined recovery snapshot leaves KeyRestore on ‘Restoring your key…’ with only Cancel. An exact-head targeted probe reproduced this. The new tests use backupStatus=unavailable and miss this branch.

The same missing occupied-device UI state also leaves Start over on a misleading failure page: Welcome.tsx:937–948 clears linked/owned before the new guard throws; Try again repeats the refusal and Back returns to the landing. Model the occupied-device mismatch in Welcome back before offering recovery actions, or refuse before changing state, including onRestore. Add a backed-up occupied-device regression case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🤖 Addressed in 41572fa. A sign-in that finds another account's key on this device no longer reaches Welcome back. The device key is signed out first, as Sign out of Buzz does without erase, and Buzz restarts and signs in again on the no-key lane. It is signed out silently only when this device has a confirmed backup record for that key (new nsec_backup_confirmed). Otherwise a Replace/Cancel confirm appears, and Cancel leaves the key in place. So the hanging restore and the misleading Start over page are both unreachable from that state. Covered by the new relaunch tests in identity.test.tsx and the persistence test.

next: [
...(onDevice ? [] : [keyForAccount(identity)]),
linkKey(identity, current),
backupKey(current),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 [P2] Preserve confirmation and opt-outs when adopting an existing key

When the browser account is unlinked, this path binds and backs up the existing device/legacy key without showing the previous account identity and backup confirmation. With an unbound existing key and a backup-capable account/build, a remembered wrong browser account can gain recovery of that identity. backUpWith also writes automatic=true rather than preserving a saved opt-out (backup.ts:187; nsec_backup_choice.rs:204–227), contrary to docs/identity.md:830–837. Confirm account/key and backup before adopting an existing key, and preserve an existing opt-out unless explicitly changed. This is source-level control-flow evidence; no live upload was attempted.

@morgmart
morgmart force-pushed the morganm/onboarding-audit branch from d107bfb to 7b7ecab Compare November 5, 2026 23:26
Rebased onto main after #2576 (one Builderlab sign-in). Combines this
branch's onboarding work with main's native NIP-FI session:

- Create account and Sign in on the landing; Create account opens the
  sign-up screen, and Other options keeps joining by community address.
  Builds without a Builderlab sign-in show only Other options.
- After sign-in, setup finishes on its own: an account with no key uses
  old Buzz's identity from this device when there is one, otherwise a
  new key; Welcome back only for a key that is elsewhere.
- Use a different account signs the first-run session out through main's
  revision-fenced release, and every sign-in after it asks the browser for
  credentials until one completes.
- A sign-in that times out waiting on the browser shows as one to finish,
  and Open Buzz restarts it.
- Connect to a community, glass buttons, and success/danger text updates.

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
Co-authored-by: Matt Toohey <contact@matttoohey.com>
Co-authored-by: Bradley Axen <baxen@squareup.com>
Co-authored-by: Morgarita <6e967af659416160ab4f2fd56d74c8c6f273791ee7bfc9468a1be37c387947be@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@morgmart
morgmart force-pushed the morganm/onboarding-audit branch from 7b7ecab to bd1d351 Compare November 6, 2026 01:18
Native keeps a key already on this device, so creating or importing over it
"succeeded" as that other key. On relaunch Welcome back, pasting the account's
saved key silently kept the device key, and Start over unlinked the account's
key before binding the wrong one. Create and import now refuse first while a
key is ready, before any account step runs.

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
Co-authored-by: Morgarita <6e967af659416160ab4f2fd56d74c8c6f273791ee7bfc9468a1be37c387947be@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@baxen baxen 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.

🤖 Approving at 83a8fc7. I reviewed the code changes, not the tests, and CI is green. 83a8fc7 fixes the destructive case @kalvinnchau found: vacant() now refuses before create, import or Start over's unlink can act on a different device key.

Remaining issues. None of them lose data, so I'm fine with them going in a fast follow-up:

  1. Occupied device + backed-up account hangs (Kalvin's follow-up P2, I confirmed it). With device key A and account key B where B is backed up, Welcome back → Saved key calls current.recovery.beginRecovery() (Welcome.tsx ~880). identity/service.ts:98 returns at once unless the status is missing. KeyRestore then sits on "Restoring your key…" with only Cancel. Welcome back should treat viewer && viewer !== linked as its own state: hide or disable restore/import and keep Use a different account. The current tests use backupStatus=unavailable, so they miss this branch.
  2. Start over on an occupied device clears linked/owned before createKey's vacant() throws. You land on a failure page where Try again just repeats the refusal. It's safe now, but confusing. Check before changing state, or don't offer Start over when another key is on the device.
  3. Existing-key adoption skips confirmation (Kalvin's first-round P2, still open). On a relaunch where the account has no key, the device key is linked and backed up with no in-app check. backUp also writes automatic=true over a saved opt-out. The hosted "Continue to Buzz as X" page (ext-builderbot-ui #403) now confirms the account in the browser, so the wrong-account risk is lower. The opt-out overwrite still goes against docs/identity.md. Preserve a saved opt-out, for example with setBackupChoice(..., unlessSaved).
  4. Docs drift: docs/identity.md still describes the key choice page ("This choice page shows the signed-in email…") and Sign in with Builderlab. This PR removes KeyChoice, and the landing now says Create account / Sign in.

The rest looks good:

  • screen_hint=signup is sent only for an explicit Create account.
  • prompt=login is kept across restarts until a sign-in completes. It matches kgoose #147640.
  • The sign-in-timed-out code is split out cleanly from callback-refused.
  • buzz://sign-in matching is exact, with a 2-minute expiry.
  • The Next step splicing in setup.run keeps failed-step retry intact, since steps.slice(index) includes the spliced steps.

Signing in to an account linked to a different key than the one on this
device rested on Welcome back, whose recovery could not work there: restore
hung and Start over failed. Sign-in now signs the device's key out first, as
Sign out of Buzz does without erase, and Buzz restarts and signs in again by
itself on the no-key lane.

The key is signed out without asking only when this device has confirmed a
stored backup of it: a new native check finds any account's backup record
here that the service receipted or a restore wrote. Otherwise "A different
account is saved on this computer" asks first, and Cancel signs out of the
new account with the device's key left in place.

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
Co-authored-by: Morgarita <6e967af659416160ab4f2fd56d74c8c6f273791ee7bfc9468a1be37c387947be@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@morgmart
morgmart merged commit 8c0cb0c into main Nov 6, 2026
60 checks passed
@morgmart
morgmart deleted the morganm/onboarding-audit branch November 6, 2026 07:18
matt2e added a commit to stuartwdouglas/buzz-app that referenced this pull request Nov 6, 2026
Follow-up to the bare `buzz://` ingress skip, addressing the PR block#2718
review.

- Bare `buzz://` keeps block#457's "unknown parameters are ignored" rule:
  `buzz://?from=console`, `buzz:///?x=1` and `buzz://#top` now also just
  bring Buzz forward, with no notice or navigation, and never displace a
  held or queued link. `parseBuzzLink` is unchanged, so they stay plain
  text in messages and Notes.
- Reconcile with block#2579: one comment at candidate selection puts the two
  no-destination ingress links side by side. Bare `buzz://` is skipped
  and never displaces a real link; `buzz://sign-in` wins selection like
  any other latest link, then requests sign-in. Sign-in behaviour is
  unchanged. `OPEN_APP_LINK` moves beside `bindDeepLinks`, so it no
  longer splits the canonical-scheme comment from `buzzLink`, and that
  comment now names both ingress-only forms.
- Tests: query and fragment bare variants; a bare link leaves an earlier
  link's Try again in place, and Try again still opens that link; a bare
  link in a later cold-start batch keeps the held link; mixed sign-in
  batches pin block#2579's current behaviour.
- docs/deep-links.md: bare `buzz://` leaves the Buzz link list for prose
  beside `buzz://sign-in`, both described as OS-only. "The latest
  arriving link wins" and "no link form of its own" are qualified so
  they stay true.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: mtoohey <mtoohey@squareup.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
matt2e added a commit to stuartwdouglas/buzz-app that referenced this pull request Nov 6, 2026
Follow-up to the bare `buzz://` ingress skip, addressing the PR block#2718
review.

- Bare `buzz://` keeps block#457's "unknown parameters are ignored" rule:
  `buzz://?from=console`, `buzz:///?x=1` and `buzz://#top` now also just
  bring Buzz forward, with no notice or navigation, and never displace a
  held or queued link. `parseBuzzLink` is unchanged, so they stay plain
  text in messages and Notes.
- Reconcile with block#2579: one comment at candidate selection puts the two
  no-destination ingress links side by side. Bare `buzz://` is skipped
  and never displaces a real link; `buzz://sign-in` wins selection like
  any other latest link, then requests sign-in. Sign-in behaviour is
  unchanged. `OPEN_APP_LINK` moves beside `bindDeepLinks`, so it no
  longer splits the canonical-scheme comment from `buzzLink`, and that
  comment now names both ingress-only forms.
- Tests: query and fragment bare variants; a bare link leaves an earlier
  link's Try again in place, and Try again still opens that link; a bare
  link in a later cold-start batch keeps the held link; mixed sign-in
  batches pin block#2579's current behaviour.
- docs/deep-links.md: bare `buzz://` leaves the Buzz link list for prose
  beside `buzz://sign-in`, both described as OS-only. "The latest
  arriving link wins" and "no link form of its own" are qualified so
  they stay true.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: mtoohey <mtoohey@squareup.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
matt2e added a commit that referenced this pull request Nov 6, 2026
…ice (#2718)

## Summary

The Builderlab console's **Open Buzz app** button (console nav and the
Agents page, `squareup/ext-builderbot-ui` `navigation.ts`) links to bare
`buzz://`. OS ingress counted that as malformed. In a running app it
showed "This link doesn't work", and on a cold start it opened Inbox
with the same notice. #2054 added that notice; before it, the link did
nothing visible.

Native code already brings Buzz to the front for any deep link
(`src-tauri/src/deep_links.rs` `accept`). With this change,
`bindDeepLinks` skips bare `buzz://` and `buzz:///` (Windows root path)
when it picks the URL to act on:

- No notice, no navigation, no failure.
- A cold start lands on the ordinary start page.
- It never displaces a real link queued in the same batch.

`deepLinkStep` and `parseBuzzLink` still return null for it, so a bare
`buzz://` in message content stays plain text. Every other malformed
link keeps the notice.

## Not in scope

These are other Builderlab links that also hit the notice:
- `buzz://sign-in` (welcome dialog **Open Buzz now**) is handled by
#2579.
- `buzz://nostr-bind?…` (Builderlab identity page **Connect**) is the
original desktop's consent flow and was never ported. The direction for
that one is still open.

## Changes
- `src/features/navigation/deep-links.ts`: the `OPEN_APP_LINK` guard in
candidate selection.
- Tests: four new `bindDeepLinks` cases: warm `buzz://`, warm
`buzz:///`, cold launch, and a bare link queued after a channel link.
All four fail without the guard and pass with it. Rejection tests that
used `buzz://` as their malformed example now use `buzz://unknown`, in
`deep-links.test.ts` and `entity-navigation.test.tsx`, so their coverage
is unchanged.
- `docs/deep-links.md`: lists `buzz://` as accepted and moves the
malformed-link examples to `buzz://unknown`.

## Validation (at 786ecad)
- `vitest run src/features/navigation
src/app/entity-navigation.test.tsx`: 10 files, 323 passed.
- `tsc --noEmit` is clean, and the pre-commit and pre-push hooks pass
(biome, design-system, clippy).
- An independent agent review found no blockers. Its one substantive
suggestion was to make sure a bare link can't displace a real one queued
with it. That is adopted, with a test.

**Deferred:** native OS ingress hasn't been exercised. That needs a
debug bundle (`just desktop-bundle --launch`), which would take over the
`buzz://` handler from the installed app. To test: with the bundle
running, `open "buzz://"` should bring it forward with no notice. Quit
the app and run `open "buzz://"` again: it should launch to the start
page with no notice. `open "buzz://unknown"` should still show the
notice.

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

---------

Signed-off-by: Stuart Douglas <sdouglas@block.xyz>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Signed-off-by: mtoohey <mtoohey@squareup.com>
Co-authored-by: Vimes <d26902ac60a55be3ce7c6bb44cf3b0a0a09cf3b89067f6d9cc6e2e5015bd59a3@remote-agent-pilot.communities.buzz.xyz>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: mtoohey <mtoohey@squareup.com>
tlongwell-block added a commit that referenced this pull request Nov 6, 2026
* eva/huddle-audio-fixes: (29 commits)
  fix(huddle): keep calls flowing under stalled sends and multiple devices
  fix(channels): wrap unread conversation stepping around the sidebar (#2682)
  fix(navigation): let bare buzz:// open Buzz without a broken-link notice (#2718)
  fix(inbox): open DM thread rows as the thread (#2587)
  test(channels): complete the context navigation fixture (#2783)
  fix(channels): open a thread's channel at its root message from the #channel link (#2377)
  fix(people): find people by a pasted npub or hex key (#2396)
  test(bestie): open the narrow long-history thread with clickRow (#2723)
  Account-first onboarding and sign-in recovery (#2579)
  fix(agents): scroll the avatar dock with a mouse wheel (#2567)
  fix(avatars): keep Done in view when the avatar popup is short (#2706)
  feat(messages): highlight reactions you've made (#2709)
  feat(usage): move Usage from the sidebar into Settings (#2742)
  fix(messages): keep a playing row mounted in the channel timeline (#2695)
  Fix plugin status declaration closure for author consumers (#2643)
  fix(navigation): keep empty new chats out of Back history (#1617)
  [buzz-app] Recover remote agent message outcomes (#2542)
  feat(messages): name background send destinations and attach the files that fit (#2406)
  feat(drafts): clear a draft from its row (#2725)
  Authorize external application identities in Buzz (#2685)
  ...

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>

# Conflicts:
#	docs/huddles.md
#	src-tauri/src/relay/huddle/audio.rs
wpfleger96 added a commit that referenced this pull request Nov 6, 2026
…leanup

* origin/main: (84 commits)
  Restore macOS Cmd-W window closing when no tab handles it (#2443)
  Make agent transcripts quieter and link finished replies to activity (#2424)
  fix(channels): wrap unread conversation stepping around the sidebar (#2682)
  fix(navigation): let bare buzz:// open Buzz without a broken-link notice (#2718)
  fix(inbox): open DM thread rows as the thread (#2587)
  test(channels): complete the context navigation fixture (#2783)
  fix(channels): open a thread's channel at its root message from the #channel link (#2377)
  fix(people): find people by a pasted npub or hex key (#2396)
  test(bestie): open the narrow long-history thread with clickRow (#2723)
  Account-first onboarding and sign-in recovery (#2579)
  fix(agents): scroll the avatar dock with a mouse wheel (#2567)
  fix(avatars): keep Done in view when the avatar popup is short (#2706)
  feat(messages): highlight reactions you've made (#2709)
  feat(usage): move Usage from the sidebar into Settings (#2742)
  fix(messages): keep a playing row mounted in the channel timeline (#2695)
  Fix plugin status declaration closure for author consumers (#2643)
  fix(navigation): keep empty new chats out of Back history (#1617)
  [buzz-app] Recover remote agent message outcomes (#2542)
  feat(messages): name background send destinations and attach the files that fit (#2406)
  feat(drafts): clear a draft from its row (#2725)
  ...

# Conflicts:
#	src/bundled/builderlab/adapter-type/Activity.test.tsx
baxen added a commit that referenced this pull request Nov 9, 2026
…und (#3064)

> **Known label change:** after **Sign out of Buzz**, the welcome screen
now leads with **Create account**, where the removed setup-route note
made it lead with **Sign in**. Sign out also removes the saved
Builderlab session, so no saved login is left to signal a returning
user. Raised in the PR chat for a decision.

## Problem

If a device was set up through **Sign in with Builderlab**, it stopped
at **Sign in / Continue without signing in** on every launch. That
happened even when a valid Builderlab session had restored from the
Keychain, and **Sign in** opened the browser again. The cause is the
`buzz.setup-route.v1` note added in #2579.

App startup doesn't need Builderlab. A key on the device is enough to
open the app, and the Builderlab login only matters per community. The
one real check the relaunch sign-in did was compare the account's linked
key with this device's key, and the saved session can do that quietly.

## Change

- **The launch gate is gone.** `setup-route.ts` and its call sites are
removed. A device with a key opens straight to the app, however it was
set up.
- **Background key check (`LinkedKeyCheck`).** With the app open, once
the saved session is signed in, it reads the account's linked key with
the same `federatedBuilderlabAccount(...).linkedKey()` call Settings →
Identity makes. It does nothing if the keys match, the account has no
linked key, there is no session or the session was refused, or the read
fails. Launch never waits on it.
- **Mismatch prompt.** If the account is linked to another key, the
account's backup status is read too, and **Your account uses a different
key** opens over the app.
- **When Builderlab holds a backup of the account's key:** the prompt
says plainly that Replace removes this computer's key. If this device
has no confirmed backup of its own key, it tells you to copy the private
key from Settings → Identity first. **Replace** runs setup's existing
`replaceKey` step: sign this key out without erasing, restart, sign in
again on its own, then take the no-key lane to **Welcome back**, which
restores the account's key. No new recovery flow. The restart's note
(`buzz.sign-in-after-restart.v1`) carries the account's linked key in
the same stored item, written in one step, so a storage failure can't
save the note without it. If the browser signs in to an account linked
to another key, or to none, setup fails before any key is made, linked
or restored, and offers only **Use a different account**. **Create
account** drops the expectation, since a new account can't be the one
Replace restarted for; Sign in, the timed-out wait's Continue, and an
**Open Buzz** that resumes the restart's sign-in keep it.
- **When the account has no backup of its key,** or backups aren't
offered (`"unavailable"`), Replace isn't offered, because Welcome back
couldn't restore the key. The prompt says the account's key lives on
another device and offers only **OK**.
- **When the backup status read fails,** nothing is shown, since whether
a backup exists is unknown. The next session revision reads again.
- **Not now**, OK and Close keep the key. The dismissal is remembered
for that exact pair of keys (`buzz.linked-key-dismissed.v1`), so it asks
again only if either key changes, or once the account's key gets a
backup after an OK.
- The prompt waits while the launch screen shows or old Buzz's
communities are being offered. The backup-consent prompt stays mounted
but paused while this one shows, so the two never stack, and a consent
prompt closed earlier stays closed.
- **Welcome button label.** **Sign in** comes first when the Builderlab
session is `signed-in` or `reauthentication-required`; otherwise
**Create account** does. Both buttons are disabled while the saved
session is still `restoring`, so the label never flips under a click and
a returning user can't start a sign-up.
- **One case kept, only for the current session.** A failed setup's
**Use a different account**, with a key already saved, still returns to
the landing with Sign in / Continue without signing in. That's the only
way out of a setup kgoose refuses to link. It's React state now and
nothing is persisted, so the next launch opens the app.
- No new community-list sync: the known-communities sync already runs
whenever the session is signed in.

Relay or server load: one kgoose read per client for each signed-in
session revision, made through the existing native forwarder, plus a
backup-status read only when there's a mismatch. There are no relay
reads, polls or retries.

## Tests

`src/features/identity/identity.test.tsx`:

- **Replaced:**
- The landing-at-relaunch test now asserts that a key opens the app
while the account check is still pending, and that a matching key asks
nothing.
- The note write/clear test now asserts that a failed setup's landing
leaves nothing that makes the next launch ask.
- The note-label test now uses a `signed-in` or
`reauthentication-required` session.
- **Added:**
- An account with no key, a failed read, or an unreachable server asks
nothing.
  - No session means no read; a later sign-in triggers the check.
- With the account's key backed up, the prompt says Replace removes this
key and to copy it first. Not now keeps the key and stays quiet through
a renewal and a relaunch for the same pair, and a new pair asks again.
- With no backup of the account's key, or backups unavailable, there's
no Replace, the prompt says where the key is, and OK is remembered.
- A failed backup read asks nothing, both after a saved Not now and on a
first check.
- After OK on the no-backup prompt, a relaunch stays quiet until the
account's key gets a backup, then offers Replace.
  - While the session restores, both welcome buttons are disabled.
- Replace from the app signs out and records the sign-in after restart.
- A refused Replace withdraws the sign-in after restart (and its
expected key), and Back returns to the app.
- The sign-in after Replace's restart to an unlinked account, or one
linked to another backed-up key, fails with no key created, bound or
restored and no Try again. Use a different account returns to the
landing. The account it replaced the key for rests on Welcome back.
Create account in place of an abandoned automatic sign-in sets up the
new account without the stop. Open Buzz resuming the pending automatic
sign-in still stops for an unlinked or other-linked account, with
nothing created, bound or restored.
- A backup prompt closed while the linked-key read is pending stays
closed after the mismatch prompt that follows is dismissed.

`src/features/identity/sign-in-requests.test.ts`: the restart note's
expected key is taken once, only while the note is fresh, and a
withdrawn note leaves none. Storage that accepts one write and then
refuses still saves the note with its key, storage that refuses every
write saves no note, and an earlier build's bare-timestamp note reads as
expecting no key.
- **Moved:** the 13 "relaunch sign-in" lane tests (device key linked to
the account, a different account, kgoose refusals, Replace/Cancel) now
reach that landing the only way it's still reachable: a first sign-in
whose link kgoose refuses, then Use a different account
(`afterRefusedLink` + `leaveWithKey`). Their assertions are unchanged,
except "no key created" now means "only the key that first setup made."
- Fail-then-pass: with `origin/main`'s `Welcome.tsx` and
`setup-route.ts` restored, all of the new behavior tests fail (checked
for the first six at a9fe6ea; the wording, Replace-gating and
remembered-pair tests assert copy and buttons the earlier dialog didn't
have).

Validation at 1a3e730 (01264c4 adds only the Create-account drop and
its test, which fails on 1a3e730; identity + IdentitySettings 616
pass, full suite passed as pre-push): identity,
`src/app/IdentitySettings` and deep-link tests pass (24 files, 708
tests), biome and tsc are clean, and the full suite passed as the
pre-push hook. The three new review-fix tests fail against 21888f6's
`Welcome.tsx`, `LinkedKeyCheck.tsx` and `BackupConsent.tsx`.

Earlier validation at 996458d: identity plus
`src/app/IdentitySettings` tests pass (610). The full `vitest run`
passed as the pre-push hook, and `pnpm check` passed at b4f49ca; the
later commits only touch `LinkedKeyCheck.tsx`, its tests and docs, and
biome and tsc are clean on them. Earlier pre-push runs failed in
unrelated files (relay, agents, app navigation) while other worktrees'
suites loaded the machine, and passed on retry. CI passed on 1eb9b89.
No Rust changed, so `cargo test` was not run.

## Deferred / not done

- **Not exercised in the native app yet.** Needs a human check (below).
- **Mid-session sign-in:** signing in to Builderlab partway through a
session (Settings, or a community sign-in) shows the prompt right away.
That's intended, since it's when the mismatch becomes relevant.

## How to try it

1. **Launch opens straight in.** On a desktop build with a Builderlab
sign-in server, set the device up with **Sign in with Builderlab**, then
quit and reopen Buzz. **Expected:** the app opens straight in, with no
welcome screen and no browser.
2. **Mismatch, account's key backed up.** You need a Builderlab account
linked to a *different* key that Builderlab has a backup of: an account
set up on another device with Identity sync on (Settings → Identity
there shows **Synced with Builderlab**). Sign in to it from this
device's Settings. **Expected:** **Your account uses a different key**
appears over the open app. It says Replacing removes this computer's
key, and, if this key has no confirmed backup, to copy its private key
from Settings → Identity first. It offers **Not now** and **Replace**.
3. **Not now is remembered.** Choose **Not now**, then quit and reopen
Buzz. **Expected:** the app opens and the prompt does *not* come back
for this same pair of keys. It asks again only if either key changes, or
after OK on the no-backup prompt once the account's key gets a backup.
To see it again with the same keys, run
`localStorage.removeItem("buzz.linked-key-dismissed.v1")` in the app's
web inspector and relaunch.
4. **Replace** (precondition: step 2's account, whose key Builderlab has
backed up). With the prompt showing, copy this computer's key first if
you want to keep it, then choose **Replace**. **Expected:** Buzz
restarts, signs in (the browser opens, since signing out also ends the
saved Builderlab session), and shows **Welcome back** with **Restore**
for the account's key. If the browser is signed in to a different
Builderlab account, setup stops with "You signed in to a different
Builderlab account…" and offers only **Use a different account**.
5. **Mismatch, no backup of the account's key.** Use an account linked
to a different key that has *no* Builderlab backup (its device has
Identity sync off). **Expected:** the prompt says the account is linked
to a key that "lives on another device, and Builderlab has no backup of
it", and offers only **OK**, with no Replace.
6. **Welcome label after sign-out.** Sign out of Buzz (no erase).
**Expected:** the welcome screen shows **Create account** first and
**Sign in** second, because signing out also removes the saved
Builderlab session. Before this PR, the setup-route note made **Sign
in** first here. See the note at the top.

---------

Signed-off-by: Bradley Axen <baxen@squareup.com>
Co-authored-by: mbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz>
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