Skip to content

feat: pair Gemini + CopilotKit in the Composer assistant - #215

Merged
jgindin merged 8 commits into
a2ui-project:mainfrom
jerelvelarde:jerel/copilotkit-assistant-refresh
Oct 2, 2026
Merged

jgindin merged 8 commits into
a2ui-project:mainfrom
jerelvelarde:jerel/copilotkit-assistant-refresh

Conversation

@jerelvelarde

@jerelvelarde jerelvelarde commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Gemini + CopilotKit for Composer

Pair Gemini's A2UI generation and editing with CopilotKit's Angular chat interface in Composer's dockable Assistant panel. Users can ask Gemini to change the current design and see the updated canvas alongside readable CopilotKit message cards.

Gemini remains the model behind the assistant. CopilotKit provides the chat presentation, message cards, and prompt composer; Composer's existing services manage model requests, catalog instructions, history, validation, and editor updates. This uses @copilotkit/angular@0.5.2 (MIT) and supersedes #122.

The renderer menu that used to be in the prompt pill is now its own PR, #244, so it can be discussed separately.

Two chat panels, chosen through DI

CopilotKit is optional. Composer can be built without it by removing one provider.

  • ChatPanelBase holds what both panels share. It is main's ChatPanel logic, unchanged apart from submitPrompt repeating the Send button's checks, because pressing Enter calls it directly.
  • ChatPanel is the dependency-free panel: main's template, styles, and unit spec, unchanged.
  • CopilotKitChatPanel extends the same base and renders it with CopilotKit. It prepares its own view of the history, because CopilotKit renders turns as Markdown and has to show a summary instead of raw canvas JSON; the code comments explain each difference. All CopilotKit code (component, template, styles, theme) lives in chat/copilotkit-chat-panel/, and global_styles.scss is unchanged from main.
  • CHAT_PANEL_COMPONENT decides which one the workspace mounts. It defaults to ChatPanel; the open-source app.config.ts provides CopilotKitChatPanel. That provider is the only reference to CopilotKit outside its folder, so a build that drops it never imports @copilotkit/angular.
  • FORCE_PLAIN_CHAT_PANEL, a local storage flag next to the existing force-1P/3P flags, makes the app mount the plain panel, so browser tests cover both panels.

The CopilotKit panel and the plain panel after the same edit, in light and dark (CopilotKit first in each pair):

CopilotKit panel and plain panel, light and dark

Input and editing behavior

These live in the shared services, so both panels get them.

  • Synchronize the current sanitized canvas immediately before every prompt. Imported designs and quick editor changes reach Gemini even before the debounced history update runs.
  • Tell Gemini to use the current surface IDs, component IDs, and bindings. Literal text changes use component updates; bound values use data-model updates.
  • Preserve the full draft when applying incremental edits, and reject updates to surfaces that don't exist, keeping the last valid canvas. ChatCoordinator.resolveLayoutUpdate documents how.
  • Resynchronize a restored draft after model changes so undoing an edit does not leave Gemini with stale context.
  • Don't treat a cancelled stream as an error. Cancellation leaves the last valid canvas intact.

The CopilotKit panel adds a single rounded input pill with an Add (+) menu for files, screenshot inclusion, and instructions. Screenshot inclusion has a checked state and an indicator on the Add button, and the menu follows the theme and supports keyboard navigation. The input stays mounted while canvas snapshots refresh, so typed text, focus, and an open menu survive. Attachments, screenshots, instructions, retry, stop, copy controls, and both themes are preserved.

See it in action

Gemini editing the current canvas through the CopilotKit panel, then the plain panel

Full-resolution MP4. Recorded on d2c581f, before the rebase onto the latest main, against the local dev servers with a deterministic local Gemini-response fixture: the CopilotKit panel editing a one-Text draft, the Add menu, and dark mode, then the same edit in the plain panel. No live model request or real API key is used.

Tests

  • Both panels: the custom-instructions and malformed-stream journeys run against the CopilotKit panel and the plain panel.
  • Assistant journeys: they open their draft through a shared design link rather than Monaco's API, and answer Gemini from a documented in-page fixture with no timers.
  • Accessibility: @axe-core/playwright (a new dev dependency) scans the chat panel against WCAG 2 A and AA in light and dark mode.
  • Visual: a new visual test screenshots the CopilotKit panel in dark mode, with a Linux baseline, to catch a white fade reappearing above the prompt.

Validation

