Home subscribed to the whole streamingReply object, so each animation frame of an answer re-ran the page (header, picker, composer, overlays) and the desktop natal panel under it. ChatStreamingTranscript now subscribes for its session and feeds ChatTranscript; Home reads one derived boolean for the stop label. ChatChartSplit is memoized. 100 frames: Home <=2 commits (was >=100), panel 0. Two source-contract tests now pin the new component instead of the old names. The onboarding typewriter still ticks at Home level (out of this ticket). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
38 lines
1.5 KiB
TypeScript
38 lines
1.5 KiB
TypeScript
"use client";
|
|
|
|
import { ChatTranscript, type ChatTranscriptProps } from "@/components/chat-transcript";
|
|
import type { StreamingReply } from "@/lib/home-types";
|
|
import { useSessionStore } from "@/stores/session-store";
|
|
|
|
type StreamingFields = "streamingText" | "streamingActivity" | "streamingThinking" | "streamingSections" | "streamingTimeline" | "streamingResponseKind";
|
|
|
|
function replyFor(state: { streamingReply: StreamingReply | null }, sessionId: string | undefined) {
|
|
const reply = state.streamingReply;
|
|
return reply && reply.sessionId === sessionId ? reply : null;
|
|
}
|
|
|
|
/**
|
|
* The transcript is the only subscriber to the streaming reply (issue #123).
|
|
* The stream commits once per animation frame; when Home subscribed, every
|
|
* frame re-rendered the whole page and the desktop chart panel with it.
|
|
*/
|
|
export function ChatStreamingTranscript(props: Omit<ChatTranscriptProps, StreamingFields>) {
|
|
const reply = useSessionStore((state) => replyFor(state, props.sessionId));
|
|
return (
|
|
<ChatTranscript
|
|
{...props}
|
|
streamingText={reply?.text ?? ""}
|
|
streamingActivity={reply?.activity}
|
|
streamingThinking={reply?.thinkingText}
|
|
streamingSections={reply?.thinkingSections}
|
|
streamingTimeline={reply?.timeline}
|
|
streamingResponseKind={reply?.responseKind}
|
|
/>
|
|
);
|
|
}
|
|
|
|
/** Whether the active reply has text yet; flips at most twice per answer, so Home can read it. */
|
|
export function useStreamingReplyHasText(sessionId: string | undefined) {
|
|
return useSessionStore((state) => Boolean(replyFor(state, sessionId)?.text));
|
|
}
|