From 74169eb292b32a32b88efb69ee5bbb8f901caba2 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Sat, 9 May 2026 11:17:09 -0700 Subject: [PATCH 1/2] fix(chat,examples-chat): wire a2uiBasicCatalog so A2UI surfaces actually render MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 4 (PR #226) shipped a working python graph that produces an AIMessage with the ---a2ui_JSON--- wire-format prefix; the chat composition's content classifier correctly identifies the message as a2ui and parses the surface (size=1). However, the template gates the final mount on `@if (classified.type() === 'a2ui' && views(); as catalog)` — without a `views` ViewRegistry, the surface never reaches the DOM and the message renders as a tool-call card instead of a Card with the declared TextField / ChoicePicker / Button. Two changes: 1. **chat-popup + chat-sidebar**: forward a `views` input through to the inner ``. They wrapped `` directly with no way to pass A2UI catalog through, leaving popup/sidebar consumers unable to render surfaces even after wiring. 2. **examples/chat/angular**: import `a2uiBasicCatalog` from `@ngaf/chat` in all three mode components and pass the result as `[views]`. The demo now renders the feedback Card on the render_demo_form path in /embed, /popup, and /sidebar. Verified live: clicking the welcome suggestion produces the AI tool call → ToolMessage("rendered") → AIMessage with prefix + JSONL → the chat-message bubble mounts with , , , . The Card title "Quick feedback" displays as designed. Lint + build + tests for both chat lib and examples-chat-angular all pass. A separate lib-level gap (a2ui:datamodel:* emit events have no consumer wiring up to the surface store) prevents form input from back-propagating to the dataModel; that's outside Phase 4 scope and will be tracked separately. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../chat/angular/src/app/modes/embed-mode.component.ts | 9 +++++++-- .../chat/angular/src/app/modes/popup-mode.component.ts | 6 ++++-- .../chat/angular/src/app/modes/sidebar-mode.component.ts | 6 ++++-- .../lib/compositions/chat-popup/chat-popup.component.ts | 7 ++++++- .../compositions/chat-sidebar/chat-sidebar.component.ts | 7 ++++++- 5 files changed, 27 insertions(+), 8 deletions(-) diff --git a/examples/chat/angular/src/app/modes/embed-mode.component.ts b/examples/chat/angular/src/app/modes/embed-mode.component.ts index cbdd71e35..7af299e31 100644 --- a/examples/chat/angular/src/app/modes/embed-mode.component.ts +++ b/examples/chat/angular/src/app/modes/embed-mode.component.ts @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT import { Component, ChangeDetectionStrategy, inject } from '@angular/core'; -import { ChatComponent, ChatWelcomeSuggestionComponent } from '@ngaf/chat'; +import { ChatComponent, ChatWelcomeSuggestionComponent, a2uiBasicCatalog } from '@ngaf/chat'; import { DEMO_AGENT } from '../shell/shell-tokens'; import { WELCOME_SUGGESTIONS } from './welcome-suggestions'; @@ -10,7 +10,7 @@ import { WELCOME_SUGGESTIONS } from './welcome-suggestions'; imports: [ChatComponent, ChatWelcomeSuggestionComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: ` - +
@for (s of suggestions; track s.value) { when an AI message content begins with the + // ---a2ui_JSON--- wire-format prefix. Without this, the surface is + // parsed correctly but never mounted (the @if gate requires views()). + protected readonly catalog = a2uiBasicCatalog(); protected send(text: string): void { void this.agent.submit({ message: text }); diff --git a/examples/chat/angular/src/app/modes/popup-mode.component.ts b/examples/chat/angular/src/app/modes/popup-mode.component.ts index bbaf7ebb2..cc5343c79 100644 --- a/examples/chat/angular/src/app/modes/popup-mode.component.ts +++ b/examples/chat/angular/src/app/modes/popup-mode.component.ts @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT import { Component, ChangeDetectionStrategy, inject } from '@angular/core'; -import { ChatPopupComponent, ChatWelcomeSuggestionComponent } from '@ngaf/chat'; +import { ChatPopupComponent, ChatWelcomeSuggestionComponent, a2uiBasicCatalog } from '@ngaf/chat'; import { DEMO_AGENT } from '../shell/shell-tokens'; import { WELCOME_SUGGESTIONS } from './welcome-suggestions'; @@ -15,7 +15,7 @@ import { WELCOME_SUGGESTIONS } from './welcome-suggestions'; Click the launcher button (bottom-right) to open the chat.

- +
@for (s of suggestions; track s.value) { . + protected readonly catalog = a2uiBasicCatalog(); protected send(text: string): void { void this.agent.submit({ message: text }); diff --git a/examples/chat/angular/src/app/modes/sidebar-mode.component.ts b/examples/chat/angular/src/app/modes/sidebar-mode.component.ts index da8719899..4ff81d99d 100644 --- a/examples/chat/angular/src/app/modes/sidebar-mode.component.ts +++ b/examples/chat/angular/src/app/modes/sidebar-mode.component.ts @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT import { Component, ChangeDetectionStrategy, inject } from '@angular/core'; -import { ChatSidebarComponent, ChatWelcomeSuggestionComponent } from '@ngaf/chat'; +import { ChatSidebarComponent, ChatWelcomeSuggestionComponent, a2uiBasicCatalog } from '@ngaf/chat'; import { DEMO_AGENT } from '../shell/shell-tokens'; import { WELCOME_SUGGESTIONS } from './welcome-suggestions'; @@ -15,7 +15,7 @@ import { WELCOME_SUGGESTIONS } from './welcome-suggestions'; Click the launcher button (right edge) to slide in the chat panel.

- +
@for (s of suggestions; track s.value) { . + protected readonly catalog = a2uiBasicCatalog(); protected send(text: string): void { void this.agent.submit({ message: text }); diff --git a/libs/chat/src/lib/compositions/chat-popup/chat-popup.component.ts b/libs/chat/src/lib/compositions/chat-popup/chat-popup.component.ts index e1db3272f..83d0102f1 100644 --- a/libs/chat/src/lib/compositions/chat-popup/chat-popup.component.ts +++ b/libs/chat/src/lib/compositions/chat-popup/chat-popup.component.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MIT import { Component, ChangeDetectionStrategy, input, model, DestroyRef, inject, DOCUMENT, effect } from '@angular/core'; import type { Agent } from '../../agent'; +import type { ViewRegistry } from '@ngaf/render'; import { ChatComponent } from '../chat/chat.component'; import { ChatLauncherButtonComponent } from '../../primitives/chat-launcher-button/chat-launcher-button.component'; import { CHAT_HOST_TOKENS } from '../../styles/chat-tokens'; @@ -63,7 +64,7 @@ import { CHAT_HOST_TOKENS } from '../../styles/chat-tokens'; - +
@@ -71,6 +72,10 @@ import { CHAT_HOST_TOKENS } from '../../styles/chat-tokens'; }) export class ChatPopupComponent { readonly agent = input.required(); + /** A2UI component catalog forwarded to the inner . Without it, + * messages classified as A2UI parse correctly but never mount a + * surface. Pass `a2uiBasicCatalog()` from `@ngaf/chat`. */ + readonly views = input(undefined); readonly open = model(false); /** * Keyboard shortcut (single key) that toggles the popup with cmd (mac) diff --git a/libs/chat/src/lib/compositions/chat-sidebar/chat-sidebar.component.ts b/libs/chat/src/lib/compositions/chat-sidebar/chat-sidebar.component.ts index 5c18780e7..24f7eff87 100644 --- a/libs/chat/src/lib/compositions/chat-sidebar/chat-sidebar.component.ts +++ b/libs/chat/src/lib/compositions/chat-sidebar/chat-sidebar.component.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MIT import { Component, ChangeDetectionStrategy, input, model } from '@angular/core'; import type { Agent } from '../../agent'; +import type { ViewRegistry } from '@ngaf/render'; import { ChatComponent } from '../chat/chat.component'; import { CHAT_HOST_TOKENS } from '../../styles/chat-tokens'; @@ -56,7 +57,7 @@ import { CHAT_HOST_TOKENS } from '../../styles/chat-tokens'; - + @@ -64,6 +65,10 @@ import { CHAT_HOST_TOKENS } from '../../styles/chat-tokens'; }) export class ChatSidebarComponent { readonly agent = input.required(); + /** A2UI component catalog forwarded to the inner . Without it, + * messages classified as A2UI parse correctly but never mount a + * surface. Pass `a2uiBasicCatalog()` from `@ngaf/chat`. */ + readonly views = input(undefined); readonly open = model(false); readonly pushContent = input(false); From 426ddf0f5d2f9738c8a46bba7b857318958e69fb Mon Sep 17 00:00:00 2001 From: Brian Love Date: Sat, 9 May 2026 11:22:40 -0700 Subject: [PATCH 2/2] docs(chat): regenerate API docs for new chat-popup/sidebar views input --- apps/website/content/docs/chat/api/api-docs.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/website/content/docs/chat/api/api-docs.json b/apps/website/content/docs/chat/api/api-docs.json index 3119b1690..9efb436aa 100644 --- a/apps/website/content/docs/chat/api/api-docs.json +++ b/apps/website/content/docs/chat/api/api-docs.json @@ -1747,6 +1747,12 @@ "type": "InputSignal", "description": "Keyboard shortcut (single key) that toggles the popup with cmd (mac)\nor ctrl (other). Set to `null` to disable. Default: 'k' — matches the\nwidely-used cmd/ctrl+K convention.", "optional": false + }, + { + "name": "views", + "type": "InputSignal>> | undefined>", + "description": "A2UI component catalog forwarded to the inner . Without it,\nmessages classified as A2UI parse correctly but never mount a\nsurface. Pass `a2uiBasicCatalog()` from `@ngaf/chat`.", + "optional": false } ], "methods": [ @@ -1963,6 +1969,12 @@ "type": "InputSignal", "description": "", "optional": false + }, + { + "name": "views", + "type": "InputSignal>> | undefined>", + "description": "A2UI component catalog forwarded to the inner . Without it,\nmessages classified as A2UI parse correctly but never mount a\nsurface. Pass `a2uiBasicCatalog()` from `@ngaf/chat`.", + "optional": false } ], "methods": [