Skip to content

fix(ai-agents): artifact streaming shows only first chunk after React Compiler#2280

Merged
malinskibeniamin merged 1 commit intomasterfrom
jb/fix-artifact-streaming-rerender
Mar 10, 2026
Merged

fix(ai-agents): artifact streaming shows only first chunk after React Compiler#2280
malinskibeniamin merged 1 commit intomasterfrom
jb/fix-artifact-streaming-rerender

Conversation

@birdayz
Copy link
Copy Markdown
Contributor

@birdayz birdayz commented Mar 10, 2026

What

Fix AI Agent Inspector artifact streaming displaying only the first text chunk instead of the full streamed response.

Why

handleArtifactUpdateEvent mutates activeTextBlock in place (appends text to parts[0].text), then passes the same object reference through onMessageUpdate. This violates React's immutability contract.

Before React Compiler (8a434ccca), this worked by accident -- setState re-rendered unconditionally regardless of reference identity. After the compiler was enabled, its automatic memoization detects the unchanged reference and skips re-renders. Only the first artifact chunk ever displays.

Implementation details

Clone the artifact block (shallow copy + deep-copy parts) when building the content blocks for the UI update. Each onMessageUpdate call now passes a structurally new object.

Regression test asserts consecutive handleArtifactUpdateEvent calls produce distinct object references (Object.is inequality) in onMessageUpdate, and that text accumulates correctly across chunks. Test fails without the fix, passes with it.

References

Introduced by React Compiler enablement: 8a434ccca ("feat: enable React Compiler for frontend build")

…enders

The artifact event handler mutates activeTextBlock in place (appending
text chunks), then passes the same object reference through
onMessageUpdate to React. This violates React's immutability contract.

Before React Compiler was enabled, this worked by accident: setState
triggered re-renders unconditionally regardless of reference identity.
After 8a434cc ("enable React Compiler"), the compiler's automatic
memoization detects the unchanged reference and skips re-renders. Result:
only the first artifact chunk is ever displayed.

Fix: shallow-clone the artifact block and deep-copy its parts array when
building the UI update, so each onMessageUpdate call produces a
structurally new object.

Add regression test that asserts consecutive artifact-update events
produce distinct object references (Object.is inequality) in the
onMessageUpdate callback.
@secpanda
Copy link
Copy Markdown

secpanda commented Mar 10, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.


const makeArtifactEvent = (
text: string,
opts: { append?: boolean; lastChunk?: boolean } = {},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Biome] reported by reviewdog 🐶

Suggested change
opts: { append?: boolean; lastChunk?: boolean } = {},
opts: { append?: boolean; lastChunk?: boolean } = {}

expect(statusBlocks).toHaveLength(1);
expect(statusBlocks[0].text).toContain('Artifact created successfully');
expect(statusBlocks[0].type === 'task-status-update' && statusBlocks[0].text).toContain(
'Artifact created successfully',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Biome] reported by reviewdog 🐶

Suggested change
'Artifact created successfully',
'Artifact created successfully'

Comment on lines +130 to +132
expect(block2?.type === 'artifact' && block2.parts[0]?.kind === 'text' && block2.parts[0].text).toBe(
'Hello world',
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Biome] reported by reviewdog 🐶

Suggested change
expect(block2?.type === 'artifact' && block2.parts[0]?.kind === 'text' && block2.parts[0].text).toBe(
'Hello world',
);
expect(block2?.type === 'artifact' && block2.parts[0]?.kind === 'text' && block2.parts[0].text).toBe('Hello world');

'Hello world',
);
expect(block3?.type === 'artifact' && block3.parts[0]?.kind === 'text' && block3.parts[0].text).toBe(
'Hello world!',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Biome] reported by reviewdog 🐶

Suggested change
'Hello world!',
'Hello world!'

makeArtifactEvent('', { append: true, lastChunk: true }),
state,
assistantMessage,
onMessageUpdate,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Biome] reported by reviewdog 🐶

Suggested change
onMessageUpdate,
onMessageUpdate

Comment on lines +159 to +161
expect(artifacts[0].type === 'artifact' && artifacts[0].parts[0]?.kind === 'text' && artifacts[0].parts[0].text).toBe(
'Hello world',
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Biome] reported by reviewdog 🐶

Suggested change
expect(artifacts[0].type === 'artifact' && artifacts[0].parts[0]?.kind === 'text' && artifacts[0].parts[0].text).toBe(
'Hello world',
);
expect(
artifacts[0].type === 'artifact' && artifacts[0].parts[0]?.kind === 'text' && artifacts[0].parts[0].text
).toBe('Hello world');

@malinskibeniamin malinskibeniamin merged commit 90ec29a into master Mar 10, 2026
17 checks passed
@malinskibeniamin malinskibeniamin deleted the jb/fix-artifact-streaming-rerender branch March 10, 2026 21:31
@birdayz birdayz changed the title fix: clone artifact block during streaming to fix React Compiler re-renders fix(ai-agents): artifact streaming shows only first chunk after React Compiler Mar 11, 2026
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.

4 participants