feat(coding-agent): added spatial navigation between sidebar and list panes
- Enable horizontal navigation between the scope sidebar and model list using left and right arrow keys. - Update UI help strings to reflect the new spatial navigation controls.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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`;
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
Reference in New Issue
Block a user