From fa9cade488b478ab00a57bf9fef8eb50424cc608 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 27 Jul 2026 11:58:53 +0200 Subject: [PATCH] fix(terminal): own Shift+Tab explicitly instead of letting it fall through MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tabindex fix stopped Shift+Tab from landing on the (now unreachable) preview panel buttons, but the underlying cause was still live: xterm.js leaves Shift+Tab uncancelled on purpose, so the browser's native reverse- tab-order focus navigation still ran on every Shift+Tab keystroke — it just landed on the next focusable chrome button instead (confirmed live: the window's own close button lit up with a focus ring). Since the app never wants Shift+Tab to move focus around its own chrome while a terminal has it, handle the chord explicitly: preventDefault it and send the same CSI Z sequence xterm would have sent ourselves. The hotkey (e.g. Claude Code's plan-mode toggle) still reaches the pty with zero focus side-effect — confirmed live, activeElement stays on the terminal's own textarea through the keystroke. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01GHgSVKPsaWRfuJMo1rc2AN --- src/main.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main.ts b/src/main.ts index 4e9c20b..0a8f408 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1520,6 +1520,18 @@ async function newSession( term.attachCustomKeyEventHandler((ev) => { if (deadKey?.handle(ev)) return false; if (ev.type !== "keydown") return true; + // Shift+Tab: xterm.js deliberately leaves this uncancelled (unlike plain + // Tab, which it does cancel) so the byte sequence still reaches + // app-level consumers like Claude Code's own plan-mode toggle — but that + // also lets the browser's native reverse-tab-order focus navigation run, + // visibly shading whatever chrome button it lands on. Send the same + // CSI Z sequence xterm would have sent and own preventDefault ourselves + // instead, so the hotkey still works with no focus side-effect. + if (ev.shiftKey && !ev.ctrlKey && !ev.altKey && !ev.metaKey && ev.code === "Tab") { + ev.preventDefault(); + invoke("write_pty", { id, data: "\x1b[Z" }).catch(() => {}); + return false; + } // Option+Left/Right word-jump. macOptionIsMeta would fix this at the // xterm.js level, but it turns *every* Option-modified key into an // ESC-prefixed byte — breaking Option-key dead-key accent composition