From 4e2077a6b7fbf8bea02fe7f000dbd0fa9d44bf52 Mon Sep 17 00:00:00 2001 From: eric Date: Mon, 1 Jun 2026 11:55:27 -0700 Subject: [PATCH] [eric] chat: keep tool body collapsed mid-stream, ease bubbles in, stop scroll-anchor fighting autoscroll --- .../src/app/pages/AgentChat/AgentChat.tsx | 29 ++++++++++++------- .../tool-bubbles/DefaultToolBubble.tsx | 19 +++++++++++- .../AgentChat/tool-bubbles/ToolCallBubble.tsx | 6 +++- .../tool-bubbles/ToolGroupBubble.tsx | 8 +++++ 4 files changed, 50 insertions(+), 12 deletions(-) diff --git a/frontend/src/app/pages/AgentChat/AgentChat.tsx b/frontend/src/app/pages/AgentChat/AgentChat.tsx index bf103154..7735ee3d 100644 --- a/frontend/src/app/pages/AgentChat/AgentChat.tsx +++ b/frontend/src/app/pages/AgentChat/AgentChat.tsx @@ -1307,19 +1307,28 @@ const AgentChat: React.FC = ({ sessionId: sessionIdProp, onClose ); })} + {/* overflow-anchor: none on the two elements that grow every frame + (live stream + thinking dots) keeps Chromium's scroll anchoring + from fighting our jam-to-bottom for the scroll position. The + committed messages above keep the default anchor, so resizing a + tool row while the user has scrolled up still holds their view. */} {id && ( - + + + )} {(awaitingResponse || (session.status === 'running' && !streamingMessageId)) && ( - + + + )} {showResumeBubble && session.status === 'stopped' && ( diff --git a/frontend/src/app/pages/AgentChat/tool-bubbles/DefaultToolBubble.tsx b/frontend/src/app/pages/AgentChat/tool-bubbles/DefaultToolBubble.tsx index 4ef70036..09dd9a48 100644 --- a/frontend/src/app/pages/AgentChat/tool-bubbles/DefaultToolBubble.tsx +++ b/frontend/src/app/pages/AgentChat/tool-bubbles/DefaultToolBubble.tsx @@ -55,7 +55,24 @@ export const DefaultToolBubble: React.FC = ({ const tc = useTermColors(); return ( - + = React.memo( const isInvokeAgent = isInvokeAgentTool(toolName); const isCreateAgent = isCreateAgentTool(toolName); const browserAgentAutoExpand = isBrowserAgent && isPending && !isStreaming; - const showBody = expanded || isStreaming || browserAgentAutoExpand; + // While the call is still streaming we keep the body CLOSED: the args land in + // bursty clumps and force-painting them mid-stream is the jitter the user feels. + // The header pill (tool name + glow) is the calm "what's running" signal; the + // full args/output live behind the chevron once the call lands and is expanded. + const showBody = expanded || browserAgentAutoExpand; const resultContent = result?.content; const hasStructuredResult = diff --git a/frontend/src/app/pages/AgentChat/tool-bubbles/ToolGroupBubble.tsx b/frontend/src/app/pages/AgentChat/tool-bubbles/ToolGroupBubble.tsx index 0c4de468..4399019a 100644 --- a/frontend/src/app/pages/AgentChat/tool-bubbles/ToolGroupBubble.tsx +++ b/frontend/src/app/pages/AgentChat/tool-bubbles/ToolGroupBubble.tsx @@ -99,6 +99,14 @@ const ToolGroupBubble: React.FC = React.memo(({ group, isSessionRunning = my: 0.5, // contain: stops new tool rows from reflowing the whole transcript. contain: 'layout style', + // Ease in instead of popping when a tool group appears mid-turn. + // Transform+opacity only, so it rides the compositor and never nudges + // layout or the scroll position. No streaming twin, so no handoff flash. + animation: 'toolGroupEnter 160ms ease-out', + '@keyframes toolGroupEnter': { + from: { opacity: 0, transform: 'translateY(4px)' }, + to: { opacity: 1, transform: 'translateY(0)' }, + }, }} >