feat(tui): open subagents with down

This commit is contained in:
Dax Raad
2026-07-10 21:01:04 -04:00
parent a4a948316b
commit 6ce62bd84a
10 changed files with 241 additions and 21 deletions
+6
View File
@@ -86,6 +86,12 @@ test("resolves a session move keybind", () => {
expect(config.keybinds.get("session.move")).toMatchObject([{ key: "ctrl+o" }])
})
test("opens the subagent picker with down", () => {
const config = resolve({}, { terminalSuspend: true })
expect(config.keybinds.get("session.child.first")).toMatchObject([{ key: "down" }])
})
test("disables suspend and assigns ctrl+z to undo when unsupported", () => {
const config = resolve({}, { terminalSuspend: false })
+48 -1
View File
@@ -5,7 +5,13 @@ import { testRender, useRenderer } from "@opentui/solid"
import { expect, test } from "bun:test"
import { onCleanup } from "solid-js"
import { TuiKeybind } from "../src/config/keybind"
import { getOpencodeModeStack, OPENCODE_BASE_MODE, OpencodeKeymapProvider, registerOpencodeKeymap } from "../src/keymap"
import {
formatKeySequence,
getOpencodeModeStack,
OPENCODE_BASE_MODE,
OpencodeKeymapProvider,
registerOpencodeKeymap,
} from "../src/keymap"
function createResolvedKeymapConfig(input: TuiKeybind.KeybindOverrides = {}) {
const keybinds = TuiKeybind.parse(input)
@@ -63,6 +69,47 @@ test("legacy page key aliases compile as page keys", async () => {
}
})
test("formats navigation keys as arrows", async () => {
const shortcuts: Record<string, string> = {}
function Harness() {
const renderer = useRenderer()
const keymap = createDefaultOpenTuiKeymap(renderer)
const config = createResolvedKeymapConfig()
const offKeymap = registerOpencodeKeymap(keymap, renderer, config)
const commands = ["session.parent", "session.child.first", "session.child.previous", "session.child.next"]
const offLayer = keymap.registerLayer({
bindings: config.keybinds.gather("test.arrows", commands),
})
const bindings = keymap.getCommandBindings({ visibility: "registered", commands })
commands.forEach((command) => {
shortcuts[command] = formatKeySequence(bindings.get(command)?.[0]?.sequence, config)
})
onCleanup(() => {
offLayer()
offKeymap()
})
return (
<OpencodeKeymapProvider keymap={keymap}>
<box />
</OpencodeKeymapProvider>
)
}
const app = await testRender(() => <Harness />)
try {
expect(shortcuts).toEqual({
"session.parent": "↑",
"session.child.first": "↓",
"session.child.previous": "←",
"session.child.next": "→",
})
} finally {
app.renderer.destroy()
}
})
test("mode-less bindings stay active when opencode mode changes", async () => {
const counts: Record<string, Record<string, number>> = {}
@@ -0,0 +1,38 @@
/** @jsxImportSource @opentui/solid */
import { testRender } from "@opentui/solid"
import { expect, test } from "bun:test"
import { mkdir } from "node:fs/promises"
import path from "node:path"
import { TuiPathsProvider } from "../../src/context/runtime"
import { PromptHistoryProvider, usePromptHistory } from "../../src/prompt/history"
import { tmpdir } from "../fixture/fixture"
test("down rejects at the newest history item with an empty prompt", async () => {
await using tmp = await tmpdir()
const state = path.join(tmp.path, "state")
await mkdir(state, { recursive: true })
let history: ReturnType<typeof usePromptHistory>
function Consumer() {
history = usePromptHistory()
return <box />
}
const app = await testRender(() => (
<TuiPathsProvider value={{ cwd: tmp.path, home: tmp.path, state, worktree: tmp.path }}>
<PromptHistoryProvider>
<Consumer />
</PromptHistoryProvider>
</TuiPathsProvider>
))
try {
await app.renderOnce()
history!.append({ text: "previous", files: [], agents: [], pasted: [] })
expect(history!.move(1, "")).toBeUndefined()
expect(history!.move(-1, "")?.text).toBe("previous")
expect(history!.move(1, "previous")?.text).toBe("")
} finally {
app.renderer.destroy()
}
})