feat(split): terminal shortcuts, voice and paste follow the focused pane

With the split open, every app-level terminal action resolved against
Pane A: Ctrl+L typed into Pane B cleared Pane A's display while xterm
sent the ^L into Pane B's PTY, and Ctrl+Shift+R restored Pane A's size.

_focusedPane() now answers with the terminal focused LAST (a mic or
header click moves DOM focus but not the user's pane): Pane B claims it
from its own textarea's focus, the primary terminal's focus gives it
back, and a destroyed pane never holds it. Ctrl+L, Ctrl+Shift+R, voice
dictation and image paste act on the focused pane. Ctrl+W deliberately
still closes the active session: it kills with no confirm, so moving it
is an owner decision (docs/tile-grid-plan.md, decision 5).

Also destroys every tile after each terminal-tile-input test: a real
reconnect timer from one test opened a socket in a later one and flaked
under full-suite load.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-10-06 10:45:16 +02:00
parent fbd69e62aa
commit fe9b209f67
4 changed files with 230 additions and 5 deletions
+13
View File
@@ -115,6 +115,7 @@
// Whether the pointer is over a link in THIS pane (the primary pane's own
// flag, app._linkHovered, belongs to its terminal alone).
this._linkHovered = false;
this._onFocusIn = null;
}
async connect() {
@@ -151,6 +152,11 @@
this._installWheelListener();
// Focusing this terminal makes it the pane the keyboard is in, so the
// app-level shortcuts, voice and paste act on it (app._focusedPane).
this._onFocusIn = () => global.app?._noteFocusedTile?.(this);
this.terminal.textarea?.addEventListener('focus', this._onFocusIn);
this.terminal.onData((data) => this._onTerminalData(data));
// Pane B has no gates of its own by default, so every app-level chord
@@ -850,6 +856,13 @@
this._onWheel = null;
}
this._detachSocket();
if (this._onFocusIn) {
this.terminal?.textarea?.removeEventListener('focus', this._onFocusIn);
this._onFocusIn = null;
}
// A destroyed pane cannot hold the keyboard: shortcuts fall back to the
// primary terminal (_focusedPane also skips a destroyed tile on its own).
if (global.app?._focusedTile === this) global.app._noteFocusedTile?.(null);
if (this.terminal) {
this.terminal.dispose();
this.terminal = null;