Skip to content

perf(ai-chat): scroll the transcript without stutter - #3432

Merged
datlechin merged 3 commits into
mainfrom
perf/ai-chat-scroll
Oct 15, 2026
Merged

datlechin merged 3 commits into
mainfrom
perf/ai-chat-scroll

Conversation

@datlechin

Copy link
Copy Markdown
Member

Why

Scrolling the AI chat stuttered. Each fenced code block embedded a full SourceEditor, rebuilt every time its row scrolled into the lazy transcript (16 to 24 ms each), and sized by an estimate that ignored the gutter, so blocks cut off lines and caught the scroll. The hand-written markdown parser pushed content indented under a list item out of the list, and the at-bottom sentinel inside the lazy stack left streaming replies unfollowed.

Change

  • Code blocks are selectable text in the editor font and theme colors, colored by the editor's grammar off the main actor. Invisible characters stay marked.
  • Markdown comes from Foundation's CommonMark and GFM parser, off the main actor after a row's first frame.
  • The transcript follows a reply while the reader is at the end and stays put once they scroll up.
  • Typing publishes on the composer draft only; reasoning text streams; thumbnails are downsampled once.

Verified

MarkdownDocumentTests.listItemHoldsIndentedContent pins the reported reply. Animation Hitches over one scripted scroll of a 24-turn transcript, Debug: 650 to 1,567 ms before, 8 to 25 ms after. TrailingPaneStateTests now changes the model instead of typing, which no longer publishes on the view model.

Screenshots

Before After
Before After
Before, dark After, dark

Not in this PR

  • TableProEditorKit leaves block-based observers registered (TextViewController+Lifecycle.swift:96-135, GutterView.swift:268, TextView+Setup.swift:45-53).
  • Walkthrough Jump to lines lands on the wrong row in Split layout.
  • The macOS 13 and 14 scroll paths did not run here.

Code blocks were each a full SourceEditor, built every time a row
scrolled into the lazy stack and sized by an estimate that ignored the
gutter, so they cut off lines and caught the scroll. They are now
selectable text in the editor's font and colors, colored off the main
actor from the editor's own grammar.

Markdown comes from Foundation's parser, so content indented under a
list item stays in the item and tables parse as GFM. Parses after a
row's first frame run off the main actor.

The end of the transcript is measured outside the lazy stack, so the
reply is followed while the reader is at the end and left alone once
they scroll up. Typing publishes on the draft only, reasoning text
streams again, and image thumbnails are downsampled once.
Signed-off-by: Dat Ngo Quoc <datlechin@gmail.com>
Signed-off-by: Dat Ngo Quoc <datlechin@gmail.com>
@datlechin
datlechin merged commit b6d1824 into main Oct 15, 2026
6 checks passed
@datlechin
datlechin deleted the perf/ai-chat-scroll branch October 15, 2026 11:25
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.

1 participant