Objective — Put the flagship OpenGenerativeUI demo on the shipped canonical rails (generateSandboxedUi → OpenGenerativeUIMiddleware → activity renderer) and adopt the canonical's streaming contract, sandbox isolation, and typed host bridge — without regressing the things the demo does better (plan/narrate protocol, design system, idiomorph morphing, ResizeObserver autosize, export, templates).
The strategic point: today the demo is a parallel implementation of the concept, not a showcase of the shipped feature — it renders via a custom useComponent widget, so none of its polish exercises (or improves) the canonical pipeline. Migrating it makes the demo a reference implementation, and every demo improvement upstreamable.
| Piece | Today | File |
|---|---|---|
| Generation entry | widgetRenderer registered via useComponent — args: title, description, html (one self-contained fragment with inline <style>/<script>) |
apps/app/src/components/generative-ui/widget-renderer.tsx, registered in src/hooks/use-generative-ui-examples.tsx |
| Design system | Layered CSS injected in-page: theme variables (light/dark), SVG class kit (.c-purple….c-red, .box, .arr), form-control styles |
widget-renderer.tsx (THEME_CSS, SVG_CLASSES_CSS, FORM_STYLES_CSS) |
| ⚠️ DS duplication | The same CSS is forked into the MCP app with a literal WARNING: Keep in sync comment |
apps/mcp/src/renderer.ts |
| Streaming UX | Partial html morphs into the iframe as tool args stream, via inlined idiomorph |
generative-ui/idiomorph-inline.ts |
| Sandbox | Plain iframe, document assembled in-page; window-global bridge (sendPrompt, openLink) |
widget-renderer.tsx, export-utils.ts |
| Libraries | Importmap: three, gsap, d3, chart.js via esm.sh; system prompt documents them |
widget-renderer.tsx • apps/agent/main.py |
| Agent | LangGraph Deep Agent (create_deep_agent) + CopilotKitMiddleware; skills dir; mandatory protocol: Acknowledge → plan_visualization → Build → Narrate; hard quality bar (“ALWAYS real WebGL… NEVER fake 3D with CSS”) |
apps/agent/main.py, apps/agent/skills/*/SKILL.md, src/plan.py |
| Extras worth protecting | Hybrid typed components (barChart/pieChart preferred for simple charts), template library, export-to-standalone-HTML overlay, demo gallery, HITL form |
generative-ui/charts/*, template-library/*, export-overlay.tsx, src/form.py |
flowchart LR
subgraph TODAY["Today — custom rails"]
A1["Deep Agent calls widgetRenderer(title, description, html)"] --> A2["useComponent widget-renderer.tsx"]
A2 --> A3["In-page assembly: theme + SVG kit + form CSS + importmap + idiomorph"]
A3 --> A4["Plain iframe + window-global bridge"]
end
subgraph TARGET["Target — canonical rails"]
B1["Deep Agent calls generateSandboxedUi (ordered params)"] --> B2["OpenGenerativeUIMiddleware — streaming activity events"]
B2 --> B3["Assembler injects the SAME design system + importmap + CSP"]
B3 --> B4["websandbox iframe + Zod-validated sandboxFunctions"]
end
TODAY -.->|"migrate, keeping demo strengths"| TARGET
Replace the widgetRenderer path with the canonical pipeline: enable openGenerativeUI on the runtime (apps/app/src/app/api/copilotkit/route.ts) so OpenGenerativeUIMiddleware converts the agent’s generateSandboxedUi tool call into open-generative-ui activity events, rendered by the built-in OpenGenerativeUIActivityRenderer. The agent’s system prompt + skills switch from “call widgetRenderer” to the canonical tool contract. widget-renderer.tsx’s custom iframe code retires; its design-system CSS moves to WS4.
plan_visualization, todos, query_data, HITL form untouched — they’re agent-side and rail-agnostic.barChart/pieChart components as renderToolCalls — the hybrid “typed for simple, open for the tail” routing is a demo strength.Today one big html arg morphs in as it streams — intermediate states can render unstyled or broken mid-stream. Canonical’s contract fixes this: placeholderMessages → css (preview gated on cssComplete) → html chunks live → jsFunctions → jsExpressions one-by-one. The demo gains placeholder cycling + css-first gating + the “watch each expression take effect” beat; the plan-card stays as the pre-generation step.
Plain iframe + window-global sendPrompt/openLink → websandbox separate-origin RPC + Zod-validated sandboxFunctions (openGenerativeUI.sandboxFunctions): typed sendPrompt, openLink behind an origin allowlist, every call schema-checked. Generated JS loses any path to the host window.
Extract the theme/SVG/form CSS (and importmap) from widget-renderer.tsx and apps/mcp/src/renderer.ts into one shared workspace package consumed by both. Kills the “keep in sync” fork — and produces exactly the artifact Plan B Phase 1 wants to adopt into the canonical assembler. This workstream is the bridge between the two plans.