Files
vibe-registry/docs/design.md
T
hellow-da-sb 8c93bde5f6 feat: vibe-registry M1 — 万物皆注册框架骨架
- core: registerLayout/Widget/Action(泛型)/Schema + 校验闸门
- definePage: 页面只管业务,结构来自注册表
- presets: admin / app-shell 预设布局(vr add 物化)
- cli: vr list/add/update 防覆盖
- examples/qa-orchestrator: 实战编排验证(workspace:* 依赖)
2026-09-03 00:48:10 +08:00

14 KiB

vibe-registry — 面向 AI 前端的"万物皆注册"页面框架(设计文档 v0.5)

状态:探究中(未定稿) · 作者:dsh-agent 与用户协作 定位:vibe coding 时代,把"AI 前端生成"从自由发挥收束到受控注册 + 声明式组合


1. 问题陈述

1.1 痛点的三张面孔

痛点 现象 根因
重复造轮子 让 AI 做个表单/列表/弹窗,它每次从零拼一遍 AI 无"既有资产"记忆,不查你已有组件
布局不统一 页面间距、栅格、导航、按钮位置随生成批次漂移 布局规范只存在于人脑/文档,AI 不会遵守
无法收敛 改一处样式要动 N 个文件,AI 改完这处崩那处 约定不成为代码,就没有强制力

1.2 为什么组件库没解决

组件库(shadcn/antd)解决了"有组件可用",但没解决两件事:

  1. AI 不会主动用——提示词劝说无效,生成时照样自创;
  2. 页面级结构(布局/区块/页面骨架)没有注册位——组件库只管原子,不管"一个后台页长什么样"。

1.3 一句话目标

让页面 = 注册项的组合,让 AI(和人)通过受控的注册/声明语法产出页面, 而不是通过自由的 JSX——从生成源头锁死重复与漂移。


2. 核心理念:万物皆注册(Everything is Registered)

2.1 三个公理

  1. 一切可复用之物必须注册:布局、区块、组件、操作、路由、表单字段、数据源……先注册,才可用。
  2. 页面是声明的产物,不是手写的产物:页面文件只声明"由哪些注册项、以什么配置组合",结构由注册表驱动渲染。
  3. 改动即配置,配置即代码:任何视觉/结构改动落在注册项配置里,版本可控、可 diff、可被 AI 精确编辑。

2.2 注册项类型(草案)

type RegistryItem =
  | { kind: 'layout'; id: string; ...; preview: { demo?: PageDemo; thumbs?: string } }  // 页面骨架,带 live 预览数据
  | { kind: 'block';  id: string; ... }        // 区块:导航/侧栏/页脚/内容容器
  | { kind: 'widget'; id: string; ...; preview?: WidgetDemo }  // 业务组件:表单/表格/弹窗(映射到 UI 库)
  | { kind: 'action'; id: string; ... }        // 行为:提交/跳转/确认(可复用事件语义)
  | { kind: 'schema'; id: string; ... }        // 数据/字段注册:表单字段、表格列
  | { kind: 'theme';  id: string; ... }        // 设计 token:间距/圆角/色板(轻量覆盖)

关键:

  • widget 允许指向任意第三方组件并注册,万物皆注册的"物"是开放的;
  • 预览数据是注册项的一部分:layout/widget 注册时自带 demo 数据,选择层渲染真实形态, 预览与实装共用同一渲染内核——预览即真实,无 mock 漂移;
  • 二次开发定制页面 = 新增 widget/layout 注册并挂入 P 层,天然获得风格/主题一体化(共用 theme + 渲染内核)。

3. 架构分层

┌─────────────────────────────────────────────┐
│  P. 选择/预览层(新,用户长时间停留的地方)     │
│     布局画廊:预设布局 live 预览 + 缩略图      │
│     IDE 联动(Webview/面板):选中→回写声明    │
│     用户只做:挑布局 → 声明业务 → 生成        │
├─────────────────────────────────────────────┤
│  A. 声明层(用户/AI 产出)                      │
│     page.tsx 声明:layout id + blocks +       │
│     配置 + 业务回调                           │
├─────────────────────────────────────────────┤
│  B. 注册表层(单一事实来源)                    │
│     registry/**/*.ts:注册项定义与配置         │
│     (类型安全:TS 推导注册项类型)              │
├─────────────────────────────────────────────┤
│  C. 极薄 schema 渲染内核                     │
│     声明 → 按 id 查注册项 → 布局槽位填充 →    │
│     widget 装配 → 输出组件树                  │
├─────────────────────────────────────────────┤
│  D. 业务接入层(页面"只管业务")               │
│     页面文件只写:数据获取 / 状态 / 事件回调,  │
│     结构全部来自 C 层输出                     │
└─────────────────────────────────────────────┘

