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 {...@@ -10,6 +10,7 @@ import {
10 type ProjectId,10 type ProjectId,
11 type ProviderApprovalDecision,11 type ProviderApprovalDecision,
12 type ServerProvider,12 type ServerProvider,
13 type ResolvedKeybindingsConfig,
13 type ScopedThreadRef,14 type ScopedThreadRef,
14 type ThreadId,15 type ThreadId,
15 type TurnId,16 type TurnId,
...@@ -415,6 +416,7 @@ interface PersistentThreadTerminalDrawerProps {...@@ -415,6 +416,7 @@ interface PersistentThreadTerminalDrawerProps {
415 splitShortcutLabel: string | undefined;416 splitShortcutLabel: string | undefined;
416 newShortcutLabel: string | undefined;417 newShortcutLabel: string | undefined;
417 closeShortcutLabel: string | undefined;418 closeShortcutLabel: string | undefined;
419 keybindings: ResolvedKeybindingsConfig;
418 onAddTerminalContext: (selection: TerminalContextSelection) => void;420 onAddTerminalContext: (selection: TerminalContextSelection) => void;
419}421}
420422
...@@ -427,6 +429,7 @@ const PersistentThreadTerminalDrawer = memo(function PersistentThreadTerminalDra...@@ -427,6 +429,7 @@ const PersistentThreadTerminalDrawer = memo(function PersistentThreadTerminalDra
427 splitShortcutLabel,429 splitShortcutLabel,
428 newShortcutLabel,430 newShortcutLabel,
429 closeShortcutLabel,431 closeShortcutLabel,
432 keybindings,
430 onAddTerminalContext,433 onAddTerminalContext,
431}: PersistentThreadTerminalDrawerProps) {434}: PersistentThreadTerminalDrawerProps) {
432 const serverThread = useStore(useMemo(() => createThreadSelectorByRef(threadRef), [threadRef]));435 const serverThread = useStore(useMemo(() => createThreadSelectorByRef(threadRef), [threadRef]));
...@@ -570,6 +573,7 @@ const PersistentThreadTerminalDrawer = memo(function PersistentThreadTerminalDra...@@ -570,6 +573,7 @@ const PersistentThreadTerminalDrawer = memo(function PersistentThreadTerminalDra
570 splitShortcutLabel={visible ? splitShortcutLabel : undefined}573 splitShortcutLabel={visible ? splitShortcutLabel : undefined}
571 newShortcutLabel={visible ? newShortcutLabel : undefined}574 newShortcutLabel={visible ? newShortcutLabel : undefined}
572 closeShortcutLabel={visible ? closeShortcutLabel : undefined}575 closeShortcutLabel={visible ? closeShortcutLabel : undefined}
576 keybindings={keybindings}
573 onActiveTerminalChange={activateTerminal}577 onActiveTerminalChange={activateTerminal}
574 onCloseTerminal={closeTerminal}578 onCloseTerminal={closeTerminal}
575 onHeightChange={setTerminalHeight}579 onHeightChange={setTerminalHeight}
...@@ -3443,6 +3447,7 @@ export default function ChatView(props: ChatViewProps) {...@@ -3443,6 +3447,7 @@ export default function ChatView(props: ChatViewProps) {
3443 splitShortcutLabel={splitTerminalShortcutLabel ?? undefined}3447 splitShortcutLabel={splitTerminalShortcutLabel ?? undefined}
3444 newShortcutLabel={newTerminalShortcutLabel ?? undefined}3448 newShortcutLabel={newTerminalShortcutLabel ?? undefined}
3445 closeShortcutLabel={closeTerminalShortcutLabel ?? undefined}3449 closeShortcutLabel={closeTerminalShortcutLabel ?? undefined}
3450 keybindings={keybindings}
3446 onAddTerminalContext={addTerminalContextToDraft}3451 onAddTerminalContext={addTerminalContextToDraft}
3447 />3452 />
3448 ))}3453 ))}
apps/web/src/components/ThreadTerminalDrawer.browser.tsx+2
...@@ -177,6 +177,7 @@ async function mountTerminalViewport(props: {...@@ -177,6 +177,7 @@ async function mountTerminalViewport(props: {
177 autoFocus={false}177 autoFocus={false}
178 resizeEpoch={0}178 resizeEpoch={0}
179 drawerHeight={320}179 drawerHeight={320}
180 keybindings={[]}
180 />,181 />,
181 { container: host },182 { container: host },
182 );183 );
...@@ -196,6 +197,7 @@ async function mountTerminalViewport(props: {...@@ -196,6 +197,7 @@ async function mountTerminalViewport(props: {
196 autoFocus={false}197 autoFocus={false}
197 resizeEpoch={0}198 resizeEpoch={0}
198 drawerHeight={320}199 drawerHeight={320}
200 keybindings={[]}
199 />,201 />,
200 );202 );
201 },203 },
apps/web/src/components/ThreadTerminalDrawer.tsx+29
...@@ -1,6 +1,7 @@...@@ -1,6 +1,7 @@
1import { FitAddon } from "@xterm/addon-fit";1import { FitAddon } from "@xterm/addon-fit";
2import { Plus, SquareSplitHorizontal, TerminalSquare, Trash2, XIcon } from "lucide-react";2import { Plus, SquareSplitHorizontal, TerminalSquare, Trash2, XIcon } from "lucide-react";
3import {3import {
4 type ResolvedKeybindingsConfig,
4 type ScopedThreadRef,5 type ScopedThreadRef,
5 type TerminalEvent,6 type TerminalEvent,
6 type TerminalSessionSnapshot,7 type TerminalSessionSnapshot,
...@@ -29,7 +30,12 @@ import {...@@ -29,7 +30,12 @@ import {
29 wrappedTerminalLinkRangeIntersectsBufferLine,30 wrappedTerminalLinkRangeIntersectsBufferLine,
30} from "../terminal-links";31} from "../terminal-links";
31import {32import {
33 isDiffToggleShortcut,
32 isTerminalClearShortcut,34 isTerminalClearShortcut,
35 isTerminalCloseShortcut,
36 isTerminalNewShortcut,
37 isTerminalSplitShortcut,
38 isTerminalToggleShortcut,
33 terminalDeleteShortcutData,39 terminalDeleteShortcutData,
34 terminalNavigationShortcutData,40 terminalNavigationShortcutData,
35} from "../keybindings";41} from "../keybindings";
...@@ -255,6 +261,7 @@ interface TerminalViewportProps {...@@ -255,6 +261,7 @@ interface TerminalViewportProps {
255 autoFocus: boolean;261 autoFocus: boolean;
256 resizeEpoch: number;262 resizeEpoch: number;
257 drawerHeight: number;263 drawerHeight: number;
264 keybindings: ResolvedKeybindingsConfig;
258}265}
259266
260export function TerminalViewport({267export function TerminalViewport({
...@@ -271,6 +278,7 @@ export function TerminalViewport({...@@ -271,6 +278,7 @@ export function TerminalViewport({
271 autoFocus,278 autoFocus,
272 resizeEpoch,279 resizeEpoch,
273 drawerHeight,280 drawerHeight,
281 keybindings,
274}: TerminalViewportProps) {282}: TerminalViewportProps) {
275 const containerRef = useRef<HTMLDivElement>(null);283 const containerRef = useRef<HTMLDivElement>(null);
276 const terminalRef = useRef<Terminal | null>(null);284 const terminalRef = useRef<Terminal | null>(null);
...@@ -282,6 +290,7 @@ export function TerminalViewport({...@@ -282,6 +290,7 @@ export function TerminalViewport({
282 const selectionActionRequestIdRef = useRef(0);290 const selectionActionRequestIdRef = useRef(0);
283 const selectionActionOpenRef = useRef(false);291 const selectionActionOpenRef = useRef(false);
284 const selectionActionTimerRef = useRef<number | null>(null);292 const selectionActionTimerRef = useRef<number | null>(null);
293 const keybindingsRef = useRef(keybindings);
285 const lastAppliedTerminalEventIdRef = useRef(0);294 const lastAppliedTerminalEventIdRef = useRef(0);
286 const terminalHydratedRef = useRef(false);295 const terminalHydratedRef = useRef(false);
287 const handleSessionExited = useEffectEvent(() => {296 const handleSessionExited = useEffectEvent(() => {
...@@ -292,6 +301,10 @@ export function TerminalViewport({...@@ -292,6 +301,10 @@ export function TerminalViewport({
292 });301 });
293 const readTerminalLabel = useEffectEvent(() => terminalLabel);302 const readTerminalLabel = useEffectEvent(() => terminalLabel);
294303
304 useEffect(() => {
305 keybindingsRef.current = keybindings;
306 }, [keybindings]);
307
295 useEffect(() => {308 useEffect(() => {
296 const mount = containerRef.current;309 const mount = containerRef.current;
297 if (!mount) return;310 if (!mount) return;
...@@ -403,6 +416,18 @@ export function TerminalViewport({...@@ -403,6 +416,18 @@ export function TerminalViewport({
403 };416 };
404417
405 terminal.attachCustomKeyEventHandler((event) => {418 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
406 const navigationData = terminalNavigationShortcutData(event);431 const navigationData = terminalNavigationShortcutData(event);
407 if (navigationData !== null) {432 if (navigationData !== null) {
408 event.preventDefault();433 event.preventDefault();
...@@ -795,6 +820,7 @@ interface ThreadTerminalDrawerProps {...@@ -795,6 +820,7 @@ interface ThreadTerminalDrawerProps {
795 onCloseTerminal: (terminalId: string) => void;820 onCloseTerminal: (terminalId: string) => void;
796 onHeightChange: (height: number) => void;821 onHeightChange: (height: number) => void;
797 onAddTerminalContext: (selection: TerminalContextSelection) => void;822 onAddTerminalContext: (selection: TerminalContextSelection) => void;
823 keybindings: ResolvedKeybindingsConfig;
798}824}
799825
800interface TerminalActionButtonProps {826interface TerminalActionButtonProps {
...@@ -848,6 +874,7 @@ export default function ThreadTerminalDrawer({...@@ -848,6 +874,7 @@ export default function ThreadTerminalDrawer({
848 onCloseTerminal,874 onCloseTerminal,
849 onHeightChange,875 onHeightChange,
850 onAddTerminalContext,876 onAddTerminalContext,
877 keybindings,
851}: ThreadTerminalDrawerProps) {878}: ThreadTerminalDrawerProps) {
852 const [drawerHeight, setDrawerHeight] = useState(() => clampDrawerHeight(height));879 const [drawerHeight, setDrawerHeight] = useState(() => clampDrawerHeight(height));
853 const [resizeEpoch, setResizeEpoch] = useState(0);880 const [resizeEpoch, setResizeEpoch] = useState(0);
...@@ -1166,6 +1193,7 @@ export default function ThreadTerminalDrawer({...@@ -1166,6 +1193,7 @@ export default function ThreadTerminalDrawer({
1166 autoFocus={terminalId === resolvedActiveTerminalId}1193 autoFocus={terminalId === resolvedActiveTerminalId}
1167 resizeEpoch={resizeEpoch}1194 resizeEpoch={resizeEpoch}
1168 drawerHeight={drawerHeight}1195 drawerHeight={drawerHeight}
1196 keybindings={keybindings}
1169 />1197 />
1170 </div>1198 </div>
1171 </div>1199 </div>
...@@ -1188,6 +1216,7 @@ export default function ThreadTerminalDrawer({...@@ -1188,6 +1216,7 @@ export default function ThreadTerminalDrawer({
1188 autoFocus1216 autoFocus
1189 resizeEpoch={resizeEpoch}1217 resizeEpoch={resizeEpoch}
1190 drawerHeight={drawerHeight}1218 drawerHeight={drawerHeight}
1219 keybindings={keybindings}
1191 />1220 />
1192 </div>1221 </div>
1193 )}1222 )}