Skip to content

Implement v0.9 Angular Renderer#855

Open
gspencergoog wants to merge 21 commits intogoogle:mainfrom
gspencergoog:angular_renderer_core
Open

Implement v0.9 Angular Renderer#855
gspencergoog wants to merge 21 commits intogoogle:mainfrom
gspencergoog:angular_renderer_core

Conversation

@gspencergoog
Copy link
Collaborator

@gspencergoog gspencergoog commented Mar 13, 2026

Description

This adds the v0.9 angular renderer and a small demo of the renderer.

Summary of Changes

This pull request implements the v0.9 Angular renderer for A2UI, featuring a dynamic component hosting mechanism and a more flexible catalog system. It includes a new demo application, migrates the v0.8 renderer, and updates the web_core library to support the new version's requirements. The changes enhance the A2UI Angular renderer by introducing version 0.9, which features a dynamic component hosting mechanism and a more flexible catalog system.

Highlights

  • v0.9 Angular Renderer Implementation: Introduced the new v0.9 Angular renderer with a dynamic, signal-based architecture for A2UI surfaces.
  • Demo Application: Added a demo application to showcase the capabilities and integration of the v0.9 Angular renderer.
  • v0.8 Renderer Migration: Migrated the existing v0.8 Angular renderer components and catalog into a dedicated 'v0_8' folder for clearer separation and maintainability.
  • Core Library (web_core) Updates: Updated the core web_core library for v0.9, including changes to logical functions, date formatting, and expression schemas.
  • Dependency Management: Updated package dependencies across renderers to include @preact/signals-core for reactive programming and date-fns for enhanced date formatting.
  • Error Handling Improvements: Enhanced error handling within the web_core's DataContext to gracefully manage exceptions during reactive function evaluations.
Changelog
  • renderers/angular/CHANGELOG.md
    • Implemented renderer for v0.9 of A2UI.
  • renderers/web_core/CHANGELOG.md
    • Updated logical functions (and, or) to require a values array argument, removing deprecated individual arguments.
    • Updated formatDate to require format parameter to align with new configuration, utilizing date-fns.
    • Added date-fns dependency for expression string formatting workflows.
    • Updated math and comparison expression schemas with preprocessing step to correctly coerce null parameters into undefined for tighter validation constraints.
    • Fixed associated tests in expressions and rendering models corresponding to validation updates.

@github-project-automation github-project-automation bot moved this to Todo in A2UI Mar 13, 2026
@google google deleted a comment from gemini-code-assist bot Mar 14, 2026
@gspencergoog gspencergoog force-pushed the angular_renderer_core branch 2 times, most recently from c6f49ae to 09d769b Compare March 14, 2026 02:09
@gspencergoog gspencergoog changed the title Angular renderer core Implement v0.9 Angular Renderer Mar 14, 2026
gemini-code-assist[bot]

