Reviewed GitHub issue and related comments.
UnderstandAnalyzed terminal UI code for cursor handling.
UnderstandImplemented Focusable interface in input components.
BuildConfirmed correct IME candidate window positioning.
VerifyAnalyze GitHub issue(s): https://github.com/badlogic/pi-mono/issues/827
For each issue:
Read the issue in full, including all comments and linked issues/PRs.
For bugs:
For feature requests:
Do NOT implement unless explicitly asked. Analyze and propose only.
Summary: When using an IME (Input Method Editor, e.g., Chinese) to filter menu options (via / command or /model selector), the IME candidate window appears in the wrong position instead of near the input cursor.
Labels: bug
State: Open
This is a terminal UI issue related to cursor positioning. IME candidate windows position themselves based on the terminal cursor location. Let me examine the relevant TUI code.
Let me check all components that contain an Input and handle keyboard input:
Now I have all the information needed for the analysis.
Issue: IME candidate window appears in wrong position when filtering menus with Input Method Editor (e.g., Chinese IME).
Root Cause: Components that contain an Input child for text filtering (like ModelSelectorComponent, SessionSelectorComponent, etc.) don't implement the Focusable interface or propagate the focused state to their child Input component.
The TUI framework uses a cursor marker mechanism for IME positioning:
focused = true, it should emit CURSOR_MARKER at the cursor positionInput component in packages/tui/src/components/input.ts does this correctly:
const marker = this.focused ? CURSOR_MARKER : "";extractCursorPosition() finds this marker and positions the hardware cursor thereThe Problem:
ModelSelectorComponent is focused via ui.setFocus(selector), the TUI sets selector.focused = trueModelSelectorComponent doesn't implement Focusable, so this property is just ignoredsearchInput: Input never gets focused = trueInput never emits CURSOR_MARKERAll components in packages/coding-agent/src/modes/interactive/components/ that contain an Input child:
| Component | Child Input Field |
|---|---|
model-selector.ts | searchInput: Input |
scoped-models-selector.ts | searchInput: Input |
session-selector.ts | SessionList.searchInput: Input |
extension-input.ts | input: Input |
login-dialog.ts | input: Input |
tree-selector.ts | input: Input |
Each affected component needs to:
Focusable interface from @mariozechner/pi-tuifocused property that propagates to the child InputExample fix for ModelSelectorComponent:
import { ..., type Focusable } from "@mariozechner/pi-tui";
export class ModelSelectorComponent extends Container implements Focusable { // ... existing code ... // Add Focusable implementation private _focused = false; get focused(): boolean { return this._focused; } set focused(value: boolean) { this._focused = value; this.searchInput.focused = value; // Propagate to child Input }}The same pattern applies to all other affected components. For SessionSelectorComponent, the focus needs to propagate through to SessionList.searchInput.
packages/coding-agent/src/modes/interactive/components/model-selector.tspackages/coding-agent/src/modes/interactive/components/scoped-models-selector.tspackages/coding-agent/src/modes/interactive/components/session-selector.tspackages/coding-agent/src/modes/interactive/components/extension-input.tspackages/coding-agent/src/modes/interactive/components/login-dialog.tspackages/coding-agent/src/modes/interactive/components/tree-selector.tsimplement
Now I'll implement the fix for all affected components. The fix involves implementing the Focusable interface and propagating the focused state to child Input components.
Now let me run the type checker to verify the changes:
All type checks pass. Now let me update the changelog:
Done. Here's a summary of the implementation:
Fixed issue #827 by implementing the Focusable interface in all components that contain an Input child for text entry. This ensures the hardware cursor is positioned correctly for IME candidate window placement.
Modified files (7):
model-selector.ts - Added Focusable interface, propagates focus to searchInput
scoped-models-selector.ts - Added Focusable interface, propagates focus to searchInput
session-selector.ts - Added Focusable interface to both SessionList (inner class) and SessionSelectorComponent (outer class). Focus propagates: SessionSelectorComponent → SessionList → searchInput
extension-input.ts - Added Focusable interface, propagates focus to input
login-dialog.ts - Added Focusable interface, propagates focus to input
tree-selector.ts - Added Focusable interface to both LabelInput (inner class) and TreeSelectorComponent (outer class). Focus propagates to LabelInput.input when label editing is active
CHANGELOG.md - Added entry under ## [Unreleased]
The TUI framework uses CURSOR_MARKER for IME cursor positioning. When a component is focused:
component.focused = trueInput component emits CURSOR_MARKER at cursor position only when focused is truePreviously, parent components (like ModelSelectorComponent) were focused but didn't propagate the state to their child Input, so no cursor marker was emitted.
const marker = this.focused ? CURSOR_MARKER : "";import { ..., type Focusable } from "@mariozechner/pi-tui";
export class ModelSelectorComponent extends Container implements Focusable { // ... existing code ... // Add Focusable implementation private _focused = false; get focused(): boolean { return this._focused; } set focused(value: boolean) { this._focused = value; this.searchInput.focused = value; // Propagate to child Input }}