Found by the second real consumer of @tangle-network/ui/chat (discovery-lab's research-factory dashboard rendering agent transcripts).
What the flag is for
TextPart.synthetic: true marks text the APP composed rather than words the model or user produced. A transcript viewer needs exactly that distinction for: delivery receipts ("delivered 0f7fafe33f"), settle rows, budget lines, and honest absence notes ("task not persisted — digest c4fcc6a…", "no tool trace was captured for this worker"). These must render visibly distinct from agent speech — and must never be silently deleted, because an absence note that vanishes turns a measurement gap into implied data.
Current behavior
chat-container.tsx:291 and run-group.tsx:321/396/415 DROP text parts flagged synthetic: true entirely.
user-message.tsx:28 concatenates ALL text parts of a user message into one bubble, so any composed part that survives gets merged INTO the user's own words — misattribution, which is worse than the drop.
Between the two, an app has no way to put composed chrome into the transcript: flagged parts vanish, unflagged parts impersonate the speaker.
Ask, in preference order
- Render synthetic text parts with a distinct muted treatment (or via an optional
renderSyntheticPart prop), instead of dropping them.
- Failing that, stop merging text parts in
UserMessage so a consumer can at least interleave composed parts safely.
- At minimum, document the drop — it is currently silent, and our absence notes disappeared without any signal until screenshot review caught it.
Workaround we ship today: split the payload before ChatContainer — agent-authored parts go through the component, synthetic parts render beside it in our own dashed treatment. It works but forfeits ChatContainer's ordering and grouping for exactly the parts that most need context.
Found by the second real consumer of
@tangle-network/ui/chat(discovery-lab's research-factory dashboard rendering agent transcripts).What the flag is for
TextPart.synthetic: truemarks text the APP composed rather than words the model or user produced. A transcript viewer needs exactly that distinction for: delivery receipts ("delivered 0f7fafe33f"), settle rows, budget lines, and honest absence notes ("task not persisted — digest c4fcc6a…", "no tool trace was captured for this worker"). These must render visibly distinct from agent speech — and must never be silently deleted, because an absence note that vanishes turns a measurement gap into implied data.Current behavior
chat-container.tsx:291andrun-group.tsx:321/396/415DROP text parts flaggedsynthetic: trueentirely.user-message.tsx:28concatenates ALL text parts of a user message into one bubble, so any composed part that survives gets merged INTO the user's own words — misattribution, which is worse than the drop.Between the two, an app has no way to put composed chrome into the transcript: flagged parts vanish, unflagged parts impersonate the speaker.
Ask, in preference order
renderSyntheticPartprop), instead of dropping them.UserMessageso a consumer can at least interleave composed parts safely.Workaround we ship today: split the payload before ChatContainer — agent-authored parts go through the component, synthetic parts render beside it in our own dashed treatment. It works but forfeits ChatContainer's ordering and grouping for exactly the parts that most need context.