Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-05-27 - UI Blocking During Text Streaming
**Learning:** During simulated text generation loops (like `setInterval` firing every 24ms), synchronous deterministic scoring and analysis functions that depend on the rapidly updating text state can bottleneck the main thread, leading to jittery render loops or missed frames.
**Action:** Use React's `useDeferredValue` for rapidly updating text state before passing it into complex computations (like `analyzeText` or `scoreDeterministic` in `useMemo`). This allows React to process the updates asynchronously and interruptibly, maintaining UI responsiveness during simulated text streaming.
8 changes: 6 additions & 2 deletions apps/quill/client/components/playground-view.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -255,7 +255,11 @@ export function PlaygroundView({
};
}, [output]);

const snapshot = useMemo(() => analyzeText(visibleOutput), [visibleOutput]);
// Defer the expensive analyzeText and scoreDeterministic calculations to a lower
// priority render cycle. This prevents them from blocking the main UI thread during
// rapid state updates like text streaming (which happens every 24ms).
const deferredOutput = useDeferredValue(visibleOutput);
const snapshot = useMemo(() => analyzeText(deferredOutput), [deferredOutput]);
Comment on lines +261 to +262

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When using useDeferredValue to defer expensive computations during rapid state updates (like text streaming), the UI can display a temporary mismatch between the latest streamed text (visibleOutput) and the deferred analysis results (snapshot, score).

To prevent user confusion, it is a recommended UX best practice to visually indicate when the deferred calculations are stale (out-of-sync with the current text). You can derive this state by comparing the current and deferred values, and then use it to apply a visual treatment (such as reducing the opacity of the <RubricSnapshot /> container or showing a subtle loading indicator) while the background calculation is pending.

  const deferredOutput = useDeferredValue(visibleOutput);
  const isStale = visibleOutput !== deferredOutput;
  const snapshot = useMemo(() => analyzeText(deferredOutput), [deferredOutput]);

Comment on lines +261 to +262

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid showing stale rubric data during streams

Because the rubric is still rendered whenever visibleOutput.length > 0 below, switching the calculation to deferredOutput lets the UI show a non-empty output with a snapshot from the previous deferred value. This happens at the start of a first stream (zeros can be shown while text is already visible) and especially when running again after a previous result, where the old result's score/details can remain visible until the deferred render catches up. The snapshot display should be gated off the same deferred value or indicate it is stale while deferredOutput !== visibleOutput.

Useful? React with 👍 / 👎.

const { score, details } = useMemo(
() =>
guide
Expand Down
Loading