devtools
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinese@json-render/devtools
@json-render/devtools
A floating inspector panel for json-render apps. Framework-agnostic core + per-framework adapters (React, Vue, Svelte, Solid).
Production-safe: the component renders when .
nullNODE_ENV === "production"一款面向json-render应用的悬浮检查器面板。核心框架无关,同时提供各框架适配层(React、Vue、Svelte、Solid)。
生产环境安全:当时,该组件会渲染。
NODE_ENV === "production"nullInstall
安装
Install the core package plus the adapter that matches the host app's renderer.
bash
undefined安装核心包以及与宿主应用渲染器匹配的适配层。
bash
undefinedReact
React
npm install @json-render/devtools @json-render/devtools-react
npm install @json-render/devtools @json-render/devtools-react
Vue
Vue
npm install @json-render/devtools @json-render/devtools-vue
npm install @json-render/devtools @json-render/devtools-vue
Svelte
Svelte
npm install @json-render/devtools @json-render/devtools-svelte
npm install @json-render/devtools @json-render/devtools-svelte
Solid
Solid
npm install @json-render/devtools @json-render/devtools-solid
undefinednpm install @json-render/devtools @json-render/devtools-solid
undefinedDrop-in usage
即插即用
Place anywhere inside the existing (or framework equivalent). No other wiring required.
<JsonRenderDevtools /><JSONUIProvider>将放置在现有(或对应框架的等价组件)内部任意位置即可,无需额外配置。
<JsonRenderDevtools /><JSONUIProvider>React
React
tsx
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>;tsx
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
Vue
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>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
Svelte
svelte
<script>
import { JsonRenderDevtools } from "@json-render/devtools-svelte";
</script>
<JSONUIProvider {registry}>
<Renderer {spec} {registry} />
<JsonRenderDevtools {spec} {catalog} {messages} />
</JSONUIProvider>svelte
<script>
import { JsonRenderDevtools } from "@json-render/devtools-svelte";
</script>
<JSONUIProvider {registry}>
<Renderer {spec} {registry} />
<JsonRenderDevtools {spec} {catalog} {messages} />
</JSONUIProvider>Solid
Solid
tsx
import { JsonRenderDevtools } from "@json-render/devtools-solid";
<JSONUIProvider registry={registry}>
<Renderer spec={spec()} registry={registry} />
<JsonRenderDevtools
spec={spec()}
catalog={catalog}
messages={messages()}
/>
</JSONUIProvider>;tsx
import { JsonRenderDevtools } from "@json-render/devtools-solid";
<JSONUIProvider registry={registry}>
<Renderer spec={spec()} registry={registry} />
<JsonRenderDevtools
spec={spec()}
catalog={catalog}
messages={messages()}
/>
</JSONUIProvider>;Controls
操作控制
- Floating toggle appears bottom-right.
- Hotkey: /
Ctrl+Cmd+Shift(configurable viaJprop).hotkey - Drawer is resizable; height persists to localStorage.
- 悬浮开关默认显示在右下角。
- 快捷键:/
Ctrl+Cmd+Shift(可通过J属性配置)。hotkey - 抽屉面板支持调整大小,高度会保存到localStorage。
Props
属性参数
- (
spec) — current spec.Spec | null - (
catalog) — catalog definition; required for the Catalog panel.Catalog | null - (
messages) — AI SDKUIMessage[]messages; scanned for spec data parts.useChat - (
initialOpen) — start open.boolean - (
position) — dock + toggle corner."bottom-right" | "bottom-left" | "right"docks at the bottom;"bottom-*"docks at the right edge full-height (recommended for app-shells that already use"right"or fixed bottom bars).100vh - (
hotkey) —string | falseby default."mod+shift+j" - (
bufferSize) — event ring-buffer cap, default 500.number - (
reserveSpace, defaultboolean) — when true the panel pushes the host app by applyingtrue/padding-bottomonpadding-right. Set tobodyto keep the panel as a pure overlay.false - (
allowDockToggle, defaultboolean) — show a toolbar button so the user can flip the panel between bottom-dock and right-dock. User choice persists totrueand overrideslocalStorageon subsequent mounts. Passpositionto lock the dock tofalse.position - (
onEvent) — optional tap.(DevtoolsEvent) => void
- (
spec) — 当前spec。Spec | null - (
catalog) — 组件目录定义;目录面板需要此参数。Catalog | null - (
messages) — AI SDK的UIMessage[]消息;会扫描其中的spec数据片段。useChat - (
initialOpen) — 初始是否展开面板。boolean - (
position) — 面板停靠位置与开关角落。"bottom-right" | "bottom-left" | "right"表示停靠在底部;"bottom-*"表示停靠在右侧并占满高度(推荐给已使用"right"或固定底部栏的应用壳)。100vh - (
hotkey) — 默认值为string | false。"mod+shift+j" - (
bufferSize) — 事件环形缓冲区上限,默认500。number - (
reserveSpace, 默认boolean) — 设为true时,面板会通过给true添加body/padding-bottom来推动宿主应用内容,预留自身空间。设为false时,面板会作为纯覆盖层存在。padding-right - (
allowDockToggle, 默认boolean) — 显示工具栏按钮,允许用户在底部停靠和右侧停靠之间切换。用户选择会保存到true,后续挂载时会覆盖localStorage参数。设为false可锁定面板停靠位置为position指定的值。position - (
onEvent) — 可选的事件监听回调。(DevtoolsEvent) => void
Panels
功能面板
- Spec — element tree rooted at ; props/visibility/events/watchers detail; integrated
spec.rootwarnings.validateSpec - State — every JSON Pointer path with inline edit via .
store.set - Actions — dispatched actions timeline (name, params, result/error, duration).
- Stream — spec patches, text chunks, token usage, lifecycle markers grouped by generation.
- Catalog — components + actions declared in the catalog with prop chips.
- Spec面板 — 以为根的元素树;展示属性/可见性/事件/监听器详情;集成
spec.root警告信息。validateSpec - 状态面板 — 所有JSON Pointer路径,支持通过在线编辑。
store.set - 动作面板 — 已分发动作的时间线(名称、参数、结果/错误、耗时)。
- 流面板 — 按生成批次分组的spec补丁、文本片段、令牌使用情况、生命周期标记。
- 目录面板 — 组件目录中声明的组件与动作,附带属性标签。
Picker (toolbar)
元素选取器(工具栏)
The element picker is a toolbar button in the panel header (Chrome-DevTools-style), not a tab. Click it to activate pick mode, then click any rendered element in the page — selection jumps to the Spec tab with that element focused. cancels.
Esc元素选取器是面板头部的一个工具栏按钮(类似Chrome开发者工具),而非标签页。点击它激活选取模式,然后点击页面中任意已渲染元素,选中后会跳转到Spec面板并聚焦该元素。按可取消选取。
EscReserved space & docking
空间预留与停靠
The panel can dock at the bottom or the right edge, and by default the user can flip between the two with a toolbar button (the choice persists to ). Set if the host app only works with one dock — the button is hidden and the dock is locked to .
localStorageallowDockToggle={false}positionPick an initial dock that fits your layout:
- Bottom dock (default) — works best for docs / marketing / content-flow sites and for app shells built with a chain (
height: 100%→html { height: 100% }→body { height: 100% }). The panel writes its height to.app { height: 100% }and applies matching--jr-devtools-offset-bottomtopadding-bottom, so non-fixed content naturally makes room.body - Right dock () — recommended for app-shell layouts that use
position="right"or100vh. Right docking sidesteps the bottom edge entirely and writes its width toposition: fixed; bottom: 0instead.--jr-devtools-offset-right
Apps that use , , or can opt specific elements in with the published CSS custom properties:
100vhposition: fixedposition: stickycss
.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)); }If the automatic body padding causes problems with a particular layout, pass to make the panel a pure overlay — the CSS custom properties are still published so you can reserve space manually.
reserveSpace={false}( is kept as a back-compat alias for whichever edge is currently active.)
--jr-devtools-offset面板可停靠在底部或右侧,默认用户可通过工具栏按钮切换两种模式(选择会保存到)。如果宿主应用仅适配一种停靠模式,可设置,此时切换按钮会隐藏,面板停靠位置将被锁定为指定的值。
localStorageallowDockToggle={false}position根据布局选择初始停靠位置:
- 底部停靠(默认) — 最适合文档/营销/内容流类网站,以及采用链式布局的应用壳(
height: 100%→html { height: 100% }→body { height: 100% })。面板会将自身高度写入.app { height: 100% },并给--jr-devtools-offset-bottom添加对应的body,非固定定位的内容会自动为面板预留空间。padding-bottom - 右侧停靠() — 推荐给使用
position="right"或100vh的应用壳布局。右侧停靠可完全避开底部边缘,面板会将自身宽度写入position: fixed; bottom: 0。--jr-devtools-offset-right
使用、或的应用,可通过已发布的CSS自定义属性为特定元素适配:
100vhposition: fixedposition: stickycss
.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内边距对特定布局造成影响,可设置让面板作为纯覆盖层存在——此时CSS自定义属性仍会发布,你可以手动预留空间。
reserveSpace={false}(作为向后兼容别名,会指向当前激活边缘的偏移量。)
--jr-devtools-offsetMultiple renderers on one page (e.g. a chat)
单页面多渲染器场景(如聊天应用)
A single can inspect many instances at once — a chat where each assistant message renders its own spec, a dashboard made of several independent widgets, etc. The recipe:
<JsonRenderDevtools /><Renderer />- One top-level so every renderer shares one state store and one action dispatcher. Devtools lives inside this provider and sees everything through it.
<JSONUIProvider> - Per-renderer specs, shared state — each assistant message renders directly, not wrapped in its own
<Renderer spec={msgSpec} registry={registry} />. State paths from different messages must not collide.StateProvider - Namespace state per turn — when the source is an AI stream, hand the agent a unique and require every element key (
messageId) and state path (<id>-root) to be prefixed with it./<id>/count - Pass +
spec={latest}—messages={all}drives the Spec panel (usually the newest assistant message's spec), whilespecfeeds the Stream panel with patches from every turn.messages - Actions and the picker are already global — captures dispatches from any
registerActionObserverin the tree, andActionProvideris written by the renderer itself, so Pick works across every rendered element regardless of which message produced it.data-jr-key
See for a full AI chat wired this way.
examples/devtools单个可同时检查多个实例——例如每条助手消息都渲染独立spec的聊天应用、由多个独立组件组成的仪表盘等。实现方案:
<JsonRenderDevtools /><Renderer />- 单个顶层— 让所有渲染器共享同一个状态存储和动作分发器。开发者工具位于该Provider内部,可通过它监控所有内容。
<JSONUIProvider> - 每个渲染器独立spec,共享状态 — 每条助手消息直接渲染,无需包裹在独立的
<Renderer spec={msgSpec} registry={registry} />中。不同消息的状态路径不能冲突。StateProvider - 按会话命名空间划分状态 — 当数据源为AI流时,给代理分配唯一的,要求每个元素键(
messageId)和状态路径(<id>-root)都以此ID为前缀。/<id>/count - 传入+
spec={latest}—messages={all}驱动Spec面板(通常是最新助手消息的spec),而spec为流面板提供所有会话的补丁数据。messages - 动作与选取器已全局化 — 会捕获树中任意
registerActionObserver的分发事件,ActionProvider由渲染器自动添加,因此选取功能可作用于所有已渲染元素,无论其来自哪条消息。data-jr-key
完整的AI聊天应用示例可查看目录。
examples/devtoolsImperative API (React only)
命令式API(仅React)
tsx
import { useJsonRenderDevtools } from "@json-render/devtools-react";
const devtools = useJsonRenderDevtools();
devtools?.open();
devtools?.toggle();
devtools?.recordEvent({ kind: "stream-text", at: Date.now(), text: "hi" });Returns in production or before the component mounts.
nulltsx
import { useJsonRenderDevtools } from "@json-render/devtools-react";
const devtools = useJsonRenderDevtools();
devtools?.open();
devtools?.toggle();
devtools?.recordEvent({ kind: "stream-text", at: Date.now(), text: "hi" });生产环境中或组件挂载前,该API会返回。
nullServer-side stream tap
服务端流监听
Capture spec patches at the API route so events persist server-side or flow into your own telemetry.
ts
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 equivalent: .
tapYamlStream在API路由层捕获spec补丁,使事件可在服务端持久化或流入自定义遥测系统。
ts
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等价方法:。
tapYamlStreamUnder the hood
底层实现
- Shadow-DOM isolated panel — the panel's styles never leak into the host app and vice versa.
- Ring-buffered event store — capped log of devtools events (state changes, action dispatches, stream patches, etc.).
- Action observer registry — each framework's reports via
ActionProvider/notifyActionDispatchinnotifyActionSettle; devtools subscribes via@json-render/core.registerActionObserver - Picker element tagging — while devtools is mounted, wraps each rendered element in
ElementRendererso the picker can map DOM → spec key. No layout impact.<span data-jr-key="..." style="display:contents">
- Shadow-DOM隔离面板 — 面板样式永远不会泄露到宿主应用,反之亦然。
- 环形缓冲事件存储 — 有限容量的开发者工具事件日志(状态变更、动作分发、流补丁等)。
- 动作观察者注册 — 各框架的通过
ActionProvider中的@json-render/core/notifyActionDispatch上报事件;开发者工具通过notifyActionSettle订阅。registerActionObserver - 选取器元素标记 — 当开发者工具挂载时,会将每个已渲染元素包裹在
ElementRenderer中,以便选取器将DOM元素映射到spec键。此操作不会影响布局。<span data-jr-key="..." style="display:contents">