From b673c52f15e93e172abaeadae2dac1de0c61df04 Mon Sep 17 00:00:00 2001 From: Julius Marminge <51714798+juliusmarminge@users.noreply.github.com> Date: Tue, 27 Oct 2026 04:33:16 -0700 Subject: [PATCH] refactor(web): composer overlay re-measures from one layout effect ChatView kept a composerMountedRef plus a second layout effect that compared it with composerMounted and, on change, reset the three composer inset refs and re-published the overlay height. That body is identical to the thread-switch reset effect. The ref also lagged: when ChatView mounted on a provider-subagent thread (composer not mounted), the reset and observer effects published with the ref's initial `true`, and the extra effect then reset and re-published in the same commit. publishComposerOverlayHeight goes through useStableCallback, which updates in an insertion effect, so layout effects already see the current render's closure. It now reads composerMounted directly, and the thread-switch reset effect also keys on composerMounted. Effects in ChatView.tsx: one useLayoutEffect removed (the composerMounted compare); the reset and resize-observer layout effects are kept, they measure DOM after commit. Co-Authored-By: Claude Opus 5.5 --- apps/web/src/components/ChatView.tsx | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 5369f2158e6e7..19726720b1427 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -708,7 +708,6 @@ export default function ChatView(props: ChatViewProps) { const composerReservedHeightRef = useRef(0); const composerExpandedHeightRef = useRef(0); const composerRestingRef = useRef(false); - const composerMountedRef = useRef(true); const [scrollToEndClearance, setScrollToEndClearance] = useState(0); const [timelineOverflows, setTimelineOverflows] = useState(false); const fanoutSelectionsAtom = draftFanoutSelectionsAtom(routeThreadKey); @@ -2127,7 +2126,7 @@ export default function ChatView(props: ChatViewProps) { const nextHeight = Math.ceil(height); if (nextHeight <= 0) return; const isResting = overlayComposerIsResting({ - composerMounted: composerMountedRef.current, + composerMounted, composerReportedResting: composerRestingRef.current, }); const modelStrip = composerOverlayElement?.querySelector( @@ -2200,8 +2199,13 @@ export default function ChatView(props: ChatViewProps) { composerReservedHeightRef.current = 0; composerExpandedHeightRef.current = 0; publishComposerOverlayHeightStable(composerOverlayElement.getBoundingClientRect().height); - // oxlint-disable-next-line react/exhaustive-effect-dependencies -- A thread switch must re-measure the overlay for the new timeline. - }, [activeThreadKey, composerOverlayElement, publishComposerOverlayHeightStable]); + }, [ + // oxlint-disable-next-line react/exhaustive-effect-dependencies -- A thread switch or a composer mount/unmount must re-measure the overlay for the new timeline. + activeThreadKey, + composerMounted, + composerOverlayElement, + publishComposerOverlayHeightStable, + ]); useLayoutEffect(() => { if (!composerOverlayElement) return; @@ -2226,15 +2230,6 @@ export default function ChatView(props: ChatViewProps) { // oxlint-disable-next-line react/exhaustive-effect-dependencies -- The scroll-to-bottom button changes the overlay's height without resizing the observed node first. showScrollToBottom, ]); - useLayoutEffect(() => { - if (composerMountedRef.current === composerMounted) return; - composerMountedRef.current = composerMounted; - if (!composerOverlayElement) return; - composerTimelineInsetRef.current = 0; - composerReservedHeightRef.current = 0; - composerExpandedHeightRef.current = 0; - publishComposerOverlayHeightStable(composerOverlayElement.getBoundingClientRect().height); - }, [composerMounted, composerOverlayElement, publishComposerOverlayHeightStable]); const openPanelPullRequestUrl = useOpenPanelPullRequestUrl(activeThreadRef); const activeThreadReferenceCopyTarget = activeThreadId === null || !isServerThread