Repository navigation
fix(web): streamed replies render a list's code block as the finished reply does - #28285
Conversation
… its closing fence is dedented The streaming splitter closed a fence opened inside a list item on any later fence marker, whatever its indentation. A line left of the item's content ends the item and its code block, so the streamed blocks and the finished reply rendered differently. The splitter now stops cutting at such a line and renders the rest of the reply as one block. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — Streamed replies keep an indented code fence and subsequent content together when indentation makes further splitting ambiguous. The change is limited to streaming presentation, without altering stored messages or finished-reply rendering. You can add or adjust custom eligibility rules. Learn more. |
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Track fence indentation during splitting apps/web/src/components/chat/markdownStreamBlocks.ts, apps/web/src/components/chat/markdownStreamBlocks.test.tsx |
The splitter stores the fence opener’s starting column and checks in-fence lines against it. The tests add streamed-prefix inputs for list-item fences. |
Estimated code review effort: 2 (Simple) | ~8 minutes
Change: Bug fix
Possibly related PRs
- pingdotgg/t3code#27417: Introduced
splitMarkdownBlocksand the streaming path that renders completed Markdown blocks. - pingdotgg/t3code#27480: Introduced the cached web Markdown renderer and retained the streaming block-selection path.
Suggested labels: bugfix, size:XS
Suggested reviewers: juliusmarminge
Merge Risk: 
Minimal · up to 684de
-
No actionable merge-blocking risk remains from the reviewed change.
Pre-merge checks: 
4
-
Full details: Title check
-
Explanation
The title names the main change: streamed replies should render a list item's code block the same way the finished reply does. It uses the conventional commit format and matches the splitter fix and the new test rows. It is specific and concise, so it passes.
Full details: Description check
-
Explanation
The pull request description includes all required sections: Problem, Change, Scope and approval, and Verification. It explains the bug, implementation, scope rationale, focused tests, fuzz testing, browser results, limitations, and agent usage.
-
Finishing Touches
-
Generate unit tests (beta)
-
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
|
Note Written by Verified in a real dev environmentre-verified unchanged head the same prompt produced byte-for-byte identical saved assistant text, 8,491 bytes. on main, "Run tests." rendered as a paragraph while streaming and moved into the second code block at completion. on the pr it was already inside that code block while streaming and remained there at completion. the condensed recording shows both transitions with the app's working timers visible. also exercised a normally closed top-level javascript fence, a normally closed list fence, and dedented text followed by an indented fence through another real codex turn. code and following paragraphs stayed in their expected blocks. in the dedent case,
before, main streaming: after, pr streaming: Not verifiednative desktop, ios, and android clients; macos and windows. only linux chrome was exercised. other providers and remote relay or tunnel connections were not exercised. list fences indented four or more spaces are a separate pre-existing case outside this change. no performance claim is made. |




Problem
While a reply streams, a code block inside a list item can render differently from the finished reply, and the message jumps when the turn ends. For example, a model writes a list item with an indented fence, then puts the closing
```at column 0:A line left of the list item's content ends the item and its code block, so the column-0 fence opens a new code block, and "Run tests." renders inside it. That is how the finished reply renders. While streaming,
splitMarkdownBlocks(#27417) treats the column-0 fence as the closer, cuts after it, and renders an empty code block followed by a plain paragraph. This breaks the module's own rule that each piece "renders exactly as it would inside the whole text".Change
The splitter now records how far the open fence's opener is indented. Inside that fence, a non-blank line further left may have ended the list item holding it, which the splitter does not track. At that line it stops cutting, and the rest of the reply renders as one block. Earlier blocks stay split. The same check also covers dedented text followed by a correctly indented closer: the dedented text ends the item, and the check stops there.
A top-level fence indented 1 to 3 spaces with dedented content also stops splitting. That case renders correctly; the only cost is that it is no longer split while streaming.
Scope and approval
A focused fix of an obvious bug, so no prior issue or discussion: the streamed render breaks an invariant that
markdownStreamBlocks.tsdocuments and that its existing test checks. It adds 4 lines to the splitter and 2 rows to its existing test table, and changes nothing else.Verification
markdownStreamBlocks.test.tsx: a dedented closer, and dedented text followed by an indented closer. Both fail onmainand pass with the fix:vp test run src/components/chat/markdownStreamBlocks.test.tsx(17 passed).vp run check:changed: format, lint, knip, typecheck, tests and React compiler pass.Before, while streaming: the second code block is empty and "Run tests." is a paragraph.
Before, finished: "Run tests." moves into the second code block.
After, while streaming: "Run tests." is already inside the second code block.
After, finished: unchanged from the streaming render.
The blank lines inside the second block come from the replay fixture's trailing newlines.
Not checked:
Prior PRs
None for this fix. #27417 introduced the splitter and its equivalence test.
Generated with Claude Opus 5.5 and GPT-6.1 Terra in T3 Code Nightly