From 3073d9d492707d4891ba1e78d95843c7003b8e79 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 19 Jul 2026 16:21:58 +0000 Subject: [PATCH 1/3] perf(web): avoid extra terminal drawer render Co-authored-by: Julius Marminge --- .../src/components/ThreadTerminalDrawer.tsx | 91 +++++++++++++------ 1 file changed, 63 insertions(+), 28 deletions(-) diff --git a/apps/web/src/components/ThreadTerminalDrawer.tsx b/apps/web/src/components/ThreadTerminalDrawer.tsx index 8591c24c71a..bfd8e24928a 100644 --- a/apps/web/src/components/ThreadTerminalDrawer.tsx +++ b/apps/web/src/components/ThreadTerminalDrawer.tsx @@ -22,6 +22,7 @@ import { Terminal, type ITheme } from "@xterm/xterm"; import { type PointerEvent as ReactPointerEvent, type ReactNode, + type RefObject, type SetStateAction, useCallback, useEffect, @@ -281,6 +282,7 @@ interface TerminalViewportProps { onAddTerminalContext: (selection: TerminalContextSelection) => void; focusRequestId: number; autoFocus: boolean; + visible: boolean; resizeEpoch: number; drawerHeight: number; keybindings: ResolvedKeybindingsConfig; @@ -292,6 +294,46 @@ interface TerminalLaunchLocation { readonly runtimeEnv?: Record; } +function useFitTerminalOnViewportChange({ + visible, + drawerHeight, + environmentId, + resizeEpoch, + terminalId, + threadId, + terminalRef, + fitAddonRef, + resizeTerminal, +}: { + visible: boolean; + drawerHeight: number; + environmentId: string; + resizeEpoch: number; + terminalId: string; + threadId: ThreadId; + terminalRef: RefObject; + fitAddonRef: RefObject; + resizeTerminal: (cols: number, rows: number) => unknown; +}) { + useEffect(() => { + if (!visible) return; + const terminal = terminalRef.current; + const fitAddon = fitAddonRef.current; + if (!terminal || !fitAddon) return; + const wasAtBottom = terminal.buffer.active.viewportY >= terminal.buffer.active.baseY; + const frame = window.requestAnimationFrame(() => { + fitTerminalSafely(fitAddon); + if (wasAtBottom) { + terminal.scrollToBottom(); + } + void resizeTerminal(terminal.cols, terminal.rows); + }); + return () => { + window.cancelAnimationFrame(frame); + }; + }, [drawerHeight, environmentId, resizeEpoch, resizeTerminal, terminalId, threadId, visible]); +} + export function TerminalViewport({ threadRef, threadId, @@ -304,6 +346,7 @@ export function TerminalViewport({ onAddTerminalContext, focusRequestId, autoFocus, + visible, resizeEpoch, drawerHeight, keybindings, @@ -358,11 +401,13 @@ export function TerminalViewport({ input: { threadId, terminalId, data }, }), ); - const resizeTerminal = useEffectEvent((cols: number, rows: number) => - runTerminalResize({ - environmentId, - input: { threadId, terminalId, cols, rows }, - }), + const resizeTerminal = useCallback( + (cols: number, rows: number) => + runTerminalResize({ + environmentId, + input: { threadId, terminalId, cols, rows }, + }), + [environmentId, runTerminalResize, terminalId, threadId], ); const terminalBuffer = terminalSession.buffer; const terminalError = terminalSession.error; @@ -807,22 +852,17 @@ export function TerminalViewport({ }; }, [autoFocus, focusRequestId]); - useEffect(() => { - const terminal = terminalRef.current; - const fitAddon = fitAddonRef.current; - if (!terminal || !fitAddon) return; - const wasAtBottom = terminal.buffer.active.viewportY >= terminal.buffer.active.baseY; - const frame = window.requestAnimationFrame(() => { - fitTerminalSafely(fitAddon); - if (wasAtBottom) { - terminal.scrollToBottom(); - } - void resizeTerminal(terminal.cols, terminal.rows); - }); - return () => { - window.cancelAnimationFrame(frame); - }; - }, [drawerHeight, environmentId, resizeEpoch, terminalId, threadId]); + useFitTerminalOnViewportChange({ + visible, + drawerHeight, + environmentId, + resizeEpoch, + terminalId, + threadId, + terminalRef, + fitAddonRef, + resizeTerminal, + }); return (
{ - if (!visible) { - return; - } - setResizeEpoch((value) => value + 1); - }, [visible]); - useEffect(() => { return () => { syncHeight(drawerHeightRef.current); @@ -1373,6 +1406,7 @@ export default function ThreadTerminalDrawer({ onAddTerminalContext={onAddTerminalContext} focusRequestId={focusRequestId} autoFocus={terminalId === resolvedActiveTerminalId} + visible={visible} resizeEpoch={resizeEpoch} drawerHeight={drawerHeight} keybindings={keybindings} @@ -1401,6 +1435,7 @@ export default function ThreadTerminalDrawer({ onAddTerminalContext={onAddTerminalContext} focusRequestId={focusRequestId} autoFocus + visible={visible} resizeEpoch={resizeEpoch} drawerHeight={drawerHeight} keybindings={keybindings} From 7c87dec4d17200d97c0958592d62efc9966ccd7e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 19 Jul 2026 16:41:15 +0000 Subject: [PATCH 2/3] refactor(web): minimize terminal drawer render fix Co-authored-by: Julius Marminge --- .../src/components/ThreadTerminalDrawer.tsx | 81 +++++-------------- 1 file changed, 22 insertions(+), 59 deletions(-) diff --git a/apps/web/src/components/ThreadTerminalDrawer.tsx b/apps/web/src/components/ThreadTerminalDrawer.tsx index bfd8e24928a..3aea95d8768 100644 --- a/apps/web/src/components/ThreadTerminalDrawer.tsx +++ b/apps/web/src/components/ThreadTerminalDrawer.tsx @@ -22,7 +22,6 @@ import { Terminal, type ITheme } from "@xterm/xterm"; import { type PointerEvent as ReactPointerEvent, type ReactNode, - type RefObject, type SetStateAction, useCallback, useEffect, @@ -294,46 +293,6 @@ interface TerminalLaunchLocation { readonly runtimeEnv?: Record; } -function useFitTerminalOnViewportChange({ - visible, - drawerHeight, - environmentId, - resizeEpoch, - terminalId, - threadId, - terminalRef, - fitAddonRef, - resizeTerminal, -}: { - visible: boolean; - drawerHeight: number; - environmentId: string; - resizeEpoch: number; - terminalId: string; - threadId: ThreadId; - terminalRef: RefObject; - fitAddonRef: RefObject; - resizeTerminal: (cols: number, rows: number) => unknown; -}) { - useEffect(() => { - if (!visible) return; - const terminal = terminalRef.current; - const fitAddon = fitAddonRef.current; - if (!terminal || !fitAddon) return; - const wasAtBottom = terminal.buffer.active.viewportY >= terminal.buffer.active.baseY; - const frame = window.requestAnimationFrame(() => { - fitTerminalSafely(fitAddon); - if (wasAtBottom) { - terminal.scrollToBottom(); - } - void resizeTerminal(terminal.cols, terminal.rows); - }); - return () => { - window.cancelAnimationFrame(frame); - }; - }, [drawerHeight, environmentId, resizeEpoch, resizeTerminal, terminalId, threadId, visible]); -} - export function TerminalViewport({ threadRef, threadId, @@ -401,13 +360,11 @@ export function TerminalViewport({ input: { threadId, terminalId, data }, }), ); - const resizeTerminal = useCallback( - (cols: number, rows: number) => - runTerminalResize({ - environmentId, - input: { threadId, terminalId, cols, rows }, - }), - [environmentId, runTerminalResize, terminalId, threadId], + const resizeTerminal = useEffectEvent((cols: number, rows: number) => + runTerminalResize({ + environmentId, + input: { threadId, terminalId, cols, rows }, + }), ); const terminalBuffer = terminalSession.buffer; const terminalError = terminalSession.error; @@ -852,17 +809,23 @@ export function TerminalViewport({ }; }, [autoFocus, focusRequestId]); - useFitTerminalOnViewportChange({ - visible, - drawerHeight, - environmentId, - resizeEpoch, - terminalId, - threadId, - terminalRef, - fitAddonRef, - resizeTerminal, - }); + useEffect(() => { + if (!visible) return; + const terminal = terminalRef.current; + const fitAddon = fitAddonRef.current; + if (!terminal || !fitAddon) return; + const wasAtBottom = terminal.buffer.active.viewportY >= terminal.buffer.active.baseY; + const frame = window.requestAnimationFrame(() => { + fitTerminalSafely(fitAddon); + if (wasAtBottom) { + terminal.scrollToBottom(); + } + void resizeTerminal(terminal.cols, terminal.rows); + }); + return () => { + window.cancelAnimationFrame(frame); + }; + }, [drawerHeight, environmentId, resizeEpoch, terminalId, threadId, visible]); return (
Date: Sun, 19 Jul 2026 16:42:28 +0000 Subject: [PATCH 3/3] refactor(web): name terminal fit synchronization hook Co-authored-by: Julius Marminge --- .../src/components/ThreadTerminalDrawer.tsx | 81 ++++++++++++++----- 1 file changed, 59 insertions(+), 22 deletions(-) diff --git a/apps/web/src/components/ThreadTerminalDrawer.tsx b/apps/web/src/components/ThreadTerminalDrawer.tsx index 3aea95d8768..bfd8e24928a 100644 --- a/apps/web/src/components/ThreadTerminalDrawer.tsx +++ b/apps/web/src/components/ThreadTerminalDrawer.tsx @@ -22,6 +22,7 @@ import { Terminal, type ITheme } from "@xterm/xterm"; import { type PointerEvent as ReactPointerEvent, type ReactNode, + type RefObject, type SetStateAction, useCallback, useEffect, @@ -293,6 +294,46 @@ interface TerminalLaunchLocation { readonly runtimeEnv?: Record; } +function useFitTerminalOnViewportChange({ + visible, + drawerHeight, + environmentId, + resizeEpoch, + terminalId, + threadId, + terminalRef, + fitAddonRef, + resizeTerminal, +}: { + visible: boolean; + drawerHeight: number; + environmentId: string; + resizeEpoch: number; + terminalId: string; + threadId: ThreadId; + terminalRef: RefObject; + fitAddonRef: RefObject; + resizeTerminal: (cols: number, rows: number) => unknown; +}) { + useEffect(() => { + if (!visible) return; + const terminal = terminalRef.current; + const fitAddon = fitAddonRef.current; + if (!terminal || !fitAddon) return; + const wasAtBottom = terminal.buffer.active.viewportY >= terminal.buffer.active.baseY; + const frame = window.requestAnimationFrame(() => { + fitTerminalSafely(fitAddon); + if (wasAtBottom) { + terminal.scrollToBottom(); + } + void resizeTerminal(terminal.cols, terminal.rows); + }); + return () => { + window.cancelAnimationFrame(frame); + }; + }, [drawerHeight, environmentId, resizeEpoch, resizeTerminal, terminalId, threadId, visible]); +} + export function TerminalViewport({ threadRef, threadId, @@ -360,11 +401,13 @@ export function TerminalViewport({ input: { threadId, terminalId, data }, }), ); - const resizeTerminal = useEffectEvent((cols: number, rows: number) => - runTerminalResize({ - environmentId, - input: { threadId, terminalId, cols, rows }, - }), + const resizeTerminal = useCallback( + (cols: number, rows: number) => + runTerminalResize({ + environmentId, + input: { threadId, terminalId, cols, rows }, + }), + [environmentId, runTerminalResize, terminalId, threadId], ); const terminalBuffer = terminalSession.buffer; const terminalError = terminalSession.error; @@ -809,23 +852,17 @@ export function TerminalViewport({ }; }, [autoFocus, focusRequestId]); - useEffect(() => { - if (!visible) return; - const terminal = terminalRef.current; - const fitAddon = fitAddonRef.current; - if (!terminal || !fitAddon) return; - const wasAtBottom = terminal.buffer.active.viewportY >= terminal.buffer.active.baseY; - const frame = window.requestAnimationFrame(() => { - fitTerminalSafely(fitAddon); - if (wasAtBottom) { - terminal.scrollToBottom(); - } - void resizeTerminal(terminal.cols, terminal.rows); - }); - return () => { - window.cancelAnimationFrame(frame); - }; - }, [drawerHeight, environmentId, resizeEpoch, terminalId, threadId, visible]); + useFitTerminalOnViewportChange({ + visible, + drawerHeight, + environmentId, + resizeEpoch, + terminalId, + threadId, + terminalRef, + fitAddonRef, + resizeTerminal, + }); return (