逆向工程 Claude 的 Generative UI:不是 iframe,不是 markdown,是 tool call + DOM 注入
> 来源: https://michaellivs.com/blog/reverse-engineering-claude-generative-ui/
> 作者: Michael Livshits(Pi coding agent 贡献者)
> 代码: https://github.com/Michaelliv/pi-generative-ui
> 日期: 2026-03-16(Claude generative UI 发布当天)
📌 一句话总结
Anthropic 给 claude.ai 加了 Generative UI——对话中直接渲染交互式 HTML 组件(滑块、图表、动画)。作者在发布几小时内就逆向出了完整架构:不是 iframe,不是 markdown 嵌入 HTML,而是 tool call + 直接 DOM 注入 + CSP 限制。然后用这个发现为终端 coding agent Pi 构建了自己的版本。
🔍 逆向发现
核心架构:Tool Call,不是 Markdown
第一个也是最重要的发现:Claude 的 Generative UI 不是在 markdown 输出中嵌入 HTML。而是调用一个名为 show_widget 的 tool,把 HTML 作为参数传递。
{
"i_have_seen_read_me": true,
"title": "compound_interest_calculator",
"loading_messages": ["计算复利...", "渲染图表..."],
"widget_code": "<style>...</style>\n<div>...</div>\n<script>...</script>"
}
两步机制:先读规范,再渲染
用户: "帮我可视化复利增长"
↓
Claude 调用 read_me(modules: ["chart", "interactive"])
↓
系统返回: Chart.js 设计规范 + 交互组件规范
↓
Claude 调用 show_widget(widget_code: "...")
↓
前端: 流式 DOM 注入 → 实时渲染
read_me 是按需加载的设计规范系统——不把所有文档塞进 context(浪费 token),只在需要时加载特定模块:
| 模块 | 内容 |
|---|---|
| `chart` | Chart.js 图表模式 |
| `interactive` | 交互组件规范 |
| `diagram` | 图表/流程图 |
| `mockup` | UI 组件 token |
| `art` | 插画规则 |
不是 iframe,是直接 DOM 注入
作者通过 4 个证据证明这不是 iframe:
1. CSS 变量继承 — var(--color-text-primary) 正常解析(同一文档,同一 CSS cascade)
2. sendPrompt() 可用 — 能调用宿主页面的函数
3. 背景透明 — 没有 iframe 容器、滚动条、白色背景
4. 流式渲染 — iframe 需要完整 HTML 才能渲染;这里是 token 到哪渲染到哪
"安全沙箱" 仅仅是 CSP,限制 只能从 4 个 CDN 加载:
cdnjs.cloudflare.comcdn.jsdelivr.netunpkg.comesm.sh
Artifacts vs Generative UI
| 维度 | Artifacts | show_widget (Generative UI) |
|---|---|---|
| **目的** | 交付物(可下载保存) | 对话增强(内嵌解释) |
| **展示** | 侧面板 + 下载按钮 | 内联在聊天中,透明背景 |
| **库支持** | 固定预打包集合 | CDN 实时下载任意库 |
| **持久性** | 跨会话持久 | 临时的,绑定到单条消息 |
| **触发语言** | "帮我做个计算器" | "展示复利是怎么增长的" |
流式渲染管线
LLM 生成 show_widget tool call
↓ widget_code 按 token 流式输出
客户端增量 HTML 解析
↓
DOM 节点实时插入页面(直接注入,非 iframe)
↓
CSS 变量立即解析(同文档)
↓
style 块 → HTML 结构 → 先渲染内容
↓
script 标签最后执行 → CDN 库加载 → 图表/交互激活
这就是为什么设计规范要求 "style → content HTML → script last" 的结构顺序。
🛠️ 作者的终端实现
难点
终端不能渲染交互式 HTML。三个方案都不理想:
- Sixel/Kitty 图形协议 → 只能截图,无交互
- 本地 Web 服务器 + 浏览器 → 离开终端
- TUI 近似渲染 → 太有限
解决方案:Glimpse(macOS WKWebView 微型库)
用一个 Swift 二进制程序在 50ms 内启动原生 WKWebView 窗口:
- 完整浏览器引擎(CSS / JS / Canvas / CDN)
- 双向 JSON 通信
- 无 Electron、无运行时依赖
流式渲染的 4 次迭代(最有价值的工程经验)
| 尝试 | 方法 | 结果 |
|---|---|---|
| **#1** | `setHTML()` 每次 delta 全量替换 | 闪烁——全页 reflow |
| **#2** | Shell page + `innerHTML` 更新 | 仍闪烁——子节点全部销毁重建 |
| **#3** | 朴素 DOM append(只追加新节点) | 失败——浏览器自动关闭未闭合标签,DOM 树结构每次都不同 |
| **#4** | **morphdom**(DOM diff 库) | ✅ 成功——最小化 patch,新节点 fadeIn 动画 |
关键技术细节:
innerHTML不执行标签 → 需要手动克隆并替换- morphdom 的
onBeforeElUpdated跳过相同节点 +onNodeAdded添加动画 - CDN 异步加载有竞态条件 → pending buffer 模式
💡 与我们的关联
1. OpenClaw 的 Canvas 功能对标
OpenClaw 已经有 Canvas(A2UI)功能——通过 canvas 工具呈现 HTML 到用户端。Claude 的 Generative UI 做的本质是同一件事,但实现路径不同:
| 维度 | Claude Generative UI | OpenClaw Canvas |
|---|---|---|
| 架构 | tool call → DOM 注入 | tool call → 独立窗口/面板 |
| 流式 | ✅ token 级流式渲染 | 一次性呈现 |
| 交互反馈 | sendPrompt() 回传 | eval + snapshot |
2. `read_me` 模式 = OpenClaw 的 Skills
Claude 的 read_me 按需加载设计规范 ≈ OpenClaw 的 Skills 按需加载专业知识。思路完全一样:基础 prompt 保持精简,专业知识按需注入。
3. 安全启示
Claude 的 "沙箱" 仅仅是 CSP allowlist——这意味着任何在 allowlist 中 CDN 上的恶意包都可能被注入。这个安全边界比真正的 iframe sandbox 弱得多。但对用户体验来说足够了(流式渲染 + CSS 变量继承 vs iframe 的延迟)。
4. morphdom 的工程启示
4 次迭代的流式 DOM 渲染经验对任何需要做 "实时 HTML 更新" 的项目都有价值。morphdom(DOM diff)是最终解决方案。
📊 评分
| 维度 | 评分(/10) |
|---|---|
| 技术深度 | 9.5 — 从逆向到实现,4 次迭代,每步都有代码 |
| 写作质量 | 9.0 — 结构清晰,发现 → 实现 → 工程经验 |
| 实用价值 | 8.0 — 对前端/AI UI 开发者直接可用 |
| 新颖性 | 8.5 — 首次完整揭示 Claude generative UI 内部架构 |
| 与我们的关联 | 7.0 — Canvas/A2UI 功能设计参考 |
| **综合** | **8.5** |
报告由深度研究助手自动生成 | 2026-03-16
来源: https://michaellivs.com/blog/reverse-engineering-claude-generative-ui/