perf: reduce active-turn chat rerenders during streaming

- Keep older assistant messages in the active turn stable while new content streams
- Prevent existing tool rows from rerendering when new tool activity is appended
- Tighten message and tool memo comparisons to isolate render work to changed rows
This commit is contained in:
Bohdan Triapitsyn
2026-04-06 00:43:19 +03:00
parent 4a3b7ec5c9
commit 4db1e6baad
5 changed files with 365 additions and 66 deletions
@@ -3,7 +3,7 @@ import type { Part } from '@opencode-ai/sdk/v2';
import { measureElement as measureVirtualElement, type VirtualItem, useVirtualizer } from '@tanstack/react-virtual';
import ChatMessage from './ChatMessage';
import { areOptionalRenderRelevantMessagesEqual, areRenderRelevantMessagesEqual } from './message/renderCompare';
import { areOptionalRenderRelevantMessagesEqual, areRelevantTurnGroupingContextsEqual, areRenderRelevantMessagesEqual } from './message/renderCompare';
import TurnItem from './components/TurnItem';
import type { AnimationHandlers, ContentChangeReason } from '@/hooks/useChatScrollManager';
import { filterSyntheticParts } from '@/lib/messages/synthetic';
@@ -480,15 +480,7 @@ const MessageRow = React.memo<MessageRowProps>(({
&& prev.onUserAnimationConsumed === next.onUserAnimationConsumed
&& prev.onContentChange === next.onContentChange
&& prev.scrollToBottom === next.scrollToBottom
&& prevTurn?.turnId === nextTurn?.turnId
&& prevTurn?.isFirstAssistantInTurn === nextTurn?.isFirstAssistantInTurn
&& prevTurn?.isLastAssistantInTurn === nextTurn?.isLastAssistantInTurn
&& prevTurn?.activityOwnerMessageId === nextTurn?.activityOwnerMessageId
&& prevTurn?.isWorking === nextTurn?.isWorking
&& prevTurn?.isGroupExpanded === nextTurn?.isGroupExpanded
&& prevTurn?.toggleGroup === nextTurn?.toggleGroup
&& prevTurn?.activityGroupSegments === nextTurn?.activityGroupSegments
&& prevTurn?.activityParts === nextTurn?.activityParts
&& areRelevantTurnGroupingContextsEqual(prevTurn, nextTurn, prev.message.info.id, resolveMessageRole(prev.message) === 'user')
&& prev.assistantHeaderMessageId === next.assistantHeaderMessageId
&& prev.isInActiveTurn === next.isInActiveTurn
&& prev.activeStreamingPhase === next.activeStreamingPhase