Gadgets — deterministic islands
read as.mdA generated interface is LLM-written React. That works for layout, forms, and state — but the moment a UI needs a real map, a checkout flow, or the camera, “generate it” stops being an option. Nobody wants an LLM improvising a payments integration, and no model reliably reinvents Leaflet’s lifecycle quirks on demand.
Gadgets are how ggui resolves that tension: the LLM composes third-party capability but never authors it. A gadget is a human-written, versioned, integrity-pinned wrapper around a browser library, exposed to generated code as a React hook with one uniform contract. The generated component calls useLeafletMap(...) the same way it calls useState — everything inside that hook is deterministic, reviewed code.
One pattern, learned once
Section titled “One pattern, learned once”Every third-party library enters the system through the same factory (createGguiGadget), so every gadget presents the same hook shape: call it with options, get back { value, status, start, … }. The generator never sees raw library APIs — not Leaflet’s imperative L.map() lifecycle, not Stripe’s redirect dance. It sees one hook convention, which means:
- Cold generations get it right more often. The model pattern-matches against one shape it has seen many times, not N library idioms it has seen unevenly.
- A new gadget costs the model nothing to learn. The wrapper’s teaching text —
description(what),usage(when),example(a concrete call) — travels with the descriptor, and the generator reads it at synthesis time. Docs for the LLM are part of the spec, not an afterthought: a wrapper without them is rejected at module load.
Two symmetric capability surfaces
Section titled “Two symmetric capability surfaces”Gadgets are the renderer-side half of a symmetric design (glossary):
| Renderer | Agent | |
|---|---|---|
| Unit | gadget | tool |
| Catalog | clientCapabilities.gadgets |
agentCapabilities.tools |
| Gives | something to render with | something to act with |
Both catalogs are declared per app and bounded by the operator, not the agent. An agent cannot smuggle a capability into a render by asking for it: a contract may only reference gadgets the operator registered on the app, and a render naming an unregistered one is rejected with gadget_not_registered before any code runs.
The trust chain
Section titled “The trust chain”Generated code runs in a sandboxed iframe with a closed Content-Security-Policy. Each gadget widens that sandbox declaratively, not implicitly:
- Provenance — the wrapper bundle is fetched by exact package + version, and marketplace-published bundles carry a
sha384subresource-integrity pin stamped at publish time. What loads is byte-identical to what was published. - Network — a gadget declares the origins it talks to (
connect: ["https://tile.openstreetmap.org"]); the renderer unions exactly those into the iframe’s CSP. A map gadget can fetch tiles; it cannot phone anywhere else, and neither can the generated code around it. - Secrets — wrappers that need a token (Mapbox, Stripe) declare the
GGUI_PUBLIC_APP_*keys they consume, and the operator supplies values through the app’s public-env channel. Keys never appear in generated code.
The result: the only non-generated code in a render is code an operator explicitly registered, at a pinned version, with a declared network footprint.
Why versioning feeds the cache
Section titled “Why versioning feeds the cache”A gadget’s version is part of the blueprint cache key. Bump a wrapper and every cached generation built against the old version stops matching automatically — no stale UI silently calling a changed hook contract. This is the same discipline that makes blueprint reuse safe in general: the cache key captures everything the compiled component depends on.
The standard library
Section titled “The standard library”Browser capabilities that need no third-party code ship as STDLIB hooks — useGeolocation, useCamera, useClipboardWrite, useClipboardPaste, useNotifications, useFilePicker, useMicrophone. They follow the same hook contract but require no operator registration; the generator can reach for them whenever the ask calls for one.
What a gadget is not
Section titled “What a gadget is not”- Not a template. A gadget renders one capability (a map, a chart, a checkout); the generated component decides where it sits, what feeds it, and what happens around it. Composition stays generative; capability stays deterministic.
- Not agent-side. Gadgets never execute in the agent’s process and never see the conversation. Anything the agent should do is a tool.
- Not a behavior escape hatch. The contract describes data flowing between agent and UI; gadgets extend what the component code can do. Neither is a channel for smuggling the other — see Sessions & the event model for the placement rule.
- Gadgets SDK — author a wrapper: the
createGguiGadgetfactory, teaching-text budgets, bundle + types publishing, operator registration. - Marketplace — publish and install gadgets across apps.
- Blueprints and the cache — how versioned capability feeds exact-match reuse.