3.0 选择/预览层(P 层)定位

  • 拿包即画廊:脚手架创建项目时把一批预设布局(源码)放进 registry/** 并生成画廊首页——npm run dev 打开即见(类 Vite 欢迎首页);
  • 默认预设全部带 live 预览:每个预设布局(admin/dashboard/landing…)内置一份"demo 数据渲染",用户一眼看到成品形态再决定;
  • 配合 IDE 联动(M3 增强):VS Code/Cursor Webview 面板或 HMR 预览路由——选布局、声明业务、实时看效果,长时间停留在"选择"而不写布局;
  • 声明业务即可:P 层把用户的"业务描述"折成 definePage 的 setup/blocks 声明;
  • 预设与自定义一体:项目 registry/** 是唯一资产空间,vr add 物化预设、直接改源码,二次开发定制页天然与预设同一渲染内核、风格/主题/布局统一。

3.1 页面文件长什么样(愿景示例)

// 页面只管业务——不写任何布局 JSX
export default definePage({
  layout: 'app-shell',                       // ← 注册的布局
  blocks: [
    { widget: 'page-header', props: { title: '订单管理' } },
    { widget: 'filter-bar',  bind: { filters } },
    { widget: 'data-table',  bind: { columns, rows, onPage } },
  ],
  // 业务:
  async setup(ctx) {
    const rows = await api.listOrders(ctx.query)
    return { rows }
  },
  actions: { onPage: async (p) => { ... } },
})

AI 生成的"页面代码"被限制成这种形状:结构来自注册表,AI 只填业务与配置。


4. 面向 AI 的受控生成(AI Gate 草案)

这是与所有"低代码引擎"的分水岭——用户是 AI,不是拖拽者。

4.1 交互协议:用户只须说"xx 布局"

用户: "订单列表页,用 admin 布局,带筛选和分页表格"
agent:  layout: 'admin'                    ← 唯一 id 契约
        blocks: [ filter-bar, data-table ] ← 全部来自注册表
        setup() { 业务 }                    ← 只填业务
  • 用户只需报布局 id(admin/dashboard/landing…),布局的栅格、骨架、样式细节全在注册项里,用户与 agent 都不需要知道实现;
  • 所有可用的 id 是公开面,agent 通过注册表目录/README 获取,不许自创 id;
  • 布局 id 是稳定契约:实现可换,id 不变(防止 agent 记错实现造成静默漂移)。

4.2 受控语法与校验闸门

  • 受控语法:AI 只能产出 definePage/registerItem 形状的代码;
  • 校验闸门:生成产物过 validate()——引用了未注册的 widget/layout → 报错退回,提示"请先注册或改用已有项";
  • 注册优先:AI 想造新组件,先走"注册申请",注册完再引用;
  • 收敛目标:同一仓库内,所有 AI 生成的页面共享同一注册表 → 布局天然统一。

4.3 扩展:万物皆可注册(注册是开放集)

  • 布局、widget、schema、action 均可由用户/agent 新增注册;
  • 新增注册 = 写一个 registerXxx({...}) 文件放 registry/**,自动纳入类型推导;
  • 未使用不进编译和源码:新注册但没页面引用 → 留在注册源、不进产物(零成本扩展);
  • 首次被引用时自动物化,进入编译与源码树。

5. 与现有方案的分界(避免"又一个低代码")

方案 我们不做 我们做
低代码平台(拖拽/配置后台) 不做可视化画布、不做运行时编辑器 配置即代码,存 git,可 review
组件库(shadcn/antd) 不重新造原子组件 组件注册化包装,统一暴露
代码生成器(v0/bolt) 不只"生成页面",不解决复用 生成受控产物,强制复用注册项
headless 框架 不做渲染运行时新轮子 渲染内核极薄,组合已有库

一句话:我们不发明 UI,只发明"约束 AI 的注册协议"。


6. 已定决策(v0.5,用户拍板)

  1. 声明格式:TS 优先——definePage/registerItem 全 TS,类型推导强、AI 好生成;YAML 仅备用于纯数据资产。
  2. 渲染内核:极薄 schema 渲染器——注册项 → 映射已有组件(antd/shadcn),内核保持 <500 行,不重造 UI。
  3. 预设布局模板:内置多个、均带简易 id 与 live 预览——如 app-shell / admin / dashboard / landing / form-page……用户(或用户告诉 agent)只需说"用 xx 布局",agent 按 id 取布局,无需理解布局实现;每个布局附带 demo 预览,选择层直接可看。
  4. 可扩展:用户可注册自己的布局/widget(文件约定,见 §8),注册是开放集;二次开发定制页可并入现有布局,与常见组件库联动,风格/主题一体化。
  5. 未使用不进编译、不进源码——只有被页面实际引用的注册项才进入编译产物与源码树;未被引用的注册项不物化、不打包。
  6. 选择/预览层(P 层):用户长时间停留在"选布局 + 声明业务",由 P 层回写声明文件。
  7. 预设物化进项目 + 拿包即画廊(类 Vite 首页)——预设与自定义物理一体,消灭两套资产:
    • 预设不是黑盒包:CLI vr add <layout> 或脚手架创建时,把预设布局以源码拷贝进项目 registry/**(类 shadcn add + Vite 模板心智);
    • 之后预设与自定义同目录、同源码、同改法——想改布局细节直接改源码,无"包内预设不可改";
    • 升级:框架发新版,vr update 拉 diff,用户选择是否同步(类 shadcn upgrade);
    • 画廊:项目 registry/** 自带 demo 数据,dev 首页列出全部已注册项 live 预览,选中即生成 definePage 声明;
    • 未使用不进产物:物化进项目的预设,仍只有被页面引用才进入编译产物。

6.1 注册与物化的统一模型(决策 5 × 7)

包内预设 registry 源 ──vr add / 脚手架──> 拷贝进项目 registry/**(与自定义同源码)
                                              │
                                  项目 registry/**(唯一资产空间)
                                              │ 被页面引用 → 物化进产物
                                              ▼
                                    编译产物 / 源码树(仅物化项)
  • 一个资产空间:项目 registry/** 是唯一事实来源,预设与自定义无差别;
  • 物化 = 被引用:bundler 树摇保证未引用注册项不打包;
  • 画廊 = dev 首页:渲染项目内全部已注册项 live 预览,生产不含画廊。

6.1 决策 5 的机制:按需物化(On-Demand Materialization)

注册表(全量,dev 可见) ──静态引用分析──> 物化清单(Materialized Set)
                                            │ 只装载被引用项
                                            ▼
                                    编译产物 / 源码树(仅物化项)
  • 触发时机:生成期(定义页面的构建/生成脚本时)做全量静态扫描,收集所有 definePage 中被引用的 layout/widget/schema id;
  • 未被引用的注册项停留在"注册源"里,不生成运行时文件、不进 bundle;
  • 好处:产物极轻、无死代码、新增预设布局零成本(不用就不存在);
  • 张力与对策:见 §6.2。

6.2 新决策暴露的两个张力(需留意,不阻塞)

  1. "未使用即不物化" vs "注册即复用":一个 widget 被页面 A 注册、但尚未被任何页面引用——它算"未使用"而被裁剪吗? → 对策:物化判断以引用为准;仅注册未引用 = 留在注册源但不进产物(符合你的设计); "想让它先进产物"需显式 include(用于模板扩展场景)。
  2. 布局 id 语义漂移:agent 记忆里的 admin 和你注册的 admin 若实现不同,会静默用错布局。 → 对策:布局 id 是稳定契约,实现可换、id 不变;id 表是文档化的公开面,进 README 供 agent 引用。

8. 扩展注册机制(文件约定)

registry/                      ← 唯一资产空间(预设 + 自定义同在此)
  layouts/    admin.ts         registerLayout('admin', { skeleton: [...] })
              app-shell.ts     ← vr add app-shell 从包内拷贝而来
  widgets/    table.ts         registerWidget('data-table', { render: Table, schema })
              filter.ts        ...
  schemas/    order.ts         registerSchema('order-fields', ...)
  actions/    submit.ts        registerAction('submit-order', ...)
  • 每个文件幂等注册一个 id;重名 id = 编译报错(唯一性强制);
  • 目录/文件路径仅组织用,id 才是引用键(满足"简易 id"诉求);
  • 预设 = vr add 拷贝进此目录的源码,与自定义无差别,可直接修改;
  • vr update 对比包内新预设 diff,用户选择同步(可保留本地改动);
  • 未引用的注册文件可留在仓库作为资产,但不出产物(决策 5)。

9. MVP 范围(建议第一刀)

阶段 M1:类型 + 渲染内核 + 静态物化(不接 UI 库)

  • registerItem() + 目录式注册表,类型推导
  • definePage() + 布局槽位填充(首内置:admin/app-shell 布局示例)
  • 注册项带 demo 预览数据的类型骨架(为 P 层铺路)
  • validate() 校验闸门(未注册引用即报错)
  • 静态引用分析:未使用注册项不进产物(决策 5 的最小实现)

阶段 M2:接入真实 UI 库 + 引入即预览

  • widget 映射 antd/shadcn(首批 8-10 个:按钮/表格/表单/弹窗/筛选条/分页…)
  • 引入即注册验证:import 注册模块 → 自动进入画廊
  • dev 首页(布局画廊):缩略图 + live 预览 + demo 数据(类 Vite 首页)
  • 2-3 个示例后台页,证明"同一注册表,AI 生成即统一"

阶段 M3:生成回写 + IDE 联动

  • 画廊选中布局 → 声明业务 → 生成 definePage → 回写文件
  • VS Code/Cursor Webview 或 HMR 面板接入

10. 命名候选(未定)

  • vibe-registry / regui / page-registry / 中文语境:万物注册?敬请给方向