Skip to content

fix(vscode): eliminate streaming transcript flicker - #13408

Merged
marius-kilocode merged 4 commits into
mainfrom
fix-session-scroll-flicker
Aug 25, 2026
Merged

fix(vscode): eliminate streaming transcript flicker#13408
marius-kilocode merged 4 commits into
mainfrom
fix-session-scroll-flicker

Conversation

@marius-kilocode

Copy link
Copy Markdown
Collaborator

Streaming session transcripts could visibly twitch while reasoning and tool output arrived. The viewport was usually at the bottom, but the browser briefly painted newly mounted content below the visible area before auto-follow corrected scrollTop. Tool cards and reasoning blocks also changed their DOM shape during the same interval, which caused short height pulses, reasoning overflow during collapse, cursor changes under a parked pointer, and copy controls to mount and unmount between streamed text parts.

The key timing problem was the existing ResizeObserver path. It observes the result of layout, so it runs after a DOM mutation has already been laid out. By then, one frame could paint with a non-zero distance from the bottom. The previous deferred-follow experiment made this worse by intentionally waiting another frame for every growth event. A transient shrink could also make the browser clamp scrollTop upward, then recover before a resize notification was delivered, leaving the transcript temporarily off the bottom.

This change observes transcript DOM mutations before layout is painted and follows immediately when the user has not paused auto-follow. It retains the resize path for non-DOM size changes and restores the bottom when a layout correction moves the viewport without changing the final content size. User wheel, pointer, touch, keyboard, scrollbar, and nested-scroll handling remain separate, so reading earlier output is not interrupted.

The rendering fixes address the other sources of visible churn:

  • Reasoning content is clipped during height changes and uses a real close animation instead of exposing markdown outside the collapsing box.
  • The transcript declares a stable text cursor, while controls retain their explicit pointer cursor.
  • Copy and feedback controls stay anchored to completed turns instead of hopping between text parts during streaming.

This follows the earlier scroll fixes rather than replacing them:

  • PR #13009, fix(ui): prevent snap-to-bottom and flickering during upward session scroll, stopped inactive resize handling from fighting upward user scrolling and established input-aware auto-follow behavior.
  • PR #13160, fix(ui): preserve streaming chat scroll position, separated streaming/layout reflow from user movement and covered native scrollbar and touch interactions.
  • PR #13273, fix(vscode): keep session scroll pinned during layout corrections, stopped stable-height layout corrections from being misclassified as user scrolling.

Those PRs fixed incorrect intent classification. This change fixes the remaining browser timing gap between DOM mutation, layout, and auto-follow correction.

@marius-kilocode
marius-kilocode force-pushed the fix-session-scroll-flicker branch 2 times, most recently from 5298d4f to c516947 Compare August 25, 2026 10:14
Comment thread packages/kilo-ui/src/components/message-part.css Outdated
Comment thread packages/kilo-ui/src/hooks/create-auto-scroll.tsx Outdated
@kilo-code-bot

kilo-code-bot Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (3 files)
  • packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/chat-view-readable-420-chromium-linux.png
  • packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/message-list-layout-correction-chromium-linux.png
  • packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/prompt-rail-sidebar-chromium-linux.png
Previous Review Summaries (3 snapshots, latest commit 2aab3fb)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 2aab3fb)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (3 files)
  • packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/chat-view-readable-420-chromium-linux.png
  • packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/message-list-layout-correction-chromium-linux.png
  • packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/prompt-rail-sidebar-chromium-linux.png

Previous review (commit e23312b)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (5 files)
  • packages/kilo-ui/src/components/message-part.css
  • packages/kilo-ui/src/hooks/create-auto-scroll.tsx
  • packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/chat-view-readable-420-chromium-linux.png
  • packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/message-list-layout-correction-chromium-linux.png
  • packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/prompt-rail-sidebar-chromium-linux.png

Previous review (commit 378c187)

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/kilo-ui/src/components/message-part.css 838 Open animation replays from height 0 when virtualized reasoning rows remount

SUGGESTION

File Line Issue
packages/kilo-ui/src/hooks/create-auto-scroll.tsx 152 canScroll() forces layout before pause/activity bailouts on a hot MutationObserver
Files Reviewed (7 files)
  • .changeset/smooth-streaming-transcript.md - 0 issues
  • packages/kilo-ui/src/components/message-part.css - 1 issue
  • packages/kilo-ui/src/hooks/create-auto-scroll.test.tsx - 0 issues
  • packages/kilo-ui/src/hooks/create-auto-scroll.tsx - 1 issue
  • packages/kilo-vscode/tests/unit/transcript-rows.test.ts - 0 issues
  • packages/kilo-vscode/webview-ui/src/context/transcript-rows.ts - 0 issues
  • packages/kilo-vscode/webview-ui/src/styles/chat-layout.css - 0 issues

Fix these issues in Kilo Cloud


Reviewed by grok-4.6 · Input: 33.4K · Output: 5.2K · Cached: 214.9K

Review guidance: REVIEW.md from base branch main

@marius-kilocode
marius-kilocode force-pushed the fix-session-scroll-flicker branch 3 times, most recently from 2aab3fb to 9bc84d2 Compare August 25, 2026 10:47

@iscekic iscekic left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approved, bot remarks below, feel free to ignore

// because the final content size is unchanged no resize entry follows, so
// the correction has to happen here or the transcript stays parked below
// its bottom until the next content update.
if (active()) bottom()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

medium: The new re-pin calls bottom() on every non-input scroll event while the session is active (working or settling), which includes user scrolls that are never marked as input — a native scrollbar drag dispatches no pointer events, so mark() in scroll-user-activity.ts never fires, and the search-highlight scrollIntoView at webview-ui/src/components/chat/MessageList.tsx:879 never calls pause(). On classic-scrollbar platforms the view is therefore yanked back to the bottom on every scroll event, so a user cannot scroll up to read earlier output during a stream or within the 300ms settle window, and in-stream search match centering is defeated (the old code left these non-input scrolls in place and re-pinned only on content resize). Fix: re-pin only when the scroll follows content growth (compare scrollHeight to the last pinned value) and treat an unmarked scroll away from the bottom as a pause.

// lays out and paints. A ResizeObserver entry arrives after that layout, so
// waiting for it lets the browser paint one frame with the new content hanging
// below the viewport, which reads as the transcript twitching as it streams.
const onContentMutate = () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

low: The new MutationObserver invokes follow() for every childList/characterData mutation of the whole transcript while the session is active, and each invocation forces a synchronous layout through the distanceFromBottom() read (plus a second one through bottom()'s scrollHeight read when the pin fires). During streaming this runs once per streamed chunk on top of the virtualizer's own re-measurement work, so a large transcript can jank at the very moment the change is meant to make it feel smooth. Fix: coalesce the pins per frame, e.g. schedule follow() once via requestAnimationFrame or a microtask flag rather than per mutation.

@marius-kilocode
marius-kilocode merged commit dee2a27 into main Aug 25, 2026
35 checks passed
@marius-kilocode
marius-kilocode deleted the fix-session-scroll-flicker branch August 25, 2026 12:07
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