fix: stop stale chat renders after session switches
Roll back aggressive chat reuse paths that froze message parts, tool state, and completed assistant bodies when sessions were inactive or backgrounded.
This commit is contained in:
@@ -373,13 +373,6 @@ const getNormalizedMessageForDisplay = (message: ChatMessageEntry): ChatMessageE
|
||||
return normalized;
|
||||
};
|
||||
|
||||
const isAssistantTextOnlyMessage = (message: ChatMessageEntry): boolean => {
|
||||
if (resolveMessageRole(message) !== 'assistant') {
|
||||
return false;
|
||||
}
|
||||
return message.parts.length > 0 && message.parts.every((part) => part?.type === 'text');
|
||||
};
|
||||
|
||||
interface MessageListProps {
|
||||
sessionKey: string;
|
||||
turnStart: number;
|
||||
@@ -1222,19 +1215,6 @@ const MessageList = React.forwardRef<MessageListHandle, MessageListProps>(({
|
||||
previousOrder: string[];
|
||||
animatedIds: Set<string>;
|
||||
}>({ sessionKey: undefined, previousOrder: [], animatedIds: new Set() });
|
||||
const baseDisplayCacheRef = React.useRef<{
|
||||
input: ChatMessageEntry[];
|
||||
output: ChatMessageEntry[];
|
||||
outputIndexById: Map<string, number>;
|
||||
} | null>(null);
|
||||
const staticRenderEntriesCacheRef = React.useRef<{
|
||||
input: ChatMessageEntry[];
|
||||
output: RenderEntry[];
|
||||
staticTurns: TurnRecord[];
|
||||
lastTurnId: string | null;
|
||||
ungroupedMessageIds: Set<string>;
|
||||
} | null>(null);
|
||||
|
||||
const stableGetAnimationHandlers = useStableEvent(getAnimationHandlers);
|
||||
const stableOnLoadOlder = useStableEvent(onLoadOlder);
|
||||
const stableScrollToBottom = useStableEvent((options?: { instant?: boolean; force?: boolean }) => {
|
||||
@@ -1256,50 +1236,6 @@ const MessageList = React.forwardRef<MessageListHandle, MessageListProps>(({
|
||||
|
||||
|
||||
const baseDisplayMessages = React.useMemo(() => streamPerfMeasure('ui.message_list.base_display_ms', () => {
|
||||
const cached = baseDisplayCacheRef.current;
|
||||
const lastMessage = messages.length > 0 ? messages[messages.length - 1] : undefined;
|
||||
const canUseTailFastPath = Boolean(lastMessage && isAssistantTextOnlyMessage(lastMessage));
|
||||
|
||||
if (cached && canUseTailFastPath && cached.input.length === messages.length && messages.length > 0) {
|
||||
let changedCount = 0;
|
||||
let changedIndex = -1;
|
||||
let idsStable = true;
|
||||
|
||||
for (let index = 0; index < messages.length; index += 1) {
|
||||
if (messages[index]?.info?.id !== cached.input[index]?.info?.id) {
|
||||
idsStable = false;
|
||||
break;
|
||||
}
|
||||
if (messages[index] !== cached.input[index]) {
|
||||
changedCount += 1;
|
||||
changedIndex = index;
|
||||
if (changedCount > 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (idsStable && changedCount === 1 && changedIndex === messages.length - 1) {
|
||||
const changedMessage = messages[changedIndex];
|
||||
const previousMessage = changedIndex > 0 ? messages[changedIndex - 1] : undefined;
|
||||
const bridgeSensitive = isUserSubtaskMessage(previousMessage) || isUserShellMarkerMessage(previousMessage);
|
||||
|
||||
if (changedMessage && isAssistantTextOnlyMessage(changedMessage) && !bridgeSensitive) {
|
||||
const outputIndex = cached.outputIndexById.get(changedMessage.info.id);
|
||||
if (outputIndex !== undefined) {
|
||||
const nextOutput = [...cached.output];
|
||||
nextOutput[outputIndex] = getNormalizedMessageForDisplay(changedMessage);
|
||||
baseDisplayCacheRef.current = {
|
||||
input: messages,
|
||||
output: nextOutput,
|
||||
outputIndexById: cached.outputIndexById,
|
||||
};
|
||||
return nextOutput;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const seenIdsFromTail = new Set<string>();
|
||||
const dedupedMessages: ChatMessageEntry[] = [];
|
||||
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
||||
@@ -1344,19 +1280,6 @@ const MessageList = React.forwardRef<MessageListHandle, MessageListProps>(({
|
||||
output.push(currentWithRole);
|
||||
}
|
||||
|
||||
const outputIndexById = new Map<string, number>();
|
||||
output.forEach((message, index) => {
|
||||
const id = message.info?.id;
|
||||
if (typeof id === 'string' && id.length > 0) {
|
||||
outputIndexById.set(id, index);
|
||||
}
|
||||
});
|
||||
baseDisplayCacheRef.current = {
|
||||
input: messages,
|
||||
output,
|
||||
outputIndexById,
|
||||
};
|
||||
|
||||
return output;
|
||||
}), [messages]);
|
||||
|
||||
@@ -1386,48 +1309,6 @@ const MessageList = React.forwardRef<MessageListHandle, MessageListProps>(({
|
||||
showTextJustificationActivity: chatRenderMode === 'sorted',
|
||||
});
|
||||
const staticRenderEntries = React.useMemo<RenderEntry[]>(() => streamPerfMeasure('ui.message_list.render_entries_ms', () => {
|
||||
const cached = staticRenderEntriesCacheRef.current;
|
||||
const lastMessage = displayMessages.length > 0 ? displayMessages[displayMessages.length - 1] : undefined;
|
||||
const hasTrailingCandidate = Boolean(lastMessage) && (
|
||||
(streamingTurn
|
||||
? (streamingTurn.userMessage.info.id === lastMessage?.info.id
|
||||
|| streamingTurn.assistantMessages.some((assistant) => assistant.info.id === lastMessage?.info.id))
|
||||
: false)
|
||||
|| (lastMessage ? projection.ungroupedMessageIds.has(lastMessage.info.id) : false)
|
||||
);
|
||||
|
||||
if (
|
||||
cached
|
||||
&& hasTrailingCandidate
|
||||
&& cached.input.length === displayMessages.length
|
||||
&& cached.staticTurns === staticTurns
|
||||
&& cached.lastTurnId === projection.lastTurnId
|
||||
&& cached.ungroupedMessageIds === projection.ungroupedMessageIds
|
||||
&& displayMessages.length > 0
|
||||
) {
|
||||
let changedCount = 0;
|
||||
let changedIndex = -1;
|
||||
let idsStable = true;
|
||||
|
||||
for (let index = 0; index < displayMessages.length; index += 1) {
|
||||
if (displayMessages[index]?.info?.id !== cached.input[index]?.info?.id) {
|
||||
idsStable = false;
|
||||
break;
|
||||
}
|
||||
if (displayMessages[index] !== cached.input[index]) {
|
||||
changedCount += 1;
|
||||
changedIndex = index;
|
||||
if (changedCount > 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (idsStable && changedCount === 1 && changedIndex === displayMessages.length - 1) {
|
||||
return cached.output;
|
||||
}
|
||||
}
|
||||
|
||||
const turnEntries = staticTurns.map((turn) => ({
|
||||
kind: 'turn' as const,
|
||||
key: `turn:${turn.turnId}`,
|
||||
@@ -1465,16 +1346,8 @@ const MessageList = React.forwardRef<MessageListHandle, MessageListProps>(({
|
||||
});
|
||||
});
|
||||
|
||||
staticRenderEntriesCacheRef.current = {
|
||||
input: displayMessages,
|
||||
output: orderedEntries,
|
||||
staticTurns,
|
||||
lastTurnId: projection.lastTurnId,
|
||||
ungroupedMessageIds: projection.ungroupedMessageIds,
|
||||
};
|
||||
|
||||
return orderedEntries;
|
||||
}), [displayMessages, projection.lastTurnId, projection.ungroupedMessageIds, staticTurns, streamingTurn]);
|
||||
}), [displayMessages, projection.lastTurnId, projection.ungroupedMessageIds, staticTurns]);
|
||||
|
||||
const trailingStreamingEntry = React.useMemo<RenderEntry | undefined>(() => {
|
||||
if (streamingTurn) {
|
||||
|
||||
Reference in New Issue
Block a user