a2ui-project / a2ui-project/a2ui

Centralize Basic Catalog implementation using portable A2UI web Components

Offen
#931 7 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen
a: api-simplicity component: standard catalog specification javascript P2 sprint ready type: feature/enhancement
Vorherrschende Sprache
TypeScript
Sterne
16.4k
Forks
1.3k
Ø Merge
2 T. 13 Std.
Gemergte PRs (30 T.)
134

Beschreibung

# Centralize Basic Catalog implementation using Web Components via Entrypoint Isolation

Currently, the A2UI Basic Catalog (Text, Button, Row, Column, TextField, etc.) is implemented separately in every framework-specific renderer (React, Angular). This duplication leads to visual inconsistencies, repeated bug fixes, and significant maintenance overhead.

This proposal centralizes the visual implementation of the 18 Basic Catalog components into standard **Web Components (Custom Elements)** authored with **Lit**. The framework-specific renderers will then act as thin "adapters" that wrap these shared components, allowing the styling and DOM structure to be written exactly once.

While A2UI's basic catalog does not define and canonical rendering style for each of the elements, with the intention of leaving room for different apps to implement it according to their own style, there are major benefits to use in sharing the default implementation of the basic catalog across platforms:
- Less work for us to maintain our basic catalog implementation
- More consistent appearance across our demos

## Default basic catalog implementation architecture: Centralized Visuals, Thin Adapters

The architecture shifts the responsibility of painting pixels down to the `web_core` layer, while keeping the structural reconciliation in the framework layer.

1. **Shared Visuals in `web_core`:** We will add `lit` as a dependency to `@a2ui/web_core` and implement the Basic Catalog components as standard Custom Elements (e.g., ``, ``).
2. **Property-First Data Passing:** Frameworks will pass the fully resolved A2UI `props` object directly to the Web Component as a JavaScript property, avoiding expensive HTML attribute serialization.
3. **Recursive Slotting:** Structural components (like `Row` or `Column`) will use standard HTML `` elements. The host framework (React/Angular) will continue to manage the lifecycle of children, rendering them *into* the slots of the shared components.

## No change to alternative basic catalog implementations or custom catalogs

This plan will share *one* implementation of the basic catalog across renderers. For this particular catalog implementation, this approach adds more complexity but has the advantage of rendering consistency, which has worthwhile for the A2UI team so we can at least have consistent and good looking demos across frameworks.

This plan has no impact on alternative implementations of the basic catalog, or implementations of other catalogs. Developers will continue to use the idiomatic, framework-specific APIs for these implementations.

## Strict Type Safety

To ensure the "thin adapter" pattern doesn't lose TypeScript's benefits (which would happen if we pass `props: any`), we will leverage the existing `ResolveA2uiProps` utility. This automatically converts Zod schemas into strict runtime interfaces.

### 1. The Shared Lit Component (`web_core`)
The Lit component defines a strict TypeScript interface for its properties and handles Shadow DOM encapsulation.

```typescript
import { LitElement, html, css } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { z } from 'zod';
import { ButtonApi } from '../../components/basic_components.js';
import type { ResolveA2uiProps } from '../../rendering/generic-binder.js';

// 1. Automatically generate the exact, strict TypeScript interface!
export type ButtonResolvedProps = ResolveA2uiProps>;

@customElement('a2ui-button')
export class A2uiButton extends LitElement {
// 2. The Lit component now demands this exact shape
@property({ type: Object }) props!: ButtonResolvedProps;

static styles = css`
:host { display: inline-block; }
button {
padding: 8px 16px;
background-color: var(--a2ui-primary-color, #007bff);
/* ... */
}
`;

render() {
return html`
this.dispatchEvent(new CustomEvent('a2ui-action'))}
>


`;
}
}
```

### 2. Angular Adapter
Angular will use its existing `ComponentBinder` to resolve signals, manually aggregate them via `computed()`, and strictly type the result to ensure compliance with the Lit component.

```typescript
import { Component, input, computed, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import type { ButtonResolvedProps } from '@a2ui/web_core/v0_9/basic_catalog/ui/button';

@Component({
selector: 'a2ui-v09-button',
template: `

@if (child()) {

}

`,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
standalone: true
})
export class ButtonComponent {
props = input>({});

// 1. FORCE the computed value to match the Lit component's expectations
resolvedProps = computed(() => ({
variant: this.props()['variant']?.value() ?? 'default',
isValid: this.props()['isValid']?.value(),
action: () => this.handleClick()
}));

child = computed(() => this.props()['child']?.value());

handleClick() { /* ... */ }
}
```

### 3. React Adapter
React will augment `JSX.IntrinsicElements` to force type checking on the custom element's properties.

```tsx
import React, { useRef, useEffect } from 'react';
import { createReactComponent } from '../../../adapter';
import { ButtonApi } from '@a2ui/web_core/v0_9/basic_catalog';
import type { ButtonResolvedProps } from '@a2ui/web_core/v0_9/basic_catalog/ui/button';

declare global {
namespace JSX {
interface IntrinsicElements {
'a2ui-button': React.DetailedHTMLProps, HTMLElement> & {
props: ButtonResolvedProps;
'ona2ui-action'?: (e: Event) => void;
};
}
}
}

export const ReactButton = createReactComponent(ButtonApi, ({ props, buildChild }) => {
const ref = useRef(null);

useEffect(() => {
if (ref.current) ref.current.props = props;
}, [props]);

return (

{props.child ? buildChild(props.child) : null}

);
});
```

---

## Addressing Binary Size: Entrypoint Isolation

A primary concern with this approach is that adding `lit` to the dependencies of `@a2ui/web_core` could inflate the binary size for "Headless A2UI" users—those who want to use the protocol logic but provide their own 100% custom UI implementations.

**Mitigation: Strict Entrypoint Isolation**

We will utilize standard package export maps (`package.json` `"exports"`) and Angular's `ng-packagr` secondary entry points to guarantee that `lit` is completely tree-shaken away for headless users.

1. **`@a2ui/web_core` (Root):** The main entrypoint remains entirely headless. **It contains zero imports of Lit.**
2. **`@a2ui/web_core/v0_9/basic_catalog/ui` (New Entrypoint):** This specific subpath export will contain the visual Lit components.
3. **Framework Isolation:**
* A headless React app importing `import { A2uiSurface } from '@a2ui/react';` will never resolve the Lit dependency.
* Only importing the basic catalog explicitly (e.g., `import { basicCatalog } from '@a2ui/react/basic-catalog';`) will trace back to the `web_core/.../ui` path, pulling in Lit.

Beitragsleitfaden

Beitragsleitfaden öffnen

Bewertung

Dieses Issue wurde noch nicht bewertet.

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.