fix(chat): GenUI streaming architecture — composition-level orchestration#246
Merged
Conversation
Card-shaped placeholder rendered in place of streaming tool-call JSON while an A2UI / json-render surface is being built. Three shimmer rows + 'Building UI…' status label. Themed via existing chat-tokens (separator color, surface-alt background) so it inherits theme overrides.
Two changes: 1. Rename ContentType value 'undetermined' to 'pending'. The new name better reflects what the state means (we're waiting for enough content to decide), and is the state the chat composition reads when deciding whether to show the GenUI skeleton. 2. Add patience for the A2UI prefix. When the first non-whitespace char is '-', stay 'pending' until either the full prefix matches (commit to 'a2ui') or enough chars arrive without matching (commit to 'markdown').
New optional input that filters out tool groups whose name is in the exclude set. Used by chat compositions to hide orchestration-only tool calls (e.g. GenUI dispatchers) whose streaming args are not meaningful to the user.
Adds isGenuiTurn(message, prevMessage) to the chat composition, which inspects message structure across three independent signals (tool_calls field, function_call content block, prev-tool-message name) to decide whether this assistant turn is producing a GenUI surface. Template changes inside the AI message branch: - <chat-tool-calls> now gets [excludeToolNames]="genuiToolNames()", so internal GenUI dispatchers don't render args JSON as cards. - New <chat-genui-skeleton /> branch renders when the classifier is 'pending' (or 'a2ui' with no surfaces yet) AND isGenuiTurn is true — bridging the gap between streaming start and the rendered surface mounting. The skeleton is a SIBLING of <a2ui-surface> and <chat-generative-ui>, not a wrapper around them — so it cleanly hands off once content disambiguates.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
6 tasks
4 tasks
blove
added a commit
that referenced
this pull request
May 11, 2026
) Live smoke against PR #246's architecture revealed the classifier patience fix isn't enough on its own: during the sub-LLM phase of a GenUI run, raw JSON envelopes stream INTO the assistant message's content BEFORE emit_generated_surface wraps them with the A2UI prefix. The first chunks arrive as '[' (JSON array open) which the classifier locks in as 'markdown' — and the patience fix only protects the '-' first-char ambiguity. Fix: on a GenUI turn, suppress chat-streaming-md unconditionally until the classifier resolves to 'a2ui' or 'json-render'. Branches restructured as @else if so they're mutually exclusive, preventing any flash of streaming JSON. Once emit_generated_surface's wrapped payload arrives and the classifier's reset-on-shrink path re-classifies to 'a2ui', the skeleton hands off to the rendered surface.
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.
Summary
Replaces PR #245's chat-message-level GenUI suppression — which broke the actual surface rendering by hiding chat-message's entire ng-content slot — with a clean separation of concerns:
Classifier (`createContentClassifier`) stays content-only. Renamed `'undetermined'` → `'pending'`, plus a patience fix: when content starts with `-`, the classifier waits until either the full `---a2ui_JSON---` prefix arrives (→ `'a2ui'`) or enough chars rule it out (→ `'markdown'`). Previously committed to markdown on the first `-`, missing every streaming A2UI payload.
Chat composition owns "is this a GenUI turn?" via a new `isGenuiTurn(message, prevMessage)` method that reads message structure (`tool_calls` field, `content[].type === 'function_call'` block, previous-tool-message name).
`` new primitive — card-shaped placeholder, now rendered from the composition template as a sibling of `` / `` — not a wrapper around them. Skeleton shows when classifier is `'pending'` AND `isGenuiTurn` is true, OR when classifier is `'a2ui'` but no surfaces have parsed yet (late-arrival guard).
`` gains an `excludeToolNames` input. Composition passes the GenUI tool names so internal dispatchers don't render args JSON as cards.
chat-message untouched — was already in the clean state since this branch forked from origin/main before PR feat(chat): suppress streaming JSON for A2UI tool calls #245.
Spec: `docs/superpowers/specs/2026-05-11-genui-streaming-architecture-design.md`.
What this fixes
The user flow today: typing indicator → flash of JSON → late "Building UI" skeleton → never resolves to the rendered surface.
After this PR: typing indicator → skeleton from the first token → rendered surface mounts as soon as envelopes parse.
Test plan
Closes #245.
🤖 Generated with Claude Code