From 58ddda36f61d30ecc8651cf95bbb73b72327f673 Mon Sep 17 00:00:00 2001 From: Ariaj Sarkar Date: Thu, 26 Mar 2026 02:11:23 +0530 Subject: [PATCH 1/2] fix(web): prevent xterm from swallowing global terminal shortcuts --- apps/web/src/components/ChatView.tsx | 1 + .../src/components/ThreadTerminalDrawer.tsx | 34 +++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index fbc887bf62..7382ab6806 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -4159,6 +4159,7 @@ export default function ChatView({ threadId }: ChatViewProps) { onCloseTerminal={closeTerminal} onHeightChange={setTerminalHeight} onAddTerminalContext={addTerminalContextToDraft} + keybindings={keybindings} /> ); })()} diff --git a/apps/web/src/components/ThreadTerminalDrawer.tsx b/apps/web/src/components/ThreadTerminalDrawer.tsx index 1bdbfb6ad6..f2d73f107d 100644 --- a/apps/web/src/components/ThreadTerminalDrawer.tsx +++ b/apps/web/src/components/ThreadTerminalDrawer.tsx @@ -1,6 +1,6 @@ import { FitAddon } from "@xterm/addon-fit"; import { Plus, SquareSplitHorizontal, TerminalSquare, Trash2, XIcon } from "lucide-react"; -import { type ThreadId } from "@t3tools/contracts"; +import { type ResolvedKeybindingsConfig, type ThreadId } from "@t3tools/contracts"; import { Terminal, type ITheme } from "@xterm/xterm"; import { type PointerEvent as ReactPointerEvent, @@ -19,7 +19,15 @@ import { isTerminalLinkActivation, resolvePathLinkTarget, } from "../terminal-links"; -import { isTerminalClearShortcut, terminalNavigationShortcutData } from "../keybindings"; +import { + isDiffToggleShortcut, + isTerminalClearShortcut, + isTerminalCloseShortcut, + isTerminalNewShortcut, + isTerminalSplitShortcut, + isTerminalToggleShortcut, + terminalNavigationShortcutData, +} from "../keybindings"; import { DEFAULT_THREAD_TERMINAL_HEIGHT, DEFAULT_THREAD_TERMINAL_ID, @@ -192,6 +200,7 @@ interface TerminalViewportProps { autoFocus: boolean; resizeEpoch: number; drawerHeight: number; + keybindings: ResolvedKeybindingsConfig; } function TerminalViewport({ @@ -206,6 +215,7 @@ function TerminalViewport({ autoFocus, resizeEpoch, drawerHeight, + keybindings, }: TerminalViewportProps) { const containerRef = useRef(null); const terminalRef = useRef(null); @@ -220,6 +230,11 @@ function TerminalViewport({ const selectionActionOpenRef = useRef(false); const selectionActionTimerRef = useRef(null); + const keybindingsRef = useRef(keybindings); + useEffect(() => { + keybindingsRef.current = keybindings; + }, [keybindings]); + useEffect(() => { onSessionExitedRef.current = onSessionExited; }, [onSessionExited]); @@ -343,6 +358,17 @@ function TerminalViewport({ }; terminal.attachCustomKeyEventHandler((event) => { + const currentKeybindings = keybindingsRef.current; + if ( + isTerminalToggleShortcut(event, currentKeybindings) || + isTerminalSplitShortcut(event, currentKeybindings) || + isTerminalNewShortcut(event, currentKeybindings) || + isTerminalCloseShortcut(event, currentKeybindings) || + isDiffToggleShortcut(event, currentKeybindings) + ) { + return false; + } + const navigationData = terminalNavigationShortcutData(event); if (navigationData !== null) { event.preventDefault(); @@ -662,6 +688,7 @@ interface ThreadTerminalDrawerProps { onCloseTerminal: (terminalId: string) => void; onHeightChange: (height: number) => void; onAddTerminalContext: (selection: TerminalContextSelection) => void; + keybindings: ResolvedKeybindingsConfig; } interface TerminalActionButtonProps { @@ -712,6 +739,7 @@ export default function ThreadTerminalDrawer({ onCloseTerminal, onHeightChange, onAddTerminalContext, + keybindings, }: ThreadTerminalDrawerProps) { const [drawerHeight, setDrawerHeight] = useState(() => clampDrawerHeight(height)); const [resizeEpoch, setResizeEpoch] = useState(0); @@ -1017,6 +1045,7 @@ export default function ThreadTerminalDrawer({ autoFocus={terminalId === resolvedActiveTerminalId} resizeEpoch={resizeEpoch} drawerHeight={drawerHeight} + keybindings={keybindings} /> @@ -1037,6 +1066,7 @@ export default function ThreadTerminalDrawer({ autoFocus resizeEpoch={resizeEpoch} drawerHeight={drawerHeight} + keybindings={keybindings} /> )} From c3455a986eea71e8d1214e0ecf2c5463e722a476 Mon Sep 17 00:00:00 2001 From: Ariaj Sarkar Date: Thu, 26 Mar 2026 02:35:02 +0530 Subject: [PATCH 2/2] fix(web): pass shortcut context to terminal bypass handler --- apps/web/src/components/ThreadTerminalDrawer.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/web/src/components/ThreadTerminalDrawer.tsx b/apps/web/src/components/ThreadTerminalDrawer.tsx index f2d73f107d..4bcd531236 100644 --- a/apps/web/src/components/ThreadTerminalDrawer.tsx +++ b/apps/web/src/components/ThreadTerminalDrawer.tsx @@ -359,12 +359,13 @@ function TerminalViewport({ terminal.attachCustomKeyEventHandler((event) => { const currentKeybindings = keybindingsRef.current; + const options = { context: { terminalFocus: true, terminalOpen: true } }; if ( - isTerminalToggleShortcut(event, currentKeybindings) || - isTerminalSplitShortcut(event, currentKeybindings) || - isTerminalNewShortcut(event, currentKeybindings) || - isTerminalCloseShortcut(event, currentKeybindings) || - isDiffToggleShortcut(event, currentKeybindings) + isTerminalToggleShortcut(event, currentKeybindings, options) || + isTerminalSplitShortcut(event, currentKeybindings, options) || + isTerminalNewShortcut(event, currentKeybindings, options) || + isTerminalCloseShortcut(event, currentKeybindings, options) || + isDiffToggleShortcut(event, currentKeybindings, options) ) { return false; }