Files
twenty/packages
Félix Malfait ab4e979352 perf(ai): render streaming markdown as memoized blocks (#22489)
## Rationale

`LazyMarkdownRenderer` re-parses and re-renders the **entire accumulated
message** through react-markdown on every throttled stream flush (10/s).
Render cost grows linearly with message length while streaming, so long
answers degrade progressively — this is the dominant jank vector in the
chat (verified in the perf audit: no memoization anywhere in the
message-render path).

## Why this is the root cause, not a symptom patch

The waste is structural: 99% of a streaming message is settled text that
cannot change, yet it re-renders because the whole string is one
react-markdown call. Splitting at real markdown block boundaries via
`marked.lexer` (already a dependency, used in the advanced text editor)
and memoizing per block means settled blocks keep their rendered
subtree; only the growing tail block re-parses per flush — cost becomes
O(tail) instead of O(message). Index keys are stable because streaming
is append-only. This is the standard memoized-markdown pattern from the
AI SDK ecosystem.

Deliberately **not** included: list virtualization for very long
threads. The audit's verdict was memoize first, virtualize only if
profiling still shows mount cost matters — virtualization changes scroll
behavior and deserves its own evaluation.

One known tradeoff: markdown reference-style links whose definition
lives in a *different* block won't resolve across blocks. Model output
uses inline links; the tradeoff is shared by every implementation of
this pattern.

## User impact

Long streaming answers stop stuttering — keystroke-to-paint stays flat
instead of degrading as the answer grows. Most noticeable on tool-heavy
turns that produce big final summaries.

## Test plan

- [ ] CI green (existing markdown rendering covered by storybook visual
tests)
- [ ] Manual: stream a long answer with code fences and tables —
identical rendering, no per-flush jank in the profiler

https://claude.ai/code/session_01Lyi6zTema2FMVVh8MD6c38

---
_Generated by [Claude
Code](https://claude.ai/code/session_01Lyi6zTema2FMVVh8MD6c38)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22489?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-02 21:25:09 +02:00
..