@@ -144,6 +144,13 @@ type AssistantMessageItem struct {
144 // docs/notes/2026-05-12-chat-rendering-perf.md. See
144 // docs/notes/2026-05-12-chat-rendering-perf.md. See
145 // streaming_markdown.go for the full algorithm.
145 // streaming_markdown.go for the full algorithm.
146 streamingContent streamingMarkdown
146 streamingContent streamingMarkdown
148+ // streamingThinking applies the same stable-prefix caching to
149+ // the thinking/reasoning section. Without this, every streaming
150+ // delta forces a full glamour re-render of the entire accumulated
151+ // thinking text, which burns CPU and starves the terminal emulator
152+ // during long reasoning traces.
153+ streamingThinking streamingMarkdown
149var _ Expandable = (*AssistantMessageItem)(nil)
156var _ Expandable = (*AssistantMessageItem)(nil)
@@ -447,13 +454,7 @@ func (a *AssistantMessageItem) cachedError(width int) string {
447// lines so the visual box matches what the user sees today.
454// lines so the visual box matches what the user sees today.
448func (a *AssistantMessageItem) renderThinking(thinking string, width int) string {
455func (a *AssistantMessageItem) renderThinking(thinking string, width int) string {
449 renderer := common.QuietMarkdownRenderer(a.sty, width)
456 renderer := common.QuietMarkdownRenderer(a.sty, width)
450− mu := common.LockMarkdownRenderer(renderer)
457+ rendered := a.streamingThinking.Render(thinking, width, renderer)
452− rendered, err := renderer.Render(thinking)
457 rendered = strings.TrimSpace(rendered)
458 rendered = strings.TrimSpace(rendered)
459 lines := strings.Split(rendered, "\n")
460 lines := strings.Split(rendered, "\n")
@@ -587,6 +588,7 @@ func (a *AssistantMessageItem) clearCache() {
587 a.contentSec.reset()
588 a.contentSec.reset()
588 a.errorSec.reset()
589 a.errorSec.reset()
589 a.streamingContent.Reset()
590 a.streamingContent.Reset()
591+ a.streamingThinking.Reset()
592// ToggleExpanded advances the F5 thinking view-mode cycle and returns
594// ToggleExpanded advances the F5 thinking view-mode cycle and returns
@@ -617,6 +619,12 @@ func (a *AssistantMessageItem) ToggleExpanded() bool {
617 case thinkingFullExpanded:
619 case thinkingFullExpanded:
618 a.thinkingViewMode = thinkingCollapsed
620 a.thinkingViewMode = thinkingCollapsed
622+ // View-mode changes alter the windowing slice applied after
623+ // glamour render. The streaming prefix cache may have been
624+ // seeded under a different slice regime, and glued renders are
625+ // not byte-identical to monolithic ones. Drop the prefix cache
626+ // so the next render is clean.
627+ a.streamingThinking.Reset()
621 return a.thinkingViewMode != thinkingCollapsed
629 return a.thinkingViewMode != thinkingCollapsed