fix(mothership): stop chat perf decay from permanently-animated streamed messages#5411
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Also unifies plain-text and special-tag rendering so a trailing Global CSS: Reviewed by Cursor Bugbot for commit 5edddda. Bugbot is set up for automated code reviews on this repo. Configure here. |
Greptile SummaryThis PR fixes a progressive performance degradation in long chat sessions caused by Streamdown's animated pipeline accumulating thousands of permanent
Confidence Score: 5/5Safe to merge — well-scoped rendering optimisation with careful state transitions and no regressions on the existing test suite. All three state latches (streamedThisSession, animationDrained, fadeCutoff) are one-way and correctly gated: the drain timer fires only after reveal settles, the key swap remounts Streamdown without touching the streaming parser, and the content-replacement reset is guarded by animationDrained so it cannot fire mid-stream. The unified render path removes the structural branch that caused re-mount flashes. No logic paths loop or leave state inconsistent. No files require special attention. Important Files Changed
Reviews (2): Last reviewed commit: "improvement(styling): one lighter transl..." | Re-trigger Greptile |
…ts replaced content
…e drain-swap flash
…ssage re-fade when options arrive
| * re-fade the entire visible segment. | ||
| */ | ||
| if (streamedContent.length > FADE_MAX_REVEALED_CHARS) fadeCutoffRef.current = true | ||
| const fadeActive = streamingTree && !fadeCutoffRef.current |
There was a problem hiding this comment.
Stale fade cutoff after replace
Medium Severity
When displayContent is replaced while animationDrained is still false, latch reset skips fadeCutoffRef even though the instance can be reused for a new logical message. If the prior message had crossed FADE_MAX_REVEALED_CHARS, the new stream keeps fadeActive false for the rest of that mount, so reveal fades never run for the replacement content.
Reviewed by Cursor Bugbot for commit 3961a9c. Configure here.
| /> | ||
| ) | ||
| })} | ||
| {parsed.hasPendingTag && isRevealing && <PendingTagIndicator />} |
There was a problem hiding this comment.
Whitespace-only text not rendered
Low Severity
Plain replies without special tags now always go through flushMarkdown, which only enqueues inline groups when pendingMarkdown.trim() is truthy. Whitespace-only text still becomes a text segment via parseSpecialTags, but it never produces an inline group, so Streamdown is omitted and the message body renders empty.
Reviewed by Cursor Bugbot for commit 3961a9c. Configure here.
…ate per updated hook rules
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 56f391b. Configure here.
…xt stays readable
…erywhere, no forced text color
|
@greptile review |


Summary
Chat perf decay (the main fix)
Text-selection follow-up (regression from #5389 that this branch merged in)
Type of Change
Testing
Tested manually in the live app (streamed multi-turn chats; measured rAF frame deltas + long tasks before/after; DOM-diffed the settle swap; verified 0 animated spans after settle, no completion flash, no re-fade when follow-ups arrive; selection checked on landing/chat/inputs in light + dark). `vitest` message-content + smooth-text + file-viewer suites pass, `bun run lint`, `bun run check:api-validation:strict` pass.
Checklist