UI Generator
read as.md The UI generator runs when a blueprint match misses and ggui has to build a component from scratch. It takes a data contract (PropsSpec + ActionSpec + StreamSpec + ContextSpec) and returns a compiled, contract-typed React module — typically a handful of LLM turns; cache hits via blueprints are what deliver the ~100ms path.
The harness
Section titled “The harness”A harness is a per-contract execution plan derived from the contract’s risk and axes. It bundles three things:
- Workflow — the topology of LLM phases and tasks. Today the dispatcher always picks
single_pass;stagedandstaged_concurrentare registered as reserved-future topologies for risk-tier routing that has not yet shipped. - Prompt + boilerplate — the system prompt and fragment set that tell the LLM how to author against
@ggui-ai/design, the active gadgets, and the contract. - Check leg — the post-conditions the output must satisfy.
Generation runs the workflow to produce source, runs the check leg, and — if checks fail — derives a revised harness and re-runs, bounded by maxIterations.
workflow → source → check → pass? ─► return │ └─ fail → derive (swap fragments / upgrade workflow / adjust prompt) → loopWorkflows
Section titled “Workflows”| Workflow | Status | Shape |
|---|---|---|
single_pass | Live — only dispatched workflow | One impl turn. |
staged | Reserved (registered, not routed) | Plan → execute. |
staged_concurrent | Reserved (registered, not routed) | Plan → parallel skeleton tasks → integrate. |
pickWorkflow is deliberately conservative — every classification today routes to single_pass. The staged topologies are wired up so a future risk-tier router can dispatch to them without a schema change, but changing the picker is a first-class experiment with its own bench gate. All three feed the same check + derive loop; the workflow only changes how source is produced, not how it’s validated.
Plain-text impl loop (no tool-call ceremony)
Section titled “Plain-text impl loop (no tool-call ceremony)”The impl phase is structured so the LLM doesn’t waste turns on deterministic ceremony:
- The LLM receives everything pre-injected — primitives docs, design tokens, the data contract with examples, gadget capability cards.
- The LLM writes the component as plain text. No tool calls required.
- The system auto-runs
self_checkandcompile_componenton the emitted source. - Failures are fed back as a structured diff for the next turn to fix.
This is what keeps healthy generations to 3–5 turns. Turns ≥ 6 is a triad-misalignment signal, not a turn-budget problem.
Check leg
Section titled “Check leg”Every workflow runs the same checks before returning:
- TypeScript — the emitted source is compiled against the real
@ggui-ai/designtype definitions via the TypeScript compiler API on a virtual filesystem. Catches wrong prop types, missing required props, invalid imports, andstrictNullChecksviolations. - Render smoke —
ReactDOMServer.renderToString()with contract-derived sample props. Catchesundefined.toLowerCase()-class runtime errors before the component reaches the renderer. - Per-axis assertions — axis-specific checks derived from the contract (e.g., does an
interactiveaxis include a focusable element? does asubmitaction have a matching form?).
If any leg fails, the harness derives a revised configuration and loops. If the final iteration still fails, generation returns ok: false with reason: "max-iterations" and the last source, compiled output, and check result attached for diagnostics — the caller decides whether to surface a fallback, retry with a different harness, or report the failure.
Multi-provider transport
Section titled “Multi-provider transport”The harness is provider-agnostic. Only the LLM transport differs:
- Claude (Anthropic) — raw API or Claude Agent SDK.
- OpenAI — Responses API or OpenAI Agents SDK.
- Google (Gemini) — GenAI API or Google ADK.
Workflow, check, and derive are identical across providers. Switching providers does not change what gets generated, only the per-turn cost and latency profile.
A deployment pins its model in ggui.json#generation.model (provider:model, e.g. anthropic:claude-haiku-4-5-20251001); the GGUI_GENERATION_MODEL env var overrides the manifest (precedence: GGUI_GENERATION_MODEL env > ggui.json#generation.model > per-provider default), and an agent may override per render via ggui_render({infra: {model}}). generation.keySource: 'own' | 'managed' declares whose provider key funds generation — self-hosted deployments always use their own key.
Output shape
Section titled “Output shape”A successful generation returns:
- A compiled component module (TSX source compiled to JS).
- A typed data contract — the same
PropsSpec/ActionSpec/StreamSpec/ContextSpecthat was the input, now frozen as the runtime wire shape. - A blueprint candidate — the contract (hashed via RFC 8785 to
contractHash) plus its variance (variantKey), which the registry stores under a stableblueprintIdfor the next handshake’s cache match.
The contract is enforced again at the MCP handler boundary so that what the agent renders matches what the component was generated to render.
→ See Architecture overview for where the generator fits in the render pipeline, and the Glossary for harness, blueprint, gadget, contract.