fix(ai-agents): artifact streaming shows only first chunk after React Compiler#2280
Merged
malinskibeniamin merged 1 commit intomasterfrom Mar 10, 2026
Merged
Conversation
…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.
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
|
|
||
| const makeArtifactEvent = ( | ||
| text: string, | ||
| opts: { append?: boolean; lastChunk?: boolean } = {}, |
Contributor
There was a problem hiding this comment.
[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', |
Contributor
There was a problem hiding this comment.
[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', | ||
| ); |
Contributor
There was a problem hiding this comment.
[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!', |
Contributor
There was a problem hiding this comment.
[Biome] reported by reviewdog 🐶
Suggested change
| 'Hello world!', | |
| 'Hello world!' |
| makeArtifactEvent('', { append: true, lastChunk: true }), | ||
| state, | ||
| assistantMessage, | ||
| onMessageUpdate, |
Contributor
There was a problem hiding this comment.
[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', | ||
| ); |
Contributor
There was a problem hiding this comment.
[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'); |
rockwotj
approved these changes
Mar 10, 2026
malinskibeniamin
approved these changes
Mar 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Fix AI Agent Inspector artifact streaming displaying only the first text chunk instead of the full streamed response.
Why
handleArtifactUpdateEventmutatesactiveTextBlockin place (appends text toparts[0].text), then passes the same object reference throughonMessageUpdate. This violates React's immutability contract.Before React Compiler (
8a434ccca), this worked by accident --setStatere-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
onMessageUpdatecall now passes a structurally new object.Regression test asserts consecutive
handleArtifactUpdateEventcalls produce distinct object references (Object.isinequality) inonMessageUpdate, 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")