Files
OpenResearch/ui
Daniel Kim 979d8e9362 Rework first-run onboarding and the New project modal (v0.1.79) (#126)
* Tighten the onboarding + new-project flows

Onboarding:
- Gate steps 1 and 2 (a signed-in agent, GitHub access) — both are hard
  requirements for chat and project creation, so Continue is disabled
  until they're met.
- Drop the data-dir step: the default suits ~everyone, it's write-once,
  and Settings > Storage already exposes it with a move flow.
- Split harness notes into not-installed vs not-signed-in, each with the
  real command (claude auth login / codex login / opencode auth login).
- Fix OpenCode reporting Connected while unauthenticated (agent_ready
  ignored authentication).

New project modal:
- Auto-open on the onboarding hand-off.
- Only show the fork choice when the user can actually push (new
  /api/github/repo-access), and say what happens when they can't.
- Name the real GitHub account in repo previews (new /api/github/account).
- Drop the branch picker; resolve the repo's real default branch server
  side instead of assuming "main".
- Describe the copy honestly: it's a single-branch, history-less snapshot,
  not a GitHub fork.
- Top-anchor the modal so the tab row doesn't move between tabs.

* Review round 1: fix onboarding lockouts, trapped name field, stale fork mode

- opencode: treat a non-empty `opencode models` list as authenticated, so
  env-key installs (no auth.json) aren't locked out by the new step-1 gate.
  Claude and Codex already had the equivalent fallback.
- Onboarding: surface a load error. A rejected probe left harnesses/git at
  null forever, which the gates read as not-ready — disabling Continue with
  no hint and no way out, on the screen that is the whole app at first boot.
- NewProjectForm: hoist the project-name field out of repoFields. A stalled
  access check used to unmount it, and `valid` needs a name, so the form
  became unsubmittable with no escape.
- NewProjectForm: reset canPush on every repo change and sync repoMode to
  the answer, so a forced "fork" can't linger and silently copy a repo the
  user can push to.
- github.rs: share one authed_get helper (with a 10s timeout — there was
  none), URL-encode owner/repo, drop the now-dead has_push_access wrapper.
- Drop the unread `known`/`defaultBranch` fields from the repo-access
  response and its TS type.
- ProjectsHome: consume the post-onboarding hand-off so a later remount
  can't re-open the modal.
- Deduplicate the paper-repo hint and the two identical code-chip CSS rules;
  tie the modal offset and max-height together with a --modal-top token.

* Review round 2: correct the opencode fallback, unblock SSH-only users

The round-1 opencode fix rested on a false premise — I claimed `opencode
models` only lists authenticated providers. Tested: under a signed-out HOME
it returns nothing, so the fallback never fired for the users it was meant to
rescue, and had the catalog ever been listed it would have marked signed-out
installs Connected. Replaced with an explicit provider env-key check, matching
claude.rs. Verified both directions: env key set -> ready, none -> gated.

- Onboarding: step 2 gets a "Skip — I use SSH keys" escape. ensure_clone
  tries ssh first, so a token isn't actually required for the use-existing
  path, and git_settings_json can't see SSH keys at all — the hard gate was
  locking out a setup the CLI's own startup note calls supported. Also covers
  the git-not-installed dead end, where the token form is unreachable.
- Onboarding: split the shared loadError into per-probe flags. A telemetry
  failure was putting a connectivity error on unrelated gates and hiding the
  actionable hint behind it.
- NewProjectForm: only force "fork" on denied access; the symmetric sync was
  undoing selectPaper's deliberate copy default for writable paper repos.
  Reset repoMode in onRepoChange instead, which is where the repo changes.
- github.rs: drop the deleted has_push_access's doc comment, left welded onto
  authed_get and describing the wrong thing.
- Bump to v0.1.79 (+ Cargo.lock): ui/dist changes don't ship without it.

* Review round 3: detect synced-env keys, unstick the branch fallback

- Harness detection: resolve API keys through orx's synced env file as well
  as the process env (new detect::api_key, used by claude.rs and opencode.rs).
  A key pasted into Settings > Environment is handed to the harness child, so
  chat works — but detect() couldn't see it, and step 1 is a hard gate.
  Verified: key only in ~/.openresearch/env now reports ready for both.
- opencode: widen the provider key list (Gemini/Groq/xAI/DeepSeek). Measured
  rather than assumed that `opencode models` can't stand in for a credential:
  signed out with no keys it still lists 7 free/bundled models, so keying off
  it would report signed-out installs as Connected.
- baseline_branch: fall back to `git ls-remote --symref` when the GitHub API
  can't answer. Without a token the branch resolved to a hardcoded "main" and
  the clone died on any master/dev repo — and the form no longer has a branch
  field to recover with. git's own ssh/https credentials need no token.
- Step 1 no longer gates on a failed probe: detection fans out to per-binary
  version/model commands that can stall, and there's no way out of onboarding.
- Step 2's skip is always offered and reworded "Skip for now": a stale gh
  token still reads as connected, hiding the escape from the users who need
  it most. Promoted to a real button rather than a muted link.

* Rebuild ui/dist after rebase

* Review round 4: stop 404 forcing a history-losing copy

- repo_meta: a 404 means "invisible to this token", which is also how a
  private repo looks to an SSH-only user who can push to it fine. It was
  reported as can_push=false, which force-forks — and this branch removed the
  "Use this repo" button in that state, so the override was gone too. The
  user would silently get a new repo with their history dropped. Report
  unknown instead, matching the fallback used everywhere else.
- remote_default_branch: bound it with a 15s timeout. ls-remote tries ssh then
  https, and a black-holed host blocks on TCP connect ~75s each — the exact
  hazard authed_get got a timeout for last round, reintroduced on the git path
  of the same request. A stall now degrades to "main" instead of hanging.
- git.rs: the new function had been inserted between ensure_clone's doc
  comment and ensure_clone, so the comment described the wrong function and
  ensure_clone was left undocumented.
- Never show a "Detecting…" spinner next to a fetch error: on a failed probe
  both steps now render the retry copy in place of the card, instead of a
  spinner that claims work is in progress beside an enabled Continue.
- onRepoChange no longer resets repoMode in paper mode, where editing the
  auto-filled repo would quietly retarget pushes to the upstream paper repo.

* Review round 5: make step 2 an honest soft gate, fix stale-state contradictions

- Step 2 no longer pretends to gate. Cloning and pushing work over SSH keys,
  so a disabled Continue beside an always-enabled "Skip for now" was two
  buttons doing the same thing — one greyed out, which reads as a bug. One
  Continue, always enabled, and the hint states what's missing instead of
  blocking on it.
- A rejected probe now clears its state as well as setting the error flag.
  Otherwise a failed *re-check* kept the old cards on screen: stale "Not
  signed in" badges and a "sign in to continue" hint next to a Continue that
  harnessError had just un-gated, with no indication the check had failed.
- Mode switches reset repoMode via a chooseMode helper. Leaving paper mode
  carries the paper's repo into the field but not its copy default — and
  because the field is pre-filled, onRepoChange never fired to reset it, so
  "Private copy" stayed selected on a repo the user never chose it for.
- Deduplicate the project-name field and blank-repo hint, both of which were
  inlined a third time in "New blank repo" (flagged since round 2).

* Review round 6: restore the Existing repo name field, kill the footgun

Round 5's nameField refactor turned it into a function but updated only two of
three call sites. `{parsed && nameField}` passes the function itself as a
React child, which renders nothing — so "Existing repo" had no Project name
input and Create stayed disabled. Both tsc and a grep of the call sites missed
it; three reviewers caught it.

Made the class of bug impossible rather than just fixing the call site: the
autoFocus parameter was the only reason nameField was a function, so it's a
plain element again like blankRepoHint. Verified in a browser this time —
name field present and submit enabled in every mode.

- Onboarding: the docstring still called step 2 a hard gate, which round 5
  made false, and it sat below the RETRY_COPY const so it documented the wrong
  symbol. Rewritten and moved.
- Onboarding: sequence-guard load(). A token save landing mid-refresh was
  overwritten by the pre-save snapshot, leaving the card reading "Not
  connected" until the next check; the save now supersedes in-flight probes.

* Review round 7: fix a Re-check lockout, share renderNote, trim comments

- load()'s .finally was sequence-guarded, so a token save (which bumps loadSeq
  to supersede the in-flight probe) left `checking` true forever and both
  Re-check buttons dead for the session. `checking` is "a load is running",
  not data a stale response can corrupt — it shouldn't be guarded. Introduced
  by round 6's guard; verified fixed by superseding a slow load in a browser.
- renderNote moved to its own module: ModelPicker, a core chat control, was
  importing it from the first-run walkthrough.
- Drop the redundant {parsed && …} guard (repoFields already returns null),
  a doubled CSS comment block, and two stacked rationale comments.
- Document the opencode agent_ready tightening where the behaviour changes:
  installs with no auth.json and no provider key now read "Not signed in".
2026-07-26 21:48:33 -07:00
..
2026-07-06 20:05:41 -07:00
2026-07-06 20:05:41 -07:00
2026-07-06 20:05:41 -07:00