Skip to content

feat(web): first-run onboarding — intro, privacy and connect-and-verify wizard, recommendations from detected accounts, and Start a new project - #808

Merged
danyaberezun merged 15 commits into
mainfrom
onboarding-first-run
Oct 28, 2026
Merged

danyaberezun merged 15 commits into
mainfrom
onboarding-first-run

Conversation

@OLavrik

@OLavrik Olga Lavrichenko (OLavrik) commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

A brand-new user opened ThinkRail to an empty Projects rail, a "No model provider connected" warning, and one Open project card. Connecting a model meant digging through Settings → Providers (JetBrains AI, then Subscriptions, then a long API-key list), and someone without a repo had nowhere to start. The analytics question popped up in the first seconds, a sign-in abandoned by a reload could park the next attempt on "Working…", and pi's terminal phrasing ("(default)", "(headless)", "press Enter", "(blank for github.com)") leaked into the login dialog. This implements the first-run redesign discussed in #742.

Approach

  • A short cold-start wizard: intro → privacy → Connect → verify → You're set. It opens on a short intro that reuses thinkrail.ai's tagline and its three "why" points. Get started leads to a privacy step (only while analytics consent is unconfirmed; its Continue is the only way on), then Connect. Once a provider is configured, a verification covers the options in place, in the same box: one model.list and model.default read, no tokens spent. When it answers, the connect screen fades out and the You're set screen fades in, with no Done button: one line naming the provider and the default model ("Anthropic connected · Claude Fable 5 is the default model") over Welcome's own two cards, Start a new project and Open project. The wizard has no project step and no progress strip, so there is one project entry point instead of two copies. Nothing animates height, and reduced motion swaps instantly. Zero models warns and offers Check again and Back to the choices. Skip setup is on every screen before done except privacy. The wizard shows only on a true cold start (no projects, no provider, not dismissed). The gate lives in the store, so a remount mid-wizard keeps it. Reaching done or skipping persists onboardingDismissed, so it never comes back. While it is active, the Projects rail and pane are not rendered, so the wizard has the whole window. It stays until a project opens.
  • Detected is earned, never hardcoded. Get started asks the host which other local tools are already signed in (provider.detect):
    • Six sources, each matched by the tool's own signed-in predicate (checked against its source code, not guessed): Claude Code → anthropic, Codex's ChatGPT sign-in → openai (falling back to legacy openai-codex), Copilot editor plugins and the Copilot CLI → github-copilot, Kimi CLI → kimi-coding, Grok Build → xai.
    • It tells a subscription from an API key: a Claude Console login, Codex in apikey mode, Grok's API-key scope and logged-out or revoked states are not sign-ins. An expired access token with a live refresh token still counts. Each tool's relocation env vars are honoured (CLAUDE_CONFIG_DIR, CODEX_HOME, XDG_CONFIG_HOME, COPILOT_HOME, KIMI_SHARE_DIR, GROK_HOME, …).
    • One card per provider, whichever source found it first.
    • Only a match whose pi provider is unconfigured and can sign in becomes a "Sign in with …" card in the Detected block, with the tool's plan as a badge. The badge shows only a known plan name (plus, pro, max, team, business, enterprise, edu). Anything else, free included, drops the badge but keeps the card.
    • Each source gets 1.5 s, so a hung mount reads as "not signed in" instead of stalling first run. Files over 1 MiB are skipped, except Claude Code's global config, which carries per-project history and gets 32 MiB. On Windows an absolute LOCALAPPDATA is honoured for Copilot.
    • One accepted false positive: a lapsed or revoked Claude Code Keychain sign-in that was never logged out still reads as signed in, because the Keychain itself is off limits.
    • When nothing is found there is no Detected block, just the full list.
    • Detection only reads those tools' own config files: no Keychain, no network, and no credential value leaves the host. It suggests and never borrows; the user still signs in through pi's own flow, because reusing another tool's rotating refresh token would sign one of them out.
  • One connect body, two homes. ConnectProviderOptions is shared by the wizard and the Welcome banner's new ConnectProviderDialog:
    • One searchable list of fixed height, so the layout does not jump. OAuth-capable providers come first, then key-only ones. JetBrains AI sits in Detected when central is installed, otherwise it is a plain row at the end of the list.
    • Each row reuses the Settings ProviderRow, with the same buttons as Settings: Sign in where the provider supports it, API key where it accepts one. Login orchestration moved into useProviderAuth so Settings and both new surfaces share it.
  • One owner for provider status. The store keeps one provider.status snapshot. It is invalidated by provider.changed, by every welcome, by a successful login frame, by a local sign-out, and whenever a provider surface opens, and it is read single-flight per version. provider.logout now broadcasts provider.changed too. The Welcome banner, the wizard gate, both connect surfaces and Settings read the same snapshot, and the banner never renders while the gate is still undecided.
  • Start a new project. project.create makes an empty git repo under ~/thinkrail/<name> with an initial commit. The project keeps the name the user typed (the folder is its slug). The Welcome card can optionally open the setting-up-a-project chat right away. That continuation is dropped if the user navigates away first. A failed bootstrap removes .git, and the folder only while it is still empty. A repo with no remote now reads "main · local" in the branch picker, so it is not mistaken for a GitHub branch.
  • First-run prompts wait. Inside the wizard, the analytics question is its own privacy step instead of a popup in the first seconds. The shell's consent window and the interview invite are held while the wizard may show, decided by selectOnboardingActive. Skipping setup brings the consent window back. Once a project exists, the wizard stops holding them.
  • Connect surfaces decide their own handoff. ConnectProviderOptions reports a newly configured provider once through onConnected. The banner's dialog then closes itself and toasts the provider and its model count before Welcome switches to "You're set". The wizard does neither: it verifies in place.
  • A new sign-in for a provider supersedes an abandoned one server-side. pi serializes credential operations per provider, so a login orphaned by a reload used to block the next one forever.

