From aa96a0fb5c147a582132dac1b66dee40f74f6178 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Tue, 25 Aug 2026 11:33:42 +0300 Subject: [PATCH] fix(chat): status row floats inside the existing bottom spacer, no extra inset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rest gap after completion doubled up: the status overlay reserved its own end inset on top of the footer's 10vh spacer. The overlay now floats within the spacer zone — its height feeds only the glide target (the live line still rests above it), the list gets no extra inset, and completion cannot shift layout because there is nothing to collapse. The finished message's metadata lands in the slack the glide left above the status row, restoring the old morph-in-place feel. The frozen height hack and the opaque background are gone — the overlay only ever covers blank spacer, not text. --- .../ui/src/components/chat/ChatContainer.tsx | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/ui/src/components/chat/ChatContainer.tsx b/packages/ui/src/components/chat/ChatContainer.tsx index 22432e68..d754a54f 100644 --- a/packages/ui/src/components/chat/ChatContainer.tsx +++ b/packages/ui/src/components/chat/ChatContainer.tsx @@ -159,7 +159,6 @@ type ChatViewportProps = { anchorMessageId: string | null; onAnchorReady: (messageId: string, anchorIndex: number) => void; onAnchorSizeChanged: (messageId: string) => void; - composerOverlayHeight: number; onIsAtEndChange: (isAtEnd: boolean) => void; onTimelineDataChange: () => void; pendingRevealWork: boolean; @@ -204,7 +203,6 @@ const ChatViewport = React.memo(({ anchorMessageId, onAnchorReady, onAnchorSizeChanged, - composerOverlayHeight, onIsAtEndChange, onTimelineDataChange, pendingRevealWork, @@ -407,7 +405,10 @@ const ChatViewport = React.memo(({ anchorMessageId={anchorMessageId} onAnchorReady={onAnchorReady} onAnchorSizeChanged={onAnchorSizeChanged} - composerOverlayHeight={composerOverlayHeight} + // Zero end inset: the footer spacer already reserves the + // zone the floating status row covers; adding its height + // again produced a double-tall blank band at rest. + composerOverlayHeight={0} onIsAtEndChange={onIsAtEndChange} onTimelineDataChange={onTimelineDataChange} listHeader={listHeader} @@ -423,7 +424,7 @@ const ChatViewport = React.memo(({
@@ -932,9 +933,6 @@ export const ChatContainer: React.FC = ({ const [statusOverlayHeight, setStatusOverlayHeight] = React.useState(0); const composerOverlayHeight = statusOverlayHeight; const statusOverlayObserverRef = React.useRef(null); - React.useEffect(() => { - setStatusOverlayHeight(0); - }, [currentSessionKey]); const onStatusOverlayNode = React.useCallback((node: HTMLDivElement | null) => { statusOverlayObserverRef.current?.disconnect(); statusOverlayObserverRef.current = null; @@ -944,10 +942,7 @@ export const ChatContainer: React.FC = ({ } const update = () => { const height = node.getBoundingClientRect().height; - // Hold the reserved height when the status row empties: collapsing - // it at end-of-stream shifts the settled content. It resets only - // on session change. - setStatusOverlayHeight((prev) => (height > prev + 1 ? height : prev)); + setStatusOverlayHeight((prev) => (Math.abs(prev - height) < 1 ? prev : height)); }; const observer = new ResizeObserver(update); observer.observe(node); @@ -1374,7 +1369,6 @@ export const ChatContainer: React.FC = ({ anchorMessageId={anchorMessageId} onAnchorReady={onAnchorReady} onAnchorSizeChanged={onAnchorSizeChanged} - composerOverlayHeight={composerOverlayHeight} onIsAtEndChange={onIsAtEndChange} onTimelineDataChange={onTimelineDataChange} messageListRef={messageListRef}