setIsOpen(false)}
+ />
+ )}
+
+ {/* Sidebar */}
+
+
+
OpenGenerativeUI
+
+ Interactive Notebook
+
+
+
+
+ {chapters.map((ch, i) => (
+ scrollTo(ch.id)}
+ className={`sidebar-link w-full text-left px-3 py-2 rounded-md text-sm mb-0.5 cursor-pointer ${
+ activeId === ch.id ? "sidebar-link--active" : ""
+ }`}
+ style={{
+ color: activeId === ch.id ? "var(--color-text-primary)" : "var(--text-secondary)",
+ }}
+ >
+ {ch.icon}
+
+ {String(i + 1).padStart(2, "0")}
+ {" "}
+ {ch.title}
+
+ ))}
+
+
+ >
+ );
+}
diff --git a/apps/notebook/src/components/theme-toggle.tsx b/apps/notebook/src/components/theme-toggle.tsx
new file mode 100644
index 0000000..e47743a
--- /dev/null
+++ b/apps/notebook/src/components/theme-toggle.tsx
@@ -0,0 +1,41 @@
+"use client";
+
+import { useTheme } from "@/hooks/use-theme";
+
+export function ThemeToggle() {
+ const { theme, setTheme } = useTheme();
+
+ const isDark =
+ theme === "dark" ||
+ (theme === "system" && typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches);
+
+ return (
+
setTheme(isDark ? "light" : "dark")}
+ className="flex items-center justify-center w-8 h-8 rounded-lg transition-colors cursor-pointer"
+ style={{
+ background: "var(--color-glass-subtle)",
+ border: "1px solid var(--color-border-glass)",
+ }}
+ aria-label="Toggle theme"
+ >
+ {isDark ? (
+
+
+
+
+
+
+
+
+
+
+
+ ) : (
+
+
+
+ )}
+
+ );
+}
diff --git a/apps/notebook/src/content/chapters/01-introduction.ts b/apps/notebook/src/content/chapters/01-introduction.ts
new file mode 100644
index 0000000..3180fba
--- /dev/null
+++ b/apps/notebook/src/content/chapters/01-introduction.ts
@@ -0,0 +1,84 @@
+import type { Chapter } from "@/lib/types";
+
+export const introduction: Chapter = {
+ id: "introduction",
+ title: "Introduction",
+ description: "What OpenGenerativeUI is and how the pieces connect.",
+ icon: "🪁",
+ cells: [
+ {
+ type: "markdown",
+ id: "intro-what",
+ content: `# OpenGenerativeUI
+
+OpenGenerativeUI is an open-source template showing how an AI agent can **generate rich, interactive UI** — not just text — using [CopilotKit](https://copilotkit.ai) and [LangGraph](https://langchain-ai.github.io/langgraph/).
+
+The agent produces charts, 3D scenes, SVG diagrams, and interactive widgets that render directly in the chat stream. Users and the agent share the same application state.`,
+ },
+ {
+ type: "markdown",
+ id: "intro-arch",
+ content: `## Architecture
+
+Three systems work together in a layered architecture:`,
+ },
+ {
+ type: "mermaid",
+ id: "intro-arch-diagram",
+ title: "System Architecture",
+ content: `graph TD
+ User([User]) -->|chat message| CK[CopilotKit\nReact hooks + runtime]
+ CK -->|forwards to agent| DA[Deep Agent\nLangGraph + tools + skills]
+ DA -->|tool calls| Tools{Tools}
+ Tools -->|plan_visualization| DA
+ Tools -->|widgetRenderer| WR[Widget Renderer\nSandboxed iframe + Idiomorph]
+ Tools -->|pieChart / barChart| WR
+ WR -->|renders in browser| User
+
+ style CK fill:#EDE9F5,stroke:#5B3FA0,color:#3E2B6F
+ style DA fill:#E3EFFC,stroke:#2663B3,color:#1A4680
+ style WR fill:#E1F5EE,stroke:#0F6E56,color:#085041
+ style User fill:#f7f6f3,stroke:#9c9a92,color:#1a1a1a
+ style Tools fill:#FAEEDA,stroke:#B8860B,color:#854F0B`,
+ },
+ {
+ type: "code",
+ id: "intro-structure",
+ language: "bash",
+ filename: "Project Structure",
+ content: `apps/
+├── app/ # Next.js frontend
+│ └── src/
+│ ├── components/
+│ │ └── generative-ui/
+│ │ └── widget-renderer.tsx # The iframe rendering engine
+│ ├── hooks/
+│ │ └── use-generative-ui-examples.tsx # CopilotKit hook registrations
+│ └── app/
+│ └── api/copilotkit/route.ts # CopilotKit runtime (connects to agent)
+└── agent/ # LangGraph Python agent
+ ├── main.py # create_deep_agent + system prompt
+ └── src/
+ ├── todos.py # AgentState schema + todo tools
+ ├── plan.py # Mandatory plan_visualization tool
+ ├── query.py # Data query tool
+ └── bounded_memory_saver.py # FIFO thread eviction`,
+ },
+ {
+ type: "markdown",
+ id: "intro-flow",
+ content: `## The Visualization Flow
+
+Every visual response follows a **mandatory 4-step workflow**:
+
+1. **Acknowledge** — Agent replies with 1-2 sentences of context
+2. **Plan** — Agent calls \`plan_visualization\` (approach, technology, key elements)
+3. **Build** — Agent calls \`widgetRenderer\` / \`pieChart\` / \`barChart\`
+4. **Narrate** — Agent adds 2-3 sentences walking through the result
+
+The plan step is never skipped — it gives the user a preview of what's coming and helps the agent organize its approach.
+
+Let's dive into each layer, starting with the **Widget Renderer**.`,
+ },
+ ],
+};
diff --git a/apps/notebook/src/content/chapters/02-agent-state.ts b/apps/notebook/src/content/chapters/02-agent-state.ts
new file mode 100644
index 0000000..8a8e115
--- /dev/null
+++ b/apps/notebook/src/content/chapters/02-agent-state.ts
@@ -0,0 +1,338 @@
+import type { Chapter } from "@/lib/types";
+
+export const widgetRenderer: Chapter = {
+ id: "widget-renderer",
+ title: "Widget Renderer",
+ description:
+ "The sandboxed iframe engine that renders agent-generated HTML, SVG, and 3D.",
+ icon: "🖼",
+ cells: [
+ {
+ type: "markdown",
+ id: "wr-overview",
+ content: `# Widget Renderer
+
+The Widget Renderer is the core rendering engine. It takes arbitrary HTML from the agent and renders it in a **sandboxed iframe** with a full design system, streaming support, and a communication bridge.
+
+## What gets injected into the iframe
+
+The iframe isn't just raw HTML. Before any agent content is inserted, the iframe shell is assembled with 6 layers:
+
+1. **Theme CSS** — Light/dark mode variables (\`--color-text-primary\`, \`--color-background-secondary\`, etc.)
+2. **SVG Classes** — Pre-built CSS classes for colored SVG elements (\`.c-purple\`, \`.c-teal\`, \`.c-blue\`)
+3. **Form Styles** — Native-looking buttons, inputs, sliders, checkboxes with animations
+4. **Bridge JS** — \`window.sendPrompt()\`, \`window.openLink()\`, auto-resize reporting
+5. **Import Map** — ES module aliases for Three.js, GSAP, D3, Chart.js from esm.sh
+6. **CSP Policy** — Restricts scripts to approved CDNs (cdnjs, esm.sh, jsdelivr, unpkg)`,
+ },
+ {
+ type: "code",
+ id: "wr-bridge",
+ language: "typescript",
+ filename: "Bridge JS (injected into every iframe)",
+ content: `// The bridge gives widgets 3 capabilities:
+
+// 1. Send a new prompt to the agent
+window.sendPrompt = (text: string) => {
+ window.parent.postMessage(
+ { type: "send-prompt", prompt: text },
+ "*"
+ );
+};
+
+// 2. Open external links (parent handles navigation)
+window.openLink = (url: string) => {
+ window.parent.postMessage(
+ { type: "open-link", url },
+ "*"
+ );
+};
+
+// 3. Auto-resize: report content height to parent
+function reportHeight() {
+ const clone = document.body.cloneNode(true);
+ clone.style.cssText = "position:absolute;left:-9999px;width:" +
+ document.body.clientWidth + "px;visibility:hidden;";
+ document.documentElement.appendChild(clone);
+ const h = clone.scrollHeight;
+ clone.remove();
+ window.parent.postMessage({ type: "widget-resize", height: h }, "*");
+}
+
+new ResizeObserver(reportHeight).observe(document.body);`,
+ },
+ {
+ type: "markdown",
+ id: "wr-streaming",
+ content: `## Streaming with Idiomorph
+
+When the agent streams HTML, the widget renderer uses **Idiomorph** for efficient DOM diffing. Instead of replacing the entire iframe on each chunk, Idiomorph morphs the existing DOM — preserving interactive state, animations, and scroll position.
+
+The playground below demonstrates this: watch the HTML stream in character-by-character (like an LLM producing tokens), while the iframe updates progressively without flickering.`,
+ },
+ {
+ type: "playground",
+ id: "wr-streaming-playground",
+ title: "Live: Streaming HTML into an iframe",
+ files: {
+ "/App.js": `import { useState, useRef, useEffect, useCallback } from "react";
+
+// This is a real demo of how the widget renderer streams HTML.
+// The HTML arrives token-by-token (like an LLM), and the iframe
+// updates progressively — just like the real widget-renderer.tsx.
+
+const FULL_HTML = \`
+
Agent Performance Dashboard
+
+
Key Metrics
+
Tool calls 1,247
+
Avg latency 340ms
+
Success rate 99.2%
+
Active threads 42
+
+
\`;
+
+export default function App() {
+ const [streamedHtml, setStreamedHtml] = useState("");
+ const [isStreaming, setIsStreaming] = useState(false);
+ const [charIndex, setCharIndex] = useState(0);
+ const [iframeHeight, setIframeHeight] = useState(60);
+ const iframeRef = useRef(null);
+ const intervalRef = useRef(null);
+
+ // Listen for resize messages from iframe (just like the real bridge)
+ useEffect(() => {
+ const handler = (e) => {
+ if (e.data?.type === "widget-resize") {
+ setIframeHeight(Math.max(60, Math.min(800, e.data.height + 10)));
+ }
+ };
+ window.addEventListener("message", handler);
+ return () => window.removeEventListener("message", handler);
+ }, []);
+
+ // Update iframe content as HTML streams in
+ useEffect(() => {
+ if (!iframeRef.current || !streamedHtml) return;
+ const doc = iframeRef.current.contentDocument;
+ if (!doc) return;
+ doc.open();
+ doc.write(\`\${streamedHtml}
+
+ \`);
+ doc.close();
+ }, [streamedHtml]);
+
+ const startStream = useCallback(() => {
+ setStreamedHtml("");
+ setCharIndex(0);
+ setIsStreaming(true);
+ setIframeHeight(60);
+ let idx = 0;
+ clearInterval(intervalRef.current);
+ intervalRef.current = setInterval(() => {
+ // Stream ~8 chars at a time (simulating token chunks)
+ idx = Math.min(idx + 8, FULL_HTML.length);
+ setStreamedHtml(FULL_HTML.slice(0, idx));
+ setCharIndex(idx);
+ if (idx >= FULL_HTML.length) {
+ clearInterval(intervalRef.current);
+ setIsStreaming(false);
+ }
+ }, 16);
+ }, []);
+
+ useEffect(() => () => clearInterval(intervalRef.current), []);
+
+ const pct = FULL_HTML.length > 0 ? Math.round((charIndex / FULL_HTML.length) * 100) : 0;
+
+ return (
+
+ {/* Streaming progress bar */}
+
+
+ {isStreaming ? "Streaming..." : "Stream HTML"}
+
+
+
+ {charIndex}/{FULL_HTML.length}
+
+
+
+ {/* Live iframe preview — auto-resizes via bridge postMessage */}
+
+ {!streamedHtml && !isStreaming ? (
+
+ Click "Stream HTML" to watch the widget render progressively
+
+ ) : (
+
+ )}
+
+
+ );
+}`,
+ },
+ },
+ {
+ type: "playground",
+ id: "wr-bridge-playground",
+ title: "Live: Bridge communication (sendPrompt + auto-resize)",
+ files: {
+ "/App.js": `import { useState, useRef, useEffect } from "react";
+
+// This demonstrates the real bridge: the iframe sends messages
+// to the parent via postMessage, and the parent listens.
+
+const WIDGET_HTML = \`
+
+
Bridge Demo Widget
+
+ These buttons call window.sendPrompt() — the parent catches the message.
+
+
+
+ Ask for a chart
+
+
+ Ask for todos
+
+
+ Ask to explain
+
+
+
+ Toggle more content (triggers auto-resize)
+
+
+\`;
+
+export default function App() {
+ const iframeRef = useRef(null);
+ const [height, setHeight] = useState(180);
+ const [messages, setMessages] = useState([]);
+
+ useEffect(() => {
+ const handler = (e) => {
+ if (e.data?.type === "widget-resize") {
+ setHeight(Math.max(80, e.data.height + 10));
+ }
+ if (e.data?.type === "send-prompt") {
+ setMessages(prev => [...prev, { text: e.data.prompt, time: new Date().toLocaleTimeString() }]);
+ }
+ };
+ window.addEventListener("message", handler);
+ return () => window.removeEventListener("message", handler);
+ }, []);
+
+ useEffect(() => {
+ if (!iframeRef.current) return;
+ const doc = iframeRef.current.contentDocument;
+ if (!doc) return;
+ doc.open();
+ doc.write(\`\${WIDGET_HTML}
+
+ \`);
+ doc.close();
+ }, []);
+
+ return (
+
+ {/* The widget iframe */}
+
+
+ {/* Parent message log — shows what the bridge sent */}
+
+
+ Parent received via postMessage:
+
+ {messages.length === 0 ? (
+
+ Click a button inside the widget above...
+
+ ) : (
+ messages.map((m, i) => (
+
+ sendPrompt: {m.text}
+ {m.time}
+
+ ))
+ )}
+
+
+ );
+}`,
+ },
+ },
+ ],
+};
diff --git a/apps/notebook/src/content/chapters/03-generative-ui.ts b/apps/notebook/src/content/chapters/03-generative-ui.ts
new file mode 100644
index 0000000..e5f7f33
--- /dev/null
+++ b/apps/notebook/src/content/chapters/03-generative-ui.ts
@@ -0,0 +1,337 @@
+import type { Chapter } from "@/lib/types";
+
+export const copilotKitIntegration: Chapter = {
+ id: "copilotkit",
+ title: "CopilotKit Integration",
+ description:
+ "How React hooks bridge the frontend to the AI agent.",
+ icon: "🔌",
+ cells: [
+ {
+ type: "markdown",
+ id: "ck-overview",
+ content: `# CopilotKit Integration
+
+CopilotKit provides the bridge between the React frontend and the LangGraph agent. It works through three layers:
+
+1. **Provider** — \`
\` wraps the app
+2. **Runtime** — A Next.js API route that connects to the LangGraph agent via \`LangGraphHttpAgent\`
+3. **Hooks** — React hooks that register component tools, frontend tools, and render tools
+
+The key insight: CopilotKit lets you register **React components as agent tools**. When the agent calls a tool like \`widgetRenderer\`, instead of returning text, CopilotKit renders your component inline in the chat.`,
+ },
+ {
+ type: "code",
+ id: "ck-runtime",
+ language: "typescript",
+ filename: "apps/app/src/app/api/copilotkit/route.ts",
+ content: `import { CopilotRuntime, LangGraphHttpAgent } from "@copilotkit/runtime";
+
+const defaultAgent = new LangGraphHttpAgent({
+ deploymentUrl: process.env.LANGGRAPH_DEPLOYMENT_URL || "http://localhost:8123",
+ agentName: "sample_agent",
+});
+
+const runtime = new CopilotRuntime({
+ agents: { default: defaultAgent },
+ a2ui: { injectA2UITool: true },
+ mcpApps: {
+ servers: process.env.MCP_SERVER_URL ? [{
+ type: "http",
+ url: process.env.MCP_SERVER_URL,
+ serverId: "example_mcp_app",
+ }] : [],
+ },
+});`,
+ },
+ {
+ type: "markdown",
+ id: "ck-hooks-intro",
+ content: `## Hook Registration
+
+All CopilotKit hooks are registered in a single custom hook. Here's the full set used in OpenGenerativeUI:
+
+| Hook | Name | What it does |
+|------|------|-------------|
+| \`useComponent()\` | \`pieChart\` | Registers PieChart as a renderable tool |
+| \`useComponent()\` | \`barChart\` | Registers BarChart as a renderable tool |
+| \`useComponent()\` | \`widgetRenderer\` | Registers the iframe widget renderer |
+| \`useFrontendTool()\` | \`toggleTheme\` | Agent can switch light/dark mode |
+| \`useRenderTool()\` | \`plan_visualization\` | Shows PlanCard while agent plans |
+| \`useHumanInTheLoop()\` | \`scheduleTime\` | Pauses for user to pick a meeting time |
+
+The playground below is a working simulation of these hooks — the "agent" picks tools, and the corresponding React components render live in a chat-like stream:`,
+ },
+ {
+ type: "playground",
+ id: "ck-hooks-playground",
+ title: "Live: Agent calls component tools, React renders them",
+ dependencies: { recharts: "2.12.7" },
+ files: {
+ "/App.js": `import { useState, useEffect, useRef } from "react";
+import { PieChart, Pie, Cell, ResponsiveContainer, BarChart, Bar, XAxis, YAxis, Tooltip } from "recharts";
+
+// Registered component tools — same concept as useComponent()
+const componentTools = {
+ pieChart: ({ title, data }) => (
+
+
{title}
+
+
+ {data.map((_, i) => | )}
+
+
+
+ {data.map((d, i) => (
+
+
+ {d.label}: {d.value}
+
+ ))}
+
+
+ ),
+ barChart: ({ title, data }) => (
+
+
{title}
+
+
+
+ {data.map((_, i) => | )}
+
+
+
+
+ ),
+ widgetRenderer: ({ title, html }) => (
+
+ ),
+};
+
+// Simulated agent tool calls with real data
+const agentActions = [
+ { type: "text", content: "Let me show you the project stats." },
+ {
+ type: "tool", tool: "pieChart", args: {
+ title: "Languages in Codebase",
+ data: [{ label: "TypeScript", value: 45 }, { label: "Python", value: 30 }, { label: "CSS", value: 15 }, { label: "Other", value: 10 }],
+ },
+ },
+ { type: "text", content: "And here's the weekly activity:" },
+ {
+ type: "tool", tool: "barChart", args: {
+ title: "Commits This Week",
+ data: [{ label: "Mon", value: 12 }, { label: "Tue", value: 8 }, { label: "Wed", value: 15 }, { label: "Thu", value: 6 }, { label: "Fri", value: 19 }],
+ },
+ },
+ { type: "text", content: "I can also render arbitrary HTML via the widget renderer:" },
+ {
+ type: "tool", tool: "widgetRenderer", args: {
+ title: "Status Indicators",
+ html: '',
+ },
+ },
+];
+
+export default function App() {
+ const [messages, setMessages] = useState([]);
+ const [running, setRunning] = useState(false);
+ const bottomRef = useRef(null);
+
+ useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]);
+
+ const run = async () => {
+ setMessages([]);
+ setRunning(true);
+ for (const action of agentActions) {
+ await new Promise(r => setTimeout(r, 900));
+ setMessages(prev => [...prev, action]);
+ }
+ setRunning(false);
+ };
+
+ return (
+
+ {/* Chat stream */}
+
+ {messages.length === 0 && !running && (
+
+ Click "Run Agent" to see component tools render live
+
+ )}
+ {messages.map((msg, i) => (
+
+ {msg.type === "text" ? (
+
+ {msg.content}
+
+ ) : (
+
+
+ useComponent("{msg.tool}") renders:
+
+ {componentTools[msg.tool](msg.args)}
+
+ )}
+
+ ))}
+ {running && (
+
+
+ Agent is thinking...
+
+ )}
+
+
+
+
+ {/* Controls */}
+
+
+ {running ? "Running..." : "Run Agent"}
+
+
+
+ );
+}`,
+ },
+ },
+ {
+ type: "markdown",
+ id: "ck-state-sync",
+ content: `## useAgent() — Bidirectional State Sync
+
+The \`useAgent()\` hook gives the frontend direct access to the agent's state. Both user and agent can modify the same state:`,
+ },
+ {
+ type: "playground",
+ id: "ck-state-playground",
+ title: "Live: Bidirectional state sync (user + agent modify same todos)",
+ files: {
+ "/App.js": `import { useState, useCallback } from "react";
+
+// Simulates useAgent() — both sides write to the same state
+export default function App() {
+ const [todos, setTodos] = useState([
+ { id: "1", title: "Set up CopilotKit provider", emoji: "🔌", status: "completed" },
+ { id: "2", title: "Register component tools", emoji: "🪝", status: "completed" },
+ { id: "3", title: "Write agent system prompt", emoji: "📝", status: "pending" },
+ ]);
+ const [isAgentRunning, setIsAgentRunning] = useState(false);
+ const [lastAction, setLastAction] = useState("");
+
+ // User action: toggle todo status
+ const userToggle = (id) => {
+ setTodos(prev => prev.map(t => t.id === id ? { ...t, status: t.status === "completed" ? "pending" : "completed" } : t));
+ setLastAction("user: agent.setState({ todos: [...] })");
+ };
+
+ // User action: add todo
+ const userAdd = () => {
+ const newTodo = { id: String(Date.now()), title: "User-created task", emoji: "👤", status: "pending" };
+ setTodos(prev => [...prev, newTodo]);
+ setLastAction("user: agent.setState({ todos: [...todos, newTodo] })");
+ };
+
+ // Agent action: adds organized todos via manage_todos tool
+ const agentAction = useCallback(async () => {
+ setIsAgentRunning(true);
+ setLastAction("agent: calling manage_todos tool...");
+ await new Promise(r => setTimeout(r, 600));
+ setLastAction("agent: plan_visualization → approach: 'organize by priority'");
+ await new Promise(r => setTimeout(r, 800));
+
+ setTodos(prev => {
+ const organized = [
+ ...prev.filter(t => t.status === "pending"),
+ { id: String(Date.now()), title: "Deploy to production", emoji: "🚀", status: "pending" },
+ { id: String(Date.now()+1), title: "Write skill playbook", emoji: "📜", status: "pending" },
+ ...prev.filter(t => t.status === "completed"),
+ ];
+ return organized;
+ });
+ setLastAction("agent: Command(update={ todos: [...sorted, ...newTodos] })");
+ setIsAgentRunning(false);
+ }, []);
+
+ const pending = todos.filter(t => t.status === "pending");
+ const done = todos.filter(t => t.status === "completed");
+
+ return (
+
+ {/* State sync indicator */}
+
+ {lastAction || "State: idle — try clicking a todo or running the agent"}
+
+
+
+ {/* Pending column */}
+
+
+ Pending ({pending.length})
+
+ {pending.map(t => (
+
userToggle(t.id)} style={{
+ padding: "8px 12px", marginBottom: 6, borderRadius: 8, cursor: "pointer",
+ background: "#fff", border: "1px solid #e5e7eb", fontSize: 13,
+ transition: "transform 0.15s", display: "flex", alignItems: "center", gap: 6,
+ }}>
+
+ {t.emoji} {t.title}
+
+ ))}
+
+ {/* Done column */}
+
+
+ Done ({done.length})
+
+ {done.map(t => (
+
userToggle(t.id)} style={{
+ padding: "8px 12px", marginBottom: 6, borderRadius: 8, cursor: "pointer",
+ background: "#f0fdf4", border: "1px solid #d1fae5", fontSize: 13, opacity: 0.8,
+ textDecoration: "line-through", display: "flex", alignItems: "center", gap: 6,
+ }}>
+
+
+
+
{t.emoji} {t.title}
+
+ ))}
+
+
+
+
+
+ + User adds todo
+
+
+ {isAgentRunning ? "Agent working..." : "Agent: organize + add tasks"}
+
+
+
+ );
+}`,
+ },
+ },
+ ],
+};
diff --git a/apps/notebook/src/content/chapters/04-copilotkit-hooks.ts b/apps/notebook/src/content/chapters/04-copilotkit-hooks.ts
new file mode 100644
index 0000000..34b353e
--- /dev/null
+++ b/apps/notebook/src/content/chapters/04-copilotkit-hooks.ts
@@ -0,0 +1,287 @@
+import type { Chapter } from "@/lib/types";
+
+export const deepAgent: Chapter = {
+ id: "deep-agent",
+ title: "Deep Agent",
+ description:
+ "The LangGraph agent: create_deep_agent, tools, state, and memory.",
+ icon: "🧠",
+ cells: [
+ {
+ type: "markdown",
+ id: "da-overview",
+ content: `# The Deep Agent
+
+The agent backend uses \`create_deep_agent\` from the \`deepagents\` library — a LangGraph-based agent factory that supports tools, middleware, skills, and bounded memory.`,
+ },
+ {
+ type: "code",
+ id: "da-main",
+ language: "python",
+ filename: "apps/agent/main.py",
+ content: `from deepagents import create_deep_agent
+from copilotkit import CopilotKitMiddleware, LangGraphAGUIAgent
+from langchain_openai import ChatOpenAI
+
+agent = create_deep_agent(
+ model=ChatOpenAI(model=os.environ.get("LLM_MODEL", "gpt-5.4-2026-03-05")),
+ tools=[query_data, plan_visualization, *todo_tools, generate_form],
+ middleware=[CopilotKitMiddleware()],
+ context_schema=AgentState,
+ skills=[str(Path(__file__).parent / "skills")],
+ checkpointer=BoundedMemorySaver(max_threads=200),
+ system_prompt="...",
+)`,
+ },
+ {
+ type: "code",
+ id: "da-tools-code",
+ language: "python",
+ filename: "apps/agent/src/todos.py — manage_todos",
+ content: `@tool
+def manage_todos(todos: list[Todo], runtime: ToolRuntime) -> Command:
+ """Manage the current todos."""
+ for todo in todos:
+ if "id" not in todo or not todo["id"]:
+ todo["id"] = str(uuid.uuid4())
+
+ return Command(update={
+ "todos": todos,
+ "messages": [ToolMessage(
+ content="Successfully updated todos",
+ tool_call_id=runtime.tool_call_id
+ )]
+ })`,
+ },
+ {
+ type: "markdown",
+ id: "da-workflow",
+ content: `## Mandatory Visualization Workflow
+
+The system prompt enforces a strict 4-step pipeline for every visual response. The playground below runs a real simulation — watch the agent execute each tool, produce actual output, and render the final widget:`,
+ },
+ {
+ type: "playground",
+ id: "da-pipeline-playground",
+ title: "Live: Full agent tool execution pipeline with rendered output",
+ files: {
+ "/App.js": `import { useState, useRef, useEffect } from "react";
+
+// Full agent pipeline simulation that ACTUALLY RENDERS the visualization
+
+export default function App() {
+ const [phase, setPhase] = useState("idle"); // idle, ack, plan, build, narrate, done
+ const [chatLog, setChatLog] = useState([]);
+ const [widgetHtml, setWidgetHtml] = useState("");
+ const iframeRef = useRef(null);
+ const bottomRef = useRef(null);
+
+ useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: "smooth" }); }, [chatLog, widgetHtml]);
+
+ // Update iframe when widget HTML is set
+ useEffect(() => {
+ if (!iframeRef.current || !widgetHtml) return;
+ const doc = iframeRef.current.contentDocument;
+ if (!doc) return;
+ doc.open();
+ doc.write(\`\${widgetHtml}\`);
+ doc.close();
+ }, [widgetHtml]);
+
+ const runPipeline = async () => {
+ setChatLog([]);
+ setWidgetHtml("");
+
+ // Step 1: Acknowledge
+ setPhase("ack");
+ await new Promise(r => setTimeout(r, 600));
+ setChatLog(prev => [...prev, {
+ type: "text",
+ content: "I'll create an interactive metrics dashboard showing the agent's tool usage and performance data.",
+ }]);
+
+ // Step 2: Plan
+ setPhase("plan");
+ await new Promise(r => setTimeout(r, 800));
+ setChatLog(prev => [...prev, {
+ type: "plan",
+ data: {
+ approach: "Multi-card dashboard with animated bar chart and live metric counters",
+ technology: "HTML + CSS (no dependencies)",
+ keyElements: [
+ "Metrics cards row with tool call count, latency, success rate",
+ "Horizontal bar chart showing tool usage distribution",
+ "Animated entrance transitions",
+ "Design system CSS variables for theming",
+ ],
+ },
+ }]);
+
+ // Step 3: Build (stream the actual widget)
+ setPhase("build");
+ await new Promise(r => setTimeout(r, 500));
+ setChatLog(prev => [...prev, { type: "building" }]);
+
+ const widgetContent = \`
+
+
+
Tool usage breakdown
+
+
+
+
+
+
\`;
+ await new Promise(r => setTimeout(r, 400));
+ setWidgetHtml(widgetContent);
+
+ // Step 4: Narrate
+ setPhase("narrate");
+ await new Promise(r => setTimeout(r, 1200));
+ setChatLog(prev => [...prev, {
+ type: "text",
+ content: "The dashboard shows the agent has handled 1,247 tool calls with a 99.2% success rate. The widgetRenderer is the most-used tool at 78%, followed by charts at 52%. Notice that plan_visualization runs at 95% — nearly every visual response includes a planning step.",
+ }]);
+ setPhase("done");
+ };
+
+ const phaseLabel = { idle: "", ack: "Acknowledging...", plan: "Planning...", build: "Building widget...", narrate: "Narrating...", done: "Complete" };
+
+ return (
+
+
+ {chatLog.length === 0 && phase === "idle" && (
+
+ Click "Run Pipeline" to execute the full Acknowledge → Plan → Build → Narrate workflow
+
+ )}
+
+ {chatLog.map((msg, i) => {
+ if (msg.type === "text") return (
+
+ {msg.content}
+
+ );
+ if (msg.type === "plan") return (
+
+
Plan
+
{msg.data.approach}
+
Tech: {msg.data.technology}
+ {msg.data.keyElements.map((el, j) => (
+
+ - {el}
+
+ ))}
+
+ );
+ if (msg.type === "building") return null;
+ return null;
+ })}
+
+ {/* The actual rendered widget */}
+ {widgetHtml && (
+
+
+ widgetRenderer output:
+
+
+
+ )}
+
+
+
+
+
+
+ {phase === "done" ? "Run Again" : phase === "idle" ? "Run Pipeline" : phaseLabel[phase]}
+
+ {phase !== "idle" && (
+
+ {["ack","plan","build","narrate"].map((p, i) => (
+
i || phase === "done") ? "#E1F5EE" : "#f0f0f0",
+ color: phase === p ? "#fff" : (["ack","plan","build","narrate"].indexOf(phase) > i || phase === "done") ? "#0F6E56" : "#9ca3af",
+ }}>
+ {p}
+
+ ))}
+
+ )}
+
+
+ );
+}`,
+ },
+ },
+ {
+ type: "code",
+ id: "da-memory-code",
+ language: "python",
+ filename: "apps/agent/src/bounded_memory_saver.py",
+ content: `class BoundedMemorySaver(MemorySaver):
+ """MemorySaver that evicts oldest threads when exceeding max_threads."""
+
+ def __init__(self, max_threads: int = 200):
+ super().__init__()
+ self.max_threads = max_threads
+ self._insertion_order: OrderedDict[str, None] = OrderedDict()
+
+ def put(self, config, checkpoint, metadata, new_versions):
+ thread_id = config["configurable"]["thread_id"]
+ self._insertion_order[thread_id] = None
+ self._insertion_order.move_to_end(thread_id)
+
+ result = super().put(config, checkpoint, metadata, new_versions)
+
+ while len(self.storage) > self.max_threads:
+ oldest, _ = self._insertion_order.popitem(last=False)
+ if oldest in self.storage:
+ del self.storage[oldest]
+ return result`,
+ },
+ ],
+};
diff --git a/apps/notebook/src/content/chapters/05-frontend-tools.ts b/apps/notebook/src/content/chapters/05-frontend-tools.ts
new file mode 100644
index 0000000..d116428
--- /dev/null
+++ b/apps/notebook/src/content/chapters/05-frontend-tools.ts
@@ -0,0 +1,388 @@
+import type { Chapter } from "@/lib/types";
+
+export const agentSkills: Chapter = {
+ id: "agent-skills",
+ title: "Agent Skills",
+ description:
+ "How skill documents teach the agent to write high-quality visual code.",
+ icon: "📜",
+ cells: [
+ {
+ type: "markdown",
+ id: "skills-overview",
+ content: `# Agent Skills
+
+The agent doesn't just have tools — it has **skills**. Skills are markdown documents loaded at startup via \`create_deep_agent(skills=[...])\` that teach the agent *how* to write HTML, SVG, and interactive code.
+
+Without skills, the agent produces generic output. With skills, it follows a consistent design system, uses proper typography, picks from a curated color palette, and structures responses with the 3-layer pattern (hook → visual → narration).
+
+## Three Skill Documents
+
+| Skill | What it teaches |
+|-------|----------------|
+| **Master Playbook** | Response philosophy, decision tree, interactive widget templates, Chart.js patterns |
+| **SVG Diagrams** | Precise SVG rules: 680px viewBox, 9 color ramps, text sizing, arrow markers |
+| **Advanced Visualization** | Design system tokens, UI mockup patterns, dashboard layouts, dark mode |`,
+ },
+ {
+ type: "code",
+ id: "skills-loading",
+ language: "python",
+ filename: "apps/agent/main.py — skills loaded at startup",
+ content: `agent = create_deep_agent(
+ model=ChatOpenAI(model="gpt-5.4-2026-03-05"),
+ tools=[query_data, plan_visualization, *todo_tools, generate_form],
+ middleware=[CopilotKitMiddleware()],
+ context_schema=AgentState,
+ # Skills are loaded from this directory at startup
+ # Each subfolder contains a SKILL.md with frontmatter
+ skills=[str(Path(__file__).parent / "skills")],
+ checkpointer=BoundedMemorySaver(max_threads=200),
+ system_prompt="...",
+)
+
+# apps/agent/skills/
+# ├── master-playbook/SKILL.md → Response philosophy + widget templates
+# ├── svg-diagrams/SKILL.md → SVG generation rules
+# └── advanced-visualization/SKILL.md → Design system + advanced patterns`,
+ },
+ {
+ type: "code",
+ id: "skills-frontmatter",
+ language: "markdown",
+ filename: "apps/agent/skills/master-playbook/SKILL.md (excerpt)",
+ content: `---
+name: "Master Agent Playbook"
+description: "Philosophy, decision-making framework, and technical skills
+ for delivering visual, interactive, and educational AI responses."
+allowed-tools: []
+---
+
+# Master Agent Playbook: Making AI Responses Extraordinary
+
+## The Core Philosophy
+
+### Think Like a Teacher, Not a Search Engine
+
+Bad: "A load path is the route that forces take through a structure."
+Good: [draws an interactive building cross-section with loads flowing]
+
+The principle: **Show, don't just tell.** Before writing any response:
+- Would a diagram make this click faster than a paragraph?
+- Would an interactive widget let the user explore themselves?
+- Would a worked example teach better than a definition?`,
+ },
+ {
+ type: "markdown",
+ id: "skills-decision-tree",
+ content: `## The Response Decision Tree
+
+The master playbook teaches the agent to choose the right output format based on what the user is asking:`,
+ },
+ {
+ type: "mermaid",
+ id: "skills-decision-diagram",
+ title: "Skill Decision Tree",
+ content: `flowchart TD
+ Q[User asks a question] --> F{Question type?}
+ F -->|Quick factual| T[1-2 sentences of text]
+ F -->|Conceptual| C{Sub-type?}
+ F -->|Build me X| A[Working code artifact]
+ F -->|Comparison| S[Side-by-side visual]
+ F -->|Emotional| W[Warm text only]
+
+ C -->|Spatial / visual| SVG[SVG diagram]
+ C -->|Process / flow| FL[Flowchart or stepper]
+ C -->|Data-driven| CH[Interactive chart]
+ C -->|Abstract| WI[Widget with controls]
+
+ style Q fill:#EDE9F5,stroke:#5B3FA0,color:#3E2B6F
+ style F fill:#FAEEDA,stroke:#B8860B,color:#854F0B
+ style C fill:#FAEEDA,stroke:#B8860B,color:#854F0B
+ style T fill:#f7f6f3,stroke:#9c9a92,color:#1a1a1a
+ style A fill:#E1F5EE,stroke:#0F6E56,color:#085041
+ style S fill:#E3EFFC,stroke:#2663B3,color:#1A4680
+ style W fill:#f7f6f3,stroke:#9c9a92,color:#1a1a1a
+ style SVG fill:#E1F5EE,stroke:#0F6E56,color:#085041
+ style FL fill:#E1F5EE,stroke:#0F6E56,color:#085041
+ style CH fill:#E3EFFC,stroke:#2663B3,color:#1A4680
+ style WI fill:#E3EFFC,stroke:#2663B3,color:#1A4680`,
+ },
+ {
+ type: "markdown",
+ id: "skills-decision-tree-cta",
+ content: `The playground below shows this in action — pick different question types and see the skill guide the agent to produce different output formats:`,
+ },
+ {
+ type: "playground",
+ id: "skills-decision-playground",
+ title: "Live: Skill-guided response format selection",
+ files: {
+ "/App.js": `import { useState, useRef, useEffect } from "react";
+
+// The skill decision tree in action: different questions → different output formats
+
+const questions = [
+ {
+ text: "How does a load balancer distribute traffic?",
+ type: "Process / flow",
+ format: "SVG flowchart",
+ output: \`
+
+
+ Incoming request
+
+
+ Load balancer
+ Round-robin algorithm
+
+
+
+
+ Server A
+ cpu: 45%
+
+ Server B
+ cpu: 62%
+
+ Server C
+ cpu: 28%
+ \`,
+ },
+ {
+ text: "Compare React vs Vue vs Svelte bundle sizes",
+ type: "Data-driven comparison",
+ format: "Interactive chart",
+ output: \`
+ Framework bundle size (production, gzipped)
+
+
+
+ Svelte compiles away the framework — the output is vanilla JS with no runtime.
\`,
+ },
+ {
+ text: "Build me a Pomodoro timer",
+ type: "Build request",
+ format: "Working interactive widget",
+ output: \`
+ Pomodoro Timer
+ 25:00
+ Focus time
+
+ Start
+ Reset
+ \`,
+ },
+];
+
+export default function App() {
+ const [selected, setSelected] = useState(null);
+ const iframeRef = useRef(null);
+
+ useEffect(() => {
+ if (!iframeRef.current || selected === null) return;
+ const doc = iframeRef.current.contentDocument;
+ if (!doc) return;
+ doc.open();
+ doc.write(\`\${questions[selected].output}\`);
+ doc.close();
+ }, [selected]);
+
+ return (
+
+ {/* Question selector */}
+
+
+ Pick a question — the skill guides the agent to the right output format:
+
+
+ {questions.map((q, i) => (
+ setSelected(i)} style={{
+ padding: "10px 14px", borderRadius: 10, border: selected === i ? "2px solid #5B3FA0" : "1px solid #e5e7eb",
+ background: selected === i ? "#f5f3ff" : "#fff", cursor: "pointer", textAlign: "left",
+ display: "flex", justifyContent: "space-between", alignItems: "center",
+ }}>
+ {q.text}
+
+ {q.type} → {q.format}
+
+
+ ))}
+
+
+
+ {/* Rendered output */}
+ {selected !== null && (
+
+
+ Agent output ({questions[selected].format})
+ Rendered in sandboxed iframe
+
+
+
+ )}
+
+ );
+}`,
+ },
+ },
+ {
+ type: "markdown",
+ id: "skills-design-system",
+ content: `## Design System Tokens
+
+The advanced visualization skill defines CSS variables that get injected into every widget. This means the agent writes code like \`color: var(--color-text-primary)\` instead of hardcoded hex values — and dark mode works automatically.
+
+The playground below renders actual HTML using these tokens, exactly as the widget renderer does:`,
+ },
+ {
+ type: "playground",
+ id: "skills-tokens-playground",
+ title: "Live: Design system tokens in action",
+ files: {
+ "/App.js": `import { useState, useRef, useEffect } from "react";
+
+// The REAL CSS variables from the skill document
+const THEME_CSS = \`
+:root {
+ --color-background-primary: #ffffff;
+ --color-background-secondary: #f7f6f3;
+ --color-background-tertiary: #efeee9;
+ --color-background-info: #E6F1FB;
+ --color-background-danger: #FCEBEB;
+ --color-background-success: #EAF3DE;
+ --color-background-warning: #FAEEDA;
+ --color-text-primary: #1a1a1a;
+ --color-text-secondary: #73726c;
+ --color-text-tertiary: #9c9a92;
+ --color-text-info: #185FA5;
+ --color-text-danger: #A32D2D;
+ --color-text-success: #3B6D11;
+ --color-text-warning: #854F0B;
+ --color-border-tertiary: rgba(0, 0, 0, 0.15);
+ --font-sans: system-ui, -apple-system, sans-serif;
+ --border-radius-lg: 12px;
+}
+@media (prefers-color-scheme: dark) {
+ :root {
+ --color-background-primary: #1a1a18;
+ --color-background-secondary: #2c2c2a;
+ --color-text-primary: #e8e6de;
+ --color-text-secondary: #9c9a92;
+ --color-border-tertiary: rgba(255, 255, 255, 0.15);
+ }
+}
+body { font-family: var(--font-sans); margin: 0; padding: 16px;
+ background: var(--color-background-primary); color: var(--color-text-primary); }
+\`;
+
+const templates = {
+ "Status cards": \`\`,
+ "Data table": \`
+
+ Tool
+ Calls
+ Avg ms
+
+
+ widgetRenderer 847 280
+ manage_todos 312 45
+ plan_visualization 823 520
+
+
\`,
+ "Info panel": \`
+
How skills guide code generation
+
+ The agent writes var(--color-text-primary) instead of hardcoded colors. This means every widget automatically supports dark mode — no extra code needed.
+
+
\`,
+};
+
+export default function App() {
+ const [selected, setSelected] = useState("Status cards");
+ const iframeRef = useRef(null);
+
+ useEffect(() => {
+ if (!iframeRef.current) return;
+ const doc = iframeRef.current.contentDocument;
+ doc.open();
+ doc.write(\`\${templates[selected]}\`);
+ doc.close();
+ }, [selected]);
+
+ return (
+
+
+ {Object.keys(templates).map(name => (
+ setSelected(name)} style={{
+ padding: "5px 12px", borderRadius: 6, border: "none", fontSize: 12, fontWeight: 600, cursor: "pointer",
+ background: selected === name ? "#5B3FA0" : "#f0f0f0",
+ color: selected === name ? "#fff" : "#6b7280",
+ }}>{name}
+ ))}
+
+
+
+
+ HTML using design tokens (not hardcoded colors):
+
+
+ {templates[selected].trim()}
+
+
+
+ );
+}`,
+ },
+ },
+ ],
+};
diff --git a/apps/notebook/src/content/chapters/06-widget-renderer.ts b/apps/notebook/src/content/chapters/06-widget-renderer.ts
new file mode 100644
index 0000000..a6ae43c
--- /dev/null
+++ b/apps/notebook/src/content/chapters/06-widget-renderer.ts
@@ -0,0 +1,254 @@
+import type { Chapter } from "@/lib/types";
+
+export const fullFlow: Chapter = {
+ id: "full-flow",
+ title: "Putting It Together",
+ description:
+ "The complete end-to-end flow from user message to rendered visualization.",
+ icon: "🔗",
+ cells: [
+ {
+ type: "markdown",
+ id: "flow-overview",
+ content: `# Putting It Together
+
+Now let's trace the **complete flow** in one working demo. Pick a prompt, and watch it flow through all three layers — ending with a real rendered widget in an iframe.`,
+ },
+ {
+ type: "playground",
+ id: "flow-full-demo",
+ title: "Live: Complete end-to-end pipeline — type a message, get a widget",
+ files: {
+ "/App.js": `import { useState, useRef, useEffect } from "react";
+
+// Prompt → tool library: maps keywords to full widget HTML output
+const widgetLibrary = {
+ dashboard: {
+ plan: { approach: "Metrics dashboard with KPI cards and bar chart", technology: "HTML + CSS", keyElements: ["KPI metric cards", "Horizontal bar chart", "Animated entrances"] },
+ title: "Agent Dashboard",
+ html: \`
+
+ Endpoint traffic
+
+
+ \`,
+ },
+ chart: {
+ plan: { approach: "Animated donut chart showing language distribution", technology: "SVG + CSS animations", keyElements: ["SVG donut ring segments", "Animated arc drawing", "Legend with percentages"] },
+ title: "Language Distribution",
+ html: \`
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TypeScript 45%
+ Python 30%
+ CSS 15%
+ Other 10%
+
+
\`,
+ },
+ diagram: {
+ plan: { approach: "Architecture flow diagram showing data path", technology: "Inline SVG (skill-guided)", keyElements: ["4 layered nodes with color ramps", "Directional arrows", "680px viewBox, responsive"] },
+ title: "Architecture Flow",
+ html: \`
+
+
+
+
+ User message Chat input
+ CopilotKit runtime LangGraphHttpAgent bridge
+ LangGraph deep agent Tools + skills + state
+ Widget renderer Sandboxed iframe output
+ \`,
+ },
+};
+
+const prompts = [
+ { text: "Show me a dashboard", key: "dashboard" },
+ { text: "Create a language chart", key: "chart" },
+ { text: "Draw the architecture", key: "diagram" },
+];
+
+export default function App() {
+ const [input, setInput] = useState("");
+ const [log, setLog] = useState([]);
+ const [phase, setPhase] = useState("idle");
+ const [widgetHtml, setWidgetHtml] = useState("");
+ const iframeRef = useRef(null);
+ const bottomRef = useRef(null);
+
+ useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: "smooth" }); }, [log, widgetHtml]);
+
+ useEffect(() => {
+ if (!iframeRef.current || !widgetHtml) return;
+ const doc = iframeRef.current.contentDocument;
+ if (!doc) return;
+ doc.open();
+ doc.write(\`\${widgetHtml}\`);
+ doc.close();
+ }, [widgetHtml]);
+
+ const send = async (promptKey) => {
+ const widget = widgetLibrary[promptKey];
+ if (!widget) return;
+ setLog([]);
+ setWidgetHtml("");
+ setInput("");
+
+ // 1. User message
+ setLog([{ type: "user", text: prompts.find(p => p.key === promptKey)?.text || input }]);
+ setPhase("ack");
+ await new Promise(r => setTimeout(r, 500));
+
+ // 2. Acknowledge
+ setLog(prev => [...prev, { type: "agent", text: "I'll create that for you." }]);
+ setPhase("plan");
+ await new Promise(r => setTimeout(r, 700));
+
+ // 3. Plan
+ setLog(prev => [...prev, { type: "plan", data: widget.plan }]);
+ setPhase("build");
+ await new Promise(r => setTimeout(r, 800));
+
+ // 4. Build — render actual widget
+ setLog(prev => [...prev, { type: "widget", title: widget.title }]);
+ setWidgetHtml(widget.html);
+ setPhase("narrate");
+ await new Promise(r => setTimeout(r, 1200));
+
+ // 5. Narrate
+ setLog(prev => [...prev, { type: "agent", text: "Here's the visualization. The data is rendered using the design system's CSS variables, so it supports dark mode automatically. Want me to modify anything?" }]);
+ setPhase("done");
+ };
+
+ return (
+
+ {/* Chat area */}
+
+ {log.length === 0 && (
+
+
Try a prompt:
+
+ {prompts.map(p => (
+ send(p.key)} style={{
+ padding: "8px 16px", borderRadius: 20, border: "1px solid #e5e7eb",
+ background: "#fff", cursor: "pointer", fontSize: 13, color: "#374151",
+ transition: "all 0.15s",
+ }}>
+ {p.text}
+
+ ))}
+
+
+ )}
+
+ {log.map((msg, i) => {
+ if (msg.type === "user") return (
+
+ {msg.text}
+
+ );
+ if (msg.type === "agent") return (
+
+ {msg.text}
+
+ );
+ if (msg.type === "plan") return (
+
+
plan_visualization
+
{msg.data.approach}
+
Tech: {msg.data.technology}
+
+ );
+ if (msg.type === "widget") return (
+
+
+ widgetRenderer: {msg.title}
+
+
+
+ );
+ return null;
+ })}
+
+ {phase !== "idle" && phase !== "done" && (
+
+
+ {phase === "ack" ? "Agent thinking..." : phase === "plan" ? "Planning visualization..." : phase === "build" ? "Rendering widget..." : "Writing narration..."}
+
+ )}
+
+
+
+
+ {/* Input bar + quick prompts */}
+
+ {phase === "done" && (
+
+ {prompts.map(p => (
+ send(p.key)} style={{
+ flex: 1, padding: "10px", borderRadius: 8, border: "1px solid #e5e7eb",
+ background: "#fff", cursor: "pointer", fontSize: 12, fontWeight: 600, color: "#374151",
+ }}>
+ {p.text}
+
+ ))}
+
+ )}
+
+
+ );
+}`,
+ },
+ },
+ {
+ type: "markdown",
+ id: "flow-extend",
+ content: `## Extending the Template
+
+To add your own domain:
+
+1. **Define state** — Add fields to \`AgentState\` in Python
+2. **Create tools** — Return \`Command(update={...})\` to modify state
+3. **Register components** — Use \`useComponent()\` for agent-renderable UI
+4. **Configure the system prompt** — Define your mandatory workflow
+
+The todo list is the starting point — replace it with your domain while keeping the same CopilotKit v2 state pattern.`,
+ },
+ ],
+};
diff --git a/apps/notebook/src/content/index.ts b/apps/notebook/src/content/index.ts
new file mode 100644
index 0000000..30a3f15
--- /dev/null
+++ b/apps/notebook/src/content/index.ts
@@ -0,0 +1,16 @@
+import { introduction } from "./chapters/01-introduction";
+import { widgetRenderer } from "./chapters/02-agent-state";
+import { copilotKitIntegration } from "./chapters/03-generative-ui";
+import { deepAgent } from "./chapters/04-copilotkit-hooks";
+import { agentSkills } from "./chapters/05-frontend-tools";
+import { fullFlow } from "./chapters/06-widget-renderer";
+import type { Chapter } from "@/lib/types";
+
+export const chapters: Chapter[] = [
+ introduction,
+ deepAgent,
+ agentSkills,
+ copilotKitIntegration,
+ widgetRenderer,
+ fullFlow,
+];
diff --git a/apps/notebook/src/hooks/use-theme.tsx b/apps/notebook/src/hooks/use-theme.tsx
new file mode 100644
index 0000000..fac7b20
--- /dev/null
+++ b/apps/notebook/src/hooks/use-theme.tsx
@@ -0,0 +1,40 @@
+"use client";
+
+import { createContext, useContext, useEffect, useState } from "react";
+
+type Theme = "dark" | "light" | "system";
+
+const ThemeContext = createContext<{ theme: Theme; setTheme: (t: Theme) => void }>({
+ theme: "system",
+ setTheme: () => {},
+});
+
+export function ThemeProvider({ children }: { children: React.ReactNode }) {
+ const [theme, setTheme] = useState("system");
+
+ useEffect(() => {
+ const root = document.documentElement;
+ root.classList.remove("light", "dark");
+
+ if (theme === "system") {
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
+ const apply = () => {
+ root.classList.remove("light", "dark");
+ root.classList.add(mq.matches ? "dark" : "light");
+ };
+ apply();
+ mq.addEventListener("change", apply);
+ return () => mq.removeEventListener("change", apply);
+ }
+
+ root.classList.add(theme);
+ }, [theme]);
+
+ return (
+
+ {children}
+
+ );
+}
+
+export const useTheme = () => useContext(ThemeContext);
diff --git a/apps/notebook/src/lib/highlight.ts b/apps/notebook/src/lib/highlight.ts
new file mode 100644
index 0000000..7f4f567
--- /dev/null
+++ b/apps/notebook/src/lib/highlight.ts
@@ -0,0 +1,13 @@
+import { createHighlighter, type Highlighter } from "shiki";
+
+let highlighterPromise: Promise | null = null;
+
+export function getHighlighter(): Promise {
+ if (!highlighterPromise) {
+ highlighterPromise = createHighlighter({
+ themes: ["github-light", "github-dark"],
+ langs: ["typescript", "tsx", "javascript", "jsx", "python", "bash", "json", "yaml", "css", "html"],
+ });
+ }
+ return highlighterPromise;
+}
diff --git a/apps/notebook/src/lib/types.ts b/apps/notebook/src/lib/types.ts
new file mode 100644
index 0000000..6f73c7b
--- /dev/null
+++ b/apps/notebook/src/lib/types.ts
@@ -0,0 +1,39 @@
+export type MarkdownCell = {
+ type: "markdown";
+ id: string;
+ content: string;
+};
+
+export type CodeCell = {
+ type: "code";
+ id: string;
+ language: string;
+ content: string;
+ filename?: string;
+};
+
+export type PlaygroundCell = {
+ type: "playground";
+ id: string;
+ files: Record;
+ dependencies?: Record;
+ activeFile?: string;
+ title?: string;
+};
+
+export type MermaidCell = {
+ type: "mermaid";
+ id: string;
+ content: string;
+ title?: string;
+};
+
+export type Cell = MarkdownCell | CodeCell | PlaygroundCell | MermaidCell;
+
+export type Chapter = {
+ id: string;
+ title: string;
+ description: string;
+ icon: string;
+ cells: Cell[];
+};
diff --git a/apps/notebook/tsconfig.json b/apps/notebook/tsconfig.json
new file mode 100644
index 0000000..120cfbd
--- /dev/null
+++ b/apps/notebook/tsconfig.json
@@ -0,0 +1,29 @@
+{
+ "compilerOptions": {
+ "target": "ES2017",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "react-jsx",
+ "incremental": true,
+ "plugins": [{ "name": "next" }],
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts",
+ ".next/dev/types/**/*.ts"
+ ],
+ "exclude": ["node_modules"]
+}
diff --git a/package.json b/package.json
index 7369638..0b6c1da 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,7 @@
"dev:app": "turbo run dev --filter='@repo/app'",
"dev:agent": "cd apps/agent && uv sync && uv run uvicorn main:app --host 0.0.0.0 --port 8123 --reload",
"dev:mcp": "turbo run dev --filter='open-generative-ui-mcp'",
+ "dev:notebook": "turbo run dev --filter='@repo/notebook'",
"build": "turbo run build",
"lint": "turbo run lint",
"clean": "turbo run clean"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 20c128f..0c489d3 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -116,6 +116,61 @@ importers:
specifier: ^5
version: 5.9.3
+ apps/notebook:
+ dependencies:
+ '@codesandbox/sandpack-react':
+ specifier: ^2.19.0
+ version: 2.20.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ mermaid:
+ specifier: ^11.4.0
+ version: 11.12.2
+ next:
+ specifier: 16.1.6
+ version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ react:
+ specifier: ^19.2.4
+ version: 19.2.4
+ react-dom:
+ specifier: ^19.2.4
+ version: 19.2.4(react@19.2.4)
+ react-markdown:
+ specifier: ^9.0.3
+ version: 9.1.0(@types/react@19.2.10)(react@19.2.4)
+ remark-gfm:
+ specifier: ^4.0.0
+ version: 4.0.1
+ shiki:
+ specifier: ^3.2.0
+ version: 3.22.0
+ devDependencies:
+ '@eslint/eslintrc':
+ specifier: ^3
+ version: 3.3.3
+ '@tailwindcss/postcss':
+ specifier: ^4
+ version: 4.1.18
+ '@types/node':
+ specifier: ^20
+ version: 20.19.31
+ '@types/react':
+ specifier: ^19
+ version: 19.2.10
+ '@types/react-dom':
+ specifier: ^19
+ version: 19.2.3(@types/react@19.2.10)
+ eslint:
+ specifier: ^9
+ version: 9.39.2(jiti@2.6.1)
+ eslint-config-next:
+ specifier: 16.1.6
+ version: 16.1.6(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+ tailwindcss:
+ specifier: ^4
+ version: 4.1.18
+ typescript:
+ specifier: ^5
+ version: 5.9.3
+
packages:
'@0no-co/graphql.web@1.2.0':
@@ -306,6 +361,45 @@ packages:
'@chevrotain/utils@11.0.3':
resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==}
+ '@codemirror/autocomplete@6.20.1':
+ resolution: {integrity: sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==}
+
+ '@codemirror/commands@6.10.3':
+ resolution: {integrity: sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==}
+
+ '@codemirror/lang-css@6.3.1':
+ resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==}
+
+ '@codemirror/lang-html@6.4.11':
+ resolution: {integrity: sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==}
+
+ '@codemirror/lang-javascript@6.2.5':
+ resolution: {integrity: sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==}
+
+ '@codemirror/language@6.12.3':
+ resolution: {integrity: sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==}
+
+ '@codemirror/lint@6.9.5':
+ resolution: {integrity: sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==}
+
+ '@codemirror/state@6.6.0':
+ resolution: {integrity: sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==}
+
+ '@codemirror/view@6.41.0':
+ resolution: {integrity: sha512-6H/qadXsVuDY219Yljhohglve8xf4B8xJkVOEWfA5uiYKiTFppjqsvsfR5iPA0RbvRBoOyTZpbLIxe9+0UR8xA==}
+
+ '@codesandbox/nodebox@0.1.8':
+ resolution: {integrity: sha512-2VRS6JDSk+M+pg56GA6CryyUSGPjBEe8Pnae0QL3jJF1mJZJVMDKr93gJRtBbLkfZN6LD/DwMtf+2L0bpWrjqg==}
+
+ '@codesandbox/sandpack-client@2.19.8':
+ resolution: {integrity: sha512-CMV4nr1zgKzVpx4I3FYvGRM5YT0VaQhALMW9vy4wZRhEyWAtJITQIqZzrTGWqB1JvV7V72dVEUCUPLfYz5hgJQ==}
+
+ '@codesandbox/sandpack-react@2.20.0':
+ resolution: {integrity: sha512-takd1YpW/PMQ6KPQfvseWLHWklJovGY8QYj8MtWnskGKbjOGJ6uZfyZbcJ6aCFLQMpNyjTqz9AKNbvhCOZ1TUQ==}
+ peerDependencies:
+ react: ^16.8.0 || ^17 || ^18 || ^19
+ react-dom: ^16.8.0 || ^17 || ^18 || ^19
+
'@copilotkit/a2ui-renderer@1.54.0-next.6':
resolution: {integrity: sha512-zDdKieiCJXnRstdkcIXpw7rjpJxUXSuGDhwFsCfetcL17pSv+Dbon18rK8Qdku8v6FCCxouUlkunh6iHcSzc9Q==}
peerDependencies:
@@ -909,6 +1003,24 @@ packages:
react-dom:
optional: true
+ '@lezer/common@1.5.1':
+ resolution: {integrity: sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==}
+
+ '@lezer/css@1.3.3':
+ resolution: {integrity: sha512-RzBo8r+/6QJeow7aPHIpGVIH59xTcJXp399820gZoMo9noQDRVpJLheIBUicYwKcsbOYoBRoLZlf2720dG/4Tg==}
+
+ '@lezer/highlight@1.2.3':
+ resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==}
+
+ '@lezer/html@1.3.13':
+ resolution: {integrity: sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==}
+
+ '@lezer/javascript@1.5.4':
+ resolution: {integrity: sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==}
+
+ '@lezer/lr@1.4.8':
+ resolution: {integrity: sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==}
+
'@lit-labs/react@2.1.3':
resolution: {integrity: sha512-OD9h2JynerBQUMNzb563jiVpxfvPF0HjQkKY2mx0lpVYvD7F+rtJpOGz6ek+6ufMidV3i+MPT9SX62OKWHFrQg==}
@@ -937,6 +1049,9 @@ packages:
resolution: {integrity: sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w==}
engines: {node: '>=8'}
+ '@marijn/find-cluster-break@1.0.2':
+ resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
+
'@mermaid-js/parser@0.6.3':
resolution: {integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==}
@@ -1023,6 +1138,9 @@ packages:
resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
engines: {node: '>=12.4.0'}
+ '@open-draft/deferred-promise@2.2.0':
+ resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==}
+
'@opentelemetry/api@1.9.0':
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
engines: {node: '>=8.0.0'}
@@ -1359,6 +1477,16 @@ packages:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+ '@react-hook/intersection-observer@3.1.2':
+ resolution: {integrity: sha512-mWU3BMkmmzyYMSuhO9wu3eJVP21N8TcgYm9bZnTrMwuM818bEk+0NRM3hP+c/TqA9Ln5C7qE53p1H0QMtzYdvQ==}
+ peerDependencies:
+ react: '>=16.8'
+
+ '@react-hook/passive-layout-effect@1.2.1':
+ resolution: {integrity: sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg==}
+ peerDependencies:
+ react: '>=16.8'
+
'@react-stately/flags@3.1.2':
resolution: {integrity: sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==}
@@ -1429,6 +1557,9 @@ packages:
'@standard-schema/utils@0.3.0':
resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
+ '@stitches/core@1.2.8':
+ resolution: {integrity: sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==}
+
'@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
@@ -1942,6 +2073,9 @@ packages:
ajv@8.17.1:
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
+ anser@2.3.5:
+ resolution: {integrity: sha512-vcZjxvvVoxTeR5XBNJB38oTu/7eDCZlwdz32N1eNgpyPF7j/Z7Idf+CUwQOkKKpJ7RJyjxgLHCM7vdIK0iCNMQ==}
+
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
@@ -2137,6 +2271,9 @@ packages:
class-variance-authority@0.7.1:
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
+ clean-set@1.1.2:
+ resolution: {integrity: sha512-cA8uCj0qSoG9e0kevyOWXwPaELRPVg5Pxp6WskLMwerx257Zfnh8Nl0JBH59d7wQzij2CK7qEfJQK3RjuKKIug==}
+
client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
@@ -2236,6 +2373,9 @@ packages:
cose-base@2.2.0:
resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==}
+ crelt@1.0.6:
+ resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
+
cross-inspect@1.0.1:
resolution: {integrity: sha512-Pcw1JTvZLSJH83iiGWt6fRcT+BjZlCDRVwYLbUcHzv/CRpB7r0MlSrGbIyQvVSNyGnbt7G4AXuyCiDR3POvZ1A==}
engines: {node: '>=16.0.0'}
@@ -2400,6 +2540,10 @@ packages:
resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==}
engines: {node: '>=12'}
+ d@1.0.2:
+ resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==}
+ engines: {node: '>=0.12'}
+
dagre-d3-es@7.0.13:
resolution: {integrity: sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==}
@@ -2510,6 +2654,10 @@ packages:
dompurify@3.3.1:
resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==}
+ dotenv@16.6.1:
+ resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
+ engines: {node: '>=12'}
+
dset@3.1.4:
resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==}
engines: {node: '>=4'}
@@ -2584,6 +2732,17 @@ packages:
es-toolkit@1.44.0:
resolution: {integrity: sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==}
+ es5-ext@0.10.64:
+ resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==}
+ engines: {node: '>=0.10'}
+
+ es6-iterator@2.0.3:
+ resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==}
+
+ es6-symbol@3.1.4:
+ resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==}
+ engines: {node: '>=0.12'}
+
esbuild@0.27.3:
resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==}
engines: {node: '>=18'}
@@ -2593,6 +2752,9 @@ packages:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
+ escape-carriage@1.3.1:
+ resolution: {integrity: sha512-GwBr6yViW3ttx1kb7/Oh+gKQ1/TrhYwxKqVmg5gS+BK+Qe2KrOa/Vh7w3HPBvgGf0LfcDGoY9I6NHKoA5Hozhw==}
+
escape-html@1.0.3:
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
@@ -2700,6 +2862,10 @@ packages:
jiti:
optional: true
+ esniff@2.0.1:
+ resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==}
+ engines: {node: '>=0.10'}
+
espree@10.4.0:
resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -2727,6 +2893,9 @@ packages:
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
engines: {node: '>= 0.6'}
+ event-emitter@0.3.5:
+ resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==}
+
event-target-shim@5.0.1:
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
engines: {node: '>=6'}
@@ -2763,6 +2932,9 @@ packages:
resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
engines: {node: '>= 18'}
+ ext@1.7.0:
+ resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==}
+
extend@3.0.2:
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
@@ -3136,6 +3308,10 @@ packages:
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
engines: {node: '>=12'}
+ intersection-observer@0.10.0:
+ resolution: {integrity: sha512-fn4bQ0Xq8FTej09YC/jqKZwtijpvARlRp6wxL5WTA6yPe2YWSJ5RJh7Nm79rK2qB0wr6iDQzH60XGq5V/7u8YQ==}
+ deprecated: The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019.
+
ipaddr.js@1.9.1:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'}
@@ -3539,6 +3715,10 @@ packages:
lucide@0.525.0:
resolution: {integrity: sha512-sfehWlaE/7NVkcEQ4T9JD3eID8RNMIGJBBUq9wF3UFiJIrcMKRbU3g1KGfDk4svcW7yw8BtDLXaXo02scDtUYQ==}
+ lz-string@1.5.0:
+ resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
+ hasBin: true
+
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
@@ -3904,6 +4084,9 @@ packages:
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
engines: {node: '>= 0.6'}
+ next-tick@1.1.0:
+ resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
+
next@16.1.6:
resolution: {integrity: sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==}
engines: {node: '>=20.9.0'}
@@ -4007,6 +4190,9 @@ packages:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
+ outvariant@1.4.0:
+ resolution: {integrity: sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==}
+
own-keys@1.0.1:
resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
engines: {node: '>= 0.4'}
@@ -4225,6 +4411,9 @@ packages:
react: ^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-devtools-inline@4.4.0:
+ resolution: {integrity: sha512-ES0GolSrKO8wsKbsEkVeiR/ZAaHQTY4zDh1UW8DImVmm8oaGLl3ijJDvSGe+qDRKPZdPRnDtWWnSvvrgxXdThQ==}
+
react-dom@19.2.4:
resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==}
peerDependencies:
@@ -4239,6 +4428,9 @@ packages:
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+ react-is@17.0.2:
+ resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
+
react-is@18.3.1:
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
@@ -4254,6 +4446,12 @@ packages:
'@types/react': '>=16'
react: '>=16'
+ react-markdown@9.1.0:
+ resolution: {integrity: sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==}
+ peerDependencies:
+ '@types/react': '>=18'
+ react: '>=18'
+
react-redux@9.2.0:
resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==}
peerDependencies:
@@ -4614,6 +4812,9 @@ packages:
stable-hash@0.0.5:
resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==}
+ static-browser-server@1.0.3:
+ resolution: {integrity: sha512-ZUyfgGDdFRbZGGJQ1YhiM930Yczz5VlbJObrQLlk24+qNHVQx4OlLcYswEUo3bIyNAbQUIUR9Yr5/Hqjzqb4zA==}
+
statuses@2.0.2:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'}
@@ -4627,6 +4828,9 @@ packages:
peerDependencies:
react: ^18.0.0 || ^19.0.0
+ strict-event-emitter@0.4.6:
+ resolution: {integrity: sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==}
+
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@@ -4672,6 +4876,9 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
+ style-mod@4.1.3:
+ resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==}
+
style-to-js@1.1.21:
resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==}
@@ -4848,6 +5055,9 @@ packages:
resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
engines: {node: '>= 0.6'}
+ type@2.7.3:
+ resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==}
+
typed-array-buffer@1.0.3:
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
engines: {node: '>= 0.4'}
@@ -5066,6 +5276,9 @@ packages:
vscode-uri@3.0.8:
resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==}
+ w3c-keyname@2.2.8:
+ resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
+
web-namespaces@2.0.1:
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
@@ -5429,6 +5642,114 @@ snapshots:
'@chevrotain/utils@11.0.3': {}
+ '@codemirror/autocomplete@6.20.1':
+ dependencies:
+ '@codemirror/language': 6.12.3
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.41.0
+ '@lezer/common': 1.5.1
+
+ '@codemirror/commands@6.10.3':
+ dependencies:
+ '@codemirror/language': 6.12.3
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.41.0
+ '@lezer/common': 1.5.1
+
+ '@codemirror/lang-css@6.3.1':
+ dependencies:
+ '@codemirror/autocomplete': 6.20.1
+ '@codemirror/language': 6.12.3
+ '@codemirror/state': 6.6.0
+ '@lezer/common': 1.5.1
+ '@lezer/css': 1.3.3
+
+ '@codemirror/lang-html@6.4.11':
+ dependencies:
+ '@codemirror/autocomplete': 6.20.1
+ '@codemirror/lang-css': 6.3.1
+ '@codemirror/lang-javascript': 6.2.5
+ '@codemirror/language': 6.12.3
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.41.0
+ '@lezer/common': 1.5.1
+ '@lezer/css': 1.3.3
+ '@lezer/html': 1.3.13
+
+ '@codemirror/lang-javascript@6.2.5':
+ dependencies:
+ '@codemirror/autocomplete': 6.20.1
+ '@codemirror/language': 6.12.3
+ '@codemirror/lint': 6.9.5
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.41.0
+ '@lezer/common': 1.5.1
+ '@lezer/javascript': 1.5.4
+
+ '@codemirror/language@6.12.3':
+ dependencies:
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.41.0
+ '@lezer/common': 1.5.1
+ '@lezer/highlight': 1.2.3
+ '@lezer/lr': 1.4.8
+ style-mod: 4.1.3
+
+ '@codemirror/lint@6.9.5':
+ dependencies:
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.41.0
+ crelt: 1.0.6
+
+ '@codemirror/state@6.6.0':
+ dependencies:
+ '@marijn/find-cluster-break': 1.0.2
+
+ '@codemirror/view@6.41.0':
+ dependencies:
+ '@codemirror/state': 6.6.0
+ crelt: 1.0.6
+ style-mod: 4.1.3
+ w3c-keyname: 2.2.8
+
+ '@codesandbox/nodebox@0.1.8':
+ dependencies:
+ outvariant: 1.4.0
+ strict-event-emitter: 0.4.6
+
+ '@codesandbox/sandpack-client@2.19.8':
+ dependencies:
+ '@codesandbox/nodebox': 0.1.8
+ buffer: 6.0.3
+ dequal: 2.0.3
+ mime-db: 1.54.0
+ outvariant: 1.4.0
+ static-browser-server: 1.0.3
+
+ '@codesandbox/sandpack-react@2.20.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@codemirror/autocomplete': 6.20.1
+ '@codemirror/commands': 6.10.3
+ '@codemirror/lang-css': 6.3.1
+ '@codemirror/lang-html': 6.4.11
+ '@codemirror/lang-javascript': 6.2.5
+ '@codemirror/language': 6.12.3
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.41.0
+ '@codesandbox/sandpack-client': 2.19.8
+ '@lezer/highlight': 1.2.3
+ '@react-hook/intersection-observer': 3.1.2(react@19.2.4)
+ '@stitches/core': 1.2.8
+ anser: 2.3.5
+ clean-set: 1.1.2
+ dequal: 2.0.3
+ escape-carriage: 1.3.1
+ lz-string: 1.5.0
+ react: 19.2.4
+ react-devtools-inline: 4.4.0
+ react-dom: 19.2.4(react@19.2.4)
+ react-is: 17.0.2
+
'@copilotkit/a2ui-renderer@1.54.0-next.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(signal-polyfill@0.2.2)':
dependencies:
'@a2ui/lit': 0.8.1(signal-polyfill@0.2.2)
@@ -6098,6 +6419,34 @@ snapshots:
react-dom: 19.2.4(react@19.2.4)
optional: true
+ '@lezer/common@1.5.1': {}
+
+ '@lezer/css@1.3.3':
+ dependencies:
+ '@lezer/common': 1.5.1
+ '@lezer/highlight': 1.2.3
+ '@lezer/lr': 1.4.8
+
+ '@lezer/highlight@1.2.3':
+ dependencies:
+ '@lezer/common': 1.5.1
+
+ '@lezer/html@1.3.13':
+ dependencies:
+ '@lezer/common': 1.5.1
+ '@lezer/highlight': 1.2.3
+ '@lezer/lr': 1.4.8
+
+ '@lezer/javascript@1.5.4':
+ dependencies:
+ '@lezer/common': 1.5.1
+ '@lezer/highlight': 1.2.3
+ '@lezer/lr': 1.4.8
+
+ '@lezer/lr@1.4.8':
+ dependencies:
+ '@lezer/common': 1.5.1
+
'@lit-labs/react@2.1.3(@types/react@19.2.10)':
dependencies:
'@lit/react': 1.0.8(@types/react@19.2.10)
@@ -6129,6 +6478,8 @@ snapshots:
dependencies:
'@lukeed/csprng': 1.1.0
+ '@marijn/find-cluster-break@1.0.2': {}
+
'@mermaid-js/parser@0.6.3':
dependencies:
langium: 3.3.1
@@ -6208,6 +6559,8 @@ snapshots:
'@nolyfill/is-core-module@1.0.39': {}
+ '@open-draft/deferred-promise@2.2.0': {}
+
'@opentelemetry/api@1.9.0': {}
'@pinojs/redact@0.4.0': {}
@@ -6526,6 +6879,16 @@ snapshots:
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
+ '@react-hook/intersection-observer@3.1.2(react@19.2.4)':
+ dependencies:
+ '@react-hook/passive-layout-effect': 1.2.1(react@19.2.4)
+ intersection-observer: 0.10.0
+ react: 19.2.4
+
+ '@react-hook/passive-layout-effect@1.2.1(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
+
'@react-stately/flags@3.1.2':
dependencies:
'@swc/helpers': 0.5.18
@@ -6617,6 +6980,8 @@ snapshots:
'@standard-schema/utils@0.3.0': {}
+ '@stitches/core@1.2.8': {}
+
'@swc/helpers@0.5.15':
dependencies:
tslib: 2.8.1
@@ -7143,6 +7508,8 @@ snapshots:
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
+ anser@2.3.5: {}
+
ansi-regex@5.0.1: {}
ansi-styles@4.3.0:
@@ -7381,6 +7748,8 @@ snapshots:
dependencies:
clsx: 2.1.1
+ clean-set@1.1.2: {}
+
client-only@0.0.1: {}
cliui@8.0.1:
@@ -7465,6 +7834,8 @@ snapshots:
dependencies:
layout-base: 2.0.1
+ crelt@1.0.6: {}
+
cross-inspect@1.0.1:
dependencies:
tslib: 2.8.1
@@ -7656,6 +8027,11 @@ snapshots:
d3-transition: 3.0.1(d3-selection@3.0.0)
d3-zoom: 3.0.0
+ d@1.0.2:
+ dependencies:
+ es5-ext: 0.10.64
+ type: 2.7.3
+
dagre-d3-es@7.0.13:
dependencies:
d3: 7.9.0
@@ -7749,6 +8125,8 @@ snapshots:
optionalDependencies:
'@types/trusted-types': 2.0.7
+ dotenv@16.6.1: {}
+
dset@3.1.4: {}
dunder-proto@1.0.1:
@@ -7883,6 +8261,24 @@ snapshots:
es-toolkit@1.44.0: {}
+ es5-ext@0.10.64:
+ dependencies:
+ es6-iterator: 2.0.3
+ es6-symbol: 3.1.4
+ esniff: 2.0.1
+ next-tick: 1.1.0
+
+ es6-iterator@2.0.3:
+ dependencies:
+ d: 1.0.2
+ es5-ext: 0.10.64
+ es6-symbol: 3.1.4
+
+ es6-symbol@3.1.4:
+ dependencies:
+ d: 1.0.2
+ ext: 1.7.0
+
esbuild@0.27.3:
optionalDependencies:
'@esbuild/aix-ppc64': 0.27.3
@@ -7914,6 +8310,8 @@ snapshots:
escalade@3.2.0: {}
+ escape-carriage@1.3.1: {}
+
escape-html@1.0.3: {}
escape-string-regexp@4.0.0: {}
@@ -8105,6 +8503,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ esniff@2.0.1:
+ dependencies:
+ d: 1.0.2
+ es5-ext: 0.10.64
+ event-emitter: 0.3.5
+ type: 2.7.3
+
espree@10.4.0:
dependencies:
acorn: 8.15.0
@@ -8127,6 +8532,11 @@ snapshots:
etag@1.8.1: {}
+ event-emitter@0.3.5:
+ dependencies:
+ d: 1.0.2
+ es5-ext: 0.10.64
+
event-target-shim@5.0.1: {}
eventemitter3@4.0.7: {}
@@ -8214,6 +8624,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ ext@1.7.0:
+ dependencies:
+ type: 2.7.3
+
extend@3.0.2: {}
fast-copy@3.0.2: {}
@@ -8662,6 +9076,8 @@ snapshots:
internmap@2.0.3: {}
+ intersection-observer@0.10.0: {}
+
ipaddr.js@1.9.1: {}
is-alphabetical@1.0.4: {}
@@ -9024,6 +9440,8 @@ snapshots:
lucide@0.525.0: {}
+ lz-string@1.5.0: {}
+
magic-string@0.30.21:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@@ -9705,6 +10123,8 @@ snapshots:
negotiator@1.0.0: {}
+ next-tick@1.1.0: {}
+
next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
dependencies:
'@next/env': 16.1.6
@@ -9822,6 +10242,8 @@ snapshots:
type-check: 0.4.0
word-wrap: 1.2.5
+ outvariant@1.4.0: {}
+
own-keys@1.0.1:
dependencies:
get-intrinsic: 1.3.0
@@ -10058,6 +10480,10 @@ snapshots:
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
+ react-devtools-inline@4.4.0:
+ dependencies:
+ es6-symbol: 3.1.4
+
react-dom@19.2.4(react@19.2.4):
dependencies:
react: 19.2.4
@@ -10072,6 +10498,8 @@ snapshots:
react-is@16.13.1: {}
+ react-is@17.0.2: {}
+
react-is@18.3.1: {}
react-markdown@10.1.0(@types/react@19.2.10)(react@19.2.4):
@@ -10114,6 +10542,24 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ react-markdown@9.1.0(@types/react@19.2.10)(react@19.2.4):
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ '@types/react': 19.2.10
+ devlop: 1.1.0
+ hast-util-to-jsx-runtime: 2.3.6
+ html-url-attributes: 3.0.1
+ mdast-util-to-hast: 13.2.1
+ react: 19.2.4
+ remark-parse: 11.0.0
+ remark-rehype: 11.1.2
+ unified: 11.0.5
+ unist-util-visit: 5.1.0
+ vfile: 6.0.3
+ transitivePeerDependencies:
+ - supports-color
+
react-redux@9.2.0(@types/react@19.2.10)(react@19.2.4)(redux@5.0.1):
dependencies:
'@types/use-sync-external-store': 0.0.6
@@ -10627,6 +11073,13 @@ snapshots:
stable-hash@0.0.5: {}
+ static-browser-server@1.0.3:
+ dependencies:
+ '@open-draft/deferred-promise': 2.2.0
+ dotenv: 16.6.1
+ mime-db: 1.54.0
+ outvariant: 1.4.0
+
statuses@2.0.2: {}
stop-iteration-iterator@1.1.0:
@@ -10666,6 +11119,8 @@ snapshots:
- micromark-util-types
- supports-color
+ strict-event-emitter@0.4.6: {}
+
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
@@ -10739,6 +11194,8 @@ snapshots:
strip-json-comments@3.1.1: {}
+ style-mod@4.1.3: {}
+
style-to-js@1.1.21:
dependencies:
style-to-object: 1.0.14
@@ -10890,6 +11347,8 @@ snapshots:
media-typer: 1.1.0
mime-types: 3.0.2
+ type@2.7.3: {}
+
typed-array-buffer@1.0.3:
dependencies:
call-bound: 1.0.4
@@ -11181,6 +11640,8 @@ snapshots:
vscode-uri@3.0.8: {}
+ w3c-keyname@2.2.8: {}
+
web-namespaces@2.0.1: {}
web-streams-polyfill@4.0.0-beta.3: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 2ea70e5..aa23776 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1,3 +1,4 @@
packages:
- "apps/app"
- "apps/mcp"
+ - "apps/notebook"