This comment was marked as resolved.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it customary to put these apps within the SDK folder for web? I noticed they definitely don't tend to be included in NPMs, but it does seem nice to bundle things together in the source at least. I guess that adds some complexity to package.json etc?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Maybe? I don't really know. I put it here because it made it easier to run (didn't have to change directories), but I could just as easily move it somewhere else. I wasn't thinking about the publishing implications, so maybe it would be better to move it. It's not really a "client" or a "server", though (it runs standalone with no server).

Copy link
Collaborator

Choose a reason for hiding this comment

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

If we segregate the codebases, can we avoid all these small changes to v0.8 stuff?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I could move these to another PR, but they are generally actual fixes for the 0.8 renderer that I encountered while implementing 0.9, or just restructuring the code layout so it is similar to 0.9 layout.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Part of the issue is that this PR is where we're adding a new version, so some of the v0.8 changes are necessary just to accommodate the move.

- Major Architecture Upgrade: Transitioned from static components per element (v0.8) to a fully dynamic component hosting paradigm (v0.9) with dynamic allocation and decoupled bindings (`ComponentHostComponent`).
- V0.8 Isolation: Extracted and locked existing legacy configurations into `/src/lib/v0_8/` workspaces backing backwards compatibility layout managers securely.
- Unified Inspector Client Dashboard: Set up a custom workspace host system dashboard (`/demo-app`) serving standalone layout widgets pipelines containing hot-reactive aggregates views.
- schemas and execution aligning updates: Fixed `BASIC_FUNCTIONS` aligning schemas and validation designs layout.
- License compliance instructions: Fixed `.github/workflows/check_license.yml` to help describe local license remediation diagnostics correctly rather than just failing triggers.
@gspencergoog gspencergoog force-pushed the angular_renderer_core branch from 87460e3 to c870088 Compare March 16, 2026 16:42
@google google deleted a comment from gemini-code-assist bot Mar 16, 2026
Expanded the try-catch block in `resolveSignal`'s effect to cover
argument resolution and ensure errors are dispatched to the surface.
Reset signal value to undefined instead of throwing to prevent crashing the effect.
Added a regression test to verify this behavior.
…ds\n\n- Tightened the `surfaceId` input type in `DynamicComponent` to be strictly\n non-nullable, reflecting its practical usage via the `Renderer`.\n- Removed redundant `if (surfaceId)` guards in form components:\n `CheckBox`, `DateTimeInput`, `MultipleChoice`, `Slider`, and `TextField`.\n- Removed unnecessary `as any` casts in `Image` and `Text` styling logic,\n as modern TypeScript correctly handles narrowed literal indexing.
…safety\n\n- Removed `as any` cast in `Button.handleClick` as `Action` types are compatible.\n- Improved indexing safety in `Image` and `Text` components by using the `in` operator\n as a type guard combined with a `keyof` assertion. This resolves\n 'implicitly has an any type' errors in strict compiler environments like\n the Angular sample app.
…oved path normalization logic for repeating children from `RowComponent` and\n `ColumnComponent` into a shared `getNormalizedPath` utility in `core/utils.ts`.\n- Added unit tests for `getNormalizedPath` in `core/utils.spec.ts`.\n- Updated `RowComponent` and `ColumnComponent` to use the shared utility.
…mplified `surfaceId()` access in `Checkbox`, `MultipleChoice`, and `Slider`.\n- Standardized quotes in `markdown.ts`.
…dererService\n\n- Access `model` directly from `_messageProcessor` instead of keeping a separate\n private reference.
…tion handling in A2uiRendererService

Updated A2uiRendererService to support multiple catalogs and independent renderer contexts by introducing a configuration-based initialization.
Refined action handling to use SurfaceGroupAction for explicit surface identification and simplified internal logic.
Updated ComponentHostComponent to resolve component types from the surface-level catalog, ensuring correct multi-catalog operation.
Fixed build errors in the demo app's AgentStubService by adopting the new initialization signature and providing necessary catalog dependencies.
Applied project-standard formatting across affected files.
…rerService

The method was inappropriate in a multi-catalog context and not used by the framework components. Callers should instead rely on the invoker provided by the catalog on the specific SurfaceModel. Updated unit tests for A2uiRendererService and ComponentHostComponent to reflect this change.
Updated getting started guide to reflect the multi-catalog initialization pattern using `RendererConfiguration`. Corrected the catalog definition example to use the proper base constructor and removed outdated provider instructions.
@gspencergoog gspencergoog marked this pull request as ready for review March 16, 2026 18:03
…r renderer

- Implemented comprehensive unit tests for all v0_8 components including Audio, Video, Icon, Divider, MultipleChoice, Tabs, Surface, DateTimeInput, and Modal.
- Improved coverage for DynamicComponent, MessageProcessor, MarkdownRenderer, and Text components.
- Added c8 for reliable coverage reporting in web_core.
- Created architectural overview documentation for web_core and polished angular README.
- Audited and removed redundant `as any` type casts to improve type safety.
- Verified all 145 tests pass and overall statement coverage is >96%.
- Replace 'any' with specific interfaces ('Example', 'Action', 'A2uiMessage') in demo app and agent stub.
- Update library unit tests to use Angular signals for 'BoundProperty' mocks.
- Ensure 'BoundProperty' mocks include 'value', 'raw', and 'onUpdate' to satisfy type definitions.
- Fix various mock mismatches in spec files discovered during testing.
- Format code with Prettier.
…ject

Ensures consistency with dataModelSub by storing the subscription object itself and calling .unsubscribe() directly in selectExample and ngOnDestroy.
gemini-code-assist[bot]

This comment was marked as resolved.

@google google deleted a comment from gemini-code-assist bot Mar 16, 2026
…type safety

- Merged capitalization and format string binding specs into 'function_binding.spec.ts'.
- Introduced 'BaseMinimalCatalog' to improve catalog extensibility and simplified 'DemoCatalog'.
- Added 'UpdatePropertyContext' and 'SubmitFormContext' interfaces for stronger typing in 'AgentStubService'.
- Implemented 'FormatStringImplementation' in 'web_core' to fix signal interpolation in template strings.
- Added 'Custom Price Slider' example to showcase reactive price formatting.
- Updated '@a2ui/web_core' dependencies across renderers.

Ref: Conversation 6a10b4d3, 4c97e1bd
…ol components

Moved versioned endpoints v0_8/ and v0_9/ alongside the primary src/ folder and populated secondary ng-package.json descriptors for discrete package exports @a2ui/angular/v0_x.

Emptied the root src/public-api.ts to enforce version choice.

Updated all sample app client files inside samples/client/angular/projects/ to import from @a2ui/angular/v0_8 and synchronized paths inside tsconfig.json mappings to support discrete angular compiler workspaces resolving on direct sources directly.

BREAKING-CHANGE: Imports from root package @a2ui/angular are no longer supported. Use @a2ui/angular/v0_8 or @a2ui/angular/v0_9 instead.
@jacobsimionato
Copy link
Collaborator

jacobsimionato commented Mar 17, 2026

Proposal: Improving Angular Component APIs via web_core Integration

Based on an analysis of the current Angular codebase and the React renderer prototype, I propose the following architectural improvements to streamline component development and share more logic across platforms.

1. Adopt web_core.GenericBinder

Currently, the Angular ComponentBinder is a manual implementation. We should replace it with the shared GenericBinder from web_core.

  • Parity: Shares 100% of complex binding logic (recursion, action wrapping, validation aggregation) with the React renderer.
  • Integration: ComponentHostComponent instantiates the binder and maps its snapshot to a single Angular Signal.

2. Strongly Typed Component API

Move away from Record<string, BoundProperty> and use the ResolveA2uiProps<T> type.

  • Benefit: Full IDE auto-complete for props().variant, props().action(), and auto-generated setters like props().setValue('val').

3. Example: Revised Component Structure

A revised component becomes significantly cleaner, with zero boilerplate for data model synchronization and validation:

@Component({
  selector: 'a2ui-v09-text-field',
  template: `
    <div class="field-container">
      <label>{{ props().label }}</label>
      <input 
        [type]="props().variant === 'obscured' ? 'password' : 'text'"
        [value]="props().value" 
        (input)="props().setValue($any($event.target).value)" 
        [class.invalid]="props().isValid === false" />
      
      @if (props().validationErrors?.[0]; as error) {
        <span class="error-text">{{ error }}</span>
      }
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class TextFieldComponent {
  // Provided as a Signal by the host component/adapter
  @Input({ required: true }) props!: Signal<ResolveA2uiProps<typeof TextFieldApi>>;
}

4. Simplified Child Rendering

Create a dedicated A2uiChild component that consumes the STRUCTURAL metadata from the binder.

  • The Binder's Role: The GenericBinder resolves ChildList properties into a list of { id: string, basePath: string } objects.
  • Template Pattern:
    <!-- Column Component -->
    <div class="column">
      @for (childMeta of props().children; track childMeta.basePath) {
        <a2ui-v09-child [meta]="childMeta" />
      }
    </div>
  • DI for Context: Use Angular Dependency Injection to provide surfaceId at the root, removing the need to pass it as an @Input through every layer.

Performance Rationale: Why the Single Signal Approach?

A common concern with a single signal (props()) instead of per-property signals is reduced granularity. However, this approach is optimal for A2UI for the following reasons:

  1. Component Scale: A2UI components are typically small "Atoms" (2–10 props). Re-running a template check for 10 properties takes microseconds—far less than the memory/CPU overhead of managing 10 separate Signal subscriptions and bridges per component instance.
  2. DOM Stability: Even if the props() signal fires and the template re-runs, Angular’s renderer only touches the real DOM for the specific attributes that actually changed.
  3. The Structural Boundary: The most expensive part of rendering is the Component Tree. Because structural properties are handled by the binder, if a child's text changes, the parent (e.g., a Column) re-evaluates but passes the same metadata to its other children. Angular's OnPush and input checking prevent those siblings from re-rendering or re-binding.
  4. Escape Hatches: For exceptionally heavy components, granularity can be regained using computed signals inside the component logic:
    data = computed(() => this.props().heavyData); // Only fires when data actually changes

This pattern significantly improves developer experience and logic sharing without sacrificing real-world performance.

@gspencergoog
Copy link
Collaborator Author

current Angular codebase

By this, I assume you mean the code in this PR?

* Bound properties.
*/
@Input() props: Record<string, BoundProperty> = {};
@Input() surfaceId!: string;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can this just accept a ComponentContext instead? That is intended to include all the information that is used below.


/**
* Service responsible for managing A2UI v0.9 rendering sessions.
* Bridges the A2UI MessageProcessor to Angular-friendly models.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shall we put a note here explaining that this is minimal wrapper around MessageProcessor to handle DI and async initialization? Especially for maintainers, so they understand not to really put logic here, and to just expose everything that MessageProcessor has?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good idea.

* Processes a list of messages.
*/
processMessages(messages: A2uiMessage[]): void {
this._messageProcessor?.processMessages(messages);
Copy link
Collaborator

Choose a reason for hiding this comment

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

If message processor is null, should this report an error? Or even queue the messages or something?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmm. Yeah, probably.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This layout with catalogs/minimal and catalogs/basic etc is awesome

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, when I implement the basic catalog, I was going to just remove the minimal one.

@gspencergoog
Copy link
Collaborator Author

Proposal: Improving Angular Component APIs via web_core Integration

Did you want me to implement this in this PR, or could it be a follow-on?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Should this still be in src/ ?

Should the v0.8 and v0.9 folders also be in src?

@jacobsimionato
Copy link
Collaborator

Proposal: Improving Angular Component APIs via web_core Integration

Did you want me to implement this in this PR, or could it be a follow-on?

I'm happy to submit this and iterate later, though I think it'd be good to iterate before implementing the basic catalog perhaps.

My biggest concern about submitting this PR is the impact on existing customers, e.g. because

  • It moves the v0.8 Angular renderer to a new location which would require imports to be updated by google3 users
  • There are changes to v0.8 logic, e.g. change detection strategy which could break people
  • The demo app may be included in releases which might stop us releasing
  • There are changes in web_core which I think are also in another PR?

I'm in favour of getting all this submitted, but I'm a bit worried about submitting it all together and then not being able to roll back different parts of it easily if problems occur. Could you potentially split it or wind back some changes? E.g. what if we keep v0.8 sources in their existing location and then just add the new sources at src/v0_9/* ? That way, we can do a 1 step migration in samples and google3 to have people point at v0.9, rather than experience all the pain twice. Or else at least break up the changes into some steps?

Sorry to add roadblocks here! If you feel that this is too much messing around and would much prefer to just submit and then deal with the consequences, I'm open to that given this is an early stage project! Either way, I think getting @sugoi-yuzuru or someone who understands the situation with Angular in google3 to take a look at the v0.8 changes could help reduce the risk.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

2 participants