feat(lit): Initial Draft of v0.9 Lit Renderer and Local Gallery#869
feat(lit): Initial Draft of v0.9 Lit Renderer and Local Gallery#869jacobsimionato wants to merge 4 commits intogoogle:mainfrom
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a significant new feature: the v0.9 Lit renderer and a local gallery application. The overall architecture, including the A2uiController, component factories, and the separation of concerns between web_core and the lit renderer, is well-thought-out. The new client capabilities generation from Zod schemas is a particularly powerful addition.
My review focuses on improving code quality and maintainability as this feature moves from a draft to a production-ready state. The main points include avoiding inline styles, improving type safety by removing any types and unsafe casts, and refactoring duplicated code. These changes will make the new renderer more robust, easier to theme, and more maintainable in the long run.
| import { CardApi } from "@a2ui/web_core/v0_9/basic_catalog"; | ||
|
|
||
| export const A2uiCard = createLitComponent(CardApi, ({ props, buildChild }) => { | ||
| return html`<div class="a2ui-card" style="border: 1px solid #ccc; border-radius: 8px; padding: 16px;">${props.child ? buildChild(props.child) : ""}</div>`; |
There was a problem hiding this comment.
Using inline styles is generally discouraged as it makes theming and maintenance difficult, and can violate Content Security Policy (CSP) in some environments. It would be better to move these styles to a dedicated CSS file or a static styles block within the component, and apply them via classes. This issue is present in several other new components as well (e.g., Column, Row, Divider, Modal, Tabs).
| export class A2uiLitModal extends LitElement { | ||
| @property({ type: Object }) accessor context!: ComponentContext; | ||
| @property({ type: Function }) accessor buildChild!: ChildBuilder; | ||
| private a2ui = new A2uiController(this as any, ModalApi); |
There was a problem hiding this comment.
Casting this to any when creating the A2uiController bypasses TypeScript's type safety. This is often a symptom of the component's type not being fully inferred at the point of property initialization. To improve type safety, consider initializing the controller in the constructor after super() has been called, where this is fully typed. This same issue is also present in A2uiLitTabs.
For example:
export class A2uiLitModal extends LitElement {
// ... properties
private a2ui: A2uiController<any>;
constructor() {
super();
this.a2ui = new A2uiController(this, ModalApi);
}
// ... rest of the class
}| const tagMap: Record<string, string> = { h1: "h1", h2: "h2", h3: "h3", h4: "h4", h5: "h5", caption: "span", body: "p" }; | ||
| const tag = tagMap[variant as string] || "p"; |
| function mapJustify(justify: string | undefined): string { | ||
| switch (justify) { | ||
| case "start": return "flex-start"; | ||
| case "center": return "center"; | ||
| case "end": return "flex-end"; | ||
| case "spaceBetween": return "space-between"; | ||
| case "spaceAround": return "space-around"; | ||
| case "spaceEvenly": return "space-evenly"; | ||
| case "stretch": return "stretch"; | ||
| default: return "flex-start"; | ||
| } | ||
| } | ||
|
|
||
| function mapAlign(align: string | undefined): string { | ||
| switch (align) { | ||
| case "start": return "flex-start"; | ||
| case "center": return "center"; | ||
| case "end": return "flex-end"; | ||
| case "stretch": return "stretch"; | ||
| default: return "stretch"; | ||
| } | ||
| } No newline at end of file |
| <div class="a2ui-choicepicker"> | ||
| ${props.label ? html`<label>${props.label}</label>` : ""} | ||
| <div class="options"> | ||
| ${props.options?.map((opt: any) => html` |
There was a problem hiding this comment.
|
|
||
| export const A2uiImage = createLitComponent(ImageApi, ({ props }) => { | ||
| const styles = { objectFit: props.fit || "fill", width: "100%" }; | ||
| return html`<img src=${props.url} class=${"a2ui-image " + (props.variant || "")} style=${styleMap(styles)} />`; |
There was a problem hiding this comment.
Using string concatenation for CSS classes is less robust than using the classMap directive from Lit. classMap handles conditional classes more cleanly and avoids potential issues with extra spaces or empty class attributes.
import { classMap } from "lit/directives/class-map.js";
// ...
export const A2uiImage = createLitComponent(ImageApi, ({ props }) => {
const styles = { objectFit: props.fit || "fill", width: "100%" };
const classes = {
'a2ui-image': true,
[props.variant || '']: !!props.variant
};
return html`<img src=${props.url} class=${classMap(classes)} style=${styleMap(styles)} />`;
});|
(I'm stealing all of this :P) |
Description of Changes
This PR introduces the initial draft implementation of the A2UI v0.9 Lit Renderer (
@a2ui/lit/v0_9), leveraging the framework-agnostic models from@a2ui/web_core/v0_9.GenericBinderlogic from web core into the native Lit element lifecycle. It manages data subscriptions and triggersrequestUpdate()on the host when A2UI data dependencies change.createLitComponentfor concisely generating pure functional/stateless elements strictly typed via Zod schemas.minimal_catalogandbasic_catalogincluding 18 standard components (Text, Button, Row, Column, etc.) and evaluation functions (capitalize, formatString, arithmetic).@a2ui/web_core'sMessageProcessorto automatically generatea2uiClientCapabilitiesdynamically from the underlying Zod API schemas, correctly resolving external$refURIs from theREF:descriptions.samples/client/lit/gallery_v0_9featuring an interactive 3-column debugging layout:Rationale
This aligns the Lit ecosystem with the 'Unified Architecture' concepts drafted in v0.9, ensuring robust separation between the A2UI state protocol (
DataModel,SurfaceModel,ComponentContext) and the actual screen painting (DOM templates).By adopting a Reactive Controller pattern, existing and new Lit components can 'opt-in' to A2UI bindings without requiring inheritance from an obstructive base class. Leveraging Zod schemas enables us to guarantee that the UI implementation remains tightly coupled to the capabilities we broadcast back to the AI Agent.
Testing/Running Instructions
To run the interactive debugger and step through the newly implemented examples locally:
Then visit the logged localhost URL (usually
http://localhost:5173) in your browser to view the progressive rendering stepper and live Data Model.