fix(app): reduce streaming CPU, scroll thrashing, and session switch overhead#31517
Open
BYK wants to merge 3 commits into
Open
fix(app): reduce streaming CPU, scroll thrashing, and session switch overhead#31517BYK wants to merge 3 commits into
BYK wants to merge 3 commits into
Conversation
…store mutation per delta - Split marked parser into light (no Shiki) and full parser. During streaming, use the light parser to avoid WASM syntax highlighting on the main thread. Code blocks render as plain <pre><code> during streaming and get highlighted when the stream ends. - Remove the redundant produce() mutation on the part store during message.part.delta events. Only the lightweight part_text_accum_delta store is updated per delta now — readPartText() prefers accum over part.text, so the UI sees latest text without a second store write per delta.
… lightweight content version
- Replace activeAssistantContentVersion string fingerprint with a lightweight
counter. Previously concatenated all active assistant message content into a
massive string on every SSE delta; now increments a number.
- Reduce scroll anchor rAF loop from 90 frames (~1.5s) to 12 (~200ms), and
the active streaming cap from 12 to 6. Reduces layout thrashing from
repeated scrollHeight reads and scrollTop writes.
- Enable diff virtualization (virtualize={true}, virtualizeDiff={true}) so
large file diffs in tool results don't render thousands of DOM nodes inline.
- Remove duplicate loadSessions() call in bootstrapDirectory — sessions were loaded at both the start and end of the bootstrap array. - Remove duplicate createResource in directory-layout.tsx that called sync.session.sync(id) on every params.id change. Session sync is already handled by session.tsx; the duplicate added unnecessary reactive tracking overhead and Promise creation on every session switch.
Contributor
|
Thanks for your contribution! This PR doesn't have a linked issue. All PRs must reference an existing issue. Please:
See CONTRIBUTING.md for details. |
Contributor
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue for this PR
Closes #31548
Type of change
What does this PR do?
Fixes several performance bottlenecks in the web UI that cause high CPU during streaming, layout thrashing, and session switch lag.
Skip Shiki WASM during streaming (
marked.tsx,markdown.tsx): The markdown parser is split into a light parser (no Shiki) and a full parser. During streaming, the light parser runs — no WASM syntax highlighting on the main thread. Code blocks appear as plain<pre><code>while streaming and get syntax-highlighted when the stream ends. This eliminates the biggest main-thread blocker during streaming.Single store mutation per SSE delta (
event-reducer.ts): Removes the redundantproduce()mutation on the part store duringmessage.part.deltaevents. Only the flatpart_text_accum_deltastore is updated per delta.readPartText()already prefers accum overpart.text, so the UI sees the latest text without the second write. This halves reactive propagation on the hottest streaming path.Reduce scroll anchor thrashing (
message-timeline.tsx): The bottom-anchor rAF loop is reduced from 90 frames (~1.5s) to 12 (~200ms), and the active streaming cap from 12 to 6. Each frame was readingscrollHeightand writingscrollTop, causing forced layout reflows.Lightweight content version counter (
message-timeline.tsx): ReplacesactiveAssistantContentVersionwhich concatenated all active assistant message content into a massive string on every SSE delta. Now it just increments a counter — same dependency tracking, zero allocation.Enable diff virtualization (
message-timeline.tsx): Setsvirtualize={true}on diff views inside tool parts so large file diffs don't render thousands of DOM nodes.Remove duplicate session loading (
bootstrap.ts,directory-layout.tsx): Removes a duplicateloadSessions()call in bootstrap (called at both start and end of the array) and a duplicatecreateResourceindirectory-layout.tsxthat calledsync.session.sync(id)on every session switch — the same call already exists insession.tsx.How did you verify your code works?
bun typecheckfrompackages/opencode— no new errors (all existing errors are pre-existing in test files)OPENCODE_CHANNEL=latest bun run script/build.ts --single— build succeeded, smoke test passedChecklist