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
null
when
NODE_ENV === "production"
.
一款面向json-render应用的悬浮检查器面板。核心框架无关,同时提供各框架适配层(React、Vue、Svelte、Solid)。
生产环境安全:当
NODE_ENV === "production"
时,该组件会渲染
null

Install

安装

Install the core package plus the adapter that matches the host app's renderer.
bash
undefined
安装核心包以及与宿主应用渲染器匹配的适配层。
bash
undefined

React

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
undefined
npm install @json-render/devtools @json-render/devtools-solid
undefined

Drop-in usage

即插即用

Place
<JsonRenderDevtools />
anywhere inside the existing
<JSONUIProvider>
(or framework equivalent). No other wiring required.
<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
    +
    J
    (configurable via
    hotkey
    prop).
  • Drawer is resizable; height persists to localStorage.
  • 悬浮开关默认显示在右下角。
  • 快捷键:
    Ctrl
    /
    Cmd
    +
    Shift
    +
    J
    (可通过
    hotkey
    属性配置)。
  • 抽屉面板支持调整大小,高度会保存到localStorage。

Props

属性参数

  • spec
    (
    Spec | null
    ) — current spec.
  • catalog
    (
    Catalog | null
    ) — catalog definition; required for the Catalog panel.
  • messages
    (
    UIMessage[]
    ) — AI SDK
    useChat
    messages; scanned for spec data parts.
  • initialOpen
    (
    boolean
    ) — start open.
  • position
    (
    "bottom-right" | "bottom-left" | "right"
    ) — dock + toggle corner.
    "bottom-*"
    docks at the bottom;
    "right"
    docks at the right edge full-height (recommended for app-shells that already use
    100vh
    or fixed bottom bars).
  • hotkey
    (
    string | false
    ) —
    "mod+shift+j"
    by default.
  • bufferSize
    (
    number
    ) — event ring-buffer cap, default 500.
  • reserveSpace
    (
    boolean
    , default
    true
    ) — when true the panel pushes the host app by applying
    padding-bottom
    /
    padding-right
    on
    body
    . Set to
    false
    to keep the panel as a pure overlay.
  • allowDockToggle
    (
    boolean
    , default
    true
    ) — show a toolbar button so the user can flip the panel between bottom-dock and right-dock. User choice persists to
    localStorage
    and overrides
    position
    on subsequent mounts. Pass
    false
    to lock the dock to
    position
    .
  • onEvent
    (
    (DevtoolsEvent) => void
    ) — optional tap.
  • spec
    (
    Spec | null
    ) — 当前spec。
  • catalog
    (
    Catalog | null
    ) — 组件目录定义;目录面板需要此参数。
  • messages
    (
    UIMessage[]
    ) — AI SDK的
    useChat
    消息;会扫描其中的spec数据片段。
  • initialOpen
    (
    boolean
    ) — 初始是否展开面板。
  • position
    (
    "bottom-right" | "bottom-left" | "right"
    ) — 面板停靠位置与开关角落。
    "bottom-*"
    表示停靠在底部;
    "right"
    表示停靠在右侧并占满高度(推荐给已使用
    100vh
    或固定底部栏的应用壳)。
  • 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
    ) — 可选的事件监听回调。

Panels

功能面板

  • Spec — element tree rooted at
    spec.root
    ; props/visibility/events/watchers detail; integrated
    validateSpec
    warnings.
  • 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.
Esc
cancels.
元素选取器是面板头部的一个工具栏按钮(类似Chrome开发者工具),而非标签页。点击它激活选取模式,然后点击页面中任意已渲染元素,选中后会跳转到Spec面板并聚焦该元素。按
Esc
可取消选取。

Reserved 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
localStorage
). Set
allowDockToggle={false}
if the host app only works with one dock — the button is hidden and the dock is locked to
position
.
Pick 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
    height: 100%
    chain (
    html { height: 100% }
    body { height: 100% }
    .app { height: 100% }
    ). The panel writes its height to
    --jr-devtools-offset-bottom
    and applies matching
    padding-bottom
    to
    body
    , so non-fixed content naturally makes room.
  • Right dock (
    position="right"
    ) — recommended for app-shell layouts that use
    100vh
    or
    position: fixed; bottom: 0
    . Right docking sidesteps the bottom edge entirely and writes its width to
    --jr-devtools-offset-right
    instead.
Apps that use
100vh
,
position: fixed
, or
position: sticky
can opt specific elements in with the published CSS custom properties:
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)); }
If the automatic body padding causes problems with a particular layout, pass
reserveSpace={false}
to make the panel a pure overlay — the CSS custom properties are still published so you can reserve space manually.
(
--jr-devtools-offset
is kept as a back-compat alias for whichever edge is currently active.)
面板可停靠在底部或右侧,默认用户可通过工具栏按钮切换两种模式(选择会保存到
localStorage
)。如果宿主应用仅适配一种停靠模式,可设置
allowDockToggle={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
使用
100vh
position: fixed
position: sticky
的应用,可通过已发布的CSS自定义属性为特定元素适配:
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内边距对特定布局造成影响,可设置
reserveSpace={false}
让面板作为纯覆盖层存在——此时CSS自定义属性仍会发布,你可以手动预留空间。
--jr-devtools-offset
作为向后兼容别名,会指向当前激活边缘的偏移量。)

Multiple renderers on one page (e.g. a chat)

单页面多渲染器场景(如聊天应用)

A single
<JsonRenderDevtools />
can inspect many
<Renderer />
instances at once — a chat where each assistant message renders its own spec, a dashboard made of several independent widgets, etc. The recipe:
  1. One top-level
    <JSONUIProvider>
    so every renderer shares one state store and one action dispatcher. Devtools lives inside this provider and sees everything through it.
  2. Per-renderer specs, shared state — each assistant message renders
    <Renderer spec={msgSpec} registry={registry} />
    directly, not wrapped in its own
    StateProvider
    . State paths from different messages must not collide.
  3. Namespace state per turn — when the source is an AI stream, hand the agent a unique
    messageId
    and require every element key (
    <id>-root
    ) and state path (
    /<id>/count
    ) to be prefixed with it.
  4. Pass
    spec={latest}
    +
    messages={all}
    spec
    drives the Spec panel (usually the newest assistant message's spec), while
    messages
    feeds the Stream panel with patches from every turn.
  5. Actions and the picker are already global
    registerActionObserver
    captures dispatches from any
    ActionProvider
    in the tree, and
    data-jr-key
    is written by the renderer itself, so Pick works across every rendered element regardless of which message produced it.
See
examples/devtools
for a full AI chat wired this way.
单个
<JsonRenderDevtools />
可同时检查多个
<Renderer />
实例——例如每条助手消息都渲染独立spec的聊天应用、由多个独立组件组成的仪表盘等。实现方案:
  1. 单个顶层
    <JSONUIProvider>
    — 让所有渲染器共享同一个状态存储和动作分发器。开发者工具位于该Provider内部,可通过它监控所有内容。
  2. 每个渲染器独立spec,共享状态 — 每条助手消息直接渲染
    <Renderer spec={msgSpec} registry={registry} />
    ,无需包裹在独立的
    StateProvider
    中。不同消息的状态路径不能冲突。
  3. 按会话命名空间划分状态 — 当数据源为AI流时,给代理分配唯一的
    messageId
    ,要求每个元素键(
    <id>-root
    )和状态路径(
    /<id>/count
    )都以此ID为前缀。
  4. 传入
    spec={latest}
    +
    messages={all}
    spec
    驱动Spec面板(通常是最新助手消息的spec),而
    messages
    为流面板提供所有会话的补丁数据。
  5. 动作与选取器已全局化
    registerActionObserver
    会捕获树中任意
    ActionProvider
    的分发事件,
    data-jr-key
    由渲染器自动添加,因此选取功能可作用于所有已渲染元素,无论其来自哪条消息。
完整的AI聊天应用示例可查看
examples/devtools
目录。

Imperative 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
null
in production or before the component mounts.
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" });
生产环境中或组件挂载前,该API会返回
null

Server-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等价方法:
tapYamlStream

Under 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
    ActionProvider
    reports via
    notifyActionDispatch
    /
    notifyActionSettle
    in
    @json-render/core
    ; devtools subscribes via
    registerActionObserver
    .
  • Picker element tagging — while devtools is mounted,
    ElementRenderer
    wraps each rendered element in
    <span data-jr-key="..." style="display:contents">
    so the picker can map DOM → spec key. No layout impact.
  • Shadow-DOM隔离面板 — 面板样式永远不会泄露到宿主应用,反之亦然。
  • 环形缓冲事件存储 — 有限容量的开发者工具事件日志(状态变更、动作分发、流补丁等)。
  • 动作观察者注册 — 各框架的
    ActionProvider
    通过
    @json-render/core
    中的
    notifyActionDispatch
    /
    notifyActionSettle
    上报事件;开发者工具通过
    registerActionObserver
    订阅。
  • 选取器元素标记 — 当开发者工具挂载时,
    ElementRenderer
    会将每个已渲染元素包裹在
    <span data-jr-key="..." style="display:contents">
    中,以便选取器将DOM元素映射到spec键。此操作不会影响布局。