Files
twenty/packages/twenty-front/src/modules/views
Félix Malfait 393e62ba9f Make AI chat streaming render cost independent of message length (#23831)
Follow-up to #23573: chip-heavy answers are long by design, and each
stream flush re-ran `protectChatReferencesForMarkdown` and
`marked.lexer` over the whole message, so render cost grew quadratically
with message length. This makes the per-flush cost proportional to the
appended text instead, and offsets the new code by removing dead AI chat
code.

## Streaming render

- **Incremental block splitting.** `getMarkdownBlocksIncrementally`
reuses blocks that can no longer change and re-tokenizes only the
trailing ones. Two trailing blocks stay unstable, not one: a loose list
followed by a blank line still merges with a later item (`- a\n\n` + `-
b` is one list token). Uses `Lexer.blockTokens` instead of
`marked.lexer` since only block raws are needed and the full lexer also
runs the inline tokenizer. Simulated stream over a 22 KB chip-heavy
message (120 chars/flush, matching the 100 ms flush throttle): 191 ms →
3.7 ms cumulative. The test suite pins char-by-char equivalence against
full `marked.lexer` output across loose lists, unclosed fences, setext
headings, tables, CRLF and chip markers.
- **Per-block reference protection.** `protectChatReferencesForMarkdown`
moved behind the existing block memo, so settled blocks never re-run
reference parsing during a stream.
- **Anchored open pattern.** `(?<!\[)\[\[+` anchors marker matching to
the start of a bracket run. The greedy `+` from #23798 backtracked at
every position inside a run, once per alternative: 429 ms → ~1 ms on a
10 KB bracket-run input. A run start always yields the same match, so no
valid marker is lost. Also an `includes('[[')` bail-out in
`findChatReferences`, which runs on every text node of the streaming
block.

## Chip lookups

`fieldMetadataItemByIdSelector` did `objectMetadataItems.find(obj =>
obj.fields.some(...))` per chip — O(workspace fields) each time the
agent's tool calls trigger a metadata refetch mid-chat. The by-id and
by-name map selectors mostly already existed with almost no consumers;
this wires `fieldMetadataItemByIdSelector`,
`objectMetadataItemFamilySelector` and `viewFromViewIdFamilySelector` to
them (adding the missing `objectMetadataItemsByIdMapSelector` and
`viewsByIdMapSelector`) and adds `areEqual` so unchanged lookups keep
referential stability.

## Offscreen messages

Settled messages (everything except the streaming last one) get
`content-visibility: auto`, so long threads skip layout and paint for
messages scrolled out of view. `contain-intrinsic-size: auto` keeps
remembered heights, so scroll positions stay accurate once a message has
been painted.

## Removed

`ReasoningSummaryDisplay`, `agentChatMessagesComponentState`,
`CHAT_THREADS_PAGE_SIZE`, `AgentResponseFormat` and `getFieldIcon` had
no consumers. `TextWithChatReferences` and
`protectChatReferencesForMarkdown` shared a duplicated segment-slicing
loop, now in `getChatReferenceSegments`, and the nine identical per-tag
markdown component entries collapse into `createChatReferenceElement`.
The branch lands at +354/−329 including the new test suite; production
code is net negative.

Incidental: `marked` added to jest's `transformIgnorePatterns` allowlist
(ESM-only, previously imported by no test).

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

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23831?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-08-05 20:31:57 +02:00
..