Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/coding-agent/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)).
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,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 <redirect URL>.";

Expand Down Expand Up @@ -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(
Expand Down
57 changes: 57 additions & 0 deletions packages/coding-agent/test/settings-focus.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { describe, expect, it } from "bun:test";
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;
readonly rows = 24;
readonly kittyProtocolActive = false;
readonly kittyEnableSequence = null;
readonly appearance = undefined;

start(): void {}
stop(): void {}
async drainInput(): Promise<void> {}
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);
});
});
Loading