Refine tool call summaries and scrolling (#2069)

* feat(app): refine tool call summaries and scrolling

Keep summary groups stable from the first tool call, preserve live activity feedback, and scroll expanded groups to the latest entry. Consolidate web scrollbar styling around the native browser mechanism.

* refactor(app): reuse tool call height constant

* fix(app): preserve tool call summary state

* fix(app): refresh web tool groups on expand

* fix(app): preserve grouped tool errors
This commit is contained in:
Mohamed Boudra
2026-07-14 21:16:29 +02:00
committed by GitHub
parent d781b68711
commit 1b8af2e58e
55 changed files with 1484 additions and 2488 deletions
+36 -47
View File
@@ -25,7 +25,6 @@ const USER_SCROLL_DELTA_EPSILON = 1;
const AUTO_SCROLL_BOTTOM_THRESHOLD_PX = 64;
const AUTO_SCROLL_RESUME_THRESHOLD_PX = 1;
const HISTORY_START_THRESHOLD_PX = 96;
import { useWebElementScrollbar } from "@/components/use-web-scrollbar";
const historyStartSlotStyle: CSSProperties = {
display: "flex",
@@ -95,6 +94,7 @@ function isScrollContainerOverscrolledPastBottom(
function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: boolean }) {
const {
segments,
liveHeadRowRevision,
boundary,
renderers,
listEmptyComponent,
@@ -131,11 +131,6 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
const pendingAutoScrollTimeoutRef = useRef<number | null>(null);
const pendingVirtualRowMeasureFramesRef = useRef(new Map<Element, number>());
const historyStartReadyRef = useRef(false);
const showDesktopWebScrollbar = !isMobileBreakpoint;
const scrollbarOverlay = useWebElementScrollbar(scrollContainerRef, {
enabled: showDesktopWebScrollbar,
contentRef,
});
const shouldUseVirtualizer = segments.historyVirtualized.length > 0;
const {
renderHistoryVirtualizedRow,
@@ -540,10 +535,11 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
));
}, [renderHistoryMountedRow, segments.historyMounted]);
const liveHeadRows = useMemo(() => {
void liveHeadRowRevision;
return segments.liveHead.map((item, index) => (
<Fragment key={item.id}>{renderLiveHeadRow(item, index, segments.liveHead)}</Fragment>
));
}, [renderLiveHeadRow, segments.liveHead]);
}, [liveHeadRowRevision, renderLiveHeadRow, segments.liveHead]);
const liveAuxiliary = useMemo(() => {
return renderLiveAuxiliary();
}, [renderLiveAuxiliary]);
@@ -564,47 +560,40 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
!liveAuxiliary;
return (
<>
<div
ref={handleScrollContainerRef}
data-testid="agent-chat-scroll"
id={`agent-chat-scroll-${shouldUseVirtualizer ? "web-dom-virtualized" : "web-dom-scroll"}`}
style={scrollContainerStyle}
>
<div ref={handleContentRef} style={contentContainerStyle}>
{historyStartSlot}
{shouldUseVirtualizer ? (
<div style={virtualRowsContainerStyle}>
{virtualRows.map((virtualRow) => {
const item = segments.historyVirtualized[virtualRow.index];
if (!item) {
return null;
}
return (
<div
key={virtualRow.key}
data-index={virtualRow.index}
ref={measureVirtualizedRowElement}
style={renderVirtualRowStyle(virtualRow.start)}
>
{renderHistoryVirtualizedRow(
item,
virtualRow.index,
segments.historyVirtualized,
)}
</div>
);
})}
</div>
) : null}
{mountedHistoryRows}
{liveHeadRows}
{liveAuxiliary}
{shouldRenderEmpty ? listEmptyComponent : null}
</div>
<div
ref={handleScrollContainerRef}
data-testid="agent-chat-scroll"
id={`agent-chat-scroll-${shouldUseVirtualizer ? "web-dom-virtualized" : "web-dom-scroll"}`}
style={scrollContainerStyle}
>
<div ref={handleContentRef} style={contentContainerStyle}>
{historyStartSlot}
{shouldUseVirtualizer ? (
<div style={virtualRowsContainerStyle}>
{virtualRows.map((virtualRow) => {
const item = segments.historyVirtualized[virtualRow.index];
if (!item) {
return null;
}
return (
<div
key={virtualRow.key}
data-index={virtualRow.index}
ref={measureVirtualizedRowElement}
style={renderVirtualRowStyle(virtualRow.start)}
>
{renderHistoryVirtualizedRow(item, virtualRow.index, segments.historyVirtualized)}
</div>
);
})}
</div>
) : null}
{mountedHistoryRows}
{liveHeadRows}
{liveAuxiliary}
{shouldRenderEmpty ? listEmptyComponent : null}
</div>
{scrollbarOverlay}
</>
</div>
);
}