perf(desktop): memoize MarkdownText plugins to stop churning Streamdown

The inline `plugins={{ math: mathPlugin, ...(isStreaming ? {} : { code }) }}`
on `<StreamdownTextPrimitive>` constructed a new object literal on every
parent render. That broke `<Streamdown>`'s outer memo and forced its
internal `rehypePlugins` / `remarkPlugins` array useMemos to rebuild,
which propagates a new identity into every `<Block>` and defeats Block's
memoization for stable historical blocks.

After memoizing on `[isStreaming]` (the only real dimension of variance),
CPU profile during a 5 s synthetic stream on the 34 MB session shows
`parser` self-time dropping out of the top 10, `compile` cut roughly in
half, and `bn$1` / `m$1` (micromark internals) leaving the top entries.

Doesn't move the visible longtask count on its own — Streamdown's
per-Block parse cost still dominates whenever the last block's content
changes — but it removes a class of unnecessary re-parses for historical
blocks during streaming. See `scripts/profile-typing-lag.md` for the
full investigation.
This commit is contained in:
Brooklyn Nicholson
2026-05-21 20:08:32 -05:00
parent 3143f79b8f
commit ea510a7c02
@@ -238,6 +238,16 @@ const HEADING_SIZES: Record<'h1' | 'h2' | 'h3' | 'h4', string> = {
const MarkdownTextImpl = () => {
const isStreaming = useAuiState(s => s.message.status?.type === 'running')
// Stable per-state plugin object. The previous inline `{ math: mathPlugin,
// ...(isStreaming ? {} : { code }) }` created a new object identity on every
// render, which churns Streamdown's outer memo + propagates new prop
// identities into every Block. The plugin set really only varies on
// `isStreaming`, so memoize on that.
const plugins = useMemo(
() => (isStreaming ? { math: mathPlugin } : { math: mathPlugin, code }),
[isStreaming]
)
const components = useMemo(
() =>
({
@@ -331,7 +341,7 @@ const MarkdownTextImpl = () => {
// on the SyntaxHighlighter component, so we don't pay code-block
// tokenization on every token even with this set.
parseIncompleteMarkdown
plugins={{ math: mathPlugin, ...(isStreaming ? {} : { code }) }}
plugins={plugins}
preprocess={preprocessMarkdown}
/>
)