diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 82771e1dd..cddd78463 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -15,11 +15,12 @@ ### Changed +- Added spatial navigation between the Model Hub sidebar and model list using left/right arrow keys - Removed "Chat about this" option from question dialogs - Redesigned ask dialogs with fixed height, removed explicit Next button, and simplified headers - Rewrote the task tool prompt for the new wire schema and to push callers toward the most specific agent type: read-only research is directed to `agent: "scout"`, and omitting `agent` is framed as an explicit decision that no listed specialist fits. - Task rendering now keeps the `⟨agent⟩` type badge on live progress and finished result rows (previously it vanished after the streaming call preview), and the Task header shows only the spawn count instead of repeating the per-item agent types. -- Redesigned `/model` into a fullscreen alternate-screen Model Hub (the `/settings` idiom) with full mouse support: a sidebar of scopes (recently used, role management, all models, per-provider), a metadata-aligned model table (role chips with thinking glyphs, context window, per-M cost), and inline role/thinking assignment strips instead of the old nested menus. Providers without credentials are listed dimmed and forward to `/login` when activated, replacing the "Only showing models with configured API keys" warning; up/down hops providers even while typing (Tab toggles row navigation), and searching grays out and skips providers without matches. +- Redesigned `/model` into a fullscreen alternate-screen Model Hub (the `/settings` idiom) with full mouse support: a sidebar of scopes (recently used, role management, all models, per-provider), a metadata-aligned model table (role chips with thinking glyphs, context window, per-M cost), and inline role/thinking assignment strips instead of the old nested menus. Providers without credentials are listed dimmed and forward to `/login` when activated, replacing the "Only showing models with configured API keys" warning; you can now use arrow keys to navigate between the sidebar and model list, and searching grays out and skips providers without matches. - OAuth logins now run inside a cancellable login dialog (Esc aborts the flow and restores the editor) instead of an inescapable pairing prompt, and the model hub only reopens after a successful login. ### Fixed diff --git a/packages/coding-agent/src/modes/components/model-hub.ts b/packages/coding-agent/src/modes/components/model-hub.ts index 51786734f..40b43e665 100644 --- a/packages/coding-agent/src/modes/components/model-hub.ts +++ b/packages/coding-agent/src/modes/components/model-hub.ts @@ -883,6 +883,20 @@ export class ModelHubComponent implements Component { return; } + // ←/→ are spatial pane switches: the sidebar sits left of the rows. + // They never reach the search caret — fuzzy queries don't need one. + if (matchesKey(data, "left")) { + this.#focus = "scope"; + return; + } + if (matchesKey(data, "right")) { + // Only views with rows can take list focus (not the locked pane). + if (rolesView || this.#isBrowserView(entry)) { + this.#focus = "list"; + } + return; + } + // Arrow ownership: scope mode hops the sidebar even while the search // bar holds the caret; list mode navigates rows. if (this.#focus === "scope") { @@ -1337,13 +1351,13 @@ export class ModelHubComponent implements Component { const entry = this.#activeEntry(); if (entry.kind === "roles") { return this.#focus === "list" - ? "↑/↓ roles · Enter pick model · x clear · t thinking · Tab providers · Esc close" - : "↑/↓ providers · Tab roles · Esc close"; + ? "↑/↓ roles · Enter pick model · x clear · t thinking · ← providers · Esc close" + : "↑/↓ providers · → roles · Esc close"; } if (entry.kind === "provider" && entry.locked) { return entry.oauth ? "Enter log in · ↑/↓ providers · Esc close" : "↑/↓ providers · Esc close"; } - const arrows = this.#focus === "scope" ? "↑/↓ providers · Tab models" : "↑/↓ models · Tab providers"; + const arrows = this.#focus === "scope" ? "↑/↓ providers · → models" : "↑/↓ models · ← providers"; const refresh = entry.kind === "provider" ? " · F5 refresh" : ""; if (this.#mode === "pick") { return `Enter use for this session · ${arrows} · type to search${refresh} · Esc close`; diff --git a/packages/coding-agent/test/model-hub.test.ts b/packages/coding-agent/test/model-hub.test.ts index 1d27122c8..97f38dd4f 100644 --- a/packages/coding-agent/test/model-hub.test.ts +++ b/packages/coding-agent/test/model-hub.test.ts @@ -437,6 +437,24 @@ describe("ModelHub", () => { hub.handleInput("\x1b"); expect(onCancel).toHaveBeenCalledTimes(1); }); + + test("left/right arrows switch between the sidebar and the model list", () => { + const modelA = makeModel("prov-a", "model-a"); + const modelB = makeModel("prov-b", "model-b"); + const { hub } = createHub({ models: [modelA, modelB] }); + installTestTheme(); + + // Right enters list mode: Down now moves the model selection, the + // scope stays on All models. + hub.handleInput("\x1b[C"); + hub.handleInput(DOWN); + expect(normalize(hub.render(220))).toContain("All available models"); + + // Left returns to the sidebar: Down hops to the first provider. + hub.handleInput(LEFT); + hub.handleInput(DOWN); + expect(normalize(hub.render(220))).toContain("prov-a ·"); + }); }); describe("provider refresh lifecycle", () => {