diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..519dd03 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,4 @@ + +## 2024-05-24 - Deferring State for Streaming Text Calculations +**Learning:** When dealing with rapidly updating text streams (e.g., streaming LLM output character-by-character at intervals of ~24ms), running expensive synchronous computations (like text analysis/scoring) on the active stream value blocks the main UI thread and causes lag/stuttering. +**Action:** Always use React's `useDeferredValue` to decouple the rapidly updating UI state from the state passed into expensive `useMemo` or synchronous operations. This allows React to prioritize rendering the stream while running the heavy calculations in the background. diff --git a/apps/quill/client/components/playground-view.tsx b/apps/quill/client/components/playground-view.tsx index 511226b..b06a6c2 100644 --- a/apps/quill/client/components/playground-view.tsx +++ b/apps/quill/client/components/playground-view.tsx @@ -1,6 +1,6 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { AnimatePresence, motion } from "framer-motion"; -import { useEffect, useMemo, useRef, useState } from "react"; +import { useDeferredValue, useEffect, useMemo, useRef, useState } from "react"; import { USE_CASE_PRESETS } from "../../src/lib/presets"; import { analyzeText, scoreDeterministic } from "../../src/lib/rubric"; import type { Guide, UseCase } from "../../src/lib/types"; @@ -255,7 +255,14 @@ export function PlaygroundView({ }; }, [output]); - const snapshot = useMemo(() => analyzeText(visibleOutput), [visibleOutput]); + // Defer the rapidly updating text stream so that expensive analysis doesn't + // block the main thread. + const deferredVisibleOutput = useDeferredValue(visibleOutput); + + const snapshot = useMemo( + () => analyzeText(deferredVisibleOutput), + [deferredVisibleOutput] + ); const { score, details } = useMemo( () => guide