From f1ebd8c4c614ccc019afd11cd73f64167832f987 Mon Sep 17 00:00:00 2001 From: Xubin Ren <52506698+Re-bin@users.noreply.github.com> Date: Mon, 17 Aug 2026 12:30:39 +0800 Subject: [PATCH] fix(tui): dismiss runtime pickers on outside click --- tui/src/app.test.ts | 21 +++++++++++++++++++++ tui/src/app.ts | 11 ++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/tui/src/app.test.ts b/tui/src/app.test.ts index fcebb8019..09cde1a29 100644 --- a/tui/src/app.test.ts +++ b/tui/src/app.test.ts @@ -736,6 +736,27 @@ describe("NanobotTui layout", () => { access_mode: "full", restrict_to_workspace: false, }]) + + await setup.mockMouse.click( + ui.runtimeControls.modelText.x + 2, + ui.runtimeControls.modelText.y, + ) + await waitUntil(() => ui.runtimeControls.visible) + ui.composer.blur() + await setup.mockMouse.click(ui.status.x, ui.status.y) + expect(ui.runtimeControls.visible).toBe(false) + expect(ui.composer.focused).toBe(true) + + await setup.mockMouse.click( + ui.runtimeControls.accessText.x + 2, + ui.runtimeControls.accessText.y, + ) + await waitUntil(() => ui.runtimeControls.visible) + ui.composer.blur() + await setup.mockMouse.click(ui.status.x, ui.status.y) + expect(ui.runtimeControls.visible).toBe(false) + expect(ui.composer.focused).toBe(true) + expect((app as unknown as { activeTurn: boolean }).activeTurn).toBe(true) app.accept({ event: "goal_status", chat_id: "chat", status: "idle" }) } finally { diff --git a/tui/src/app.ts b/tui/src/app.ts index 906867abb..5cce93ff1 100644 --- a/tui/src/app.ts +++ b/tui/src/app.ts @@ -487,10 +487,15 @@ export class NanobotTui { flexDirection: "column", backgroundColor: RGBA.defaultBackground(), onMouseDown: (event) => { + if (event.button !== 0) return + // Runtime pickers are transient popovers. A primary click anywhere + // outside their trigger or body dismisses them; hide() restores the + // composer focus through the shared visibility callback. + this.dismissRuntimeControls() // Selection belongs to transcript/input content, never to empty chrome. // Clearing it here prevents default-background cells from lingering as // opaque blocks in terminals with differential repainting. - if (event.button === 0 && event.target && !event.target.selectable) { + if (event.target && !event.target.selectable) { this.renderer.clearSelection() } }, @@ -1732,6 +1737,10 @@ export class NanobotTui { this.activeMentionQuery = null } + private dismissRuntimeControls(): void { + if (this.runtimeControls.visible) this.runtimeControls.hide() + } + private applyWorkspaceScope(scope: WorkspaceScopePayload): void { if (scope.project_path) { this.hostWorkspace = scope.project_path