diff --git a/studio/frontend/src/components/assistant-ui/tool-ui-python.tsx b/studio/frontend/src/components/assistant-ui/tool-ui-python.tsx index e3f4a0aafe..1e816132b8 100644 --- a/studio/frontend/src/components/assistant-ui/tool-ui-python.tsx +++ b/studio/frontend/src/components/assistant-ui/tool-ui-python.tsx @@ -118,22 +118,63 @@ function DownloadBtn({ code, name = "script.py" }: { code: string; name?: string ); } -/** Syntax-highlighted code via Streamdown + shiki; inherits parent container. */ +/** Syntax-highlighted code via Streamdown + shiki; inherits parent container. + * The script is always in the DOM (a plain monospace placeholder), but shiki + * only tokenizes once the block scrolls near the viewport, so a long transcript + * with many scripts doesn't highlight every one up front. Falls back to + * immediate highlight when IntersectionObserver is unavailable (SSR / tests). */ function HighlightedCode({ code: source, language }: { code: string; language: string }) { + const display = useMemo(() => truncate(source), [source]); const markdown = useMemo( - () => `\`\`\`${language}\n${truncate(source)}\n\`\`\``, - [source, language], + () => `\`\`\`${language}\n${display}\n\`\`\``, + [display, language], ); + const containerRef = useRef(null); + const [highlight, setHighlight] = useState( + () => typeof IntersectionObserver === "undefined", + ); + useEffect(() => { + if (highlight) return; + const el = containerRef.current; + if (!el) return; + const io = new IntersectionObserver( + (entries) => { + if (entries.some((entry) => entry.isIntersecting)) { + setHighlight(true); + io.disconnect(); + } + }, + // Highlight just before the block enters view so it's colorized by the + // time the user reaches it, without tokenizing off-screen scripts. + { rootMargin: "200px" }, + ); + io.observe(el); + return () => io.disconnect(); + }, [highlight]); return ( -
- - {markdown} - +
+ {highlight ? ( + + {markdown} + + ) : ( + // A div, not a
: the container's [&_pre]:!p-0 would override a
+        // 
's padding and shift the content by p-3 when shiki swaps in. Keep
+        // the same p-3, and whitespace-pre (not pre-wrap) so long lines scroll in
+        // the container's overflow-auto exactly like the highlighted 
, rather
+        // than wrapping taller and then collapsing when shiki swaps in.
+        
+ {display} +
+ )}
); }