Repository navigation
feat(identity): first-run welcome screen with community lookup, Join Community and the invite page - #1802
Closed
matt2e wants to merge 6 commits into
Closed
feat(identity): first-run welcome screen with community lookup, Join Community and the invite page#1802matt2e wants to merge 6 commits into
matt2e wants to merge 6 commits into
Conversation
… screen On native desktop with no human key in the credential store, the app now opens on a welcome screen instead of the "Your Buzz identity" key import/create screen: title "Buzz", the subtitle "Your people, your agents, your projects — all in one place.", a primary "Connect with Builderlab" button, and a disabled community URL field and "Scan a QR code" button labelled "Coming soon". A successful Builderlab sign-in ends on a "Connected to Builderlab" page showing the signed-in email (a dead end for now); a failed or canceled sign-in shows the error with a Back button to the welcome screen. Loading, storage-error/retry and ready handling are unchanged. FOUNDATION edit (src/app/App.tsx), kept to the minimum needed: - line 2: import `Welcome` from `../features/identity/Welcome` instead of `IdentitySetup` - lines 49/51: the JSX element wrapping `ConnectedApp` changes from `<IdentitySetup identity=…>` to `<Welcome identity=…>` No other FOUNDATION file (services.ts, main.tsx, communities/service.ts) is touched. Builderlab OAuth reuse: the Settings card signs in through `createOAuthSession(signal => browserCredential(ctx.host, signal))` in src/bundled/builderlab. The welcome screen calls that same session and OAuth client (native `oauth_callback_begin/wait/cancel`, then `/v1/auth/login/exchange` and `/v1/auth/me` over `plugin_host_request`). Because `HostService.request` is plugin-owner-scoped and the plugin's session is not exposed to App, new `oauth/first-run.ts` binds a `Host` to the plugin's own bundled native grant (`block.builderlab`/`bundled`, granted at build time from BUZZ_BUILDERLAB_ORIGIN). That credential is not yet shared with Settings → Integrations → Builderlab. `IdentitySetup` stays for the credential-free fixture (tests/fixtures/identity.html, unchanged) and its tests; its native title bar is extracted into `SetupWindowHeader`, shared with `Welcome`. No Rust or `.env.local` changes. Tests: identity.test.tsx covers the title/subtitle, both disabled "Coming soon" controls, Connect invoking the sign-in entry point and the connected page, failure → Back and cancel → welcome, and the error-state and Linux/Windows title-bar gates now rendered by `Welcome`. first-run.test.ts checks the bundled grant request shape. The window-chrome browser spec asserts the new heading after the launch handoff. docs/identity.md and the Builderlab README describe the new first-launch screen. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
…oin a community
A successful Builderlab sign-in on the first-launch welcome screen no longer
dead-ends on "Connected to Builderlab". It now continues through two new
screens inside `Welcome`:
1. **Setting up Buzz** — runs four steps in order, showing the current label
beside a spinner (the design system's `CircleNotchIcon` with the
`motion-safe:animate-spin` recipe `ProfileButton` already uses):
- "Downloading keys… [TODO]" — placeholder, waits 2 s
- "Creating keys…" — real: `identity.create()`, the same native
`identity_create` command as the key setup screen, so a human key lands
in the credential store and the next launch opens as a returning user.
`identity.create` reports failure through its snapshot, so the step
re-raises that error; the page shows "Couldn’t finish setup" with the
message and a Back button to the welcome screen, matching the OAuth
failure handling.
- "Uploading keys… [TODO]" — placeholder, waits 2 s
- "Finding communities… [TODO]" — placeholder, waits 2 s
2. **Join a community** — a black-and-white wireframe showing only what a
relay publishes before admission: three example pending-invite rows with
an avatar placeholder, the hostname as title (the client's existing
fallback, since the relay hard-codes `name: "Buzz Relay"`) and a disabled
Join — no hint line, so the example list is a plain array of hosts; then
a disabled Community URL field with a disabled Join to its right,
bottom-aligned with the input, and disabled "Scan a QR code" and
"Create a community" buttons, both "— Coming soon". No display names,
member counts, owners or "invite required" badges, which are not knowable
pre-join. This page is the end of first run for now.
Replacing placeholders: the steps live in `setupSteps()` in
`src/features/identity/setup.ts` as `{ label, run }` entries; swap a `[TODO]`
step's `run` for the real call and drop the marker from its label. The delay
is injected (`delay` prop on `Welcome`, default `wait`) so tests gate each
step instead of waiting 6 s.
Keeping onboarding mounted: `Welcome` previously returned its children as
soon as identity became `ready`, which would have unmounted the setup page
into `ConnectedApp` the moment step 2 saved the key. A small `createSetup`
store (same shape as `createOAuthSession`) now owns step progress, and
`Welcome` renders children only when identity is ready *and* setup is idle.
**No FOUNDATION file was touched.** `App.tsx` already mounts `Welcome`; the
gate change lives entirely in `src/features/identity`. `services.ts`,
`main.tsx` and `communities/service.ts` are unchanged. No Rust changes.
Tests (identity.test.tsx): the sign-in → step labels in order → key created
during "Creating keys…" → identity ready without releasing `ConnectedApp` →
Join a community with three rows carrying no subtitle text, four disabled Join
buttons (three rows plus the URL field's) and every disabled "Coming soon"
control;
and the creation-failure path (later steps never run, Back returns to the
welcome screen with identity still missing). `window-chrome.spec.mjs` still
lands on the "Buzz" heading after the launch handoff, so it is unchanged.
docs/identity.md and the Builderlab README describe the new screens.
Validation: `vitest run src/features/identity` (77 tests), `tsc --noEmit`,
`pnpm lint`, `git diff --check`. Not exercised in the native app; the
"Creating keys…" step writes a real Keychain item on a true first run.
Squashed for the stacked PR: a2273f6 (drop the subtitle from the invite rows)
and f315ab9 (a disabled Join beside the community URL field) are folded into
this commit, so it introduces the page in the form those two follow-ups left it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
…nd community page
On the first-run **Join a community** page the Community URL field and its
Join button now work. A typed address may omit its scheme
(`relay.example.com` → `wss://relay.example.com`); `wss://`/`https://` are
accepted in any case, while `ws://`/`http://`, credentials, paths, queries and
fragments are rejected. Join is enabled only while the address normalizes;
Enter submits; an invalid submit shows "Please enter a valid community URL"
under the field. Join opens the new page, and Cancel there returns with the
typed address still in the field.
New reusable `FindCommunity` (src/features/communities/FindCommunity.tsx) with
props `{ url, onCancel, onJoin?, request?, titleId? }`; it holds no `Welcome`
state. Logic lives in `find-community.ts`:
- Finding: "Finding community", the hostname beside the `CircleNotchIcon`
spinner recipe from "Setting up Buzz", and Cancel. Cancel calls `onCancel`
at once and aborts the lookup, so a late answer never lands.
- Found: only what the relay publishes — icon via `communityIcon()`, hostname
in monospace, "Buzz relay v{version}" when NIP-11 carries `version`, and,
when `/api/join-policy` returns a policy, the old client's consent copy:
"I am 18 years of age or older." (iff `age_attestation_required`) and
"I agree to the Buzz Terms of Service and Privacy Policy." with links to
`{origin}/api/join-policy/terms|privacy` opened like `CommunityDialog`'s
(`target="_blank"`). Join/"Accept and join" needs every shown box ticked and
an `onJoin`; `Welcome` passes none, so first run shows "— Coming soon"
disabled, because joining needs the Communities service that FOUNDATION
`services.ts` creates only after identity is ready. No name, description,
member count, owner or invite badge.
- Error, classified rather than echoed: network/timeout/5xx → "Could not
reach the relay. Check your connection and try again." with Retry + Cancel;
non-JSON NIP-11 or `software`/`name` not Buzz → "This address isn't a Buzz
community." (Cancel only); a Buzz relay without `read_state_snapshot`,
`icon` or `buzz_v1` → "No community is set up at this address." (Cancel
only). A policy 404 means "none" only after NIP-11 confirmed a Buzz relay;
3xx/5xx/non-object/malformed policy answers are the retryable error, never
a silent consent waiver.
Requests are unauthenticated `GET /` and `GET /api/join-policy`, fired in
parallel through the existing native `relay_http` path (`nativeRelayRequest`
with no body), which needs no identity. The fetcher is injectable for tests.
No change to `relay/native.ts`: NIP-11 already comes back as the full
untyped document.
Normalizer: `relayOrigin()` stays strict because saved IDs/aliases must never
resolve to an origin; new sibling `enteredRelayOrigin()` in `destination.ts`
prefixes `wss://` for scheme-less human input and delegates. Join-policy
shape validation moved out of `native-api.ts` into `join-policy.ts`
(`JoinPolicy`, `joinPolicy()`), shared by the native `info` route and the new
probe; `CommunityInfo.policy` now names that type. Behaviour of the `info`
route is unchanged.
No FOUNDATION file was touched (App.tsx, services.ts, main.tsx,
communities/service.ts). No Rust, no `.env.local`, no new dependencies. The
`Welcome` gate (children only when identity is ready and setup idle) is
unchanged; the find/join pages are two `useState`s inside it.
Tests: destination.test.ts ports the relevant relayProbe.test.mjs cases under
the secure-origin rule; find-community.test.ts covers the NIP-11 and policy
classification matrix, parallel issue with the NIP-11 verdict first, and
abort pass-through; FindCommunity.test.tsx covers finding → found with and
without a policy, consent gating, Join disabled without `onJoin`, Cancel
discarding a late answer, Retry, and the Cancel-only errors;
identity.test.tsx covers Join disabled on empty/invalid, Enter submitting,
the error copy, navigation with the exact `relay_http` GETs to the canonical
origin, and Cancel preserving the URL. docs/identity.md describes the field
and page.
Validation: `vitest run` on src/features/identity and the touched
src/features/communities files (213 tests), `tsc --noEmit`, `pnpm lint`,
`biome check` on both feature directories, `git diff --check`, and
tests/browser/window-chrome.spec.mjs in Chromium (passes; its "Buzz" heading
expectation is unchanged). Not exercised in the native app: no live relay was
probed and no real first run was performed, so the real `relay_http` GET
against a deployed relay, the Terms/Privacy links opening in the system
browser from WKWebView, and the page's appearance in the packaged window
remain to be tried by hand.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
…, drop the version line, and put Cancel left of Join Three changes to the first-run Find community page from c0c47f6: 1. The found state's heading "Community found" is now "Join Community". 2. The "Buzz relay v{version}" line is gone, and with it `FoundCommunity.version` (nothing else read it). The icon and hostname stay. 3. Cancel is on the left and Join / "Accept and join" on the right, and the unreachable error's pair is now Cancel then Retry. Design-system check: the only written rule is docs/design-system.md §settings forms ("secondary action first, primary Save last"); `Dialog`/`AlertDialog` render `.buzz-dialog-actions` as a trailing flex row in source order with no reversal anywhere in src, the design-system viewer specimens put Cancel first, and 47 of 48 dialog footers in the repo (e.g. CommunityDialog, SignOut, CommunityRail) do the same. The left-aligned onboarding rows in IdentitySetup put the primary first, but no doc prescribes that, so the documented footer order wins. Row alignment is unchanged (left, like Welcome's other pages); only the order moved. Product decision recorded here because the branch's history took a detour: the original commit (6ef1f5d) also added a NIP-98-signed `POST /query` membership probe that skipped Join Community for an admitted key through an "Already a member" page, and the next commit (e3aa2ac) removed it again, because the first-run Find community page must send no authenticated / signed request before the user chooses to join. The two are squashed for the stacked PR, so this commit contains only the three changes above. The only pre-join traffic remains the two unauthenticated `relay_http` GETs from c0c47f6 — `GET /` (NIP-11) and `GET /api/join-policy`: `findCommunity(url, request, signal)` takes no viewer, `RelayFetch` has no body parameter and `nativeRelayFetch` is a GET-only wrapper. `/query` reads in `native-api.ts` are the post-join Communities service and are unchanged. Tests: FindCommunity.test.tsx covers the renamed heading, no version text, and Cancel preceding Join and Retry in DOM order; FindCommunity.test.tsx and find-community.test.ts assert the lookup issues exactly the two GETs with no body; identity.test.tsx adds the journey sign-in → setup → URL → Join Community with `relay_http` called exactly twice, both GET, no POST. docs/identity.md describes the renamed page and states that nothing is signed pre-join. No FOUNDATION file (App.tsx, services.ts, main.tsx, communities/service.ts), no Rust, no new dependencies. Validation of the two original commits: `vitest run src/features/communities src/features/identity` (353 tests at the second), `tsc --noEmit`, `pnpm lint`, `biome check` on both directories, `git diff --check`. Not exercised in the native app. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
…page and enter the app
Join on the first-run **Join Community** page (`FindCommunity` rendered by
`Welcome`) now joins the community and opens the app on it, instead of being a
disabled "Coming soon" placeholder. This is the first slice: admission read,
local save, enter the app. No invite code, no `accept-policy`/`claim`, no kind-0
publish, and no profile seeded from the Builderlab email.
FOUNDATION edit (src/app/App.tsx), the one line approved for this change:
`<Welcome identity={services.identity} communities={services.communities}>`
(formatted over three lines). No other FOUNDATION file (services.ts, main.tsx,
communities/service.ts) is touched. No Rust changes.
Why through the live service: `createCommunities` is created at startup and
hydrates once, when `identity.ready` resolves during "Creating keys…", so a join
performed from `Welcome` must go through that instance; writing storage or
calling native commands directly would be invisible to it. `Welcome` takes
`communities: Pick<Communities, "snapshot" | "joined">`, exactly what it reads.
`Welcome.onJoin` is the Add-a-community dialog's code-less path with the same
owners: guard `communities.snapshot().status === "ready"`, then
`inspectProfile(found.url)` (NIP-11 again, then the existing NIP-98-signed
`POST /query` for the key's own kind 0, which proves the relay admits this key),
then `communities.joined({ id: found.url, name: found.host, icon? },
read.exists ? read.profile : profile)` — the same canonical-origin id
`CommunityDialog` saves, so the two paths dedupe against each other — then
`setup.reset()` so the gate lifts and `ConnectedApp` mounts with the new
community already selected. A comment marks the seam where the planned profile
step goes, between the save and the reset. Failures render the existing
`Failure` page with Back to the find page, typed URL intact: 401/403 on the read
→ "This community needs an invite"; any other read failure → the lookup's
"Could not reach the relay…" copy, classified rather than echoed; a `joined()`
storage failure → the service's own message ("Could not save this community on
this device. Try again."), which throws before any state changes.
`FindCommunity`: `onJoin` may return a promise; while it is pending the page
shows the existing spinner recipe with "Joining…" and disables Join and Cancel,
then re-enables. A rejected `onJoin` only ends the wait (the caller owns the
copy). The primary button is always "Join" — never "Accept and join" — with
consent boxes gating it rather than renaming it.
Tests: identity.test.tsx adds a native-relay fixture (stateful `identity_restore`
that learns the key after `identity_create`, `relay_sign` with a real keypair,
`relay_http` answering NIP-11 with `self` and icon, policy 404, gated `/query`)
and a real `createCommunities(…, identity.ready, connectNativeTransport)`
instance for three journeys: sign-in → setup → URL → Join Community → Join → the
exact `relay_http` sequence (GET `/`, GET `/api/join-policy`, GET `/`, POST
`/query` `[{kinds:[0], authors:[viewer], limit:5, consistency:"strong"}]`) with
the page held and children hidden until the read answers, then children render,
the snapshot selects the community and `localStorage["buzz-client.v1:<viewer>"]`
holds the membership, selection and relay-seeded profile with no `/events`; the
403 → needs-an-invite page → Back → find page → Cancel with the URL intact; and
a throwing `Storage.setItem` → storage message with nothing joined. The other
`Welcome` screens get an inert `communities` stub so their exact `invoke`
assertions stay untouched (the real service polls `get_os_idle_seconds`).
FindCommunity.test.tsx asserts the "Join" label with a policy shown and adds the
pending/rejected-join case. docs/identity.md describes the join, what is signed
and what is not; docs/communities.md names `Welcome` as the second caller of
`communities.joined`.
Validation: `vitest run src/features/identity src/features/communities src/app`
(61 files, 744 tests), `tsc --noEmit`, `pnpm lint`, `git diff --check`. Not
exercised in the native app: a true first run against a deployed relay (the
host's NIP-98 signing of `/query`, a closed relay's 403, the Keychain write),
the "Opening Buzz" mark during the first session's restore after the gate lifts,
and the Terms/Privacy links opening in the system browser remain to be tried by
hand.
Stacked-PR note: `main` has carried #644 (20a778e) since this commit was
written, and its `joined()` also queues the add for the account's
known-communities sync on the device record. The whole-record `toEqual` in the
Join journey therefore expects `sync: { known: [], outbox: [{ url:
"wss://relay.example.com:8443", removed: false }] }`; first run sends nothing
from that outbox. This is the test hunk of the later rebase chore 5bf651b,
folded in where the assertion is introduced.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
…n invite page
When Join on the first-run **Join Community** page gets a 401/403, the
**This community needs an invite** page rendered by `Welcome` told the user
only that the key had no invite, with a Back button. Relay admins admit people
by npub (the old client's invite dialog, the mobile person-invite page and
`buzz-admin add-member --pubkey` all take one), and a denied first-run user has
no app to open Settings in, so this page is the only place they can learn what
to send. It now does what the old desktop client's `MembershipDenied` screen
did:
- Body: "{host} admits members by invite, and this key has none yet. Send your
public key to a community administrator so they can add you, then try
again." The title is unchanged.
- A **Your public key (npub)** block: the full npub via `npubEncode` from
`nostr-tools/nip19` — never hex, never `formatPublicKey`'s abbreviation, per
DESIGN.md "Public identity text" — in `font-mono text-mono break-all
select-text`, a ghost compact `Button` with `CopyIcon` labelled "Copy npub"
that writes the npub with `navigator.clipboard.writeText`, an inline
`role="status"` line ("Public key copied." / "Could not copy. Select the key
above to copy it.") because `Welcome` renders outside `ToastProvider`, and
the note "This is your public identity — it's safe to share." This is the
Profile panel's "Public key" recipe (ProfilePanel.tsx:519-547). The viewer
is narrowed from the identity snapshot (`status === "ready"`), and if the
npub cannot be encoded the block is omitted rather than showing hex.
- Actions in the branch's footer order: **Back** (secondary, left; unchanged
behaviour, typed URL intact) and **Try again** (primary, right), which
re-runs the same `join(found)` — NIP-11 plus the signed `POST /query` —
so an admin's add takes effect without retyping the address. While the
retry is in flight both buttons are disabled and the label reads "Trying
again…"; a repeat denial re-renders this page, any other failure renders
the plain failure page as before.
The key panel's container is a bare wireframe (plain `border-standard`
border, no colour), marked with a code comment, like the other first-run
wireframe pages on this branch, so designers can see it needs styling. No
invite-code field is added: that needs the `/api/invites/claim` +
`accept-policy` flow left out of the Join slice.
Implementation stays in `src/features/identity/Welcome.tsx`: `joinError`
gains an optional `denied: FoundCommunity`, the local `Failure` helper gains
optional `publicKey` / `onRetry` / `busy` props and owns the copy status, and
`npubOf()` does the narrowing and encoding. No FOUNDATION file (App.tsx,
services.ts, main.tsx, communities/service.ts), no Rust, no new dependencies.
Tests (identity.test.tsx): the denied journey now asserts the extended body,
the created key's npub rendered under the "Your public key (npub)" heading
with no hex anywhere, Copy writing that npub to a stubbed
`navigator.clipboard.writeText` and the inline status appearing, then Back →
find page → Cancel with the URL intact as before. A new journey flips the
relay to admit the key, clicks Try again, asserts both buttons disabled and
"Trying again…" while the gated read is held, the exact two further
`relay_http` calls (GET `/`, signed POST `/query` for the key's kind 0), and
then app entry with the membership selected. Both fail against the previous
`Welcome` (no "Try again" button). docs/identity.md describes the page.
Validation: `vitest run src/features/identity` (5 files, 90 tests),
`tsc --noEmit`, `pnpm lint`, `biome check` on both files, `git diff --check`.
Not exercised in the native app: the real clipboard write from WKWebView and
a true admin add-then-Try-again against a deployed relay remain to be tried
by hand.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
This was referenced Oct 28, 2026
Closed
matt2e
marked this pull request as ready for review
October 28, 2026 12:26
Contributor
Author
|
🤖 Closing: this stack was cut as chronological slices of the branch history rather than logical, independently reviewable layers. It will be replaced by a stack built from the squashed diff. Nothing from this PR was merged. |
This was referenced Oct 28, 2026
This was referenced Oct 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 1 of 12 of the
onboarding-flowstack (the first-run onboarding work reviewed on #1061, split into stacked PRs per the plan). Base:main. Each PR is a contiguous slice of the original branch's history, cherry-picked ontomain; the combined tree of the whole stack equalsonboarding-flowat572d69398rebased ontomain(verified by diff, see the stack notes in the last PR). Built onmainatdf5180b4a(2026-10-28, theorigin/mainof the build);mainhas moved on since and the stack is not rebased onto the newer tip.Stack
1. #1802
onboarding/01-welcome-find-join← this PR2. #1803
onboarding/02-profile-step-landing-url3. #1804
onboarding/03-builderlab-tenants4. #1805
onboarding/04-key-choice-import-welcome-back5. #1806
onboarding/05-link-and-native-backup6. #1807
onboarding/06-unlink-restore-create-my-key7. #1808
onboarding/07-connect-choice8. #1809
onboarding/08-invite-links-known-communities9. #1810
onboarding/09-onboarding-layouts-and-copy10. #1811
onboarding/10-navigation-deadline11. #1812
onboarding/11-review-fixes12. #1813
onboarding/12-do-not-merge-keychain-slotScope
Replaces
IdentitySetupwithWelcomeon a native first run: Builderlab sign-in through the bundled plugin's native grant (oauth/first-run.ts), the "Setting up Buzz" step store (setup.ts), the Join a community page, the reusable Find community page (src/features/communities/FindCommunity.tsx) with classified lookup failures and consent boxes,enteredRelayOrigin/join-policy.ts, the real Join (signed admission read →communities.joined→ enter the app), and the This community needs an invite page with the npub, Copy and Try again.FOUNDATION: this PR carries two of the three
src/app/App.tsxedits on the stack — the import swap and the<Welcome identity=…>element (approved in the original commit1a3b9d67a, "FOUNDATION edit (src/app/App.tsx), kept to the minimum needed"), and thecommunities={services.communities}prop (approved inf707951b0, "the one line approved for this change"). No other FOUNDATION file (services.ts,main.tsx,communities/service.ts,plugins/api.ts) is touched anywhere in the stack.Caveat for the reviewer — Matt's call, not decided here: this level ships the
[TODO]placeholder steps ("Downloading keys… [TODO]", "Uploading keys… [TODO]", "Finding communities… [TODO]", each a 2 s wait) and the disabled "Coming soon" controls on a native first run with no key. They are replaced by real steps in PRs 04–06 of the stack. Open question for Matt: may PRs 01–06 land onmainone by one with those placeholders visible between merges, or should the stack be held and merged back-to-back once all six are approved?Squashes vs the original branch:
a2273f663+f315ab9b4folded into the Join a community commit;6ef1f5d74+e3aa2ac13(a signed membership probe added and removed again — the product decision is that nothing signed is sent pre-join) squashed into one commit holding only the surviving heading / version-line / button-order changes; the test fix-up S1 (thesyncoutbox expectationmain'sjoined()writes since #644) folded into the Join commit. The net-zero revert pair581f5ec9d+37c6cb5cfis dropped.Checks at this level (worktree
/tmp/onb-stack, head ofonboarding/01-welcome-find-join)bin/pnpm exec tsc --noEmit— 0 errorsbin/pnpm lint— 0 errors (21 pre-existing infos)bin/pnpm design:check— all checks passgit diff --check origin/main...HEAD— cleanbin/pnpm vitest run src/features/identity src/features/communities src/bundled/builderlab src/app— 92 files, 1273 tests passed, 0 failedbin/pnpm test:browser --project=chromium --no-deps tests/browser/window-chrome.spec.mjs— 1 passedNative evidence — not claimed here
No per-level native run was performed for this PR. Native first-run testing happens on the tree of the final DO-NOT-MERGE PR of the stack (#1813), which carries the isolated debug Keychain slot; the human in-app check for this PR's behaviour is owed there, so this PR stays a draft without
buzz-review-completeduntil Matt confirms it.🤖 Generated with Claude Code