From 7832f3a31b20e71a7834dc66847b90413f0f7448 Mon Sep 17 00:00:00 2001 From: Trevor Miller Date: Tue, 1 Sep 2026 17:36:52 -0700 Subject: [PATCH] feat(ux): side-arrow step nav; Claude leads and is the default engine Back/Next become full-height strips flanking the step card, so the pointer can click through every step without the controls moving as the card's content resizes. The footer keeps only the final action. The engine grid lists Claude first and the bare shell last, and a fresh open preselects Claude (a remembered recent engine still wins). Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_0168Vr2KdUf9dNR9vBZGcpmx --- src/renderer/src/components/EntryScreen.tsx | 1243 ++++++++++--------- tests/ux/entry-wizard.spec.ts | 18 +- 2 files changed, 650 insertions(+), 611 deletions(-) diff --git a/src/renderer/src/components/EntryScreen.tsx b/src/renderer/src/components/EntryScreen.tsx index 169bf8d6..4b8ffc54 100644 --- a/src/renderer/src/components/EntryScreen.tsx +++ b/src/renderer/src/components/EntryScreen.tsx @@ -307,7 +307,7 @@ export function EntryScreen({ // Captured once at mount — the tag stays on the restored engine even as the // user clicks around; clicks persist for the NEXT open via selectEngine. const [recentEngine] = useState(readLastEngine) - const [engine, setEngine] = useState(recentEngine ?? 'local') + const [engine, setEngine] = useState(recentEngine ?? 'claude') // Resume is self-contained: its own engine, independent of the New-session picker. const [resumeEngine, setResumeEngine] = useState( recentEngine && isAiEngine(recentEngine) && recentEngine !== 'openrouter' @@ -669,10 +669,10 @@ export function EntryScreen({ : projParent ? tilde(projParent) : parentLabel - // Registry order + the bare shell. Derived so a newly registered engine can - // never be silently missing here (this list and the scratch row below had - // already drifted apart). - const engineOptions = ['local', ...ENGINE_IDS] as SessionEngine[] + // Registry order (Claude first) + the bare shell last. Derived so a newly + // registered engine can never be silently missing here (this list and the + // scratch row below had already drifted apart). + const engineOptions = [...ENGINE_IDS, 'local'] as SessionEngine[] const daemonLabel = location === 'remote' ? remoteHost?.label || remoteHost?.sshTarget || 'Remote SSH' : 'Local' const selectedWorkspaceLabel = @@ -896,80 +896,151 @@ export function EntryScreen({ /> - - - {stepTitle} -

{stepHint}

