fix: drop stale chat memo boundaries

Remove remaining chat row and body memo comparators that were still keeping finished background session responses blank or stale in the UI.
This commit is contained in:
Bohdan Triapitsyn
2026-04-07 21:48:56 +03:00
parent 952132a08d
commit 46d166e205
3 changed files with 10 additions and 189 deletions
@@ -30,7 +30,6 @@ import type { TurnGroupingContext } from './lib/turns/types';
import { copyTextToClipboard } from '@/lib/clipboard';
import { FadeInOnReveal } from './message/FadeInOnReveal';
import { streamPerfCount } from '@/stores/utils/streamDebug';
import { areOptionalRenderRelevantMessagesEqual, areRelevantTurnGroupingContextsEqual, areRenderRelevantMessageInfoEqual, areRenderRelevantPartsEqual } from './message/renderCompare';
const ToolOutputDialog = React.lazy(() => import('./message/ToolOutputDialog'));
@@ -1128,18 +1127,4 @@ const ChatMessage: React.FC<ChatMessageProps> = ({
);
};
export default React.memo(ChatMessage, (prev, next) => {
const prevRole = deriveMessageRole(prev.message.info);
return areRenderRelevantMessageInfoEqual(prev.message.info, next.message.info)
&& areRenderRelevantPartsEqual(prev.message.parts, next.message.parts)
&& areOptionalRenderRelevantMessagesEqual(prev.previousMessage, next.previousMessage)
&& areOptionalRenderRelevantMessagesEqual(prev.nextMessage, next.nextMessage)
&& prev.onContentChange === next.onContentChange
&& areRelevantTurnGroupingContextsEqual(prev.turnGroupingContext, next.turnGroupingContext, prev.message.info.id, prevRole.isUser)
&& prev.assistantHeaderMessageId === next.assistantHeaderMessageId
&& prev.isInActiveTurn === next.isInActiveTurn
&& prev.activeStreamingPhase === next.activeStreamingPhase
&& prev.animateUserOnMount === next.animateUserOnMount
&& prev.onUserAnimationConsumed === next.onUserAnimationConsumed;
});
export default ChatMessage;
+8 -138
View File
@@ -797,7 +797,7 @@ interface UngroupedMessageRowProps {
activeStreamingPhase?: StreamPhase | null;
}
const UngroupedMessageRow: React.FC<UngroupedMessageRowProps> = React.memo(({
const UngroupedMessageRow: React.FC<UngroupedMessageRowProps> = ({
message,
previousMessage,
nextMessage,
@@ -823,18 +823,7 @@ const UngroupedMessageRow: React.FC<UngroupedMessageRowProps> = React.memo(({
activeStreamingPhase={message.info.id === activeStreamingMessageId ? activeStreamingPhase : null}
/>
);
}, (prev, next) => {
return areRenderRelevantMessagesEqual(prev.message, next.message)
&& areOptionalRenderRelevantMessagesEqual(prev.previousMessage, next.previousMessage)
&& areOptionalRenderRelevantMessagesEqual(prev.nextMessage, next.nextMessage)
&& prev.onMessageContentChange === next.onMessageContentChange
&& prev.getAnimationHandlers === next.getAnimationHandlers
&& prev.scrollToBottom === next.scrollToBottom
&& prev.shouldAnimateUserMessage === next.shouldAnimateUserMessage
&& prev.onUserAnimationConsumed === next.onUserAnimationConsumed
&& prev.activeStreamingMessageId === next.activeStreamingMessageId
&& prev.activeStreamingPhase === next.activeStreamingPhase;
});
};
UngroupedMessageRow.displayName = 'UngroupedMessageRow';
@@ -867,7 +856,7 @@ const turnContainsMessageId = (turn: TurnRecord, messageId: string | null | unde
return turn.assistantMessages.some((assistant) => assistant.info.id === messageId);
};
const MessageListEntry: React.FC<MessageListEntryProps> = React.memo(({
const MessageListEntry: React.FC<MessageListEntryProps> = ({
entry,
onMessageContentChange,
getAnimationHandlers,
@@ -919,96 +908,10 @@ const MessageListEntry: React.FC<MessageListEntryProps> = React.memo(({
stickyUserHeader={stickyUserHeader}
/>
);
}, areMessageListEntryPropsEqual);
};
MessageListEntry.displayName = 'MessageListEntry';
function areMessageListEntryPropsEqual(prevProps: MessageListEntryProps, nextProps: MessageListEntryProps): boolean {
if (prevProps.stickyUserHeader !== nextProps.stickyUserHeader) return false;
if (prevProps.chatRenderMode !== nextProps.chatRenderMode) return false;
if (prevProps.shouldAnimateUserMessage !== nextProps.shouldAnimateUserMessage) return false;
const prevEntry = prevProps.entry;
const nextEntry = nextProps.entry;
if (prevEntry.kind !== nextEntry.kind) return false;
if (prevEntry.key !== nextEntry.key) return false;
if (prevEntry.kind === 'turn' && nextEntry.kind === 'turn') {
if (prevEntry.turn !== nextEntry.turn || prevEntry.isLastTurn !== nextEntry.isLastTurn) {
return false;
}
const prevExpanded = (prevProps.turnUiStates.get(prevEntry.turn.turnId) ?? { isExpanded: prevProps.defaultActivityExpanded }).isExpanded;
const nextExpanded = (nextProps.turnUiStates.get(nextEntry.turn.turnId) ?? { isExpanded: nextProps.defaultActivityExpanded }).isExpanded;
if (prevExpanded !== nextExpanded) {
return false;
}
if (prevEntry.isLastTurn && prevProps.sessionIsWorking !== nextProps.sessionIsWorking) {
return false;
}
if (prevProps.activeStreamingMessageId !== nextProps.activeStreamingMessageId) {
const prevAffected = turnContainsMessageId(prevEntry.turn, prevProps.activeStreamingMessageId);
const nextAffected = turnContainsMessageId(nextEntry.turn, nextProps.activeStreamingMessageId);
if (prevAffected || nextAffected) {
return false;
}
}
if (prevProps.activeStreamingPhase !== nextProps.activeStreamingPhase) {
const prevAffected = turnContainsMessageId(prevEntry.turn, prevProps.activeStreamingMessageId);
const nextAffected = turnContainsMessageId(nextEntry.turn, nextProps.activeStreamingMessageId);
if (prevAffected || nextAffected) {
return false;
}
}
if (prevProps.activeStreamingPhase !== nextProps.activeStreamingPhase) {
const prevAffected = turnContainsMessageId(prevEntry.turn, prevProps.activeStreamingMessageId);
const nextAffected = turnContainsMessageId(nextEntry.turn, nextProps.activeStreamingMessageId);
if (prevAffected || nextAffected) {
return false;
}
}
return true;
}
if (prevEntry.kind === 'ungrouped' && nextEntry.kind === 'ungrouped') {
if (prevProps.activeStreamingMessageId !== nextProps.activeStreamingMessageId) {
const messageId = prevEntry.message.info.id;
const prevActive = prevProps.activeStreamingMessageId === messageId;
const nextActive = nextProps.activeStreamingMessageId === messageId;
if (prevActive !== nextActive) {
return false;
}
}
if (prevProps.activeStreamingPhase !== nextProps.activeStreamingPhase) {
const messageId = prevEntry.message.info.id;
if (prevProps.activeStreamingMessageId === messageId || nextProps.activeStreamingMessageId === messageId) {
return false;
}
}
if (prevProps.activeStreamingPhase !== nextProps.activeStreamingPhase) {
const messageId = prevEntry.message.info.id;
if (prevProps.activeStreamingMessageId === messageId || nextProps.activeStreamingMessageId === messageId) {
return false;
}
}
return (
areRenderRelevantMessagesEqual(prevEntry.message, nextEntry.message)
&& areOptionalRenderRelevantMessagesEqual(prevEntry.previousMessage, nextEntry.previousMessage)
&& areOptionalRenderRelevantMessagesEqual(prevEntry.nextMessage, nextEntry.nextMessage)
);
}
return false;
}
// Inner component that renders staged turn entries.
const StaticHistoryList: React.FC<{
entries: RenderEntry[];
@@ -1028,7 +931,7 @@ const StaticHistoryList: React.FC<{
shouldAnimateUserMessage: (message: ChatMessageEntry) => boolean;
onUserAnimationConsumed: (messageId: string) => void;
activeStreamingPhase?: StreamPhase | null;
}> = React.memo(({ entries, shouldVirtualize, virtualRows, totalSize, measureElement, contentRef, onMessageContentChange, getAnimationHandlers, scrollToBottom, stickyUserHeader, defaultActivityExpanded, turnUiStates, onToggleTurnGroup, chatRenderMode, shouldAnimateUserMessage, onUserAnimationConsumed, activeStreamingPhase }) => {
}> = ({ entries, shouldVirtualize, virtualRows, totalSize, measureElement, contentRef, onMessageContentChange, getAnimationHandlers, scrollToBottom, stickyUserHeader, defaultActivityExpanded, turnUiStates, onToggleTurnGroup, chatRenderMode, shouldAnimateUserMessage, onUserAnimationConsumed, activeStreamingPhase }) => {
const renderEntry = React.useCallback((entry: RenderEntry) => {
return (
<MessageListEntry
@@ -1096,25 +999,7 @@ const StaticHistoryList: React.FC<{
{paddingBottom > 0 ? <div aria-hidden="true" style={{ height: `${paddingBottom}px` }} /> : null}
</div>
);
}, (prevProps, nextProps) => {
return prevProps.entries === nextProps.entries
&& prevProps.shouldVirtualize === nextProps.shouldVirtualize
&& prevProps.virtualRows === nextProps.virtualRows
&& prevProps.totalSize === nextProps.totalSize
&& prevProps.measureElement === nextProps.measureElement
&& prevProps.contentRef === nextProps.contentRef
&& prevProps.onMessageContentChange === nextProps.onMessageContentChange
&& prevProps.getAnimationHandlers === nextProps.getAnimationHandlers
&& prevProps.scrollToBottom === nextProps.scrollToBottom
&& prevProps.stickyUserHeader === nextProps.stickyUserHeader
&& prevProps.defaultActivityExpanded === nextProps.defaultActivityExpanded
&& prevProps.turnUiStates === nextProps.turnUiStates
&& prevProps.onToggleTurnGroup === nextProps.onToggleTurnGroup
&& prevProps.chatRenderMode === nextProps.chatRenderMode
&& prevProps.shouldAnimateUserMessage === nextProps.shouldAnimateUserMessage
&& prevProps.onUserAnimationConsumed === nextProps.onUserAnimationConsumed
&& prevProps.activeStreamingPhase === nextProps.activeStreamingPhase;
});
};
StaticHistoryList.displayName = 'StaticHistoryList';
@@ -1133,7 +1018,7 @@ const StreamingTailContent: React.FC<{
onUserAnimationConsumed: (messageId: string) => void;
activeStreamingMessageId?: string | null;
activeStreamingPhase?: StreamPhase | null;
}> = React.memo(({
}> = ({
entry,
onMessageContentChange,
getAnimationHandlers,
@@ -1167,22 +1052,7 @@ const StreamingTailContent: React.FC<{
activeStreamingPhase={activeStreamingPhase}
/>
);
}, (prev, next) => {
return prev.entry === next.entry
&& prev.onMessageContentChange === next.onMessageContentChange
&& prev.getAnimationHandlers === next.getAnimationHandlers
&& prev.scrollToBottom === next.scrollToBottom
&& prev.stickyUserHeader === next.stickyUserHeader
&& prev.sessionIsWorking === next.sessionIsWorking
&& prev.defaultActivityExpanded === next.defaultActivityExpanded
&& prev.turnUiStates === next.turnUiStates
&& prev.onToggleTurnGroup === next.onToggleTurnGroup
&& prev.chatRenderMode === next.chatRenderMode
&& prev.shouldAnimateUserMessage === next.shouldAnimateUserMessage
&& prev.onUserAnimationConsumed === next.onUserAnimationConsumed
&& prev.activeStreamingMessageId === next.activeStreamingMessageId
&& prev.activeStreamingPhase === next.activeStreamingPhase;
});
};
StreamingTailContent.displayName = 'StreamingTailContent';
@@ -35,7 +35,6 @@ import { ToolRevealOnMount } from './parts/ToolRevealOnMount';
import { StaticToolRow } from './parts/ProgressiveGroup';
import { isExpandableTool, isStandaloneTool } from './parts/toolRenderUtils';
import TurnActivity from '../components/TurnActivity';
import { areRenderRelevantPartsEqual } from './renderCompare';
type SubtaskPartLike = Part & {
type: 'subtask';
@@ -1549,37 +1548,4 @@ const MessageBody: React.FC<MessageBodyProps> = ({ isUser, ...props }) => {
return <AssistantMessageBody {...props} />;
};
export default React.memo(MessageBody, (prev, next) => {
return prev.sessionId === next.sessionId
&& prev.messageId === next.messageId
&& prev.isUser === next.isUser
&& areRenderRelevantPartsEqual(prev.parts, next.parts)
&& prev.isMessageCompleted === next.isMessageCompleted
&& prev.messageFinish === next.messageFinish
&& prev.messageCompletedAt === next.messageCompletedAt
&& prev.messageCreatedAt === next.messageCreatedAt
&& prev.syntaxTheme === next.syntaxTheme
&& prev.isMobile === next.isMobile
&& prev.hasTouchInput === next.hasTouchInput
&& prev.copiedCode === next.copiedCode
&& prev.expandedTools === next.expandedTools
&& prev.streamPhase === next.streamPhase
&& prev.allowAnimation === next.allowAnimation
&& prev.shouldShowHeader === next.shouldShowHeader
&& prev.hasTextContent === next.hasTextContent
&& prev.copiedMessage === next.copiedMessage
&& prev.showReasoningTraces === next.showReasoningTraces
&& prev.agentMention === next.agentMention
&& prev.turnGroupingContext === next.turnGroupingContext
&& prev.errorMessage === next.errorMessage
&& prev.userActionsMode === next.userActionsMode
&& prev.stickyUserHeaderEnabled === next.stickyUserHeaderEnabled
&& prev.onCopyCode === next.onCopyCode
&& prev.onToggleTool === next.onToggleTool
&& prev.onShowPopup === next.onShowPopup
&& prev.onContentChange === next.onContentChange
&& prev.onCopyMessage === next.onCopyMessage
&& prev.onAuxiliaryContentComplete === next.onAuxiliaryContentComplete
&& prev.onRevert === next.onRevert
&& prev.onFork === next.onFork;
});
export default MessageBody;