Strata generative UI — how Elle builds with Strata
Elle composes runtime UI (“generated sheets”) from a closed, Zod-validated catalog that maps 1:1 onto real Strata components. The catalog is the single source of truth on both sides of the wire; there is no second list to drift. Full interaction doctrine:
apps/web-v2/docs/AGENT_APP_UI_CONVENTIONS.md§ “Generated sheets compose from the closed catalog”.
The pipeline
packages/ui-schema (@layers/ui-schema) ← the vocabulary
src/catalog.ts ELLE_UI_CATALOG — per-component Zod prop schemas,
whenToUse guidance, uiComponent binding, acceptsChildren
src/tree.ts ElleUITreeSchema + normalize/validate + node/depth caps
src/action.ts ElleUIActionSchema (navigate | message Elle)
src/prompt.ts renderCatalogIndex() — the prompt-embedded index
apps/elle ← the producer
src/tools/render-sheet.ts the renderSheet tool; inputSchema IS
ElleUITreeSchema (validation at the boundary)
src/prompts/system-prompt.ts embeds renderCatalogIndex() — prompt and
schema cannot drift
apps/web-v2 ← the renderer
client/components/elle-ui/registry.tsx catalog name → @layers/ui adapter
client/components/elle-ui/renderer.tsx re-validates EVERY node, fails
closed per node
client/components/elle-ui/sheet-host.tsx mounts on the canvas behind a
view-link
Doctrine
- One vocabulary. Every catalog entry maps onto a real Strata component. Never an Elle-only component; never bespoke generated DOM.
- Fail closed per node. Unknown component or invalid props renders an inline placeholder in that slot; one bad node never takes down the sheet.
Gridis the only container (side-by-side intent). The shell owns vertical rhythm — trees never author it.- Data binds by
dataPathJSON Pointers (/toolResults/<toolName>/…) into the turn’s tool results; prefer pointers over inlining large arrays. An unresolvable pointer renders an honest “data unavailable” placeholder. - Actions are the only commitment. A component may carry an
ElleUIAction(navigate — validated against the route catalog — or message Elle). Everything else is display. - Generated views render on the canvas behind a view-link, never inline in the chat stream.
Adding a component to Elle’s vocabulary
- The component exists in
packages/ui(or add it there first — seecomponents.md). It must be composable from JSON-serializable props; function/ReactNode props get a data projection (icon names, declarative columns). - Add the entry to
ELLE_UI_CATALOGinpackages/ui-schema/src/catalog.ts:uiComponent, a sharpwhenToUse(this is prompt text — it steers Elle), the Zodpropsprojection,acceptsChildren. - Rebuild the package (
pnpm buildinpackages/ui-schema— it shipsdist/). Elle’s system prompt picks the entry up automatically viarenderCatalogIndex(). - Add the adapter in
apps/web-v2/client/components/elle-ui/registry.tsxmapping the catalog props onto the real component (resolvedataPath, map icon names, wire actions). - Verify in the workbench’s catalog browser, then live: Elle runs locally
via
apps/elle(pnpm dev, Studio at :4111) — a local restart picks up prompt/catalog changes.
Current binding exception (to fix, not to copy): MediaCard/MediaGrid
bind to web-v2-local live-post-card — new entries must bind to
@layers/ui components so the catalog stays app-agnostic.