-
- - {/* ── Step · Action ────────────────────────────────────────── */} - {step === 'action' && ( -
- {actionOptions.map(({ id, label, hint, icon: Icon }) => ( - + + + {stepTitle} +

{stepHint}

+
+ + {/* ── Step · Action ────────────────────────────────────────── */} + {step === 'action' && ( +
+ {actionOptions.map(({ id, label, hint, icon: Icon }) => ( + - ))} -
- )} + + ))} +
+ )} - {/* ── Step · Workspace ─────────────────────────────────────── */} - {step === 'workspace' && ( - <> - {targetOptions.length > 0 && ( -
-
Target
-
- {targetOptions.map(({ id, label, hint, icon: Icon }) => ( - - ))} + + ))} +
- - )} + )} - {target === 'scratch' && ( -

- A throwaway session in the app-owned scratch dir. No repo, nothing to file away. -

- )} + {target === 'scratch' && ( +

+ A throwaway session in the app-owned scratch dir. No repo, nothing to file + away. +

+ )} - {target === 'repo' && ( - <> - {remoteHosts.length > 0 && ( + {target === 'repo' && ( + <> + {remoteHosts.length > 0 && ( +
+
Location
+
+ + +
+
+ )} +
+
Name
+
+ { + setProjName(e.target.value) + setScaffoldErr('') + }} + placeholder="project-name" + className="flex-1 font-mono" + /> + +
+
+ + )} + + {target === 'existing' && ( + <>
Location
@@ -984,7 +1055,8 @@ export function EntryScreen({
+ {remoteHosts.length === 0 && ( +
+ SSH profiles are configured in Settings → SSH Hosts. +
+ )}
- )} -
-
Name
-
- { - setProjName(e.target.value) - setScaffoldErr('') - }} - placeholder="project-name" - className="flex-1 font-mono" - /> - -
-
- - )} - {target === 'existing' && ( - <> -
-
Location
-
- - -
- {remoteHosts.length === 0 && ( -
- SSH profiles are configured in Settings → SSH Hosts. + {location === 'remote' && remoteHosts.length > 0 && ( +
+
Host
+
+ {remoteHosts.map((h) => ( + + ))} +
)} -
- {location === 'remote' && remoteHosts.length > 0 && ( -
-
Host
-
- {remoteHosts.map((h) => ( + {location === 'remote' ? ( +
+
+ setCwd(e.target.value)} + onKeyDown={(e) => + e.key === 'Enter' && loadRemoteDir(cwd, { select: true }) + } + placeholder={remoteHost?.defaultCwd || '~ (remote home)'} + className="border-0 bg-transparent font-mono shadow-none focus-visible:ring-0" + /> - ))} -
-
- )} - - {location === 'remote' ? ( -
-
- setCwd(e.target.value)} - onKeyDown={(e) => - e.key === 'Enter' && loadRemoteDir(cwd, { select: true }) - } - placeholder={remoteHost?.defaultCwd || '~ (remote home)'} - className="border-0 bg-transparent font-mono shadow-none focus-visible:ring-0" - /> - - - -
-
- {remoteListingLoading && !remoteListing ? ( -
- {' '} - Loading folders… -
- ) : remoteListingErr ? ( -
- {remoteListingErr} -
- ) : remoteListing && remoteListing.entries.length === 0 ? ( -
- No child folders. -
- ) : ( - remoteListing?.entries.map((d) => ( - - )) - )} -
-
- ) : ( - <> - {(pins.length > 0 || recents.length > 0) && ( -
- {pins.length > 0 && ( -
-
Pinned
-
- {pins.map(workspaceChip)} -
+ + +
+
+ {remoteListingLoading && !remoteListing ? ( +
+ {' '} + Loading folders…
- )} - {recents.length > 0 && ( -
-
Recent
-
- {recents.map(workspaceChip)} -
+ ) : remoteListingErr ? ( +
+ {remoteListingErr} +
+ ) : remoteListing && remoteListing.entries.length === 0 ? ( +
+ No child folders.
+ ) : ( + remoteListing?.entries.map((d) => ( + + )) )}
- )} -
-
Folder
-
- - setCwd(e.target.value)} - placeholder={intent === 'resume' ? 'Every workspace' : '~ (home)'} - className="flex-1 font-mono" - /> - {/* Resume can legitimately span every repo — a new - session cannot, so the escape hatch is resume-only. */} - {intent === 'resume' && cwd.trim() && ( -
+ ) : ( + <> + {(pins.length > 0 || recents.length > 0) && ( +
+ {pins.length > 0 && ( +
+
Pinned
+
+ {pins.map(workspaceChip)} +
+
+ )} + {recents.length > 0 && ( +
+
Recent
+
+ {recents.map(workspaceChip)} +
+
+ )} +
+ )} +
+
Folder
+
+ - )} + setCwd(e.target.value)} + placeholder={intent === 'resume' ? 'Every workspace' : '~ (home)'} + className="flex-1 font-mono" + /> + {/* Resume can legitimately span every repo — a new + session cannot, so the escape hatch is resume-only. */} + {intent === 'resume' && cwd.trim() && ( + + )} +
-
- - )} + + )} - {/* Remote only — the local path input above already IS the + {/* Remote only — the local path input above already IS the confirmation, so repeating it there is noise. */} - {location === 'remote' && ( -
- - {daemonLabel} - - {selectedWorkspaceLabel} - -
- )} - - )} - - )} - - {/* ── Step · Model ─────────────────────────────────────────── */} - {step === 'model' && ( - <> -
-
Engine
-
- {engineOptions.map((e) => ( - - ))} -
-
+
+ )} + + )} + + )} - {engine === 'openrouter' && ( + {/* ── Step · Model ─────────────────────────────────────────── */} + {step === 'model' && ( + <>
-
Harness
-
- {(['codex', 'hermes'] as const).map((h) => ( +
Engine
+
+ {engineOptions.map((e) => ( ))}
- )} - {isAiEngine(engine) ? ( -
+ {engine === 'openrouter' && (
-
Model
- +
Harness
+
+ {(['codex', 'hermes'] as const).map((h) => ( + + ))} +
- {!(engine === 'openrouter' && openrouterHarness === 'hermes') && ( + )} + + {isAiEngine(engine) ? ( +
-
Reasoning effort
- +
Model
+
- )} -
- ) : ( -

- A plain shell — no model, no agent. -

- )} - - )} - - {/* ── Step · Loop goal ─────────────────────────────────────── */} - {step === 'goal' && ( - <> -
-
Goal
-