From 28e4f0568515b19382020537e191274bf2c5cd1f Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Fri, 9 Oct 2026 18:29:16 +0000 Subject: [PATCH 1/5] fix(web): terminal drawer keeps its height after the window shrinks --- .../src/components/ThreadTerminalDrawer.tsx | 40 +++++++++---------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/apps/web/src/components/ThreadTerminalDrawer.tsx b/apps/web/src/components/ThreadTerminalDrawer.tsx index bd45f39ad2bd..68b5b2b851c5 100644 --- a/apps/web/src/components/ThreadTerminalDrawer.tsx +++ b/apps/web/src/components/ThreadTerminalDrawer.tsx @@ -104,9 +104,8 @@ function maxDrawerHeight(): number { return Math.max(MIN_DRAWER_HEIGHT, Math.floor(window.innerHeight * MAX_DRAWER_HEIGHT_RATIO)); } -function clampDrawerHeight(height: number): number { +function clampDrawerHeight(height: number, maxHeight = maxDrawerHeight()): number { const safeHeight = Number.isFinite(height) ? height : DEFAULT_THREAD_TERMINAL_HEIGHT; - const maxHeight = maxDrawerHeight(); return Math.min(Math.max(Math.round(safeHeight), MIN_DRAWER_HEIGHT), maxHeight); } @@ -1181,13 +1180,16 @@ export default function ThreadTerminalDrawer({ false, Schema.Boolean, ); - const controlledDrawerHeight = clampDrawerHeight(height); + // The stored height is the user's preference; the window only clamps what renders. + const controlledDrawerHeight = height; + const [windowMaxDrawerHeight, setWindowMaxDrawerHeight] = useState(maxDrawerHeight); const [drawerHeightState, setDrawerHeightState] = useState(() => ({ threadId, height: controlledDrawerHeight, })); - const drawerHeight = + const preferredDrawerHeight = drawerHeightState.threadId === threadId ? drawerHeightState.height : controlledDrawerHeight; + const drawerHeight = clampDrawerHeight(preferredDrawerHeight, windowMaxDrawerHeight); const setDrawerHeight = useCallback( (update: SetStateAction) => { setDrawerHeightState((current) => { @@ -1201,11 +1203,9 @@ export default function ThreadTerminalDrawer({ }, [controlledDrawerHeight, threadId], ); - const setDrawerHeightFromWindowResize = useEffectEvent((nextHeight: number) => { - setDrawerHeight(nextHeight); - }); const [resizeEpoch, setResizeEpoch] = useState(0); const drawerHeightRef = useRef(drawerHeight); + const preferredDrawerHeightRef = useRef(preferredDrawerHeight); const lastSyncedHeightRef = useRef(controlledDrawerHeight); const onHeightChangeRef = useRef(onHeightChange); const resizeStateRef = useRef<{ @@ -1399,11 +1399,14 @@ export default function ThreadTerminalDrawer({ drawerHeightRef.current = drawerHeight; }, [drawerHeight]); + useEffect(() => { + preferredDrawerHeightRef.current = preferredDrawerHeight; + }, [preferredDrawerHeight]); + const syncHeight = useCallback((nextHeight: number) => { - const clampedHeight = clampDrawerHeight(nextHeight); - if (lastSyncedHeightRef.current === clampedHeight) return; - lastSyncedHeightRef.current = clampedHeight; - onHeightChangeRef.current(clampedHeight); + if (lastSyncedHeightRef.current === nextHeight) return; + lastSyncedHeightRef.current = nextHeight; + onHeightChangeRef.current(nextHeight); }, []); useEffect(() => { @@ -1464,33 +1467,26 @@ export default function ThreadTerminalDrawer({ } const onWindowResize = () => { - const clampedHeight = clampDrawerHeight(drawerHeightRef.current); - const changed = clampedHeight !== drawerHeightRef.current; - if (changed) { - setDrawerHeightFromWindowResize(clampedHeight); - drawerHeightRef.current = clampedHeight; - } - if (!resizeStateRef.current) { - syncHeight(clampedHeight); - } + setWindowMaxDrawerHeight(maxDrawerHeight()); setResizeEpoch((value) => value + 1); }; window.addEventListener("resize", onWindowResize); return () => { window.removeEventListener("resize", onWindowResize); }; - }, [syncHeight, visible]); + }, [visible]); useEffect(() => { if (!visible) { return; } + setWindowMaxDrawerHeight(maxDrawerHeight()); setResizeEpoch((value) => value + 1); }, [visible]); useEffect(() => { return () => { - syncHeight(drawerHeightRef.current); + syncHeight(preferredDrawerHeightRef.current); }; }, [syncHeight]); From d668876968f0a31b78b52f96ae233e6ebbc11cc3 Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Sat, 10 Oct 2026 00:44:26 +0000 Subject: [PATCH 2/5] fix(web): a terminal drag that ends where it started keeps the stored height --- apps/web/src/components/ThreadTerminalDrawer.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/ThreadTerminalDrawer.tsx b/apps/web/src/components/ThreadTerminalDrawer.tsx index 68b5b2b851c5..83555ca0492c 100644 --- a/apps/web/src/components/ThreadTerminalDrawer.tsx +++ b/apps/web/src/components/ThreadTerminalDrawer.tsx @@ -1451,7 +1451,9 @@ export default function ThreadTerminalDrawer({ if (event.currentTarget.hasPointerCapture(event.pointerId)) { event.currentTarget.releasePointerCapture(event.pointerId); } - if (!didResizeDuringDragRef.current) { + // A drag that ends where it started keeps the stored preference, which may + // be taller than the window-capped height the drag started from. + if (!didResizeDuringDragRef.current || drawerHeightRef.current === resizeState.startHeight) { return; } setDrawerHeight(drawerHeightRef.current); From 737eef9503912f4dfc16ab3d976097fe2850ed7b Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Sat, 10 Oct 2026 00:50:03 +0000 Subject: [PATCH 3/5] fix(web): a terminal drag stays within a window that shrinks mid-drag --- apps/web/src/components/ThreadTerminalDrawer.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/web/src/components/ThreadTerminalDrawer.tsx b/apps/web/src/components/ThreadTerminalDrawer.tsx index 83555ca0492c..bdb2b145fd8a 100644 --- a/apps/web/src/components/ThreadTerminalDrawer.tsx +++ b/apps/web/src/components/ThreadTerminalDrawer.tsx @@ -1469,6 +1469,13 @@ export default function ThreadTerminalDrawer({ } const onWindowResize = () => { + // React only renders the stored preference, so clamp a live drag height here. + const resizeState = resizeStateRef.current; + const liveHeight = clampDrawerHeight(drawerHeightRef.current); + if (resizeState && liveHeight !== drawerHeightRef.current) { + drawerHeightRef.current = liveHeight; + resizeState.drawer?.style.setProperty("height", `${liveHeight}px`); + } setWindowMaxDrawerHeight(maxDrawerHeight()); setResizeEpoch((value) => value + 1); }; From 9dfd3a9d381849c3c3872be1efd0a7477baf7bb7 Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Sat, 10 Oct 2026 00:54:25 +0000 Subject: [PATCH 4/5] fix(web): a terminal drag keeps its live height through a window resize --- apps/web/src/components/ThreadTerminalDrawer.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/ThreadTerminalDrawer.tsx b/apps/web/src/components/ThreadTerminalDrawer.tsx index bdb2b145fd8a..6468fa967606 100644 --- a/apps/web/src/components/ThreadTerminalDrawer.tsx +++ b/apps/web/src/components/ThreadTerminalDrawer.tsx @@ -1395,7 +1395,13 @@ export default function ThreadTerminalDrawer({ onHeightChangeRef.current = onHeightChange; }, [onHeightChange]); - useEffect(() => { + useLayoutEffect(() => { + // A render mid-drag (the window cap moved) must not replace the live height. + const resizeState = resizeStateRef.current; + if (resizeState) { + resizeState.drawer?.style.setProperty("height", `${drawerHeightRef.current}px`); + return; + } drawerHeightRef.current = drawerHeight; }, [drawerHeight]); From c967da4cf7f0986f5ccaa2da11daa7398d64f215 Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Sat, 10 Oct 2026 01:08:41 +0000 Subject: [PATCH 5/5] fix(web): a terminal drag that saves nothing restores the rendered height --- apps/web/src/components/ThreadTerminalDrawer.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/web/src/components/ThreadTerminalDrawer.tsx b/apps/web/src/components/ThreadTerminalDrawer.tsx index 6468fa967606..f6509289d45a 100644 --- a/apps/web/src/components/ThreadTerminalDrawer.tsx +++ b/apps/web/src/components/ThreadTerminalDrawer.tsx @@ -1460,6 +1460,11 @@ export default function ThreadTerminalDrawer({ // A drag that ends where it started keeps the stored preference, which may // be taller than the window-capped height the drag started from. if (!didResizeDuringDragRef.current || drawerHeightRef.current === resizeState.startHeight) { + // React skips the write when the rendered height did not change, so put + // back the preference as the current window caps it. + const renderedHeight = clampDrawerHeight(preferredDrawerHeightRef.current); + drawerHeightRef.current = renderedHeight; + resizeState.drawer?.style.setProperty("height", `${renderedHeight}px`); return; } setDrawerHeight(drawerHeightRef.current);