使用 json-render/ink 将 JSON 规格渲染为交互式终端 UI从 Schema、组件目录到状态流式渲染的完整指南【免费下载链接】design.mdA format specification for describing a visual identity to coding agents. DESIGN.md gives agents a persistent, structured understanding of a design system.项目地址: https://gitcode.com/GitHub_Trending/de/design.md导读json-render/ink是一个基于 Ink 的终端渲染器它把 JSON 规格spec转换为交互式终端组件树支持标准组件、数据绑定、可见性控制、动作系统与动态属性。在 AGENTS 技能体系中本技能由vercel-labs/json-render提供来源记录见 skills-lock.json它专门用于把 AI 生成的结构化规格实时渲染成终端界面。读完本文你将掌握如何用defineCatalog声明组件目录、用扁平元素映射flat element map组织规格、用状态表达式实现单向/双向数据绑定、用事件系统把用户交互绑定到动作、用repeat渲染动态列表以及通过useUIStream实现流式规格渲染的完整链路。快速开始从 Catalog 到渲染器的最小链路json-render/ink的核心设计是规格spec与实现implementation分离你只需要用一份 JSON 描述界面剩下的渲染、交互、状态管理全部由库接管。最小链路分三步。import { defineCatalog } from json-render/core; import { schema } from json-render/ink/schema; import { standardComponentDefinitions, standardActionDefinitions, } from json-render/ink/catalog; import { defineRegistry, Renderer, type Components } from json-render/ink; import { z } from zod; // 1. 用 schema 标准组件 自定义组件创建目录catalog const catalog defineCatalog(schema, { components: { ...standardComponentDefinitions, CustomWidget: { props: z.object({ title: z.string() }), slots: [], description: Custom widget, }, }, actions: standardActionDefinitions, }); // 2. 注册组件实现标准组件内建只需注册自定义组件 const { registry } defineRegistry(catalog, { components: { CustomWidget: ({ props }) Text{props.title}/Text, } as Componentstypeof catalog, }); // 3. 渲染 function App({ spec }) { return ( JSONUIProvider initialState{{}} Renderer spec{spec} registry{registry} / /JSONUIProvider ); }defineCatalog(schema, { components, actions })schema来自json-render/ink/schema定义扁平元素映射的规格components用 zod 对象描述每个组件的propsslots声明可嵌套的子槽位description用于生成提示词。standardComponentDefinitions与standardActionDefinitions提供了全部标准组件与动作的定义。defineRegistry(catalog, { components })把组件定义映射到组件实现React 函数组件Componentstypeof catalog是目录感知的类型别名让自定义组件获得类型推导。Renderer spec{spec} registry{registry}按规格渲染组件树外层JSONUIProvider是合并了状态、动作、可见性、焦点等上下文的组合 Provider见后文 Providers 小节。这里的类型约束来自技能文档的声明Components 是目录感知的类型化组件映射catalog-aware。从仓库结构看本技能文档位于.agents/skills/ink/SKILL.md与tdd、agent-dx-cli-scale等技能并列是 AGENTS 技能体系中对JSON 驱动的终端 UI 渲染能力的封装。规格结构扁平元素映射Flat Element Map规格使用**扁平元素映射 单一根键root key**的组织方式而不是嵌套树。所有元素平铺在一个elements对象里通过children字段引用其他元素的 key 来建立父子关系root字段指定渲染入口{ root: main, elements: { main: { type: Box, props: { flexDirection: column, padding: 1 }, children: [heading, content] }, heading: { type: Heading, props: { text: Dashboard, level: h1 }, children: [] }, content: { type: Text, props: { text: Hello from the terminal! }, children: [] } } }这种设计的优点规格本身是纯 JSON可以由 LLM 或服务端程序直接生成元素之间通过字符串 key 引用便于增量更新配合 JSONL patch 流式更新见流式渲染小节。技能文档同时提供了一个flatToTree导出用于把扁平元素映射转换为树形结构例如在需要树形遍历的场景使用。每个元素的核心字段字段说明type组件类型名必须是目录中已定义的名字标准组件或自定义组件props传给组件的属性值可以是字面量或动态表达式见动态属性表达式children子元素 key 数组决定渲染树的结构on事件绑定映射{ 事件名: { action, ... } }把组件emit的事件映射到动作visible可见性条件见可见性条件repeat从状态数组重复渲染见Repeat 动态列表标准组件目录标准组件分三大类覆盖了终端 UI 的绝大多数场景。布局类Box— Flexbox 布局容器终端里的div。用于分组、间距、边框、对齐。默认flexDirection为row需要纵向堆叠时显式设置为column。Text— 文本输出支持可选样式颜色、加粗、斜体等。Newline— 插入空行。必须位于flexDirection: column的 Box 内。Spacer— 弹性空白沿主轴方向扩展填充剩余空间。内容类Heading— 章节标题四级样式h1加粗下划线、h2加粗、h3加粗暗色、h4暗色。Divider— 水平分隔线可带居中的标题。Badge— 彩色行内标签变体default、info、success、warning、error。Spinner— 带动画的加载指示器可带标签。ProgressBar— 水平进度条取值 0–1。Sparkline— 使用 Unicode 块字符的行内迷你趋势图。BarChart— 带标签和数值的水平条形图。Table— 带表头和行数据的表格。List— 项目符号或编号列表。ListItem— 结构化列表行支持标题、副标题、前导/尾随文本。Card— 带边框的容器可带标题。KeyValue— 键值对展示。Link— 可点击的 URL可带自定义标签。StatusLine— 带彩色图标的状态消息info、success、warning、error。Markdown— 以终端样式渲染 Markdown 文本。交互类组件交互方式事件TextInput文本输入框submit、changeSelect方向键导航的选择菜单changeMultiSelect空格切换的多选change、submitConfirmInput是/否确认提示confirm、denyTabs左右方向键切换的标签栏change可见性条件基于状态的条件渲染任何元素都可以用visible字段基于状态控制显示/隐藏。语法为{ $state: /path }— 状态路径对应的值为真时显示{ $state: /path, eq: value }— 状态值等于value时显示{ $state: /path, not: true }— 状态值为假时显示{ $and: [cond1, cond2] }— 所有条件为真时显示AND{ $or: [cond1, cond2] }— 任一条件为真时显示OR可见性条件由VisibilityProvider提供支持见 Providers 小节技能文档在终端 UI 设计指南中建议用 Tabs 做多视图 UI并在子内容上配合 visible 条件。动态属性表达式渲染期求值的数据绑定任何 prop 值都可以是数据驱动的表达式在渲染时解析{ $state: /state/key }— 从状态模型读取单向读。{ $bindState: /path }— 双向绑定用于表单组件的自然值属性natural value prop。{ $bindItem: field }— 双向绑定到 repeat 重复项repeat item的字段。{ $cond: condition, $then: value, $else: value }— 条件值。{ $template: Hello, ${/name}! }— 把状态值插值进字符串。技能文档特别强调了一个易错点组件不使用statePathprop 做双向绑定而是把{ $bindState: /path }直接写在表单组件的自然值属性上。双向绑定由useBoundProphook 支撑对应$bindState/$bindItem表达式见 Key Exports 小节。事件系统从 emit 到 action 的完整链路组件用emit触发命名事件规格中元素的on字段把事件映射到动作绑定CustomButton: ({ props, emit }) ( Box Text{props.label}/Text {/* emit(press) 触发规格 on.press 中绑定的动作 */} /Box ),{ type: CustomButton, props: { label: Submit }, on: { press: { action: submit } }, children: [] }事件被ActionProvider接住并分发到对应动作处理器见 Providers 小节。在组件实现里可通过useActions访问动作上下文、用useAction取单个动作的 dispatch 函数。内建动作setState / pushState / removeStatesetState、pushState、removeState三个状态动作内建并自动处理无需注册{ action: setState, params: { statePath: /activeTab, value: home } } { action: pushState, params: { statePath: /items, value: { text: New } } } { action: removeState, params: { statePath: /items, index: 0 } }setState— 写入单个状态路径JSON Pointer 路径。pushState— 向状态数组追加元素。removeState— 按索引从状态数组移除元素。技能文档指出schemaInk 扁平元素映射规格本身就包含这些内建状态动作因此用defineCatalog(schema, ...)创建的目录天然支持它们。Repeat基于状态数组的动态列表在容器元素上使用repeat字段从状态数组渲染列表项{ type: Box, props: { flexDirection: column }, repeat: { statePath: /items, key: id }, children: [item-row] }statePath— 要遍历的状态数组路径。key— 列表项的稳定 key 字段对应 React key用于 diff 与更新。在重复的子元素内部{ $item: field }— 读取当前项的字段。{ $index: true }— 取当前索引。{ $bindItem: field }— 对重复项字段做双向绑定与动态属性表达式配合使用。流式渲染useUIStream 渐进渲染 JSONL patch对于AI 生成界面这种典型场景——规格是一点点生成出来的——useUIStream支持从 JSONL patch 流渐进渲染规格import { useUIStream } from json-render/ink; const { spec, send, isStreaming } useUIStream({ api: /api/generate });spec— 当前已累积并解析的规格对象直接传给Renderer。send— 向服务端发送消息例如用户的后续输入。isStreaming— 流式进行中的标志可用于显示加载状态配合Spinner/ProgressBar。api— 服务端流式接口地址服务端以 JSONL 形式输出规格的 patch 增量。服务端提示词生成catalog.prompt()./server导出用于从目录生成 AI 系统提示词把有哪些组件、各自 props 是什么编码进提示词让 LLM 生成合法规格import { catalog } from ./catalog; const systemPrompt catalog.prompt({ system: You are a terminal assistant. });这解决了 AI 生成的规格不符合 schema的核心痛点提示词中包含每个组件含自定义组件的 props 描述与槽位约束配合description字段模型生成结果更容易落在 schema 内。Providers上下文分工与受控模式Provider职责StateProvider跨组件共享状态JSON Pointer 路径。可通过可选storeprop 切换受控模式ActionProvider处理通过事件系统分发的动作VisibilityProvider基于状态的条件渲染ValidationProvider表单字段校验FocusProvider管理交互组件之间的焦点JSONUIProvider所有上下文的组合 Provider外部 Store受控模式把StateStore传给StateProvider或JSONUIProvider即可切换到外部状态管理import { createStateStore, type StateStore } from json-render/ink; const store createStateStore({ count: 0 }); StateProvider store{store}{children}/StateProvider store.set(/count, 1); // React 自动重新渲染两个关键行为框架无关createStateStore创建的内存StateStore是框架无关的StateStore接口允许接入任意外部状态管理实现。受控优先级提供了store时initialState和onStateChange都会被忽略。createRenderer更高层的聚合 APIcreateRenderer把 Provider 装配与组件注册打包成一个可直接使用的渲染组件import { createRenderer } from json-render/ink; import { standardComponents } from json-render/ink; import { catalog } from ./catalog; const InkRenderer createRenderer(catalog, { ...standardComponents, // 在这里覆盖/扩展自定义组件 }); // InkRenderer 已包含全部 Providerstate、visibility、actions、focus render( InkRenderer spec{spec} state{{ activeTab: overview }} / );注意与defineRegistry的差异createRenderer内建全部 Provider状态、可见性、动作、焦点并允许把standardComponents预构建的标准组件实现整体传入再按需覆盖而RendererdefineRegistry是更底层、更显式的组合方式。stateprop 直接注入初始状态。Key Exports 速查导出用途defineRegistry从目录创建类型安全的组件注册表Renderer使用注册表渲染规格createRenderer高层 API创建内建 Provider 的渲染组件JSONUIProvider所有上下文的组合 ProviderschemaInk 扁平元素映射规格含内建状态动作standardComponentDefinitions所有标准组件的目录定义standardActionDefinitions标准动作的目录定义standardComponents预构建的标准组件实现useStateStore访问状态上下文useStateValue从状态读取单个值useBoundProp$bindState/$bindItem表达式的双向绑定useActions访问动作上下文useAction获取单个动作的 dispatch 函数useOptionalValidationuseValidation的非抛错变体useUIStream从 API 端点流式获取规格createStateStore创建框架无关的内存StateStoreStateStore接入外部状态管理的接口Components类型化组件映射目录感知Actions类型化动作映射目录感知ComponentContext类型化组件上下文目录感知flatToTree扁平元素映射转树结构终端 UI 设计指南技能文档在结尾给出了面向终端的 UI 设计准则这是用这套渲染器做出像样终端界面的实践总结布局以 Box 为主用flexDirection、padding、gap组织空间。注意默认flexDirection是row。尊重终端宽度终端约 80–120 列宽主结构优先纵向布局flexDirection: column。用 Box 的borderStyle做视觉分组可选single、double、round、bold。用命名终端色red、green、yellow、blue、magenta、cyan、white、gray。组件语义化Heading做章节标题Divider分隔区块Badge标状态KeyValue展示带标签数据Card做带边框分组。多视图用 Tabs用 Tabs 切换视图并在子内容上用visible条件配合。趋势图按需选型行内趋势用Sparkline数值对比用BarChart。技能在仓库中的定位在 AGENTS 技能体系中本技能.agents/skills/ink/SKILL.md与agent-dx-cli-scaleCLI 的 Agent 体验评分、tdd红-绿-重构开发流程并列存放于.agents/skills/目录。它服务的目标是让编码 Agent 能把结构化规格实时渲染为可交互的终端界面——这与仓库README.md所描述的让 Agent 持久、结构化地理解设计系统的 DESIGN.md 理念一脉相承DESIGN.md 用 YAML front matter 存设计令牌、用 Markdown 散文存设计意图见 docs/spec.md 与 PHILOSOPHY.md而json-render/ink则提供了把这类结构化规格可视化到终端的运行时能力。技能来源vercel-labs/json-render的记录见 skills-lock.json各技能以computedHash锁定版本保证技能内容可复现、可审计。【免费下载链接】design.mdA format specification for describing a visual identity to coding agents. DESIGN.md gives agents a persistent, structured understanding of a design system.项目地址: https://gitcode.com/GitHub_Trending/de/design.md创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考