feat(onboarding): a fresh install always reaches a workspace; no provider CLI is required (#995) - #1047
Merged
Conversation
…ean Mac (#995 stage 1) The recorder runs the REAL checkPrerequisites with only the machine's edges simulated: - a fresh HOME, where every provider CLI installs on macOS; - launchd's PATH; - /bin/sh; - a temp Electron app path, which for the packaged case holds the OpenCode runtime tree #994 ships. The recordings were taken on main 82babd2, before any policy change, and show the wall. Both clean machines report ready:false with blocking [claude, codex], even the packaged one where OpenCode is bundled and usable, and even though Grok was found. Without RECORD_FIRST_RUN=1 the test re-runs both clean simulations live and fails if a provider row no longer matches its recording, so the fixtures cannot drift from what the probes report. Rows found at a machine-wide path (this Mac's npm-global Grok under /opt/homebrew) are recording-machine facts and are not compared. The README explains this. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…first project (#995 stage 2) Claude Code and Codex were `required`, so a Mac without both met a SetupGate with no Continue button. The stage-1 recordings show the wall: - the packaged app on a clean Mac, with OpenCode bundled and usable, was ready:false and blocked on [claude, codex]; - so was a Grok-only machine. Changes: - `required` is removed from provider descriptors. SetupToolStatus.provider marks what the gate must treat as an agent CLI, without the lockout. - src/shared/setup/readiness.ts is the one policy: - usableProviders: found, bundled included; - firstSessionKind: the default provider if usable, else the first usable one in registry order, else a terminal. checkPrerequisites stamps both onto SetupCheckResult, so no consumer re-derives them. ready/blocking are gone; nothing blocks. - Each provider carries a copyable install command and docs URL. These are the providers' native installers, since a fresh Mac has no npm or brew. Each URL was checked on 2026-09-19. Grok's only published distribution is npm. Tests: - The recorder now saves the whole result as `check`. The probe rows are identical to the baseline recording, and the pre-#995 verdicts are kept in baseline-main-82babd21.json. - readiness.test.ts runs the policy over the recorded machines: - packaged clean Mac → opencode; - Grok-only → grok; - zero providers → terminal, via the one documented edit that unsets machine-wide installs; - developer machine → claude. - The live system check asserts the policy on the real probe result. On the macOS CI runner that is the genuine zero-provider Mac. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…up can be reopened (#995) Stages 3–6 of docs/decomposition/onboarding-first-run.md. - Setup store (features/setup/store.ts): one shared check for the gate, the bootstrap and the pickers, with one in-flight probe. - SetupGate: - It opens by itself only when no provider is usable, or when a Homebrew-installable helper is missing. - "Continue with a terminal" is the explicit acknowledgment. Escape does not decide it. - Missing providers show a copyable install command and a docs link. The manual path override now applies to any missing provider. - Reopenable: the `open-setup` command, and File › Setup… (added to NATIVE_MENU_COMMAND_IDS). The spawn error names that place instead of "open Setup", which did not exist after launch. - Bootstrap: - A fresh install waits for the first-run decision instead of spawning Claude under the gate. - It opens firstSessionKind, and falls back to a terminal if that spawn fails, so the run always has a project and autosave. - The persisted-fallback recovery shell uses the same verdict without waiting. - The first project's cwd is home when launchd started the app in /. - Pickers (path picker, new agent, new agent in, provider switch): - Missing providers get a "Not installed · File › Setup…" hint but stay selectable, since a probe can be wrong. - The path picker preselects the machine's first usable provider. Tests: firstRun.renderer.test.tsx runs the real useWorkspace and bootstrap and the real SetupGate against the RECORDED checks. The cases: - no provider: panel, install commands, no spawn until answered; - a terminal on Continue, and restoreStatus fresh; - Retry after installing gives an agent; - the packaged app opens the bundled OpenCode; - a failed spawn falls back to a terminal; - Open Setup reopens and re-probes. Fail-first: 5 of 6 fail against main's bootstrap. Also added: - PathPickerModal preselect and hint tests on the packaged recording; - defaultWorkspaceCwd tests; - catalog governance: 124 commands, 43 approved additions, and the File menu plus Setup. Closes #995 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Juliusolsson05
added a commit
that referenced
this pull request
Sep 19, 2026
…1047 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…utton does Review of #1047 (CHANGES REQUESTED): 1. The panel stamped the interaction-owner marker and said aria-modal but never moved focus. The keyboard router's ownership branch does not stop propagation and a terminal pane forwards keys straight to its PTY, so with focus left in an agent pane a user could type — and press Enter — into the live shell underneath a panel that looked modal, and Tab walked out into the background UI. It now composes the shared Dialog primitive, which owns focus containment, the inert background, the marker and Escape (components/ui/README.md makes that the primitive's job), and the hand-rolled window listener is gone. 2. "Continue with a terminal" recorded the skipped optional helpers BEFORE closing, inside the same try. A setup.json write failure (a full disk, a read-only state dir) therefore left the panel up with no Escape and the fresh-install bootstrap waiting on an answer that could never arrive — the lockout class #995 removes. The close moved to finally: recording a skip is best effort, the acknowledgment is not. 3. The button promised a terminal project in cases where none would be opened (a returning user with a restored workspace; a panel the user opened whose re-probe found no provider). The store now tracks whether a fresh-install bootstrap is parked on the answer, and only then does the button read "Continue with a terminal"; otherwise it is Continue or Close. Escape and click-outside are refused only in that same state, instead of for every automatic panel. 4. A failed install's output is capped and scrollable: unbounded, it pushed the footer and its only button past the viewport. Tests: focus containment and the inert background, the skip failure still answering the panel, and the Close wording when nothing waits. All three fail against the previous gate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…mber the answer Codex review of #1047 (policy/bootstrap lens), CHANGES REQUESTED: 1. BLOCKING. The panel tells users to run OpenCode's installer, which writes ~/.opencode/bin and appends its PATH export to ~/.zshrc. The login-shell probe runs `$SHELL -lc`, and `zsh -lc` never sources .zshrc, so the directory was invisible to both resolution layers: the gate said "Not installed", the user ran the command it gave them, pressed Retry, and it still said "Not installed" — the one loop this feature exists to close. WELL_KNOWN_BIN_DIRS now includes it, and ~/.grok/bin for the same reason. A system test installs a stub there and fails without the change. 2. The provider-less answer is persisted in setup.json beside the skipped helpers. In memory only, it reopened the modal on every launch AND in every new window, each being its own renderer process. 3. Only the panel that ASKED records an answer. Closing a panel opened from the menu used to durably skip mitmproxy, while Escape in the same panel recorded nothing. 4. open-setup no longer closes a panel a parked bootstrap is waiting on: a menu item called Setup… must not silently decide "continue with a terminal" when Escape and click-outside are refused on purpose. 5. resetSetupStoreForTests settles a parked waiter, so one test's bootstrap can no longer resolve into the next one. 6. The live policy assertion is concrete per environment instead of restating firstSessionKindFor, which could only fail for a stamping mismatch. 7. The fixtures README says that recording rewrites the developer's real setup.json, and the plan records the accepted probe-deadline residual. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…indow's bootstrap Codex review of #1047, P1. awaitFirstRunDecision treated only a live provider or this run's dismissal as an answer. A machine that had already chosen "continue with a terminal" therefore parked forever when a SECOND window opened: its workspace slice is absent, so bootstrap takes the fresh path, while the panel correctly refused to show itself because the answer was persisted — leaving nothing to release the wait. One `decided()` predicate now covers all three ways the question is already answered, and it is used both before parking and inside the subscription. The new test opens such a window and expects a terminal project with no dialog. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The catalog governance counts collide: main gained `enable-goal-loop-mcp` (#1006) while this branch adds `open-setup` (#995). Both exist now, so every pinned number moves by one — 125 commands, 119 literal, 44 approved additions — and the three arithmetic comments name both. Also: `git add -A` re-staged this worktree's stale submodule pointers over the ones the merge brought in, which `tsc -b` caught as a missing jumpToLatest on OpencodeTerminalHeadless (#1043's package bump). Every gitlink is back to main's commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Closes #995. Built to
docs/decomposition/onboarding-first-run.md; its Status section maps each stage to its artifact and states the deviations.The wall, recorded
Stage 1 runs the real
checkPrerequisites()on a simulated clean Mac. Only the machine's edges are simulated:/bin/sh;On main (
82babd21) both clean machines returnedready:false, blocking:[claude,codex]. That includes the packaged app, where OpenCode is bundled and usable, and a machine where Grok was found. Recordings:testing/fixtures/first-run/, baselinebaseline-main-82babd21.json.What changes
requiredis gone.src/shared/setup/readiness.tsis the one policy:usableProviders, plusfirstSessionKind, which is the default provider if usable, else the first usable one, else a terminal.checkPrerequisitesstamps both onto the result, and the gate, bootstrap and pickers read those fields.firstSessionKindand falls back to a terminal if that spawn fails./.Tests
testing/system/first-run/prerequisites.firstRun.test.tsre-runs both clean simulations live on every push. It fails if a provider row drifts from its recording, and it asserts the policy on the live result. On the macOS CI runner, which has no provider CLI, that is the genuine zero-provider Mac.readiness.test.ts: the policy over the recorded machines. The packaged clean Mac gets opencode, Grok-only gets grok, zero providers gets a terminal, the developer machine gets claude, and every machine the old gate locked out had something to run.firstRun.renderer.test.tsx: the realuseWorkspaceand bootstrap plus the real SetupGate, fed the recorded checks. It covers:restoreStatus: fresh;Fail-first: 5 of the 6 fail against main's
useBootstrap.ts.Path picker preselect and hint on the packaged recording,
defaultWorkspaceCwd, and catalog governance: 124 commands and File-menu plus Setup.1,330 tests pass across the touched areas, and
npx tsc -bis clean.Not in this PR
🤖 Generated with Claude Code