perf: reduce chat streaming rerenders

- Keep historical chat messages stable during streaming
- Isolate active stream state to the current message path
- Reduce chat container churn from sync message updates
This commit is contained in:
Bohdan Triapitsyn
2026-04-05 16:56:05 +03:00
parent e56f2f99cf
commit c88e68e5f7
4 changed files with 93 additions and 30 deletions
@@ -106,6 +106,7 @@ type ChatViewportProps = {
isLoadingOlder: boolean;
sessionIsWorking: boolean;
streamingMessageId: string | null;
activeStreamingPhase: import('./message/types').StreamPhase | null;
retryOverlay: {
sessionId: string;
message: string;
@@ -135,6 +136,7 @@ const ChatViewport = React.memo(({
isLoadingOlder,
sessionIsWorking,
streamingMessageId,
activeStreamingPhase,
retryOverlay,
handleMessageContentChange,
getAnimationHandlers,
@@ -173,6 +175,7 @@ const ChatViewport = React.memo(({
messages={renderedMessages}
sessionIsWorking={sessionIsWorking}
activeStreamingMessageId={streamingMessageId}
activeStreamingPhase={activeStreamingPhase}
retryOverlay={retryOverlay}
onMessageContentChange={handleMessageContentChange}
getAnimationHandlers={getAnimationHandlers}
@@ -218,6 +221,7 @@ const ChatViewport = React.memo(({
&& prev.isLoadingOlder === next.isLoadingOlder
&& prev.sessionIsWorking === next.sessionIsWorking
&& prev.streamingMessageId === next.streamingMessageId
&& prev.activeStreamingPhase === next.activeStreamingPhase
&& prev.retryOverlay === next.retryOverlay
&& prev.handleMessageContentChange === next.handleMessageContentChange
&& prev.getAnimationHandlers === next.getAnimationHandlers
@@ -296,6 +300,15 @@ export const ChatContainer: React.FC = () => {
[currentSessionId],
),
);
const activeStreamingPhase = useStreamingStore(
React.useCallback(
(s) => {
if (!streamingMessageId) return null;
return s.messageStreamStates.get(streamingMessageId)?.phase ?? null;
},
[streamingMessageId],
),
);
const sessionMessageCount = useSessionMessageCount(currentSessionId ?? '');
const [suspendDetachedTailUpdates, setSuspendDetachedTailUpdates] = React.useState(false);
// Messages from sync system
@@ -803,6 +816,7 @@ export const ChatContainer: React.FC = () => {
isLoadingOlder={timelineController.isLoadingOlder}
sessionIsWorking={sessionIsWorking}
streamingMessageId={streamingMessageId}
activeStreamingPhase={activeStreamingPhase}
retryOverlay={retryOverlay}
handleMessageContentChange={handleMessageContentChange}
getAnimationHandlers={getAnimationHandlers}