[Bug]: UI slows down as the conversation grows: streaming markdown re-parse, main-thread highlighting, and full session rebuild on approval
まだ誰も着手していません。
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 25/100
- issue の種類
- バグ
- 明瞭さ
- 説明が足りない
- 活発さ
- 活発
- 技術スタック
- javascript, markdown, wasm
調査の方向性
Start by comparing the shipped bundle references in out/renderer/assets/index-RTchhMTQ.js:104-105 and :236 with their source counterparts, then inspect workspace-screen-BQcc80qC.js:1389 and :1422. Reproduce a long streaming conversation, an approval, and chat switching while measuring main-thread work. Done should mean those flows remain responsive without work scaling unnecessarily with transcript length.
索引モデルが issue の本文から書いたものです。
説明
Summary
The GUI becomes sluggish well before the machine is actually under load. Reading the shipped renderer bundle, I think there are a few places where main-thread work grows with conversation length rather than staying constant. I could not attach a profiler recording since this build ships without source maps, so the file and line references below are into the minified out/renderer/assets bundle, and the function names are the minified ones. I would rather point at specific code than just say "it feels slow".
Reproduction steps
- Open a project and work through a conversation that grows to a few thousand messages, with several code blocks and many tool calls.
- Let the assistant stream a long reply.
- Approve a permission request or answer a question.
- Click another chat in the left panel, then switch back.
Expected behavior
Streaming should stay responsive regardless of how much is already in the transcript, and switching chats should be immediate. Work that does not depend on the full transcript length should not be repeated on every token.
Actual behavior
- While a reply streams in, typing and scrolling stutter, and it gets noticeably worse as the reply gets longer.
- Approving a permission request freezes the UI for a moment, longer on long conversations.
- Switching chats freezes the transcript for a second or two, matching what is described in #113.
What I found in the bundle
out/renderer/assets/index-RTchhMTQ.js:236 configures the markdown renderer with mode: "streaming", and the memo comparator next to it bails out on e.content !== t.content. As far as I can tell, every token that arrives changes content, which means the whole block gets re-parsed and the DOM diffed again. For a reply of a few thousand tokens that is a few thousand full re-parses, all on the main thread.
out/renderer/assets/index-RTchhMTQ.js:104-105 builds the highlight cache key by walking the whole string, roughly for (let l = 0; l < e.length; l++) s = (s << 5) + s + e.charCodeAt(l) | 0. That loop runs before the cache lookup, so it happens even on a hit. On a miss it then calls codeToTokens synchronously, which is the Oniguruma WASM tokenizer running on the main thread. The cache is also small, around 300 entries and 2e6 tokens, so in a long session blocks get evicted and tokenized again. I did not see tokenizeMaxLineLength passed to codeToTokens.
out/renderer/assets/workspace-screen-BQcc80qC.js:1422 has a function that maps over every message in a session, shallow-copies the one that holds the permission item, and returns a brand new session object. Since the store is consumed through useSyncExternalStore, that new top-level reference re-renders every subscriber. The cost scales with the number of messages, and because the same path runs on each approval, it compounds over a session.
out/renderer/assets/workspace-screen-BQcc80qC.js:1389 starts a setInterval at 1000ms for relative timestamps like "3m 12s". It only stops when the subscriber set empties, so as long as one such label is on screen it keeps ticking and waking everything subscribed to it.
out/renderer/assets/workspace-screen-BQcc80qC.js:1389 also renders the tool call list with a plain e.map(...), and I could not find a virtual scroller in the bundle for the transcript or the tool list. react-window, @tanstack/virtual and react-virtual are all absent. The only virtualizer string in the bundle belongs to the diff viewer. A run with several hundred tool calls mounts all of them as DOM nodes. The checkpoint dialog does something similar with a max-h plus overflow-y-auto, which clips visually but still creates every node.
Things I checked that look fine
Mentioning these so nobody spends time on them: terminal scrollback is at the xterm default of 1000 lines and the app does not override it, PTY replay is chunked at 64KB with throttling, resize handling is already rAF-throttled, and the URL detection buffer is capped at 256 characters. Those are reasonable and I would leave them alone.
Ideas, if useful
- Parse only the unfinished trailing block while streaming, and freeze completed blocks behind
memo. Or expose a switch between streaming and static parsing. - Move highlighting into a worker, key the cache off the string reference or a cheap sample instead of a full scan, and pass
tokenizeMaxLineLengthso very long blocks degrade to plain text. - Index permission items by id so an approval updates one message rather than rebuilding the session, and subscribe per message.
- Virtualize the transcript and the tool list.
- Drop the relative-time tick to something like 30s, and only run it when there is a running task.
None of these are exposed as settings today, which is part of why there is nothing to toggle when the UI gets slow.
Screenshots, recordings, or sanitized logs
No recording attached. Findings come from reading the shipped bundle on Windows, GUI 0.1.40, and I have not verified them against a profiler, so please treat the above as a starting point rather than a measurement.
Safety check
- I removed credentials and private project information from this report.
- 主要言語
- Shell
- スター
- 85
- フォーク
- 5
- 平均マージ
- 2時間 46分
- マージ済み PR(30日)
- 1
環境構築
このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
CommandCodeAI/desktop のほかの issue
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
CommandCodeAI/desktop#125 ·
-
[Feedback]: add copy block under a code block対応中かも @aliarain が 5 日前に担当しました。 オープンenhancement
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
CommandCodeAI/desktop#94 · 担当者 1 名 ·
-
[Feedback]: Add Expand & Collapse for changes対応中かも @aliarain が 5 日前に担当しました。 オープンenhancement
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
CommandCodeAI/desktop#66 · コメント 1 件 · リアクション 2 件 · 担当者 1 名 ·
-
bug
難易度 3/5 1〜2日 初心者へのやさしさ 68/100
CommandCodeAI/desktop#126 ·
-
bug
難易度 4/5 3〜5日 初心者へのやさしさ 35/100
CommandCodeAI/desktop#124 ·
CommandCodeAI/desktop の issue をすべて見る
似ている issue
-
package-update
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
oSoWoSo/vOid_Community_repOsitory#207 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
alunduil/alunduil-chezmoi#815 ·
メンテナーはふだん 1 日以内に返信
-
good first issue new package
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
wimpysworld/deb-get#2035 ·
メンテナーはふだん 1 日以内に返信
-
automation documentation
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
メンテナーはふだん 1 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 92/100
termux/termux-packages#32085 ·
メンテナーはふだん 1 日以内に返信