Skip to content

fix(web): streamed replies render a list's code block as the finished reply does - #28285

Merged
maria-rcks merged 1 commit into
pingdotgg:mainfrom
only21mil:fix/audit-m018
Nov 1, 2026
Merged

maria-rcks merged 1 commit into
pingdotgg:mainfrom
only21mil:fix/audit-m018

Conversation

@only21mil

@only21mil only21mil commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

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:

- Install package:

  ```
  vp i

```

Run tests.

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.ts documents 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

  • Added two rows to the existing every-prefix table in markdownStreamBlocks.test.tsx: a dedented closer, and dedented text followed by an indented closer. Both fail on main and pass with the fix: vp test run src/components/chat/markdownStreamBlocks.test.tsx (17 passed).
  • Fuzzed 45,000 random documents built from list, fence, indent and text lines, comparing the split render with the whole render. Mismatches went from 16 to 4, with no new ones. The 4 left are a separate, pre-existing case: a fence opener indented 4 or more spaces inside a list item, which the splitter never treats as a fence.
  • vp run check:changed: format, lint, knip, typecheck, tests and React compiler pass.
  • In the browser, with a Replay (dev) turn that streams the reply above:

Before, while streaming: the second code block is empty and "Run tests." is a paragraph.

Before, streaming

Before, finished: "Run tests." moves into the second code block.

Before, finished

After, while streaming: "Run tests." is already inside the second code block.

After, streaming

After, finished: unchanged from the streaming render.

After, finished

The blank lines inside the second block come from the replay fixture's trailing newlines.

Not checked:

  • Fences indented 4 or more spaces inside list items (the separate case above).
  • Desktop and mobile apps: desktop wraps the same web renderer, and mobile does not use this splitter.

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

… 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>
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XS 0-9 changed lines (additions + deletions). labels Oct 31, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 684de7e

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.

@coderabbitai

coderabbitai Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 15fe283 and 684de7e.

Files selected for processing: 2
  • apps/web/src/components/chat/markdownStreamBlocks.test.tsx
  • apps/web/src/components/chat/markdownStreamBlocks.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 0 remain after this review.

Walkthrough

splitMarkdownBlocks now tracks the indentation of a fence opener and returns null when an in-fence line contains non-whitespace text before that indentation. The streamed-prefix tests add two list-item cases with fenced code blocks.

Changes: Streamed Markdown fence handling

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 splitMarkdownBlocks and 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: Passed 4
Check name Status Explanation
Title check Passed 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 t…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description check Passed 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, b…
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

@maria-rcks

maria-rcks commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator

Note

Written by gpt-6.1-sol on behalf of Maria

Verified in a real dev environment

re-verified unchanged head 684de7e7b7ce23563e5a46ede5a6f77ad46bcbca against github/main at 955e2bfbb5 on linux. installed both checkouts with vp i, ran one background dev server at a time, and drove the actual web app with repository playwright and google chrome. created the project and threads through the ui and submitted real codex gpt-6.1-sol turns through the local proxy. the final comparison used separate isolated data directories, with no replay, mocks, fixtures, or injected messages.

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, y stayed a paragraph and b appeared in the next code block. checked those exact elements again after reloading.

blacksmith-check run --id <task-worker> 'cd apps/web && vp test run src/components/chat/markdownStreamBlocks.test.tsx': 17 passed. current github checks pass. no source changes or pushes were needed.

before, main streaming:

main streams Run tests as a paragraph

after, pr streaming:

pr streams Run tests inside the code block

real codex streaming to completion on main and the pr

normal fences and the second dedent case in the real client

Not verified

native 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.

@maria-rcks
maria-rcks merged commit d05e4f8 into pingdotgg:main Nov 1, 2026
32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XS 0-9 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants