fix(terminal): the Path and Clear keys act on the tile or Pane B that has the keyboard

The phone keyboard's Path key (insertTerminalText) and its clear-prompt key
(clearTerminalInput) always wrote to the main pane: into its local-echo
overlay, or to the active session. With the tile grid open that overlay is
parked behind the grid, so a picked path landed there unseen and only reached
the session later, and with the split open a path meant for Pane B went to
Pane A.

Both now ask _focusedPane() first. When a tile or Pane B holds the keyboard,
the path is sent to that pane's session through the exactly-once queue, and
clearing sends Ctrl+U there (those panes have no overlay, so the TUI owns the
line), then the pane's own terminal takes focus. The main pane's behaviour is
unchanged. Left over from the final checkup's dictation fix (c10), which
covered voice input only.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-10-09 10:22:23 +02:00
parent 28708cfa14
commit 5e2e9bb833
2 changed files with 73 additions and 0 deletions
+19
View File
@@ -4605,6 +4605,15 @@ Object.assign(CodemanApp.prototype, {
/** Insert editable text at the active prompt without pressing Enter. */ /** Insert editable text at the active prompt without pressing Enter. */
insertTerminalText(text) { insertTerminalText(text) {
if (!this.activeSessionId || !text) return; if (!this.activeSessionId || !text) return;
// A tile or the split's Pane B holds the keyboard: the text belongs to that
// pane's session. Those panes have no local-echo overlay, and the main
// overlay is parked behind the grid, so send it straight to the pane.
const pane = this._focusedPane?.();
if (pane && !pane.isPrimary) {
this._sendInputAsync(pane.sessionId, text);
pane.terminal?.focus();
return;
}
// Under predict the text goes out via sendInput (bypasses onData), so the // Under predict the text goes out via sendInput (bypasses onData), so the
// hook never sees it: clear outstanding predictions here instead. // hook never sees it: clear outstanding predictions here instead.
if (this._localEchoPolicy === 'predict') this._predictiveEcho?.clearPredictions(); if (this._localEchoPolicy === 'predict') this._predictiveEcho?.clearPredictions();
@@ -4628,6 +4637,16 @@ Object.assign(CodemanApp.prototype, {
if (!this.activeSessionId) return; if (!this.activeSessionId) return;
if (typeof CjkInput !== 'undefined') CjkInput.clear(); if (typeof CjkInput !== 'undefined') CjkInput.clear();
// A tile or Pane B holds the keyboard: its TUI owns the editable buffer
// (no local-echo overlay there), so kill the line in that pane's session,
// never in the parked main pane's.
const pane = this._focusedPane?.();
if (pane && !pane.isPrimary) {
this._sendInputAsync(pane.sessionId, '\x15');
this.showToast?.('Input cleared', 'success');
pane.terminal?.focus();
return;
}
if (this._inputFlushTimeout) { if (this._inputFlushTimeout) {
clearTimeout(this._inputFlushTimeout); clearTimeout(this._inputFlushTimeout);
this._inputFlushTimeout = null; this._inputFlushTimeout = null;
+54
View File
@@ -225,6 +225,60 @@ describe('mobile filesystem picker actions', () => {
expect(terminalHarness.cjkClear).toHaveBeenCalled(); expect(terminalHarness.cjkClear).toHaveBeenCalled();
}); });
it('sends a picked path to the tile or Pane B that holds the keyboard, never the parked main overlay', () => {
const appendText = vi.fn();
const sendInput = vi.fn();
const _sendInputAsync = vi.fn();
const mainFocus = vi.fn();
const tileFocus = vi.fn();
const app = {
activeSessionId: 'session-1',
_localEchoEnabled: true,
_localEchoOverlay: { appendText },
terminal: { focus: mainFocus },
sendInput,
_sendInputAsync,
_focusedPane: () => ({ terminal: { focus: tileFocus }, sessionId: 'tile-2', isPrimary: false, tile: {} }),
};
terminalHarness.mixin.insertTerminalText.call(app, '/mnt/d/AI/project');
expect(_sendInputAsync).toHaveBeenCalledWith('tile-2', '/mnt/d/AI/project');
expect(appendText).not.toHaveBeenCalled();
expect(sendInput).not.toHaveBeenCalled();
expect(tileFocus).toHaveBeenCalledOnce();
expect(mainFocus).not.toHaveBeenCalled();
});
it('clears the prompt of the tile or Pane B that holds the keyboard with Ctrl+U, leaving the main overlay alone', () => {
const clear = vi.fn();
const sendInput = vi.fn(() => Promise.resolve());
const _sendInputAsync = vi.fn();
const showToast = vi.fn();
const tileFocus = vi.fn();
const app = {
activeSessionId: 'session-1',
_inputFlushTimeout: null,
_pendingInput: 'main pane text',
_localEchoEnabled: true,
_localEchoOverlay: { getFlushed: () => ({ count: 0, text: '' }), clear, suppressBufferDetection: vi.fn() },
sendInput,
_sendInputAsync,
showToast,
terminal: { focus: vi.fn() },
_focusedPane: () => ({ terminal: { focus: tileFocus }, sessionId: 'tile-2', isPrimary: false, tile: {} }),
};
terminalHarness.mixin.clearTerminalInput.call(app);
expect(_sendInputAsync).toHaveBeenCalledWith('tile-2', '\x15');
expect(sendInput).not.toHaveBeenCalled();
expect(clear).not.toHaveBeenCalled();
expect(app._pendingInput).toBe('main pane text');
expect(showToast).toHaveBeenCalledWith('Input cleared', 'success');
expect(tileFocus).toHaveBeenCalledOnce();
});
it('uses Ctrl+U to clear the TUI-owned prompt when local echo is disabled', () => { it('uses Ctrl+U to clear the TUI-owned prompt when local echo is disabled', () => {
const sendInput = vi.fn(() => Promise.resolve()); const sendInput = vi.fn(() => Promise.resolve());
const app = { const app = {