perf: migrate chat rendering to virtua (#1651)

* refactor: migrate chat history virtualization to virtua

* refactor: render loaded chat history directly

* refactor: finish virtua migration

* perf: defer tool body rendering

* perf: queue deferred tool body mounts

* perf: quiet and defer markdown file probes

* perf: defer markdown code highlighting

* perf: stabilize markdown plugin lists

* perf: defer mermaid markdown rendering

* perf: delay markdown file reference annotation

* perf: attach markdown table listeners on demand

* perf: trim markdown render overhead
This commit is contained in:
Bohdan Triapitsyn
2026-06-15 03:29:40 +03:00
committed by GitHub
parent e372c8d8cb
commit a45376d585
24 changed files with 621 additions and 671 deletions
@@ -1,6 +1,5 @@
import React from 'react';
import type { AnimationPlaybackControls } from 'motion';
import { RuntimeAPIContext } from '@/contexts/runtimeAPIContext';
import { PatchDiff } from '@pierre/diffs/react';
import { cn } from '@/lib/utils';
@@ -201,29 +200,59 @@ const LiveDuration: React.FC<{ start: number; end?: number; active: boolean }> =
return <>{formatDuration(start, end, now)}</>;
};
const EXPANDED_CONTENT_TRANSITION_MS = 0;
const deferredToolBodyMounts: Array<{ active: boolean; fn: () => void }> = [];
let deferredToolBodyFrame: number | undefined;
const useAnimatedExpandedContent = (isExpanded: boolean) => {
const [shouldRender, setShouldRender] = React.useState(isExpanded);
const flushDeferredToolBodyMounts = () => {
while (deferredToolBodyMounts.length > 0) {
const item = deferredToolBodyMounts.pop();
if (!item) {
break;
}
if (item.active) {
item.fn();
deferredToolBodyFrame = deferredToolBodyMounts.length > 0
? window.requestAnimationFrame(flushDeferredToolBodyMounts)
: undefined;
return;
}
}
deferredToolBodyFrame = undefined;
};
const scheduleDeferredToolBodyMount = (fn: () => void) => {
if (typeof window === 'undefined') {
fn();
return () => undefined;
}
const item = { active: true, fn };
deferredToolBodyMounts.push(item);
if (deferredToolBodyFrame === undefined) {
deferredToolBodyFrame = window.requestAnimationFrame(() => {
deferredToolBodyFrame = window.requestAnimationFrame(flushDeferredToolBodyMounts);
});
}
return () => {
item.active = false;
};
};
const useDeferredExpandedContent = (isExpanded: boolean) => {
const [shouldRender, setShouldRender] = React.useState(false);
React.useEffect(() => {
if (typeof window === 'undefined') {
setShouldRender(isExpanded);
return;
}
if (isExpanded) {
setShouldRender(true);
return;
}
const timer = window.setTimeout(() => {
if (!isExpanded) {
setShouldRender(false);
}, EXPANDED_CONTENT_TRANSITION_MS);
return;
}
return () => {
window.clearTimeout(timer);
};
return scheduleDeferredToolBodyMount(() => {
setShouldRender(true);
});
}, [isExpanded]);
return shouldRender;
@@ -1573,6 +1602,7 @@ interface ToolExpandedContentProps {
state: ToolStateUnion;
syntaxTheme: { [key: string]: React.CSSProperties };
currentDirectory: string;
isExpanded: boolean;
onShowPopup?: (content: ToolPopupContent) => void;
}
@@ -1581,6 +1611,7 @@ const ToolExpandedContent: React.FC<ToolExpandedContentProps> = React.memo(({
state,
syntaxTheme,
currentDirectory,
isExpanded,
onShowPopup,
}) => {
const { t } = useI18n();
@@ -1873,7 +1904,7 @@ const ToolExpandedContent: React.FC<ToolExpandedContentProps> = React.memo(({
</blockquote>
),
{
maxHeightClass: 'max-h-60',
maxHeightClass: isWriteLikeTool && writeLikeInputPatch && isExpanded ? 'max-h-[50vh]' : 'max-h-60',
className: part.tool === 'bash' ? 'tool-input-surface p-0 rounded-none' : 'tool-input-surface',
}
)}
@@ -1979,8 +2010,6 @@ const ToolPartContent: React.FC<ToolPartProps> = ({
const onContentChangeRef = React.useRef(onContentChange);
onContentChangeRef.current = onContentChange;
const expandedContentRef = React.useRef<HTMLDivElement>(null);
const expandedContentAnimationRef = React.useRef<AnimationPlaybackControls | null>(null);
const expandedContentMountedRef = React.useRef(false);
React.useLayoutEffect(() => {
if (isTaskTool) {
@@ -1992,9 +2021,6 @@ const ToolPartContent: React.FC<ToolPartProps> = ({
return;
}
expandedContentMountedRef.current = true;
expandedContentAnimationRef.current?.stop();
expandedContentAnimationRef.current = null;
element.style.height = isExpanded ? 'auto' : '0px';
element.style.overflow = isExpanded ? 'visible' : 'hidden';
@@ -2003,13 +2029,6 @@ const ToolPartContent: React.FC<ToolPartProps> = ({
}
}, [isExpanded, isTaskTool, shouldNotifyStructuralChange]);
React.useEffect(() => {
return () => {
expandedContentAnimationRef.current?.stop();
expandedContentAnimationRef.current = null;
};
}, []);
const stateWithData = state as ToolStateWithMetadata;
const metadata = stateWithData.metadata;
const partMetadata = (part as unknown as { metadata?: unknown }).metadata;
@@ -2662,7 +2681,8 @@ const ToolPartContent: React.FC<ToolPartProps> = ({
const iconStyle = !isTaskTool && isError ? TOOL_ERROR_ICON_STYLE : TOOL_NORMAL_ICON_STYLE;
const titleStyle = !isTaskTool && isError ? TOOL_ERROR_TITLE_STYLE : TOOL_NORMAL_TITLE_STYLE;
const shouldRenderExpandedContent = useAnimatedExpandedContent(isExpanded);
const shouldRenderTaskSummary = useDeferredExpandedContent(isTaskTool && (taskSummaryEntries.length > 0 || isActive || shouldTreatAsFinalized || !!taskSessionId));
const shouldRenderExpandedContent = useDeferredExpandedContent(!isTaskTool && isExpanded);
if (!shouldTreatAsFinalized && !isActive && !isTaskTool) {
return null;
@@ -2795,7 +2815,7 @@ const ToolPartContent: React.FC<ToolPartProps> = ({
</div>
{}
{isTaskTool && (taskSummaryEntries.length > 0 || isActive || shouldTreatAsFinalized || taskSessionId) ? (
{shouldRenderTaskSummary ? (
<TaskToolSummary
entries={taskSummaryEntries}
isExpanded={isExpanded}
@@ -2822,10 +2842,6 @@ const ToolPartContent: React.FC<ToolPartProps> = ({
{shouldRenderExpandedContent ? (
<div
className="relative ml-2 pl-3"
style={{
opacity: isExpanded ? 1 : 0,
transform: isExpanded ? 'translateY(0)' : 'translateY(-4px)',
}}
>
<span
aria-hidden="true"
@@ -2837,6 +2853,7 @@ const ToolPartContent: React.FC<ToolPartProps> = ({
state={state}
syntaxTheme={syntaxTheme}
currentDirectory={currentDirectory}
isExpanded={isExpanded}
onShowPopup={onShowPopup}
/>
</div>
@@ -272,7 +272,8 @@ const UserTextPart: React.FC<UserTextPartProps> = ({ part, messageId, agentMenti
"[&_[data-component='markdown-code']_code]:inline",
]
)}
disableLinkSafety
disableLinkSafety
enableFileReferences={false}
/>
) : (
plainTextContent
@@ -3,14 +3,14 @@
*
* Replaces per-line <SyntaxHighlighter> with:
* 1. ONE Prism.highlight() call to tokenize all code at once
* 2. @tanstack/react-virtual to only render visible rows
* 2. virtua to only render visible rows
*
* This drops mount cost from O(N * Prism) to O(1 * Prism) + O(visible_rows).
* For a 2000-line file, ~2000 SyntaxHighlighter instances → ~30 plain <div>s.
*/
import React from 'react';
import { useVirtualizer } from '@tanstack/react-virtual';
import { Virtualizer } from 'virtua';
import Prism from 'prismjs';
// Ensure common languages are loaded (react-syntax-highlighter lazy-loads them,
@@ -244,52 +244,31 @@ const VirtualizedRows: React.FC<VirtualizedRowsProps> = React.memo(({
lineStyles,
}) => {
const parentRef = React.useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: lines.length,
getScrollElement: () => parentRef.current,
estimateSize: () => ROW_HEIGHT,
overscan: 20, // render 20 extra rows above/below viewport
});
const viewportHeight = `min(${lines.length * ROW_HEIGHT}px, ${maxHeight})`;
return (
<div
ref={parentRef}
className="typography-code font-mono w-full min-w-0 oc-virtualized-prism"
style={{ maxHeight, overflow: 'auto' }}
style={{ height: viewportHeight, maxHeight, overflow: 'auto' }}
>
{prismThemeCss ? <style>{prismThemeCss}</style> : null}
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
width: '100%',
position: 'relative',
}}
<Virtualizer
data={lines}
itemSize={ROW_HEIGHT}
bufferSize={ROW_HEIGHT * 20}
scrollRef={parentRef}
>
{virtualizer.getVirtualItems().map((vItem) => {
const line = lines[vItem.index];
return (
<div
key={vItem.index}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${vItem.size}px`,
transform: `translateY(${vItem.start}px)`,
}}
>
<Row
line={line}
language={language}
showLineNumbers={showLineNumbers}
style={lineStyles?.(line)}
/>
</div>
);
})}
</div>
{(line, index) => (
<Row
key={index}
line={line}
language={language}
showLineNumbers={showLineNumbers}
style={lineStyles?.(line)}
/>
)}
</Virtualizer>
</div>
);
});