Generate and Validate Solar Components with AI Agents
Use the registry manifest and structured ContractErrors to give AI agents the context they need to generate reliable Solar components every time.
Solar is designed from the ground up for AI-generated UI. Rather than hoping a model guesses your conventions correctly, Solar makes every component’s contract machine-readable: the registry exposes a full JSON manifest of available components and their prop schemas, and validation failures return structured ContractError objects the agent can parse and self-correct from — no regex, no string parsing. This guide walks you through the complete generation workflow, from seeding your agent’s context to recovering from errors and regenerating.
Import your components so they self-register on load. Each component file calls registry.register() at the bottom, so a bare import is enough.
// main.js — import components to trigger self-registrationimport './components/Button.js'import './components/Card.js'import './components/Counter.js'import { registry } from './solar/index.js'// Now every component is in the catalogconsole.log(registry.manifest())
Order matters if components depend on each other. Import leaf components (like Button) before composites (like Card) so slot validation can resolve names correctly.
2
Feed the manifest to your agent
Call registry.manifest() to get the full catalog as a formatted JSON string, then paste it directly into the agent’s context or system prompt.
const manifest = registry.manifest()// manifest is a JSON string — paste it into your agent's context as-isconsole.log(manifest)
The agent now knows exactly which components exist, what props they accept, which are required, and what slot constraints apply — before it writes a single line.
3
Send the agent a generation prompt
Write a concrete prompt that targets a specific component. Include the data shape and the API endpoint the component should use.
Generate a UserCard Solar component that accepts a userId (number, required)and displays user data fetched from /api/users/:id.Show the user's name and email. While loading, show a "Loading..." paragraph.On error, show the error message. Use a Button (variant "secondary") to triggera refresh.
The agent uses the manifest you provided to know what Button looks like and how to slot it correctly. It doesn’t need to infer prop names or types.
4
Validate the output
Run the generated component with real props. If the agent got a prop type wrong, Solar throws a ContractError immediately at the call site — not a runtime crash deep in the render tree.
import { mountComponent } from './solar/index.js'import UserCard from './components/UserCard.js'try { mountComponent(UserCard, { userId: 1 }, document.getElementById('app'))} catch (err) { if (err.name === 'ContractError') { // Structured — safe to serialize and feed back to the agent console.error(JSON.stringify(err.toJSON(), null, 2)) } else { throw err }}
The ContractError JSON includes a fix field written in plain English, so you can pass the entire object back to the agent without any transformation.
5
Mount the generated component
Once validation passes, mount the component to the DOM with mountComponent.
import { mountComponent } from './solar/index.js'import UserCard from './components/UserCard.js'mountComponent(UserCard, { userId: 42 }, document.getElementById('app'))
mountComponent handles the full lifecycle — initial render, re-renders on state changes, and cleanup on unmount.
Give the agent a system prompt that states the Solar rules explicitly. Paste the manifest JSON directly into the <paste manifest JSON here> placeholder.
You are generating Solar components. Follow these rules:- Use defineComponent({ name, props, render }) — name must be a PascalCase string- Props schema: each prop is { type, required?, default?, enum?, accepts? }- Valid types: string, number, boolean, function, object, array, any, slot- render() must return createElement(type, props, ...children) or h([...])- One component per file, default export is the defineComponent call- Call registry.register(YourComponent) at the bottom of each file- Slot props must pass a vnode produced by the named component (e.g. accepts: "Button" means you must pass Button({ ... }), not createElement("button", ...))Available components (from registry.manifest()):<paste manifest JSON here>
Keep the system prompt short and literal. Models respond better to rules they can match mechanically (“name must be PascalCase”) than to rules requiring judgment (“name should be descriptive”). Solar’s rigid structure is an advantage here — there are very few ways to write a valid component.
When the agent produces code that violates a prop contract, catch the ContractError, serialize it with toJSON(), and send the structured JSON back to the agent as a follow-up message.
import { mountComponent, ContractError } from './solar/index.js'import UserCard from './components/UserCard.js'async function mountWithSelfCorrection(component, props, container, agent) { try { mountComponent(component, props, container) } catch (err) { if (err.name === 'ContractError') { // toJSON() includes a "fix" field the agent can act on directly const errorPayload = err.toJSON() const correctedCode = await agent.send( `Your component threw a ContractError. Fix it and regenerate:\n\n` + JSON.stringify(errorPayload, null, 2) ) // Load the corrected component and try again // ... dynamic import or eval of correctedCode } else { throw err } }}
The fix field in the error payload is written in plain English — for example, "Pass a string value for \"label\"". You don’t need to add any explanation; the agent can act on it directly.
The h() array notation is more token-efficient than createElement() chains, which makes it a better target format for generation. Both produce identical vnodes.
Component names in h() arrays resolve automatically from the registry — 'Button' dispatches to the registered Button component, including full prop validation. Plain lowercase strings like 'div' or 'p' fall through to createElement() as native DOM elements.
Use registry.list() if you want to format the manifest yourself before including it in a prompt. It returns the same data as registry.manifest() but as a JavaScript array rather than a JSON string, so you can filter, sort, or reformat it before sending.