From d931b372d7745eeadf0e0f37f75582c318da0529 Mon Sep 17 00:00:00 2001 From: roboomp Date: Sun, 21 Jun 2026 16:35:29 +0000 Subject: [PATCH 1/2] fix(tui): restored ask focus after settings Restored the editor-surface focus target after closing the fullscreen settings overlay so pending ask selectors keep keyboard input instead of returning to the main prompt editor. Added a regression test for the overlay close sequence. Fixes #3203 --- packages/coding-agent/CHANGELOG.md | 2 + .../modes/controllers/editor-surface-focus.ts | 13 +++++ .../modes/controllers/selector-controller.ts | 3 +- .../coding-agent/test/settings-focus.test.ts | 57 +++++++++++++++++++ 4 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 packages/coding-agent/src/modes/controllers/editor-surface-focus.ts create mode 100644 packages/coding-agent/test/settings-focus.test.ts diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index f4881bed1a..8cc83ce161 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -4,6 +4,8 @@ ### Fixed +- Fixed returning from `/settings` while an `ask` tool prompt is pending so focus returns to the ask selector instead of the main prompt editor ([#3203](https://github.com/can1357/oh-my-pi/issues/3203)). + - Fixed Codex image reads re-encoding to WebP and then failing the next request with an invalid `input_image.image_url`; Codex-bound images now stay in PNG/JPEG-compatible formats. - Fixed the `/model` thinking picker labeling the OpenAI GPT-5.5 top effort as `max` instead of the catalog-declared `xhigh` ([#3194](https://github.com/can1357/oh-my-pi/issues/3194)). diff --git a/packages/coding-agent/src/modes/controllers/editor-surface-focus.ts b/packages/coding-agent/src/modes/controllers/editor-surface-focus.ts new file mode 100644 index 0000000000..444a5c52ab --- /dev/null +++ b/packages/coding-agent/src/modes/controllers/editor-surface-focus.ts @@ -0,0 +1,13 @@ +import type { Component } from "@oh-my-pi/pi-tui"; + +interface EditorSurfaceFocusContext { + editor: Component; + hookSelector?: Component; + hookInput?: Component; + hookEditor?: Component; +} + +/** Returns the editor-surface component that should receive input after modal UI closes. */ +export function getEditorSurfaceFocusTarget(ctx: EditorSurfaceFocusContext): Component { + return ctx.hookSelector ?? ctx.hookInput ?? ctx.hookEditor ?? ctx.editor; +} diff --git a/packages/coding-agent/src/modes/controllers/selector-controller.ts b/packages/coding-agent/src/modes/controllers/selector-controller.ts index 7ecf68e901..65f4aa18ee 100644 --- a/packages/coding-agent/src/modes/controllers/selector-controller.ts +++ b/packages/coding-agent/src/modes/controllers/selector-controller.ts @@ -17,6 +17,7 @@ import { getPluginsCacheDir, MarketplaceManager, } from "../../extensibility/plugins/marketplace"; +import { getEditorSurfaceFocusTarget } from "./editor-surface-focus"; import { getAvailableThemes, getSymbolTheme, @@ -109,7 +110,7 @@ export class SelectorController { let overlayHandle: OverlayHandle | undefined; const done = () => { overlayHandle?.hide(); - this.ctx.ui.setFocus(this.ctx.editor); + this.ctx.ui.setFocus(getEditorSurfaceFocusTarget(this.ctx)); this.ctx.ui.requestRender(); }; const selector = new SettingsSelectorComponent( diff --git a/packages/coding-agent/test/settings-focus.test.ts b/packages/coding-agent/test/settings-focus.test.ts new file mode 100644 index 0000000000..952b74be97 --- /dev/null +++ b/packages/coding-agent/test/settings-focus.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, it } from "bun:test"; +import { Text, TUI, type Terminal, type TerminalAppearance } from "@oh-my-pi/pi-tui"; +import { getEditorSurfaceFocusTarget } from "@oh-my-pi/pi-coding-agent/modes/controllers/editor-surface-focus"; + +class MemoryTerminal implements Terminal { + readonly columns = 80; + readonly rows = 24; + readonly kittyProtocolActive = false; + readonly kittyEnableSequence = null; + readonly appearance = undefined; + + start(): void {} + stop(): void {} + async drainInput(): Promise {} + write(): void {} + moveBy(): void {} + hideCursor(): void {} + showCursor(): void {} + clearLine(): void {} + clearFromCursor(): void {} + clearScreen(): void {} + setTitle(): void {} + setProgress(): void {} + onAppearanceChange(_callback: (appearance: TerminalAppearance) => void): void {} +} + +const inertRenderScheduler = { + now: () => 0, + scheduleImmediate: () => {}, + scheduleRender: () => ({ cancel() {} }), +}; + +describe("settings overlay focus restore", () => { + it("returns focus to a pending ask selector instead of the prompt editor", () => { + const ui = new TUI(new MemoryTerminal(), false, { renderScheduler: inertRenderScheduler }); + const editor = new Text("editor", 0, 0); + const settingsOverlay = new Text("settings", 0, 0); + const askSelector = new Text("ask", 0, 0); + const ctx = { + editor, + hookSelector: askSelector, + hookInput: undefined, + hookEditor: undefined, + }; + + ui.setFocus(editor); + const overlay = ui.showOverlay(settingsOverlay, { fullscreen: true }); + + ui.setFocus(askSelector); + expect(ui.getFocused()).toBe(settingsOverlay); + + overlay.hide(); + ui.setFocus(getEditorSurfaceFocusTarget(ctx)); + + expect(ui.getFocused()).toBe(askSelector); + }); +}); From 8a46c0c1243a88889d3de66f3de63ac25b08f944 Mon Sep 17 00:00:00 2001 From: roboomp Date: Sun, 21 Jun 2026 16:35:47 +0000 Subject: [PATCH 2/2] style: bun run fix --- .../coding-agent/src/modes/controllers/selector-controller.ts | 2 +- packages/coding-agent/test/settings-focus.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/coding-agent/src/modes/controllers/selector-controller.ts b/packages/coding-agent/src/modes/controllers/selector-controller.ts index 65f4aa18ee..137cb87e24 100644 --- a/packages/coding-agent/src/modes/controllers/selector-controller.ts +++ b/packages/coding-agent/src/modes/controllers/selector-controller.ts @@ -17,7 +17,6 @@ import { getPluginsCacheDir, MarketplaceManager, } from "../../extensibility/plugins/marketplace"; -import { getEditorSurfaceFocusTarget } from "./editor-surface-focus"; import { getAvailableThemes, getSymbolTheme, @@ -69,6 +68,7 @@ import { TreeSelectorComponent } from "../components/tree-selector"; import { UserMessageSelectorComponent } from "../components/user-message-selector"; import type { SessionObserverRegistry } from "../session-observer-registry"; import { buildCopyTargets } from "../utils/copy-targets"; +import { getEditorSurfaceFocusTarget } from "./editor-surface-focus"; const MANUAL_LOGIN_TIP = "Tip: You can complete pairing with /login ."; diff --git a/packages/coding-agent/test/settings-focus.test.ts b/packages/coding-agent/test/settings-focus.test.ts index 952b74be97..ff9e9fb641 100644 --- a/packages/coding-agent/test/settings-focus.test.ts +++ b/packages/coding-agent/test/settings-focus.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "bun:test"; -import { Text, TUI, type Terminal, type TerminalAppearance } from "@oh-my-pi/pi-tui"; import { getEditorSurfaceFocusTarget } from "@oh-my-pi/pi-coding-agent/modes/controllers/editor-surface-focus"; +import { type Terminal, type TerminalAppearance, Text, TUI } from "@oh-my-pi/pi-tui"; class MemoryTerminal implements Terminal { readonly columns = 80;