diff --git a/apps/web/src/components/chat/CommandOutputPanel.tsx b/apps/web/src/components/chat/CommandOutputPanel.tsx index bfa98583a843..fa28d83ba19a 100644 --- a/apps/web/src/components/chat/CommandOutputPanel.tsx +++ b/apps/web/src/components/chat/CommandOutputPanel.tsx @@ -1,6 +1,6 @@ import type { EnvironmentId, ThreadId, TurnItemId } from "@t3tools/contracts"; import { TERMINAL_PALETTES, terminalOutputSpans } from "@t3tools/shared/terminalOutput"; -import { useLayoutEffect, useMemo, useRef } from "react"; +import { useLayoutEffect, useRef } from "react"; import { useTheme } from "../../hooks/useTheme"; import { useServerConfigs } from "../../state/entities"; @@ -22,6 +22,13 @@ export function useCommandOutputStreaming(environmentId: EnvironmentId): boolean * shows its final output. Mount it only when `useCommandOutputStreaming` holds. * It subscribes only while mounted, so collapsed rows and other threads receive nothing. */ +// Parsing ANSI output is linear in its length, and the compiler would redo it on +// every render because the layout effect runs after it. +function useTerminalOutputSpans(text: string, resolvedTheme: keyof typeof TERMINAL_PALETTES) { + "use memo"; + return terminalOutputSpans(text, TERMINAL_PALETTES[resolvedTheme]); +} + export function CommandOutputPanel(props: { readonly environmentId: EnvironmentId; readonly threadId: ThreadId; @@ -38,10 +45,7 @@ export function CommandOutputPanel(props: { const followRef = useRef(true); const text = data?.output.text ?? ""; const { resolvedTheme } = useTheme(); - const spans = useMemo( - () => terminalOutputSpans(text, TERMINAL_PALETTES[resolvedTheme]), - [text, resolvedTheme], - ); + const spans = useTerminalOutputSpans(text, resolvedTheme); // This panel only re-renders when its output changes, so keep the end in view after each. useLayoutEffect(() => {