fix(tui): restore composer focus after runtime controls

This commit is contained in:
Xubin Ren
2026-08-17 20:56:10 +08:00
parent cd6a11b3c5
commit c5d2e0ddf1
3 changed files with 14 additions and 4 deletions
+7
View File
@@ -650,6 +650,7 @@ describe("NanobotTui layout", () => {
visible: boolean visible: boolean
menuRoot: { getChildren(): unknown[] } menuRoot: { getChildren(): unknown[] }
} }
composer: TextareaRenderable
titleText: TextRenderable titleText: TextRenderable
status: TextRenderable status: TextRenderable
meta: TextRenderable meta: TextRenderable
@@ -680,8 +681,11 @@ describe("NanobotTui layout", () => {
expect(modelRows.every((row) => !row.selectable)).toBe(true) expect(modelRows.every((row) => !row.selectable)).toBe(true)
const fast = modelRows.find((row) => row.plainText.includes("fast")) const fast = modelRows.find((row) => row.plainText.includes("fast"))
if (!fast) throw new Error("fast model row was not rendered") if (!fast) throw new Error("fast model row was not rendered")
ui.composer.blur()
expect(ui.composer.focused).toBe(false)
await setup.mockMouse.click(fast.x + 2, fast.y) await setup.mockMouse.click(fast.x + 2, fast.y)
await waitUntil(() => sent.includes("/model fast")) await waitUntil(() => sent.includes("/model fast"))
expect(ui.composer.focused).toBe(true)
await setup.mockMouse.click( await setup.mockMouse.click(
ui.runtimeControls.accessText.x + 2, ui.runtimeControls.accessText.x + 2,
@@ -692,7 +696,10 @@ describe("NanobotTui layout", () => {
const accessRows = ui.runtimeControls.menuRoot.getChildren() as TextRenderable[] const accessRows = ui.runtimeControls.menuRoot.getChildren() as TextRenderable[]
const full = accessRows.find((row) => row.plainText.includes("Full access")) const full = accessRows.find((row) => row.plainText.includes("Full access"))
if (!full) throw new Error("full access row was not rendered") if (!full) throw new Error("full access row was not rendered")
ui.composer.blur()
expect(ui.composer.focused).toBe(false)
await setup.mockMouse.click(full.x + 2, full.y) await setup.mockMouse.click(full.x + 2, full.y)
expect(ui.composer.focused).toBe(true)
expect(scopes).toEqual([{ expect(scopes).toEqual([{
project_path: "/tmp/nanobot-workspace", project_path: "/tmp/nanobot-workspace",
+4 -1
View File
@@ -531,7 +531,10 @@ export class NanobotTui {
} }
}, },
onStatus: (message) => { this.status.content = message }, onStatus: (message) => { this.status.content = message },
onVisibilityChange: () => this.updateMeta(), onVisibilityChange: (visible) => {
this.updateMeta()
if (!visible) this.composer.focus()
},
}, },
) )
this.title.add(this.titleText) this.title.add(this.titleText)
+3 -3
View File
@@ -33,7 +33,7 @@ interface RuntimeControlsOptions {
onModel: (name: string) => void onModel: (name: string) => void
onAccess: (scope: WorkspaceScopePayload) => void onAccess: (scope: WorkspaceScopePayload) => void
onStatus: (message: string) => void onStatus: (message: string) => void
onVisibilityChange: () => void onVisibilityChange: (visible: boolean) => void
} }
/** Model and workspace policy controls expose one small, mouse-optional interface. */ /** Model and workspace policy controls expose one small, mouse-optional interface. */
@@ -139,7 +139,7 @@ export class RuntimeControls {
this.kind = null this.kind = null
this.menu.hide() this.menu.hide()
this.renderColors() this.renderColors()
this.options.onVisibilityChange() this.options.onVisibilityChange(false)
} }
setTheme(theme: RuntimeControlsTheme): void { setTheme(theme: RuntimeControlsTheme): void {
@@ -261,7 +261,7 @@ export class RuntimeControls {
private opened(): void { private opened(): void {
this.renderColors() this.renderColors()
this.options.onVisibilityChange() this.options.onVisibilityChange(true)
} }
private apply(choice: Choice): void { private apply(choice: Choice): void {