
devtools
热门适用于任何 json-render 应用的开箱即用开发者面板(Inspector Panel)。当用户需要调试生成式 UI(Generative UI)、查看 spec 树结构、在运行时修改状态、跟踪已触发的 action、实时监听流式 patch、浏览 Catalog 目录,或使用元素选择器拾取 DOM 节点以查找对应的 spec key 时使用。触发词包括“添加 devtools”、“调试 json-render”、“查看 spec”、“为什么这个元素没渲染”、“查看运行时状态”,以及针对 `@json-render/devtools` 监听流或捕获 action 日志的请求。
适用于任何 json-render 应用的开箱即用开发者面板(Inspector Panel)。当用户需要调试生成式 UI(Generative UI)、查看 spec 树结构、在运行时修改状态、跟踪已触发的 action、实时监听流式 patch、浏览 Catalog 目录,或使用元素选择器拾取 DOM 节点以查找对应的 spec key 时使用。触发词包括“添加 devtools”、“调试 json-render”、“查看 spec”、“为什么这个元素没渲染”、“查看运行时状态”,以及针对 `@json-render/devtools` 监听流或捕获 action 日志的请求。
@json-render/devtools
适用于 json-render 应用的悬浮调试面板。核心库具备框架无关特性(Framework-agnostic),并提供适配各个框架的 Adapter(React、Vue、Svelte、Solid)。
生产环境安全:当 NODE_ENV === "production" 时,组件会直接渲染为 null。
安装
安装核心包以及与宿主应用渲染器匹配的 Adapter。
# React
npm install @json-render/devtools @json-render/devtools-react
# Vue
npm install @json-render/devtools @json-render/devtools-vue
# Svelte
npm install @json-render/devtools @json-render/devtools-svelte
# Solid
npm install @json-render/devtools @json-render/devtools-solid
开箱即用
将 <JsonRenderDevtools /> 放置在现有 <JSONUIProvider>(或各框架对应 Provider)内部的任意位置即可,无需额外接线配置。
React
import { JsonRenderDevtools } from "@json-render/devtools-react";
<JSONUIProvider registry={registry} handlers={handlers}>
<Renderer spec={spec} registry={registry} />
<JsonRenderDevtools spec={spec} catalog={catalog} messages={messages} />
</JSONUIProvider>;
Vue
<script setup>
import { JsonRenderDevtools } from "@json-render/devtools-vue";
</script>
<template>
<JSONUIProvider :registry="registry">
<Renderer :spec="spec" :registry="registry" />
<JsonRenderDevtools :spec="spec" :catalog="catalog" :messages="messages" />
</JSONUIProvider>
</template>
Svelte
<script>
import { JsonRenderDevtools } from "@json-render/devtools-svelte";
</script>
<JSONUIProvider {registry}>
<Renderer {spec} {registry} />
<JsonRenderDevtools {spec} {catalog} {messages} />
</JSONUIProvider>
Solid
import { JsonRenderDevtools } from "@json-render/devtools-solid";
<JSONUIProvider registry={registry}>
<Renderer spec={spec()} registry={registry} />
<JsonRenderDevtools
spec={spec()}
catalog={catalog}
messages={messages()}
/>
</JSONUIProvider>;
控制方式
- 悬浮切换按钮显示在右下角。
- 快捷键:
Ctrl/Cmd+Shift+J(可通过hotkeyprop 自定义)。 - 抽屉面板支持调整大小;高度会自动持久化到 localStorage。
Props 属性
spec(Spec | null) — 当前 spec。catalog(Catalog | null) — catalog 定义;Catalog 面板必需参数。messages(UIMessage[]) — AI SDK 的useChat消息数组;用于解析提取其中的 spec 数据片段。initialOpen(boolean) — 初始状态是否打开面板。position("bottom-right" | "bottom-left" | "right") — 停靠位置与切换按钮所在角。"bottom-*"停靠在底部;"right"停靠在右侧边缘全高展示(推荐已使用100vh或固定底部栏的应用 Shell 使用)。hotkey(string | false) — 快捷键,默认为"mod+shift+j"。bufferSize(number) — 事件环形缓冲区容量上限,默认 500。reserveSpace(boolean,默认true) — 为true时,面板会通过在body上添加padding-bottom/padding-right来推开宿主应用布局。设为false则将面板作为纯浮层叠加展示。allowDockToggle(boolean,默认true) — 是否在工具栏显示切换按钮,允许用户在底部停靠与右侧停靠之间自由切换。用户选择会持久化至localStorage,并在后续挂载时覆盖position参数。传入false可将停靠位置锁定为position设定的值。onEvent((DevtoolsEvent) => void) — 可选的事件监听回调。
功能面板
- Spec — 以
spec.root为根节点的元素树;提供 props/可见性/事件/监听器(watchers)详情;内置validateSpec校验警告提示。 - State — 展示每个 JSON Pointer 路径,支持通过
store.set进行内联编辑。 - Actions — 已触发 action 的时间线(显示名称、参数、结果/报错、执行耗时)。
- Stream — 按生成批次分组展示 spec patch、文本 Chunk、Token 使用量及生命周期标记。
- Catalog — 目录中声明的组件与 action,带 prop 状态标签(chips)。
元素拾取器 Picker(工具栏)
元素拾取器是面板顶部工具栏中的一个按钮(类似 Chrome DevTools 风格),而不是独立的标签页。点击激活拾取模式后,点击页面中任意已渲染的元素,视图会自动跳转至 Spec 标签页并定位聚焦该元素。按 Esc 键可取消拾取。
空间预留与停靠策略
面板支持停靠在底部或右侧边缘,默认情况下用户可以通过工具栏按钮在这两种模式间切换(选择结果会持久化至 localStorage)。如果宿主应用仅兼容某一种停靠方式,可设置 allowDockToggle={false},此时按钮隐藏且停靠位置锁定为 position。
根据你的页面布局选择初始停靠方式:
- 底部停靠(默认) — 最适合文档/营销/内容流类网站,以及采用
height: 100%链式继承的应用 Shell(即html { height: 100% }→body { height: 100% }→.app { height: 100% })。面板会将自身高度写入--jr-devtools-offset-bottom并给body施加对应的padding-bottom,使非固定定位的内容自动留出空间。 - 右侧停靠(
position="right") — 推荐使用了100vh或position: fixed; bottom: 0的应用 Shell 布局使用。右侧停靠完全规避了底部边缘冲突,会将宽度写入--jr-devtools-offset-right。
使用了 100vh、position: fixed 或 position: sticky 的应用,可以通过接入官方暴露的 CSS 自定义属性来做适配:
.composer { bottom: var(--jr-devtools-offset-bottom, 0); }
.sidebar { right: var(--jr-devtools-offset-right, 0); }
.app-shell { height: calc(100vh - var(--jr-devtools-offset-bottom, 0)); }
如果自动在 body 上加 padding 对特定布局造成了干扰,可以设置 reserveSpace={false} 让面板变为纯悬浮遮罩 — CSS 自定义属性依然会正常发布,方便你手动预留空间。
(--jr-devtools-offset 作为兼容性保留别名,始终指向当前生效边缘的偏移量。)
单页面多渲染器场景(例如 AI Chat 对话)
单个 <JsonRenderDevtools /> 可以同时调试多个 <Renderer /> 实例 — 例如在 AI 聊天中,每条 Assistant 消息都渲染各自的 spec,或者由多个独立 Widget 组成的仪表盘等。实现方案如下:
- 顶层统一配置
<JSONUIProvider>— 确保所有 Renderer 共享同一个状态 Store 和 Action 派发器。Devtools 放置在该 Provider 内部即可捕获全局数据。 - Spec 按渲染器隔离,State 共享 — 每条 Assistant 消息直接渲染
<Renderer spec={msgSpec} registry={registry} />,不要外面包裹独立的StateProvider。不同消息的状态路径切记不能冲突。 - 按对话 Turn 对 State 做命名空间隔离 — 数据源为 AI 流时,为 Agent 传入唯一的
messageId,并要求所有元素 Key(<id>-root)和 State 路径(/<id>/count)必须以该messageId为前缀。 - 传入
spec={latest}+messages={all}—spec用于驱动 Spec 面板(通常传入最新的 Assistant 消息 spec),而messages则为 Stream 面板提供每一轮对话的 patch 增量。 - Actions 与 Picker 本身即是全局生效 —
registerActionObserver会自动捕获组件树中任意ActionProvider派发的动作,且渲染器自身会自动注入data-jr-key,因此无论元素由哪一条消息生成, Picker 都能正常工作。
完整实现方案请参考 examples/devtools 示例。
命令式 API(仅限 React)
import { useJsonRenderDevtools } from "@json-render/devtools-react";
const devtools = useJsonRenderDevtools();
devtools?.open();
devtools?.toggle();
devtools?.recordEvent({ kind: "stream-text", at: Date.now(), text: "hi" });
在生产环境或组件挂载完成前调用将返回 null。
服务端流式监听(Stream Tap)
在 API Route 路由处理中捕获 spec patch,从而将事件持久化到服务端或接入自定义的数据埋点系统中。
import { tapJsonRenderStream, createEventStore } from "@json-render/devtools";
import { pipeJsonRender } from "@json-render/core";
const events = createEventStore({ bufferSize: 1000 });
const tapped = tapJsonRenderStream(result.toUIMessageStream(), events);
writer.merge(pipeJsonRender(tapped));
YAML 等价实现函数:tapYamlStream。
底层实现原理
- Shadow-DOM 隔离面板 — 面板样式采用 Shadow DOM 隔离,绝对不会污染宿主应用样式,反之亦然。
- 环形缓冲区事件 Store — 定长记录 devtools 事件日志(状态变更、action 派发、流式 patch 等)。
- Action Observer 注册表 — 各框架的
ActionProvider通过@json-render/core中的notifyActionDispatch/notifyActionSettle进行上报;devtools 通过registerActionObserver完成事件订阅。 - Picker 元素标记 — 当 devtools 挂载时,
ElementRenderer会用<span data-jr-key="..." style="display:contents">包裹每个已渲染元素,从而让 Picker 建立 DOM 到 spec key 的映射关系,且完全不影响页面布局。