Rebased onto the current main (492122b, including #245's move to ErrorLogger, which the shared chat panel base follows); head is 3b50ad1.

  • Formatting, lint, typechecks, the production build, and all 1,658 shell unit tests pass.
  • Full e2e: all 80 pass, with no failures or retries.

Scope and merge order

This PR doesn't depend on #129 and can merge on its own. #244 (the renderer menu) builds on it, and #216 builds on #244.

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request integrates CopilotKit into the chat panel, replacing custom chat UI elements with CopilotKit components, and updates the shell's global styles and typography to use 'Plus Jakarta Sans' and 'Spline Sans Mono'. It also enhances the Components Gallery to support live editing of component properties and raw JSON drafts, enabling a seamless handoff to the Composer workspace. Feedback from the review highlights several critical opportunities to prevent runtime TypeError crashes, specifically by adding defensive nullish fallbacks for message content, safely accessing nested component arrays, handling unresolved property schemas, and robustly validating boolean or array types within JSON Schema matching.

Comment thread shell/src/app/gallery/gallery.ts Resolved
@jerelvelarde jerelvelarde changed the title feat: use controlled CopilotKit views for the Composer assistant feat: pair Gemini + CopilotKit in the Composer assistant Sep 16, 2026
@jerelvelarde
jerelvelarde marked this pull request as ready for review September 16, 2026 19:12
@jerelvelarde

Copy link
Copy Markdown
Contributor Author

Worked through all five automated review findings. Every one is already prevented on the current head — but four of the guards live in files the review did not have in view, and one was fixed in the commit immediately after the reviewed revision (9a44e4a → 8365fee). Details are in the per-comment replies; summary:

Finding Status Guard
isLayoutSnapshot(message.content) No change needed isLayoutSnapshot(text?: string) returns false for nullish input; already covered
updateComponents.components.length Test added .filter(isRenderA2uiItem) requires Array.isArray(components)
resolveComponentPropertiesSchema() ?? {} No change needed Non-nullable return; every early exit returns {}
Boolean JSON Schema sub-schemas Test added resolvePropertySchema normalizes non-objects to {} on both entry paths
Array-valued schema type Already fixed Implemented in 8365fee, covered by an existing test

No production code changed. What the findings did expose is that three of these guards were enforced only by convention — a later refactor could have removed one without failing a test. f1e3f5d adds a regression test for each of those three, exercising the exact crash scenarios described.

Validation on f1e3f5d: 1,626 unit tests across 85 files pass (up from 1,623), shell line coverage 93.55%. Prettier, ESLint, and tsc --noEmit across both workspaces are clean.

@github-actions

github-actions Bot commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

⚡ A2UI Composer PR Preview

Your automated preview is successfully live (commit 344db54):
👉 Launch PR Preview

Note: This environment will be wiped automatically when the PR is merged or closed.

@jerelvelarde

Copy link
Copy Markdown
Contributor Author

Synced with main through #217, #119, and #129. Head is now 49df21f.

Main changed the same chat-panel files this branch rewrites, so I carried its changes into the CopilotKit panel:

  • The parse-error card gets role="alert", aria-live="assertive", an aria-hidden icon, and a labelled Errors-tab button, with main's tests ported.
  • The Instructions item in the + menu shows Instructions (includes N MCP Servers) when the active catalog supports MCP, with main's test adapted to the menu.
  • getParseErrorMessage takes the typed FailureParseResult.

Two other fixes: the disabled assistant panel is back on --mat-sys-surface to match #119's theme test, and the assistant e2e expects main's new default model, gemini-3.7-flash.

@jerelvelarde

Copy link
Copy Markdown
Contributor Author

Synced with the latest main (6794bc1: #197, #230) through the updated #217, #119, and #129. Head is now 9c8d101.

  • Gallery merge. This branch's array-valued schema type support is kept alongside feat(gallery): reorganize and edit selected-catalog examples #129's rewritten property matcher, and its Gallery tests now pass the ErrorLogger that Error handling/4 telemetry and e2e #197 added to the schema resolver.
  • Main's new malformed-stream chat test. It sends a prompt before any renderer has announced a catalog. This branch intentionally doesn't submit until a catalog is active, and the test's stub renderer never announces one. So the test now loads the real sample renderer first; its parse-error card and Errors-tab assertions are unchanged, and they pass.

Formatting, lint, typechecks, the production build, and all unit tests pass. Full e2e before that test fix: 79 passed, with 1 flaky (the React shrink test that is also flaky on unchanged main); the fixed spec now passes on its own.

@jerelvelarde

Copy link
Copy Markdown
Contributor Author

Synced with main (834cafe) through #129. Head is now 9fee5c5.

  • Custom Instructions (Custom instructions #233) is now an item in the + menu, next to Instructions. It shows the active preset's name, and main's tests are adapted to the menu.
  • Assistant journeys. Main's basic catalog now declares callMcpTool, so the Instructions item reads "Instructions (includes N MCP Servers)". Main also changed the default model again. The journeys now match the item by its leading name, and check the Gemini streaming endpoint without pinning a model name.

Full e2e: everything passes apart from main's custom-instructions journey, which fails the same way on unchanged main.

jerelvelarde added a commit to jerelvelarde/a2ui-composer that referenced this pull request Sep 25, 2026
A catalog property typed as, e.g., ["string", "null"] now accepts any of the listed types. Moved here from a2ui-project#215 so the assistant PR no longer changes Gallery code.
jerelvelarde added a commit to jerelvelarde/a2ui-composer that referenced this pull request Sep 25, 2026
A renderer-owned Slack catalog (samples/react-slack-catalog, served on
port 3460) previews A2UI surfaces as Slack Block Kit messages and
exports the same validated Block Kit JSON, with no Slack credentials.

- 11 Slack components with Gallery examples, native TextField and
  date-only DateTimeInput controls with writable bindings, aligned
  tables, diagnostics for approximated layouts, and Block Kit export.
- A built-in Book a Car form as real canvas content.
- Catalog-aware Gemini/CopilotKit generation and refinement, plus a
  switchRenderer frontend tool; catalog responses from a detached or
  replaced renderer cannot overwrite the active catalog.
- Slack assets in local, GitHub Pages, and PR-preview builds.
- Browser journeys wait for the preview to settle before interacting,
  so a late render cannot reset a field mid-typing.

Built on a2ui-project#215 and independent of the Gallery redesign in a2ui-project#129; the
Gallery journey reads examples from main's Gallery.
@jerelvelarde
jerelvelarde force-pushed the jerel/copilotkit-assistant-refresh branch from 9fee5c5 to 31bef1b Compare September 25, 2026 16:04
@jerelvelarde

Copy link
Copy Markdown
Contributor Author

Rebuilt so this no longer depends on #129. The branch is now one commit on main (19b54a0), covering 28 files and no Gallery code. The array-valued schema fix it used to carry moved into #129, where that code lives. The assistant journeys now start from a draft written through the JSON editor instead of going through #129's Gallery editor. The description and recording are updated.

Full e2e: 78 pass, including all six assistant journeys. The one failure is main's custom-instructions journey, which fails the same way on unchanged main.

}

private resolveLayoutUpdate(updates: unknown[]): unknown[] {
const surfaceId = (item: unknown, command: string): string | undefined => {

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.

Please add comments explaining what this is all doing and why it needs to be done.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added a doc comment in a2dfceb, plus inline comments on each step.

In short, the model can answer an edit two ways. A full replacement starts with createSurface and replaces the draft as-is. An incremental edit only sends updates to surfaces that already exist. Because the editor replays the whole message list to render the preview, an incremental edit written on its own would erase the draft, so it's appended to the current draft instead.

Before appending, every surface an update targets is checked against the surfaces the draft and the response create (minus the ones they delete). If the model invents a surface ID, for example by copying one from the prompt's examples, it's rejected like any other validation failure and the draft is left unchanged.

import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {FileIngestionService, AttachedFile} from '../file-ingestion/file-ingestion.service';
import {FormsModule} from '@angular/forms';
import {

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.

Instead of these changes, I think we need to have separate chat panel implementations for the "plain chat panel" and the CopilotKit chat panel, and use dependency injection.

We could have a common base class that has all the common stuff.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in a2dfceb.

  • ChatPanelBase holds the shared state and actions: submitting and retrying prompts, attachments, screenshots, renderer switching, the instruction dialogs, and how history is presented.
  • ChatPanel is the plain panel. Its template and styles are main's, unchanged, and it now extends the base.
  • CopilotKitChatPanel extends the same base. It lives in chat/copilotkit-chat-panel/ with its template, styles, and theme, so global_styles.scss is back to main's.
  • The workspace mounts whatever CHAT_PANEL_COMPONENT provides. It defaults to ChatPanel, and the open-source app.config.ts provides the CopilotKit panel. That provider is the only reference to CopilotKit outside its folder, so a build that drops it never imports @copilotkit/angular.

Both panels after the same edit, light and dark (CopilotKit on the left of each pair, the plain panel on the right):

CopilotKit panel and plain panel, light and dark

jerelvelarde added a commit to jerelvelarde/a2ui-composer that referenced this pull request Sep 25, 2026
A renderer-owned Slack catalog (samples/react-slack-catalog, served on
port 3460) previews A2UI surfaces as Slack Block Kit messages and
exports the same validated Block Kit JSON, with no Slack credentials.

- 11 Slack components with Gallery examples, native TextField and
  date-only DateTimeInput controls with writable bindings, aligned
  tables, diagnostics for approximated layouts, and Block Kit export.
- A built-in Book a Car form as real canvas content.
- Catalog-aware Gemini/CopilotKit generation and refinement, plus a
  switchRenderer frontend tool; catalog responses from a detached or
  replaced renderer cannot overwrite the active catalog.
- Slack assets in local, GitHub Pages, and PR-preview builds.
- Browser journeys wait for the preview to settle before interacting,
  so a late render cannot reset a field mid-typing.

Built on a2ui-project#215 and independent of the Gallery redesign in a2ui-project#129; the
Gallery journey reads examples from main's Gallery.
jerelvelarde added a commit to jerelvelarde/a2ui-composer that referenced this pull request Sep 25, 2026
A renderer-owned Slack catalog (samples/react-slack-catalog, served on
port 3460) previews A2UI surfaces as Slack Block Kit messages and
exports the same validated Block Kit JSON, with no Slack credentials.

- 11 Slack components with Gallery examples, native TextField and
  date-only DateTimeInput controls with writable bindings, aligned
  tables, diagnostics for approximated layouts, and Block Kit export.
- A built-in Book a Car form as real canvas content.
- Catalog-aware Gemini/CopilotKit generation and refinement, plus a
  switchRenderer frontend tool; catalog responses from a detached or
  replaced renderer cannot overwrite the active catalog.
- Slack assets in local, GitHub Pages, and PR-preview builds.
- Browser journeys wait for the preview to settle before interacting,
  so a late render cannot reset a field mid-typing.

Built on a2ui-project#215 and independent of the Gallery redesign in a2ui-project#129; the
Gallery journey reads examples from main's Gallery.
@jgindin

jgindin commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

Why are we adding a renderer selector to the chat panel? This might be interesting, but it seems to be orthogonal to the point of this PR. I'd be interested in hearing more about this aspect of the change -- it might well be a nice additional, but I don't know that changing renderers is something users will do that often. But, I also recognize that as you look to bring more renderers on board (Slack, Teams, etc...), it actually might be very useful.

I suggest we pull this out from this PR and create a new PR with just that feature so we can discuss it more.

}
}

function installFixtureInBrowser(fakeApiKey: string): void {

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.

This is doing a bunch of surgery... Can we add comments to the code explaining what's going on and why?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added comments in e3259dc. The file header explains why it works this way: Composer calls Gemini from the browser through the @google/genai SDK, which streams server-sent events over fetch. page.route can only answer with a complete body, so it can't send a response chunk by chunk or hold one open until the app cancels it, which the Stop journey needs. So the fixture replaces window.fetch inside the page before the app loads: it records Gemini requests for assertions, answers them from scripted scenarios, and passes every other request through. Each part now has a comment.

Comment thread shell/e2e/helpers/gemini-fixture.ts Outdated
return headers;
};

const sleep = (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms));

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.

I've found that using timeouts often leads to flaky tests or, if you make the timeout high enough to never be flaky, it causes the tests to just take a lot longer to run.

Are there elements we can use and wait for them to be "ready", taking advantage of Playwright's support for this instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed. The delay is gone in e3259dc. The only journey that used it was the Stop test, and it already holds the stream open until the app cancels it, so it waits for the Stop button, clicks it, and checks the result. There are no timers left in the fixture or the assistant journeys.

Comment thread shell/e2e/copilot-assistant.e2e.ts Outdated
];
await page.evaluate(
json => {
const monaco = (

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.

This feels like we're cheating :-)

Shouldn't we be locating the DOM element, and using locator.fill to populate the text?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fair point. locator.fill can't reach Monaco here: in Chrome, Monaco takes input through a textbox element that uses the EditContext API rather than a <textarea>, and typed or inserted text goes through its auto-closing brackets, which rewrites the JSON. So in e3259dc the journeys open their draft the way a user opens a shared design link: the test encodes the JSON exactly as the Share button does (d1. + deflate-raw + base64url, see QueryParser.compressPayload) and navigates to #a2ui=…. Nothing writes through Monaco's API anymore. Reading the draft back still uses the model, like main's getMonacoContent helper.

Comment thread shell/e2e/copilot-assistant.e2e.ts Outdated

function replaceDraftText(draft: string, from: string, to: string): string {
const updated = draft.replaceAll(from, to);
expect(updated).not.toBe(draft);

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.

This expect seems like we're just testing the Javascript runtime.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed in e3259dc.

Comment thread shell/e2e/copilot-assistant.e2e.ts Outdated

function luminanceChannel(channel: number): number {
const normalized = channel / 255;
return normalized <= 0.03928 ? normalized / 12.92 : Math.pow((normalized + 0.055) / 1.055, 2.4);

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.

Please add a comment explaining all of this and the magic numbers.

Same for the contrastRatio method below.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Those were the WCAG relative-luminance and contrast-ratio formulas. They're gone in e3259dc, replaced by axe (see the next comment).

readonly sharedA2uiError = this.startupConfigState.sharedA2uiError;

private readonly destroyRef = inject(DestroyRef);
private rendererSelectionRequest = 0;

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.

Why do we need to count the number of requests to change the renderer?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It detected that another renderer selection had started (or startup had re-resolved the renderer) while the origin confirmation dialog was open, so the older request wouldn't overwrite the newer choice when the user answered. In #244 the counter is gone, per your next comment.

this.startupConfigState.setSelectedRendererId(rendererId);
async setSelectedRendererId(rendererId: string | null, signal?: AbortSignal): Promise<boolean> {
signal?.throwIfAborted();
const request = ++this.rendererSelectionRequest;

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.

Instead of counting requests, can we just check if the desired renderer is not the active renderer (below on line 93)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes. In #244 it compares the selection before and after the confirmation instead: if the selected renderer, the resolved URL, or that renderer's configured URL changed while the dialog was open, the request is stale and is rejected. There's a comment explaining it.

const rendererUrl = active?.rendererUrl;
if (rendererUrl) {
const isAllowed = signal
? await this.isOriginAllowed(rendererUrl, signal)

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.

Since the signal parameter to the isOriginAllowed method is optional, there's no need to use the elvis operator.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in #244.


async isOriginAllowed(url: string): Promise<boolean> {
private parseRendererUrl(url: string): URL {
const baseUrl = globalThis.document?.baseURI || this.environmentContext.getBaseOrigin();

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.

Why the added check against globalThis.document?.baseURI?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same reason as in RenderedFrame: it resolves a page-relative renderer URL like samples/react-slack-catalog/ under the sub-path Composer is served from. It moved to #216, which adds that renderer; #244 resolves renderer URLs the way main does.

const confirmed = await this.confirmOrigin(origin);
const confirmed = signal
? await this.confirmOrigin(origin, signal)
: await this.confirmOrigin(origin);

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.

Again, the signal parameter is optional, so this can be simplified.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in #244.

jerelvelarde added a commit to jerelvelarde/a2ui-composer that referenced this pull request Sep 29, 2026
A renderer-owned Slack catalog (samples/react-slack-catalog, served on
port 3460) previews A2UI surfaces as Slack Block Kit messages and
exports the same validated Block Kit JSON, with no Slack credentials.

- 11 Slack components with Gallery examples, native TextField and
  date-only DateTimeInput controls with writable bindings, aligned
  tables, diagnostics for approximated layouts, and Block Kit export.
- A built-in Book a Car form as real canvas content.
- Catalog-aware Gemini/CopilotKit generation and refinement, plus a
  switchRenderer frontend tool; catalog responses from a detached or
  replaced renderer cannot overwrite the active catalog.
- Slack assets in local, GitHub Pages, and PR-preview builds.
- Browser journeys wait for the preview to settle before interacting,
  so a late render cannot reset a field mid-typing.

Built on a2ui-project#215 and independent of the Gallery redesign in a2ui-project#129; the
Gallery journey reads examples from main's Gallery.
@jerelvelarde

Copy link
Copy Markdown
Contributor Author

Why are we adding a renderer selector to the chat panel? [...] I suggest we pull this out from this PR and create a new PR with just that feature so we can discuss it more.

Thanks for the thorough review. The renderer selector is now its own PR, #244. The reason for it is the one you mention: with the Slack renderer, and more formats to come, switching output formats from the prompt instead of Settings is what makes that workflow practical. #216 now builds on #244, since its assistant tool switches to the Slack renderer when you ask for a Slack message.

This PR is now the CopilotKit panel behind DI plus the general fixes. The shared chat panel base is main's ChatPanel again apart from the Enter-key guard, both panels are covered by the browser journeys, and the test changes you asked for are in. Replies to each comment are inline; the renderer ones point to #244, and the base-URL ones to #216.

@jerelvelarde
jerelvelarde force-pushed the jerel/copilotkit-assistant-refresh branch from d2c581f to ba97421 Compare September 30, 2026 15:51
jerelvelarde added a commit to jerelvelarde/a2ui-composer that referenced this pull request Sep 30, 2026
A renderer-owned Slack catalog (samples/react-slack-catalog, served on
port 3460) previews A2UI surfaces as Slack Block Kit messages and
exports the same validated Block Kit JSON, with no Slack credentials.

- 11 Slack components with Gallery examples, native TextField and
  date-only DateTimeInput controls with writable bindings, aligned
  tables, diagnostics for approximated layouts, and Block Kit export.
- A built-in Book a Car form as real canvas content.
- Catalog-aware Gemini/CopilotKit generation and refinement, plus a
  switchRenderer frontend tool; catalog responses from a detached or
  replaced renderer cannot overwrite the active catalog.
- Slack assets in local, GitHub Pages, and PR-preview builds.
- Browser journeys wait for the preview to settle before interacting,
  so a late render cannot reset a field mid-typing.

Built on a2ui-project#215 and independent of the Gallery redesign in a2ui-project#129; the
Gallery journey reads examples from main's Gallery.
Gemini keeps generating and editing A2UI; CopilotKit's Angular chat
interface (@copilotkit/angular 0.5.2) provides the Assistant panel's
message cards and prompt composer. Composer's existing services still
own model requests, catalog instructions, history, validation, and
editor updates.

- One rounded input pill with an Add (+) menu for attachments, a
  screenshot, Instructions, and Custom Instructions, plus a renderer
  selector that waits for the new renderer's catalog.
- The current canvas is synchronized before every prompt, and the model
  is told to use the draft's real surface IDs, component IDs, and
  bindings. Incremental edits keep the full draft; updates to unknown
  surfaces are rejected and the last valid canvas is kept.
- The conversation input survives canvas snapshot changes, and a
  restored draft is resynchronized after model changes.
- Attachments, screenshots, retry, stop, copy, the parse-error card, and
  light/dark themes are preserved.

This branch is built directly on main and no longer depends on the
Gallery redesign in a2ui-project#129: the assistant journeys open their draft
through the JSON editor instead of the Gallery.
The chat panel's shared state and actions move into ChatPanelBase. The
dependency-free ChatPanel (main's template) and CopilotKitChatPanel both
extend it, and the workspace mounts whichever CHAT_PANEL_COMPONENT names.
The token defaults to ChatPanel; the open-source app provides the
CopilotKit panel in app.config.ts. All CopilotKit code, including its
stylesheet and theme, now lives in chat/copilotkit-chat-panel, so a build
without that provider never imports @copilotkit/angular.

Also documents resolveLayoutUpdate in ChatCoordinator.
…d name

The selector no longer maps specific renderer IDs to its own labels, so new
or internal renderers need no code change to appear correctly.
…nstructions journey

The CopilotKit panel moves both instruction links into the Add (+) menu.
The journey also waits for the dialog's open animation before measuring it.
…se as main's panel

- Remove the renderer selector (the pill menu, RendererSelection, and the
  renderer switching changes in StartupResolution, SettingsService, and
  RenderedFrame). It moves to its own PR so it can be discussed separately.
- ChatPanelBase is main's ChatPanel logic again: its history, status text,
  and bubble classes are unchanged. The only differences are that the class
  is abstract, two fields are protected, and submitPrompt repeats the Send
  button's checks because pressing Enter calls it directly.
- The CopilotKit panel prepares its own view of the history, with comments
  on how and why it differs from the plain panel.
- Add FORCE_PLAIN_CHAT_PANEL, a local storage flag that makes the app mount
  the plain panel, so browser tests can cover both panels.
- Run the custom instructions and malformed-stream journeys against both
  the CopilotKit panel and the plain panel.
- Open the assistant journeys' draft through a shared design link instead
  of setting Monaco's model directly.
- Check the chat panel with @axe-core/playwright instead of a hand-written
  contrast calculation.
- Replace the dark-mode gradient check with a visual test of the CopilotKit
  panel.
- Document the Gemini fixture and drop its timed delay; the Stop journey
  already holds the stream open until the app cancels it.
@jerelvelarde
jerelvelarde force-pushed the jerel/copilotkit-assistant-refresh branch from ba97421 to 3b50ad1 Compare October 1, 2026 21:58
jerelvelarde added a commit to jerelvelarde/a2ui-composer that referenced this pull request Oct 1, 2026
A renderer-owned Slack catalog (samples/react-slack-catalog, served on
port 3460) previews A2UI surfaces as Slack Block Kit messages and
exports the same validated Block Kit JSON, with no Slack credentials.

- 11 Slack components with Gallery examples, native TextField and
  date-only DateTimeInput controls with writable bindings, aligned
  tables, diagnostics for approximated layouts, and Block Kit export.
- A built-in Book a Car form as real canvas content.
- Catalog-aware Gemini/CopilotKit generation and refinement, plus a
  switchRenderer frontend tool; catalog responses from a detached or
  replaced renderer cannot overwrite the active catalog.
- Slack assets in local, GitHub Pages, and PR-preview builds.
- Browser journeys wait for the preview to settle before interacting,
  so a late render cannot reset a field mid-typing.

Built on a2ui-project#215 and independent of the Gallery redesign in a2ui-project#129; the
Gallery journey reads examples from main's Gallery.
jerelvelarde added a commit to jerelvelarde/a2ui-composer that referenced this pull request Oct 1, 2026
A catalog property typed as, e.g., ["string", "null"] now accepts any of the listed types. Moved here from a2ui-project#215 so the assistant PR no longer changes Gallery code.
@jgindin
jgindin merged commit 5b563f9 into a2ui-project:main Oct 2, 2026
13 checks passed
jerelvelarde added a commit to jerelvelarde/a2ui-composer that referenced this pull request Oct 2, 2026
A renderer-owned Slack catalog (samples/react-slack-catalog, served on
port 3460) previews A2UI surfaces as Slack Block Kit messages and
exports the same validated Block Kit JSON, with no Slack credentials.

- 11 Slack components with Gallery examples, native TextField and
  date-only DateTimeInput controls with writable bindings, aligned
  tables, diagnostics for approximated layouts, and Block Kit export.
- A built-in Book a Car form as real canvas content.
- Catalog-aware Gemini/CopilotKit generation and refinement, plus a
  switchRenderer frontend tool; catalog responses from a detached or
  replaced renderer cannot overwrite the active catalog.
- Slack assets in local, GitHub Pages, and PR-preview builds.
- Browser journeys wait for the preview to settle before interacting,
  so a late render cannot reset a field mid-typing.

Built on a2ui-project#215 and independent of the Gallery redesign in a2ui-project#129; the
Gallery journey reads examples from main's Gallery.
jerelvelarde added a commit to jerelvelarde/a2ui-composer that referenced this pull request Oct 5, 2026
A renderer-owned Slack catalog (samples/react-slack-catalog, served on
port 3460) previews A2UI surfaces as Slack Block Kit messages and
exports the same validated Block Kit JSON, with no Slack credentials.

- 11 Slack components with Gallery examples, native TextField and
  date-only DateTimeInput controls with writable bindings, aligned
  tables, diagnostics for approximated layouts, and Block Kit export.
- A built-in Book a Car form as real canvas content.
- Catalog-aware Gemini/CopilotKit generation and refinement, plus a
  switchRenderer frontend tool; catalog responses from a detached or
  replaced renderer cannot overwrite the active catalog.
- Slack assets in local, GitHub Pages, and PR-preview builds.
- Browser journeys wait for the preview to settle before interacting,
  so a late render cannot reset a field mid-typing.

Built on a2ui-project#215 and independent of the Gallery redesign in a2ui-project#129; the
Gallery journey reads examples from main's Gallery.
jerelvelarde added a commit to jerelvelarde/a2ui-composer that referenced this pull request Oct 6, 2026
A renderer-owned Slack catalog (samples/react-slack-catalog, served on
port 3460) previews A2UI surfaces as Slack Block Kit messages and
exports the same validated Block Kit JSON, with no Slack credentials.

- 11 Slack components with Gallery examples, native TextField and
  date-only DateTimeInput controls with writable bindings, aligned
  tables, diagnostics for approximated layouts, and Block Kit export.
- A built-in Book a Car form as real canvas content.
- Catalog-aware Gemini/CopilotKit generation and refinement, plus a
  switchRenderer frontend tool; catalog responses from a detached or
  replaced renderer cannot overwrite the active catalog.
- Slack assets in local, GitHub Pages, and PR-preview builds.
- Browser journeys wait for the preview to settle before interacting,
  so a late render cannot reset a field mid-typing.

Built on a2ui-project#215 and independent of the Gallery redesign in a2ui-project#129; the
Gallery journey reads examples from main's Gallery.
jerelvelarde added a commit to jerelvelarde/a2ui-composer that referenced this pull request Oct 9, 2026
A catalog property typed as, e.g., ["string", "null"] now accepts any of the listed types. Moved here from a2ui-project#215 so the assistant PR no longer changes Gallery code.
jerelvelarde added a commit to jerelvelarde/a2ui-composer that referenced this pull request Oct 9, 2026
A renderer-owned Slack catalog (samples/react-slack-catalog, served on
port 3460) previews A2UI surfaces as Slack Block Kit messages and
exports the same validated Block Kit JSON, with no Slack credentials.

- 11 Slack components with Gallery examples, native TextField and
  date-only DateTimeInput controls with writable bindings, aligned
  tables, diagnostics for approximated layouts, and Block Kit export.
- A built-in Book a Car form as real canvas content.
- Catalog-aware Gemini/CopilotKit generation and refinement, plus a
  switchRenderer frontend tool; catalog responses from a detached or
  replaced renderer cannot overwrite the active catalog.
- Slack assets in local, GitHub Pages, and PR-preview builds.
- Browser journeys wait for the preview to settle before interacting,
  so a late render cannot reset a field mid-typing.

Built on a2ui-project#215 and independent of the Gallery redesign in a2ui-project#129; the
Gallery journey reads examples from main's Gallery.
jerelvelarde added a commit to jerelvelarde/a2ui-composer that referenced this pull request Oct 15, 2026
A renderer-owned Slack catalog (samples/react-slack-catalog, served on
port 3460) previews A2UI surfaces as Slack Block Kit messages and
exports the same validated Block Kit JSON, with no Slack credentials.

- 11 Slack components with Gallery examples, native TextField and
  date-only DateTimeInput controls with writable bindings, aligned
  tables, diagnostics for approximated layouts, and Block Kit export.
- A built-in Book a Car form as real canvas content.
- Catalog-aware Gemini/CopilotKit generation and refinement, plus a
  switchRenderer frontend tool; catalog responses from a detached or
  replaced renderer cannot overwrite the active catalog.
- Slack assets in local, GitHub Pages, and PR-preview builds.
- Browser journeys wait for the preview to settle before interacting,
  so a late render cannot reset a field mid-typing.

Built on a2ui-project#215 and independent of the Gallery redesign in a2ui-project#129; the
Gallery journey reads examples from main's Gallery.
jerelvelarde added a commit to jerelvelarde/a2ui-composer that referenced this pull request Oct 16, 2026
A renderer-owned Slack catalog (samples/react-slack-catalog, served on
port 3460) previews A2UI surfaces as Slack Block Kit messages and
exports the same validated Block Kit JSON, with no Slack credentials.

- 11 Slack components with Gallery examples, native TextField and
  date-only DateTimeInput controls with writable bindings, aligned
  tables, diagnostics for approximated layouts, and Block Kit export.
- A built-in Book a Car form as real canvas content.
- Catalog-aware Gemini/CopilotKit generation and refinement, plus a
  switchRenderer frontend tool; catalog responses from a detached or
  replaced renderer cannot overwrite the active catalog.
- Slack assets in local, GitHub Pages, and PR-preview builds.
- Browser journeys wait for the preview to settle before interacting,
  so a late render cannot reset a field mid-typing.

Built on a2ui-project#215 and independent of the Gallery redesign in a2ui-project#129; the
Gallery journey reads examples from main's Gallery.
jerelvelarde added a commit to jerelvelarde/a2ui-composer that referenced this pull request Oct 19, 2026
A renderer-owned Slack catalog (samples/react-slack-catalog, served on
port 3460) previews A2UI surfaces as Slack Block Kit messages and
exports the same validated Block Kit JSON, with no Slack credentials.

- 11 Slack components with Gallery examples, native TextField and
  date-only DateTimeInput controls with writable bindings, aligned
  tables, diagnostics for approximated layouts, and Block Kit export.
- A built-in Book a Car form as real canvas content.
- Catalog-aware Gemini/CopilotKit generation and refinement, plus a
  switchRenderer frontend tool; catalog responses from a detached or
  replaced renderer cannot overwrite the active catalog.
- Slack assets in local, GitHub Pages, and PR-preview builds.
- Browser journeys wait for the preview to settle before interacting,
  so a late render cannot reset a field mid-typing.

Built on a2ui-project#215 and independent of the Gallery redesign in a2ui-project#129; the
Gallery journey reads examples from main's Gallery.
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.

2 participants