Merge pull request #532 from fluxlabs/fix/arrow-keys-escape-sequence-splitting-493

fix: prevent arrow keys from inserting escape sequences as text
This commit is contained in:
TÂCHES 2026-03-15 17:18:00 -06:00 committed by GitHub
commit 946fce4ee6
3 changed files with 23 additions and 2 deletions

View file

@ -730,8 +730,17 @@ export class Editor implements Component, Focusable {
return;
}
// Regular characters
// Regular characters — reject partial escape sequence remnants that can
// occur when event loop latency causes the StdinBuffer to split an escape
// sequence (e.g. \x1b flushed as ESC, then "[D" arrives as text).
if (data.charCodeAt(0) >= 32) {
if (data[0] === "[" && data.length >= 2 && data.length <= 8) {
const last = data[data.length - 1]!;
// CSI navigation remnants: [A-F (arrows/home/end), [H, [Z (shift-tab), [<n>~ (func keys)
if (/^[A-FHZ]$/.test(last) || last === "~") {
return; // Drop CSI remnant (e.g. "[D", "[C", "[5~")
}
}
this.insertCharacter(data);
}
}

View file

@ -112,7 +112,10 @@ export class ProcessTerminal implements Terminal {
* to handle the case where the response arrives split across multiple events.
*/
private setupStdinBuffer(): void {
this.stdinBuffer = new StdinBuffer({ timeout: 10 });
// 50ms matches xterm's default escapeCodeTimeout and gives enough headroom
// for escape sequences that arrive split across multiple stdin data events
// (e.g. \x1b arriving separately from [D due to event loop latency).
this.stdinBuffer = new StdinBuffer({ timeout: 50 });
// Kitty protocol response pattern: \x1b[?<flags>u
const kittyResponsePattern = /^\x1b\[\?(\d+)u$/;

View file

@ -137,6 +137,15 @@ migratePiCredentials(authStorage)
// Run onboarding wizard on first launch (no LLM provider configured)
if (!isPrintMode && shouldRunOnboarding(authStorage)) {
await runOnboarding(authStorage)
// Clean up stdin state left by @clack/prompts.
// readline.emitKeypressEvents() adds a permanent data listener and
// readline.createInterface() may leave stdin paused. Remove stale
// listeners and pause stdin so the TUI can start with a clean slate.
process.stdin.removeAllListeners('data')
process.stdin.removeAllListeners('keypress')
if (process.stdin.setRawMode) process.stdin.setRawMode(false)
process.stdin.pause()
}
// Non-blocking update check — runs at most once per 24h, fire-and-forget