Skip to content

perf(ui): render streamed markdown incrementally#11038

Merged
marius-kilocode merged 4 commits into
mainfrom
perf-renderer-self-test
Jun 9, 2026
Merged

perf(ui): render streamed markdown incrementally#11038
marius-kilocode merged 4 commits into
mainfrom
perf-renderer-self-test

Conversation

@marius-kilocode

@marius-kilocode marius-kilocode commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Long streamed responses repeatedly parsed, sanitized, rebuilt, and diffed the entire accumulated Markdown document for every visible update. The cost grows with response length even though earlier top-level blocks are already complete and cannot change.

This change splits streaming Markdown into stable completed blocks and one mutable tail. Completed blocks retain stable cache keys and mounted DOM nodes, while only the active tail is reparsed and replaced. Reference-style Markdown and unfinished fenced code retain the conservative existing path, and completion still performs the canonical full render. Sanitization, syntax highlighting, Mermaid rendering, copy controls, and streaming cadence remain unchanged.

The DOM implementation and block splitting live in Kilo-owned files. Shared upstream files contain only the narrow hooks needed to return block metadata and invoke the incremental renderer, with kilocode_change annotations on every changed line.

Why this is worth doing

Markdown rendering becomes more important as responses and sessions grow. With the original renderer, every streamed update revisits the full accumulated response, so work increases throughout a long answer even though almost all earlier blocks are already final. In a large session, that repeated parsing, sanitization, temporary DOM creation, and reconciliation also competes with the retained transcript, tool cards, timeline, scrolling, and background session updates on the same renderer thread.

This optimization changes the growing part of that workload from the full response to the active Markdown tail. The benefit therefore compounds where it matters most: long answers, documentation-heavy work, code reviews, plans, and mature sessions with substantial transcript DOM. It is not a substitute for future transcript virtualization, but it removes avoidable per-update work before that retained-DOM cost is addressed. The reduction in transient nodes is particularly valuable because it lowers allocation and garbage-collection pressure rather than only moving CPU work elsewhere.

Profile results

The benchmark replayed the same Agent Manager workload for each implementation: 13,531 Markdown characters in 677 foreground chunks, two concurrent background sessions, and 1,353 total part updates. Every run settled to the same 540-element Markdown DOM and canonical completed HTML.

Metric Original renderer Stable block cache Incremental tail DOM Improvement vs original
Chromium script duration 733.6 ms 569.1 ms 468.1 ms median 36.2% lower
HTML parsing 73.9 ms 45.1 ms 14.5 ms median 80.4% lower
Transient Chromium nodes 34,779 11,621 3,229 median 90.7% lower
Trace script time 3,951.8 ms 3,142.8 ms 2,900.8 ms median 26.6% lower
Style recalculation 193.7 ms 141.7 ms 183.1 ms median 5.5% lower
Layout 87.4 ms 74.8 ms 84.1 ms median 3.8% lower

The final incremental implementation was profiled twice. Chromium script duration measured 462.7 ms and 473.4 ms, while HTML parsing measured 14.7 ms and 14.3 ms. The reported final values are the medians of those two confirmation runs.

Comment thread packages/ui/src/kilocode/markdown-incremental-dom.ts
@kilo-code-bot

kilo-code-bot Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
packages/ui/src/components/markdown.tsx 521–539 incremental.reset() not called in onCleanup — Comment boundary nodes for every live incremental block accumulate as strong references until GC after component unmount. reset() is already called in three other code paths (content change, fast-path, incremental decline); the onCleanup handler is the only one that does not clear the records array. Adding incremental.reset() alongside the existing pendingFrame, copyCleanup, and signal cleanup releases references eagerly.

SUGGESTION

File Line Issue
packages/ui/src/kilocode/markdown-incremental-dom.ts 48 remove() loop can walk past record boundary if end comment is externally removed before the traversal reaches it (prior review finding — still open).
Resolved Issues (fixed in new commits)
File Issue
packages/kilo-vscode/tests/markdown-incremental-dom.spec.ts ✅ All 3 previously missing test cases have been restored: "promotes an unchanged tail and appends the next block without wrappers", "runs streaming hooks only when incremental rendering handles the update", and "falls back when there is no stable prefix" — all ported to the Playwright browser spec (commit d363965df9).
Files Reviewed (7 files)
  • .changeset/faster-streamed-markdown.md — no issues
  • packages/kilo-vscode/tests/markdown-incremental-dom.spec.ts — 0 issues (test migration resolved)
  • packages/ui/src/components/markdown-stream.ts — no issues
  • packages/ui/src/components/markdown.tsx — 1 issue (WARNING, unresolved)
  • packages/ui/src/kilocode/markdown-incremental-dom.ts — 1 issue (SUGGESTION, unresolved)
  • packages/ui/src/kilocode/markdown-stable-blocks.ts — no issues
  • packages/ui/src/kilocode/markdown-stable-blocks.test.ts — no issues

Fix these issues in Kilo Cloud


Reviewed by claude-4.6-sonnet-20260217 · 588,104 tokens

Review guidance: REVIEW.md from base branch main

Comment thread packages/kilo-vscode/tests/markdown-incremental-dom.spec.ts
@marius-kilocode marius-kilocode enabled auto-merge June 9, 2026 10:44
@marius-kilocode marius-kilocode disabled auto-merge June 9, 2026 10:44
@marius-kilocode marius-kilocode merged commit ff2e66e into main Jun 9, 2026
21 checks passed
@marius-kilocode marius-kilocode deleted the perf-renderer-self-test branch June 9, 2026 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants