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键。此操作不会影响布局。