Changes

  • web / panels:
    • new OnboardingWizard, ConnectProviderOptions, ConnectProviderDialog, ConnectedLine, CreateProjectDialog, useProviderAuth and detectAccounts;
    • useConsentDraft is shared by AnalyticsConsentDialog and the wizard's privacy step;
    • startChatInTab takes an options object (preferredGroupId, prompt, draft);
    • WelcomePanel gates the wizard through the store's onboardingWizard;
    • ProvidersSettings uses the shared hook;
    • BranchPicker marks local-only repos.
  • web / store, shell:
    • onboardingWizard state and selectOnboardingActive;
    • Shell holds consent and interview prompts while onboarding is active;
    • ProjectHome drops the rail and pane in place without remounting Welcome.
  • web / auth: the login dialog's select is rendered as two choice cards, and Continue sits next to Cancel. loginOptions translates pi's terminal phrasing into dialog copy.
  • web / chat: Skills rows show a bold name over a two-line description, and the full text appears on hover.
  • web / shell layout: Balanced's lower right group opens on the TODO List, with Review beside it. On a fresh workspace the plan is what there is to look at; review comments only exist once there is a diff. The tool-rails, bottom-panel and rail-layout e2e specs now expect that default.
  • contracts: project.create (optional displayName), provider.detect + DetectedAccount, AppConfig.onboardingDismissed; PROTOCOL_VERSION / ONBOARDING_PROTOCOL_VERSION / LAYOUT_PLAN_PRESET_PROTOCOL_VERSION = 94.
  • server:
    • the project.create handler;
    • auth/detectAccounts behind provider.detect (six sources, each bounded by a 1.5 s timeout; resolves each to the first provider the current pi runtime can OAuth into; plan badges limited to known plan names);
    • providerLogin supersedes a same-provider pending login silently;
    • provider.logout broadcasts provider.changed;
    • project.create takes an optional displayName, and its rollback never removes files it didn't create;
    • custom layout presets accept the plan tool (clients refuse to save one to a pre-94 host, with an explanation). The contract's LayoutToolId already listed it, but the settings validator did not, so a preset saved from the new Balanced default was rejected.
  • scripts: new on-demand bun run smoke:logins. It boots a throwaway host, starts every provider's sign-in methods over the wire, walks every select branch, and checks each reaches its first user step. It cancels everything and stores nothing.
  • e2e:
    • onboarding fixtures reach the host through the page's baseURL, so binary and desktop runs reset between tests;
    • the host's XDG_CONFIG_HOME, COPILOT_HOME, KIMI_SHARE_DIR and GROK_HOME are pinned inside the e2e home, and GROK_AUTH_PATH, CLAUDE_SECURESTORAGE_CONFIG_DIR and CLAUDE_CODE_OAUTH_TOKEN are cleared, so detection never reads the developer's real config.
  • shared (test only, a main flake from fix(shared): re-check the Central artifact once a moved watch is live #830): jbcentral.test.ts "re-arms deeper…" now waits for the watcher's second re-arm instead of sleeping a fixed 5 ms.
  • locales: every new onboarding string lives in packages/locales/en.json, under panels.onboarding.*, panels.connectProvider.* and panels.createProject.*. check:i18n passes.
  • Docs: SPECs updated for panels, store, shell, auth, contracts, server auth and scripts. README now covers the first-launch walkthrough and when the privacy switch is offered.

Not in this PR:

  • Claude subscription sign-in is a terms question, not a code one. Anthropic's Claude Code legal page does not let third-party apps offer Claude.ai login or route requests through Free/Pro/Max plans. pi's built-in anthropic OAuth sign-in, and detection recommending it when Claude Code is found, fall under that. This PR leaves pi's provider list as it is. Whether to hide that sign-in, drop the Claude Code match, or point Claude users at an API key or JetBrains AI needs a product and legal decision.
  • Central sign-in can't launch without a terminal. Central CLI 2.0.0's central login needs a TTY and exits with bubbletea: could not open TTY when ThinkRail spawns it, so ThinkRail shows "couldn't launch". This likely affects the desktop app too and needs its own fix.
  • The connection check doesn't validate the credential. It reads the model list and spends no tokens, so a mistyped API key still shows "connected".

Screenshots

1280×800 viewport, captured with the hermetic e2e host (no provider configured; its stub "E2E Test Provider" and Central fixture appear in the list). Before = origin/main at 9d88588e (the later main commits don't touch these screens). The wizard's after shots are from this branch rebased onto 2c97cb1a.

The wizard, step by step

Intro, before = first launch on main

Before After

Privacy. Only while analytics consent is unconfirmed. Before, the consent window popped up over the first screen.

Connect. Nothing detected, so the Detected block holds only the Central fixture. Before = the banner's "Connect a provider" (Settings → Providers).

Before After

Zero models. The connection worked but no models came back: Check again re-runs the read, and Back returns to the choices.

Connect, with detected accounts. Claude Code (Max), Codex (ChatGPT Plus) and the Copilot CLI are signed in on this machine (1280×920, so the whole block fits).

You're set. Reached right after the in-place check, with no Done button: the provider and the default model over Welcome's two cards. No tokens are spent.

Start a new project from that card.

Around the wizard

Anthropic sign-in method

Before After

GitHub Copilot first step

Before After

Skills dialog

Before After

Branch picker on a repo with no remote

Before After

Related issues

Implements the first-run proposals from #742. Closes nothing. Login ownership, a hermetic smoke:logins and cancellable detection I/O are tracked in #949.

Checklist

  • Fast gates pass: bun run lint, bun run typecheck, bun run test
    • On c0283dcf (merged with main at 6cd6f905), these pass locally and in CI: check:deps, check:boundaries, check:seams, check:spec-surface, check:i18n, check:compiler, lint, typecheck and the full bun run test. That includes the detectAccounts scenario tests: logout, Console vs subscription, keyring, expired tokens, Windows paths and LOCALAPPDATA, BOM, oversized files, known plan names.
  • E2E suite passes for app-affecting changes. CI on c0283dcf: every source, binary and desktop shard is green with no flaky retries. Locally, bun run e2e on 2f6c6170 gave 656 passed and 1 failed: extension-dialogs.spec.ts:178, which flakes at the same rate on main. The onboarding specs settle the host before each cold start, fail rather than skip on a leaked provider, and sign every provider out in afterEach. The flake audit, with retries off, is in the PR comments.
    • On an earlier head: onboarding.spec.ts against the compiled binary (bun run e2e:binary), and bun run smoke:logins 56/56 sign-in flows reaching their first step.
  • Before/after screenshots are included for frontend changes, or marked not applicable
  • Relevant SPEC.md / top-level specs updated to reflect any boundary, contract, or behavior change
  • I have read the Contributing guide and agree to the Code of Conduct

@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

🌐 Website preview

Preview retired when this pull request closed.

@OLavrik Olga Lavrichenko (OLavrik) changed the title feat(web): first-run onboarding — intro and setup wizard, one-click Anthropic connect, and Start a new project feat(web): first-run onboarding — intro and three-step setup wizard, recommendations from detected accounts, and Start a new project Oct 24, 2026
@OLavrik Olga Lavrichenko (OLavrik) changed the title feat(web): first-run onboarding — intro and three-step setup wizard, recommendations from detected accounts, and Start a new project feat(web): first-run onboarding — intro and connect-and-verify wizard, recommendations from detected accounts, and Start a new project Oct 26, 2026
@OLavrik Olga Lavrichenko (OLavrik) changed the title feat(web): first-run onboarding — intro and connect-and-verify wizard, recommendations from detected accounts, and Start a new project feat(web): first-run onboarding — intro, privacy and connect-and-verify wizard, recommendations from detected accounts, and Start a new project Oct 26, 2026
@OLavrik
Olga Lavrichenko (OLavrik) marked this pull request as ready for review October 26, 2026 21:51

@jetbrains-air jetbrains-air Bot 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.

Requesting changes — please address the blocking inline finding.

Comment thread apps/web/src/panels/OnboardingWizard.tsx Outdated
Comment thread apps/web/src/panels/ConnectProviderOptions.tsx
Comment thread apps/web/src/panels/setupPrompt.ts Outdated

@jetbrains-air jetbrains-air Bot 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.

Approved — ready to merge.

@OLavrik
Olga Lavrichenko (OLavrik) added this pull request to the merge queue Oct 26, 2026
@OLavrik
Olga Lavrichenko (OLavrik) removed this pull request from the merge queue due to a manual request Oct 26, 2026

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

Requesting changes. Reviewed 828a2ae.

Blocking

  1. The privacy step can override an opt-out that was already confirmed (useAnalyticsConsent.ts:57-73, OnboardingWizard.tsx:196-223). If sharing is turned off in Settings → Privacy or in another window while the step is open, Continue saves the stale draft = true over it. → Once analyticsConsentConfirmed is true, adopt the saved analyticsEnabled (or advance) and persist only an explicit wizard choice. Add a test: opt out in Settings during the step, press Continue, sharing stays off.

  2. Provider status is held in several independent caches with incomplete invalidation (useHasProvider.ts:10-24, useProviderAuth.tsx:52-62,94-95, WelcomePanel.tsx:37, ProviderWarningBanner.tsx:15):

    • logout sends no provider.changed, so the wizard later verifies a signed-out provider and drops it from the list after Back;
    • a login that finishes while disconnected is never picked up after reconnect;
    • on hosts < 92, "No model provider connected" stays under "You're set";
    • the warning banner can flash before the wizard.

    → Keep one store-owned, single-flight status snapshot, invalidated by provider.changed, local login/logout and every welcome. Pass hasProvider to the banner and don't render it while the gate is pending.

  3. The onboarding e2e is nondeterministic, and its failures can be masked (onboarding.spec.ts:41-67):

    • the non-waiting count() falls back to a real provider (Anthropic in Settings, where e2e-apikey sits behind "Show more"; Bedrock in the wizard/dialog);
    • the 8×200 ms loop can exit without answering the prompt;
    • a timeout can skip the in-body finally cleanup; the retry then hits test.skip(anyProviderConfigured), and Playwright reports failed+skipped as flaky (green). Later onboarding tests on that lane then skip silently.

    → Wait for the exact e2e-apikey row (click show-more in Settings), drive its known prompt and assert success. Move cleanup to afterEach (log out every canLogout provider). Skip only when all configured providers are env; otherwise fail.

  4. The post-create setup continuation isn't fenced (setupPrompt.ts:13-15, defaultWorkspace.ts:6-23, ProjectTree.tsx:231-235). Navigating elsewhere while workspace.list is pending lets the stale continuation activate the new project's Default workspace and open the setup chat. From the rail, a list failure becomes an unhandled rejection (void adopt(...)). → Carry a navigation generation through the continuation, re-check it before activating Default and before startChatInTab, and catch errors.

  5. The API key is hidden for OAuth-capable providers although the copy promises it (ConnectProviderOptions.tsx:217, panels.connectProvider.description, panels.onboarding.connect.subtitle). Anthropic, OpenAI, xAI, Kimi and Copilot offer only Sign in, with no route to Settings. Given the PR's own note on Anthropic's terms, first run steers Claude users to subscription sign-in and hides the API-key path. → Show the API key button as Settings does, or fix the copy and link to Settings → Providers.

Non-blocking

  1. Login ownership (useProviderAuth.tsx:64-78, ConnectProviderDialog.tsx:54-57,71-75, OnboardingWizard.tsx:150, providerLogin.ts:49-50):

    • closing the dialog or pressing Skip while loginStart is pending leaves a hidden active login;
    • the handoff's unconditional clearLogin() can drop a newer login without cancelling it;
    • supersede silently kills another client's live login (its dialog stays on "Working…") and leaks the loginAnalytics entry.

    → Bind the login to its owning operation (cancel a loginId that arrives after the owner closed) and clear only the matching terminal login. Publish a terminal frame for the superseded loginId and call dropLogin.

  2. smoke:logins isn't hermetic and can pass falsely (scripts/smoke-provider-logins.ts):

    • the spawned host auto-loads the repo-root .env despite the restricted env;
    • USERPROFILE is not set;
    • an immediate success and a 0/0 run count as passes;
    • wire requests have no timeout, so the script hangs if the host dies;
    • there is a free-port race, and kill() isn't awaited before rmSync.

    → Use cwd: sandbox with --no-env-file and set USERPROFILE. Accept only authUrl/deviceCode/prompt, and fail on zero outcomes. Bound requests and reject them on close. Let the host bind port 0 and await its exit before cleanup.

  3. detectAccounts timeouts don't cancel I/O (detectAccounts.ts:79-86,262). With a probed path on a hung mount, each detect leaves up to 9 FS ops pending. On Bun 1.4.3 (16 CPUs), 16 blocked ops stall unrelated async FS on the host. → Make detection single-flight and reuse a pending read per path until it settles.

  4. createProject rollback removes more than it created (projects.ts:402). rm -rf of the new folder also deletes anything written into it during bootstrap (hook output, a concurrent writer). → Remove .git, then use a non-recursive rmdir.

  5. plan in Balanced breaks "Save as preset" against v91 hosts (presets.ts:83). Preset saves aren't protocol-gated, and the old validator rejects plan. → Gate preset saves that contain plan on protocol 92, with an explanation.

Nits

  • The cold-start predicate is derived twice (selectors.ts:120, WelcomePanel.tsx:45-60) → use one store selector.
  • panels.connectProvider.* and panels.onboarding.connect.* carry identical copy, there are two "Continue" keys, and offerSetup is always true → dedupe the keys and drop the option.
  • The test at settings.test.ts:381 duplicates :392 → delete one.
  • loginPromptMessage hardcodes English UI fragments → use locale keys via t.
  • zh-CN panels.welcome.openProjectSubtitle is stale after the rewording → delete it.
  • Assertions on the non-existent connect-show-all (welcome.spec.ts:68) and onboarding-steps (onboarding.spec.ts:146) are vacuous, and the connect-detected count is checked before the list renders (:148) → remove them, or move them after render.
  • The chmod 0o400 failure injection doesn't work as root (onboarding.spec.ts:317) → reuse the directory trick from analytics-consent.spec.ts.
  • No tests cover: zero models → Back, createProject rollback, the 1.5 s cap, onConnected firing once.
  • The live, workflow and artifact host spawners don't pin the new detection env vars → use one shared env helper.
  • Stale docs:
    • apps/web/SPEC.md:143 (compiler bailout) and :163 (LoginDialog mount);
    • "Settings while it is open" → "Settings → Providers";
    • the PR body's "JetBrains AI pinned first" / "Recommended" → the code actually places it last and labels the block "Detected".
  • Other small issues:
    • after Back from zero models, the provider can't be re-verified;
    • the display name becomes the slug;
    • BranchPicker shows "local only" on a load error.

@OLavrik

Copy link
Copy Markdown
Collaborator Author

Thanks for the thorough review, danyaberezun. Everything except 6–8 is fixed in 2c5de64. Items 6–8 are tracked in #949.

Blocking

  1. Consent overwrite. Until the user flips the switch on the privacy step (or in the consent dialog), a decision confirmed elsewhere is adopted, not overwritten. The switch shows the saved value, and Continue / Done advances without writing. Only a choice made on that surface is persisted. New e2e: confirm an opt-out over the settings wire while the step is open, press Continue, and sharing stays off. It fails on the old hook.
  2. Provider status.
    • It now has one owner: the store's providerStatus snapshot, read single-flight per providerStatusVersion.
    • The version bumps on provider.changed, on every welcome (so a login that finished while disconnected is picked up), on a successful untargeted login frame (this covers hosts below 92), on a local sign-out or Retry, and when a provider surface opens.
    • provider.logout now broadcasts provider.changed.
    • The Welcome banner takes hasProvider and isn't rendered while the gate is pending.
    • New e2e: connect and then sign out in Settings during the intro, press Get started, and the wizard stays on Connect with the provider listed. It fails on the old caches.
  3. Onboarding e2e.
    • The helper waits for the exact e2e-apikey row (it clicks show-more in Settings), drives its known secret prompt, and each test asserts its own success.
    • afterEach signs out every canLogout provider.
    • requireColdProviders skips only when every configured provider is env and fails otherwise.
    • The count() fallback, the polling loop and the in-body finally sign-outs are gone.
  4. Setup continuation. It proceeds only while the new project's home is still current (selected, no workspace active), and it starts the chat only while that Default workspace is still the active one. The rail swallows a failed workspace.list read, so void adopt(...) no longer rejects. I used a "still where the user left it" predicate instead of a new navigation counter. It answers the same question without another store field, and returning to the project's home would legitimately resume the setup.
  5. API key. The connect surfaces show the same buttons as Settings (Sign in and/or API key), so the copy holds for every provider.

Non-blocking

  1. createProject rollback. It removes .git, then the folder only if it is still empty. There is a unit test with a failing pre-commit hook that writes a file.
  2. plan presets. A preset set that places plan isn't sent to a host below LAYOUT_PLAN_PRESET_PROTOCOL_VERSION (92). Layout settings explains why instead.

Nits

  • The cold-start predicate is now selectOnboardingEligible, used by both selectOnboardingActive and Welcome.
  • Locales:
    • panels.onboarding.connect.* is gone (the wizard uses panels.connectProvider.*);
    • both Continue keys became common.continue;
    • offerSetup is dropped;
    • loginPromptMessage takes t;
    • the stale zh-CN key is removed.
  • The duplicate settings test is deleted.
  • The vacuous assertions are removed. The connect-detected count now waits for the list.
  • The privacy-failure test uses the directory trick instead of chmod.
  • New tests:
    • zero models → Check again, and zero models → Back;
    • the 1.5 s cap, with a hung stat, while other sources still answer;
    • createProject rollback;
    • the banner path toasts exactly once.
  • Every hermetic spawner (Playwright web server, artifact and live-restart hosts, workflow harness, artifact probes) now takes toolHomeEnv(home) from the new @thinkrail/shared/toolHomes.
  • Docs: apps/web/SPEC.md (compiler list, LoginDialog mounts), "Settings → Providers", and the PR body (Detected rather than Recommended, JetBrains AI placement).
  • Small issues:
    • zero models now offers Check again, since Back alone couldn't return to a configured provider;
    • project.create takes an optional displayName, so the project keeps the typed name;
    • useBranchList reports failed, so BranchPicker never says "local only" for an unreadable repo.

Verification on 2c5de64: all fast gates pass, bun run test passes, and bun run e2e is 8/8 shards green with 646 passed.

@jetbrains-air jetbrains-air Bot 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.

Approved — ready to merge.

@jetbrains-air jetbrains-air Bot 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.

Approved — ready to merge.

@jetbrains-air jetbrains-air Bot 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.

Approved — ready to merge.

@jetbrains-air jetbrains-air Bot 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.

Approved — ready to merge.

…etween the dialog and the wizard's privacy step, let custom presets place the TODO List, and align the layout e2e specs with Balanced opening on it
… lands, verify a provider connected before the wizard reaches Connect, and record the new-project Default exception in the top-level docs
…ed by every change, adopt a consent choice confirmed elsewhere, fence the new-project setup continuation, offer API keys on every provider, and make the onboarding e2e deterministic
…ending login on skip, split the wizard into step components, drop the viewport-cap table, and emit a created project once
…model.list is empty, instead of a fixed-delay re-read

@jetbrains-air jetbrains-air Bot 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.

Approved — ready to merge.

@jetbrains-air jetbrains-air Bot 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.

Approved — ready to merge.

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

Requesting changes. Reviewed 3b7dc1e. Items 1–5 from my previous review are fixed. Before merge, please fix the three provider-status gaps below in one commit, and rebase.

  1. While this window has its own login open, another login's success no longer invalidates provider status (apps/web/src/store/appStore.ts:4294-4303). The active-login guard returns {} before the providerStatusVersion bump, so on a host without the new login-success provider.changed the snapshot stays stale. → Compute the bump before the guard and return it on the guard path too; keep the guard for dialog ownership. Test: a success frame for another loginId while a login is active leaves activeLogin unchanged and bumps providerStatusVersion.
  2. Welcome no longer re-reads provider status on entry (apps/web/src/panels/useProviderStatus.ts:33-35, WelcomePanel.tsx:37). main re-read it on every Welcome mount. Now a credential change the host doesn't push (a pi CLI login/logout) leaves the banner wrong until a provider surface opens or the socket reconnects. → Invalidate the shared snapshot when Welcome mounts (reloadProviderStatus() in a mount effect). Test: remounting Welcome issues a fresh provider.status read.
  3. The connect handoff runs on a stale snapshot (apps/web/src/panels/ConnectProviderOptions.tsx:91-103). The effect ignores refreshing, so pressing Get started while the post-logout re-read is in flight verifies the signed-out provider and adds it to the baseline. Reconnecting it after Back then never triggers verification; only Skip setup gets out. → Skip the effect while auth.refreshing, and add refreshing to its deps. Test (e2e): hold the post-logout provider.status reply (as hideModels holds model.list), press Get started, and assert no verification starts.
  4. The branch conflicts with main after #817 (LoginDialog.tsx, BranchPicker.tsx, ChangesPanel.tsx, ProviderWarningBanner.tsx, ProvidersSettings.tsx, and the panels and shell SPEC.md). → Rebase onto main.
  5. The PR body is stale, and it becomes the squash commit message: it says PROTOCOL_VERSION / ONBOARDING_PROTOCOL_VERSION = 92 (now 94, plus LAYOUT_PLAN_PRESET_PROTOCOL_VERSION = 94) and "Submit sits next to Cancel" (now Continue). → Update it.

@danyaberezun

Copy link
Copy Markdown
Collaborator

Heads-up: we're taking over the items from my last review (R1–R3, the rebase onto main, the PR body) and will push them to this branch shortly. Please hold pushes to onboarding-first-run until we report back here.

Resolve the conflicts with #817's button system: BranchPicker keeps the local-only
marker on both trigger forms, LoginDialog keeps Continue beside Cancel at size l,
ProviderWarningBanner keeps the connect dialog with main's banner styling, and
ProvidersSettings drops main's inline ProviderRow for the shared one. Move the
onboarding buttons (wizard steps, connect options, create-project, ProviderRow)
onto the five variants and three sizes.
…her login's success, and hand off only from a current snapshot
@danyaberezun

Copy link
Copy Markdown
Collaborator

Pushed 378b345c on top of 3b7dc1e7 (fast-forward, no history rewritten):

  • 6a181499 merges main. Conflicts with [Design] Unify buttons into one system and move the app onto it #817's button system are resolved in LoginDialog, BranchPicker, ChangesPanel, ProviderWarningBanner, ProvidersSettings and the panels and shell specs. The onboarding buttons now use the five variants and three sizes, and buttonUsage.test.ts is green.

  • 378b345c fixes items 1–3 of my last review:

    1. applyLoginFrame bumps providerStatusVersion before the active-login guard (store unit test).
    2. useHasProvider invalidates the snapshot on mount, so Welcome re-reads status on entry. The e2e test writes auth.json outside ThinkRail, then returns to Welcome.
    3. The connect handoff runs only on a current, successful snapshot. The e2e test holds the post-logout provider.status reply across Get started.

    All three tests fail with the fixes reverted.

  • The PR body now says protocol 94 and Continue.

Local checks: lint, typecheck, every check:* gate and bun run test pass, and bun run e2e passes on all 8 shards (656 tests).

@jetbrains-air jetbrains-air Bot 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.

Approved — ready to merge.

@danyaberezun danyaberezun 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 378b345. Items 1–5 of review 5661150983 and items 1–3 of review 5675255042 are fixed and covered by tests, main is merged in, and CI is green. Items 6–8 stay in #949.

@danyaberezun
danyaberezun added this pull request to the merge queue Oct 27, 2026
@danyaberezun
danyaberezun removed this pull request from the merge queue due to a manual request Oct 27, 2026
…eardown step, and drop wall-clock and inherited git-config dependence
…port, so a sign-in that lands before the opening re-read still hands off
…e projects folder with retries, and count the toast before it auto-dismisses

@jetbrains-air jetbrains-air Bot 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.

Approved — ready to merge.

@jetbrains-air jetbrains-air Bot 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.

Approved — ready to merge.

@jetbrains-air jetbrains-air Bot 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.

Approved — ready to merge.

@danyaberezun

Copy link
Copy Markdown
Collaborator

Flake audit of this PR's tests, started at 378b345cf. Retries were off, 8 lanes ran with CPU burners, and two independent audits ran alongside. Fixes: 526b20cc8, 2f6c61709, f9a9ed54e, c0283dcff.

Product race (found by the stress test)

  • ConnectProviderOptions: the baseline waited for a current snapshot. A sign-in that finished before the dialog's opening provider.status re-read was absorbed into the baseline, so the banner dialog never closed or toasted. It failed 1/320 under load and 3/3 with that re-read held. → The baseline is the first report again, as panels/SPEC.md describes. Only the announcement waits for a current, successful snapshot. A new e2e holds the opening re-read until after the sign-in.

Test determinism

  • Cold starts were checked after the first page load, and that load latches the wizard gate. Two things leaked into that check: a Central runtime replacement still running after the reset reports the previous providers, and a credential written late by a failed test reaches the next one. → openColdStart: reset, wait for jbcentral ≠ configuring, sign out every canLogout provider, assert a cold start, then load the page.
  • afterEach stopped at its first failing step. → It uses allSettled, then removeTree, which retries the ENOTEMPTY caused by the open repo's index.lock. The first error is rethrown.
  • expect.poll does not retry a callback that throws, and the host writes config.json non-atomically. → A tolerant readConfig().
  • The "toasts once" count ran after the other assertions, so it could land after the 5 s auto-dismiss. → The count now runs as soon as the toast is visible.
  • detectAccounts asserted the 1.5 s cap as elapsed < 3000, which fails on an event-loop stall. → Fake timers: still unsettled at 1499 ms, ["kimi-cli"] at 1500 ms.
  • In the rollback test, an inherited GIT_CONFIG_COUNT or GIT_CONFIG_PARAMETERS overrode the test's hook. → Both are cleared while the test runs.
  • From main (fix(shared): re-check the Central artifact once a moved watch is live #830), fixed here so the merge queue doesn't trip on it: jbcentral.test.ts "re-arms deeper…" slept a fixed 5 ms before checking two chained setTimeout(0) re-arms. It failed 2 of ~120 CI unit jobs (this PR and remove-jetbrains-from-websites). → It now waits for the third watch, with a 1 s deadline.

Evidence (retries 0)

  • Onboarding ×20 under load: 340/340.
  • Welcome, consent and projects: 210/210.
  • Rails and bottom panel: 148/148.
  • PR unit files: ×50 and ×30 under load, 0 failures.
  • jbcentral fix under load: 2000/2000 (5/2000 failed before it).
  • CI on c0283dcff: 19/19 green, with no flaky retries in any source, binary or desktop shard.

Not from this PR

  • visualize.spec.ts had flaky passes on this branch's Oct 27 runs. The same window hit 5 other branches and merge-queue runs. It passed 450/450 here and 450/450 on main.
  • extension-dialogs.spec.ts:178 (toBeInViewport after scrolling the tool-input list) flakes locally at the same rate on main (16/80) as here (18/80). It hasn't shown up in CI. It needs its own fix.

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