Skip to content

perf(mothership): restore static markdown parse for settled chat messages#5751

Merged
waleedlatif1 merged 2 commits into
stagingfrom
fix/mothership-chat-scroll-lag
Jul 17, 2026
Merged

perf(mothership): restore static markdown parse for settled chat messages#5751
waleedlatif1 merged 2 commits into
stagingfrom
fix/mothership-chat-scroll-lag

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Chat scroll got laggy after the mothership v0.8 merge (feat(mothership): mixture of models, search agent, persistent subagents, fork chat, inline questions (mothership v0.8) #5410), which removed mode='static' from the chat markdown renderer. Every message then rendered through Streamdown's streaming parser — remend + incomplete-markdown repair + per-block re-parse, running the rehype raw/sanitize/harden chain once per block.
  • Because chat rows are virtualized, every up/down scroll remounts the messages crossing the overscan boundary and re-pays that N-block cost. That's the lag.
  • Restore mode='static' for never-streamed mounts (reloaded history, or an in-session message scrolled out of the virtualized window and back): one whole-document parse instead of streaming's per-block re-parse. A completed message needs no incomplete-markdown repair, so static is the correct — and much cheaper — mode.
  • In-session streaming is unchanged: it keeps the streaming parser for its mount life, so there's no drain re-parse flash.
  • Cache Prism highlight output in a module-level bounded LRU (512, LRU eviction) so a code block re-highlights at most once across the unmount/remount virtualization does on scroll — a component useMemo would not survive the unmount.

Root cause

Verified by commit archaeology + reading streamdown@2.5.0's compiled parser: mode: 'static' renders the whole message in one ReactMarkdown pass and skips parseIncompleteMarkdown; mode: 'streaming' splits into N blocks and renders each as its own ReactMarkdown subtree, running the expensive rehype-raw chain per block.

Type of Change

  • Bug fix (performance regression)

Testing

  • tsc clean (0 errors), biome clean, 108/108 message-content tests pass.
  • /cleanup (8 parallel quality passes: effects, state, memo, callback, react-query, url-state, emcn, comments) returned zero findings.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 17, 2026 11:40pm

Request Review

@cursor

cursor Bot commented Jul 17, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
UI rendering and performance only; parser-mode splitting is nuanced but scoped to chat markdown display with no auth or data-path changes.

Overview
Fixes chat scroll lag after mothership v0.8 by no longer forcing every message through Streamdown’s expensive streaming parser on every virtualized remount.

Streamdown mode: Reloaded history (and any mount that never streamed this session) now use mode='static' — one whole-document parse instead of per-block rehype work. Messages that streamed in-session stay on the streaming parser for the mount’s life (parserTree), even after animation drains; only the animate/remount key (streamingTree) drops at drain so spans shed without re-parsing through static (which would flash on unbalanced markdown).

Syntax highlighting: Code blocks go through a module-scoped bounded LRU cache (512 entries) for Prism output, since row virtualization remounts rows and component useMemo would not survive.

Reviewed by Cursor Bugbot for commit 76892a3. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR improves rendering performance for virtualized chat messages. The main changes are:

  • Uses static markdown parsing for settled messages that never streamed during the current mount.
  • Keeps the streaming parser active for messages streamed during the current mount.
  • Adds a bounded module-level LRU cache for Prism highlight output.
  • Skips caching when the requested Prism grammar is unavailable.

Confidence Score: 5/5

This looks safe to merge.

  • The unavailable-grammar path now returns before cache insertion, so later grammar registration is not hidden by stale fallback output.
  • Registered grammars use resolved language names in the cache key.
  • The imported Prism helper matches the arguments used by the new wrapper.
  • No blocking issue related to the previous highlighting failure remains.

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/chat-content.tsx Selects the markdown parser from the message lifecycle and caches registered-grammar Prism output across virtualized remounts.

Reviews (2): Last reviewed commit: "fix(mothership): don't cache fallback-hi..." | Re-trigger Greptile

…ages

Settled/reloaded chat messages rendered through Streamdown's streaming
parser (remend + incomplete-markdown repair + per-block re-parse, running
the rehype raw/sanitize/harden chain once per block). Because rows are
virtualized, every up/down scroll remounted the messages crossing the
overscan boundary and re-paid that N-block cost — the scroll lag.

- Render never-streamed mounts (reloaded history, or an in-session message
  scrolled out of the virtualized window and back) with mode='static': one
  whole-document parse instead of streaming's per-block re-parse. In-session
  streaming keeps the streaming parser for its mount life (no drain flash).
- Cache Prism highlight output in a module-level bounded LRU so a code block
  re-highlights at most once across the unmount/remount virtualization does
  on scroll (a component useMemo would not survive the unmount).
@waleedlatif1
waleedlatif1 force-pushed the fix/mothership-chat-scroll-lag branch from 4d39557 to b79985a Compare July 17, 2026 23:36
An unregistered language highlighted via the JavaScript fallback was cached
under its own name, so if that Prism grammar registered later in the session
a remount would keep serving the stale fallback render. Resolve the grammar
inside the highlight helper and skip the cache entirely on the fallback path.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 76892a3. Configure here.

@waleedlatif1
waleedlatif1 merged commit 87edbc5 into staging Jul 17, 2026
20 checks passed
@waleedlatif1
waleedlatif1 deleted the fix/mothership-chat-scroll-lag branch July 17, 2026 23:49
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