authorgravatar for 144548552+AriajSarkar@users.noreply.github.comAriaj <144548552+AriajSarkar@users.noreply.github.com> 2026-04-18 01:41:04+05:30
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-04-17 13:11:04-07:00
log39ca3ee858e764dba3293aa9cb69452c2265440d
tree6abf0a7a5ae8e79ff3a77777cef48a73d69ade99
parent52a60678026549f8db66165e254c49eecfb69920
signature Signed by PGP key B5690EEEBB952194

fix(web): bypass xterm for global terminal shortcuts (#1580)


3 files changed, 36 insertions(+), 0 deletions(-)

apps/web/src/components/ChatView.tsx+5
......@@ -10,6 +10,7 @@ import {
1010 type ProjectId,
1111 type ProviderApprovalDecision,
1212 type ServerProvider,
13 type ResolvedKeybindingsConfig,
1314 type ScopedThreadRef,
1415 type ThreadId,
1516 type TurnId,
......@@ -415,6 +416,7 @@ interface PersistentThreadTerminalDrawerProps {
415416 splitShortcutLabel: string | undefined;
416417 newShortcutLabel: string | undefined;
417418 closeShortcutLabel: string | undefined;
419 keybindings: ResolvedKeybindingsConfig;
418420 onAddTerminalContext: (selection: TerminalContextSelection) => void;
419421}
420422
......@@ -427,6 +429,7 @@ const PersistentThreadTerminalDrawer = memo(function PersistentThreadTerminalDra
427429 splitShortcutLabel,
428430 newShortcutLabel,
429431 closeShortcutLabel,
432 keybindings,
430433 onAddTerminalContext,
431434}: PersistentThreadTerminalDrawerProps) {
432435 const serverThread = useStore(useMemo(() => createThreadSelectorByRef(threadRef), [threadRef]));
......@@ -570,6 +573,7 @@ const PersistentThreadTerminalDrawer = memo(function PersistentThreadTerminalDra
570573 splitShortcutLabel={visible ? splitShortcutLabel : undefined}
571574 newShortcutLabel={visible ? newShortcutLabel : undefined}
572575 closeShortcutLabel={visible ? closeShortcutLabel : undefined}
576 keybindings={keybindings}
573577 onActiveTerminalChange={activateTerminal}
574578 onCloseTerminal={closeTerminal}
575579 onHeightChange={setTerminalHeight}
......@@ -3443,6 +3447,7 @@ export default function ChatView(props: ChatViewProps) {
34433447 splitShortcutLabel={splitTerminalShortcutLabel ?? undefined}
34443448 newShortcutLabel={newTerminalShortcutLabel ?? undefined}
34453449 closeShortcutLabel={closeTerminalShortcutLabel ?? undefined}
3450 keybindings={keybindings}
34463451 onAddTerminalContext={addTerminalContextToDraft}
34473452 />
34483453 ))}
apps/web/src/components/ThreadTerminalDrawer.browser.tsx+2
......@@ -177,6 +177,7 @@ async function mountTerminalViewport(props: {
177177 autoFocus={false}
178178 resizeEpoch={0}
179179 drawerHeight={320}
180 keybindings={[]}
180181 />,
181182 { container: host },
182183 );
......@@ -196,6 +197,7 @@ async function mountTerminalViewport(props: {
196197 autoFocus={false}
197198 resizeEpoch={0}
198199 drawerHeight={320}
200 keybindings={[]}
199201 />,
200202 );
201203 },
apps/web/src/components/ThreadTerminalDrawer.tsx+29
......@@ -1,6 +1,7 @@
11import { FitAddon } from "@xterm/addon-fit";
22import { Plus, SquareSplitHorizontal, TerminalSquare, Trash2, XIcon } from "lucide-react";
33import {
4 type ResolvedKeybindingsConfig,
45 type ScopedThreadRef,
56 type TerminalEvent,
67 type TerminalSessionSnapshot,
......@@ -29,7 +30,12 @@ import {
2930 wrappedTerminalLinkRangeIntersectsBufferLine,
3031} from "../terminal-links";
3132import {
33 isDiffToggleShortcut,
3234 isTerminalClearShortcut,
35 isTerminalCloseShortcut,
36 isTerminalNewShortcut,
37 isTerminalSplitShortcut,
38 isTerminalToggleShortcut,
3339 terminalDeleteShortcutData,
3440 terminalNavigationShortcutData,
3541} from "../keybindings";
......@@ -255,6 +261,7 @@ interface TerminalViewportProps {
255261 autoFocus: boolean;
256262 resizeEpoch: number;
257263 drawerHeight: number;
264 keybindings: ResolvedKeybindingsConfig;
258265}
259266
260267export function TerminalViewport({
......@@ -271,6 +278,7 @@ export function TerminalViewport({
271278 autoFocus,
272279 resizeEpoch,
273280 drawerHeight,
281 keybindings,
274282}: TerminalViewportProps) {
275283 const containerRef = useRef<HTMLDivElement>(null);
276284 const terminalRef = useRef<Terminal | null>(null);
......@@ -282,6 +290,7 @@ export function TerminalViewport({
282290 const selectionActionRequestIdRef = useRef(0);
283291 const selectionActionOpenRef = useRef(false);
284292 const selectionActionTimerRef = useRef<number | null>(null);
293 const keybindingsRef = useRef(keybindings);
285294 const lastAppliedTerminalEventIdRef = useRef(0);
286295 const terminalHydratedRef = useRef(false);
287296 const handleSessionExited = useEffectEvent(() => {
......@@ -292,6 +301,10 @@ export function TerminalViewport({
292301 });
293302 const readTerminalLabel = useEffectEvent(() => terminalLabel);
294303
304 useEffect(() => {
305 keybindingsRef.current = keybindings;
306 }, [keybindings]);
307
295308 useEffect(() => {
296309 const mount = containerRef.current;
297310 if (!mount) return;
......@@ -403,6 +416,18 @@ export function TerminalViewport({
403416 };
404417
405418 terminal.attachCustomKeyEventHandler((event) => {
419 const currentKeybindings = keybindingsRef.current;
420 const options = { context: { terminalFocus: true, terminalOpen: true } };
421 if (
422 isTerminalToggleShortcut(event, currentKeybindings, options) ||
423 isTerminalSplitShortcut(event, currentKeybindings, options) ||
424 isTerminalNewShortcut(event, currentKeybindings, options) ||
425 isTerminalCloseShortcut(event, currentKeybindings, options) ||
426 isDiffToggleShortcut(event, currentKeybindings, options)
427 ) {
428 return false;
429 }
430
406431 const navigationData = terminalNavigationShortcutData(event);
407432 if (navigationData !== null) {
408433 event.preventDefault();
......@@ -795,6 +820,7 @@ interface ThreadTerminalDrawerProps {
795820 onCloseTerminal: (terminalId: string) => void;
796821 onHeightChange: (height: number) => void;
797822 onAddTerminalContext: (selection: TerminalContextSelection) => void;
823 keybindings: ResolvedKeybindingsConfig;
798824}
799825
800826interface TerminalActionButtonProps {
......@@ -848,6 +874,7 @@ export default function ThreadTerminalDrawer({
848874 onCloseTerminal,
849875 onHeightChange,
850876 onAddTerminalContext,
877 keybindings,
851878}: ThreadTerminalDrawerProps) {
852879 const [drawerHeight, setDrawerHeight] = useState(() => clampDrawerHeight(height));
853880 const [resizeEpoch, setResizeEpoch] = useState(0);
......@@ -1166,6 +1193,7 @@ export default function ThreadTerminalDrawer({
11661193 autoFocus={terminalId === resolvedActiveTerminalId}
11671194 resizeEpoch={resizeEpoch}
11681195 drawerHeight={drawerHeight}
1196 keybindings={keybindings}
11691197 />
11701198 </div>
11711199 </div>
......@@ -1188,6 +1216,7 @@ export default function ThreadTerminalDrawer({
11881216 autoFocus
11891217 resizeEpoch={resizeEpoch}
11901218 drawerHeight={drawerHeight}
1219 keybindings={keybindings}
11911220 />
11921221 </div>
11931222 )}