diff --git a/.ua/backlog.json b/.ua/backlog.json index 1d50f16..b2ef55e 100644 --- a/.ua/backlog.json +++ b/.ua/backlog.json @@ -240,6 +240,14 @@ "acceptance": "AlertProps 新增 title? / closable? / onClose?;关闭时先退场(opacity+上移,200ms)再从 DOM 移除;容器 role=alert;只消费 --ua-* 令牌;demo 新增 sec-alert 展示无标题与可关闭带标题两种;manifest/文档/llms-full.txt 同步;npm run check 全绿", "status": "done", "notes": "与 toast 的分工保持:alert 常驻在页面流里、toast 全局浮动;关闭后由调用方自行决定是否重新渲染;body/title/message 三层结构让 flex 布局能把 × 对齐到首行;对现有 alert({message}) 调用完全兼容" + }, + { + "id": "d24", + "title": "重写 README 并把组件清单纳入 verify 防漂移", + "motivation": "README 只有标题一行,新人/Agent 从仓库首页拿不到任何信息;而文档里的组件数此前已经漂移过(17 vs 27)", + "acceptance": "README 覆盖定位 / 快速开始 / 28 组件清单 / 目录表 / 三道门禁 / Agent 接入 / 分支流水线;新增 标记区块,ua verify 校验区块 id 集合与全部清单 docs.components 完全一致(缺/多都报);负向测试(删 avatar)能拦下;AGENTS.md 同步;npm run check 全绿", + "status": "done", + "notes": "不做自动写入(避免工具偷偷改文档),只做一致性校验:新增组件时 verify 报「README 组件区块缺少 xxx」,由人补一行;标记区块用 · 分隔便于人读与机器切分" } ] } diff --git a/.ua/index.json b/.ua/index.json index 0fd39cb..127dd57 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-23T09:04:21.317Z", + "generatedAt": "2026-10-23T09:05:40.038Z", "repoRoot": ".", "packages": [ { diff --git a/.ua/iterations.log b/.ua/iterations.log index 110c0b9..346c29f 100644 --- a/.ua/iterations.log +++ b/.ua/iterations.log @@ -28,3 +28,4 @@ {"time": "2026-10-23T17:03:02+08:00", "id": "d21", "result": "done", "summary": "无障碍收尾:modal × 补 aria-label/type、spinner role=status、skeleton aria-hidden、tablist aria-label + 禁用页签 aria-disabled;conventions 记录基线;check 全绿"} {"time": "2026-10-23T17:03:49+08:00", "id": "d22", "result": "done", "summary": "新增头像 avatar(src 图片 / 首字占位 + size + 四档语义底色),demo 独立 sec-avatar;中途被 verify 的 demo 锚点检查拦下已补;check 全绿"} {"time": "2026-10-23T17:04:27+08:00", "id": "d23", "result": "done", "summary": "alert 新增 title / closable / onClose 与 role=alert,关闭走 200ms 退场后摘除;demo 独立 sec-alert;manifest/llms 同步,check 全绿"} +{"time": "2026-10-23T17:05:46+08:00", "id": "d24", "result": "done", "summary": "重写 README(定位/快速开始/28 组件/目录/门禁/Agent 接入/流水线),组件清单做成标记区块并由 ua verify 校验防漂移;负向测试通过,check 全绿"} diff --git a/AGENTS.md b/AGENTS.md index 19c82c0..dfc10f5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,7 +27,7 @@ - `apps/demo`(@ui-arch/demo):交互演示,产物 apps/demo/dist/demo.html - `apps/docs`:文档站,纯产物(ua docs 生成) -**门禁一条龙**:`npm run check` = build + style + typecheck + smoke + index + verify,提交前必过。`ua verify` 校验清单 ↔ 文档 ↔ 演示一致(docs.components 的 id 必须在 api.exports 有对应条目、props 必须带 n/t/d、demo 锚点必须在 `apps/demo/main.ts` 中存在),并检查皮肤包样式表无硬编码颜色(`#hex` 一律禁止,必须走 `--ua-*` 令牌)。 +**门禁一条龙**:`npm run check` = build + style + typecheck + smoke + index + verify,提交前必过。`ua verify` 校验清单 ↔ 文档 ↔ 演示一致(docs.components 的 id 必须在 api.exports 有对应条目、props 必须带 n/t/d、demo 锚点必须在 `apps/demo/main.ts` 中存在),检查皮肤包样式表无硬编码颜色(`#hex` 一律禁止,必须走 `--ua-*` 令牌),并核对 README 的 `` 区块与清单组件集合完全一致。 ## Agent 查询组件库的三种方式 diff --git a/README.md b/README.md index 5ca2bec..dc0eded 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,54 @@ -# UI +# @ui-arch · 私人组件库 +自研、零依赖、Agent 友好的 DOM 组件库单体仓:**signal 微框架 + headless 表格内核 + 令牌化皮肤组件**,所有文档与 MCP 注册表都从一个清单派生。 + +## 快速开始 + +```bash +pnpm install # 包间依赖用 workspace:*,npm 不支持 +npm run check # 门禁一条龙:build → style → typecheck → smoke → index → verify +npm run demo # 打开演示:apps/demo/dist/demo.html +npm run docs # 生成人类文档站 apps/docs/index.html + Agent 全量文档 llms-full.txt +``` + +## 组件(共 28 个) + + +accordion · alert · avatar · badge · breadcrumb · button · card · checkbox · dataTable · drawer · form · menu · modal · pagination · popover · progress · radioGroup · select · skeleton · slider · spinner · switchButton · tabs · tag · textField · textarea · toast · tooltip + + +分组:表单(button / textField / textarea / checkbox / switchButton / select / radioGroup / slider / form / formItem)、反馈(alert / toast / spinner / progress / skeleton / badge)、浮层(tooltip / popover / modal / drawer / menu)、导航(tabs / breadcrumb / pagination / accordion)、数据(dataTable / tag / avatar / card)。 + +## 目录 + +| 路径 | 说明 | +|---|---| +| `packages/core/frame` | @ui-arch/frame:signal / computed / effect + hyperscript(无虚拟 DOM) | +| `packages/core/table-core` | @ui-arch/table-core:headless 表格(filter → sort → paginate 派生管线) | +| `packages/skin/ui` | @ui-arch/ui:28 个 DOM 组件 + ui.css(只消费 `--ua-*` 令牌) | +| `packages/design/tokens` | @ui-arch/tokens:设计令牌(暗色只换值不换名) | +| `packages/tools/cli` | @ui-arch/cli:`ua` 命令行与 stdio MCP server | +| `apps/demo` | 交互演示(esbuild 打包成单文件) | +| `apps/docs` | 文档站纯产物(`ua docs` 生成) | + +## 三道门禁 + +1. `ua style`:注释规范——导出必须有 `/*** 作用 ***/`、接口字段行尾注释、行注释用中文。 +2. `ua verify`:清单 ↔ 文档 ↔ 演示一致(id 唯一、exports 对应、props 带 n/t/d、steps ≥ 3、demo 锚点存在),皮肤样式表禁止 `#hex` 硬编码颜色。 +3. `ua index`:刷新 `.ua/index.json` / `llms.txt`,供 MCP 与 Agent 查询。 + +## Agent 接入 + +- 静态:`llms.txt`(速览)/ **`llms-full.txt`(全量:逐组件参数 / 回调 / 手把手 / 令牌)** +- 命令行:`node packages/tools/cli/bin/ua.js list | info | docs | verify | mcp` +- MCP:在 `~/.workbuddy/mcp.json` 注册 `ua mcp`,可用 `list_components` / `get_component` / `search_components` + +规范见 `AGENTS.md`(接入手册)、`ARCHITECTURE.md`(分层与依赖方向)、`CODING.md`(编码规范)、`ITERATION.md`(自动迭代协议)。 + +## 分支流水线 + +``` +qa/probe ──门禁通过──▶ qa/stable ──人工审阅──▶ main(受保护) +``` + +自动化只在 `qa/stable` 做 fast-forward 合并,`main` 仅由人审后合入。 diff --git a/llms-full.txt b/llms-full.txt index e62b07f..771e072 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-23T09:04:21.317Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-23T09:05:40.038Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 diff --git a/packages/tools/cli/bin/ua.js b/packages/tools/cli/bin/ua.js index 127ee68..8faf0e8 100644 --- a/packages/tools/cli/bin/ua.js +++ b/packages/tools/cli/bin/ua.js @@ -576,15 +576,33 @@ function checkSkinCss(pkg) { return issues; } +/*** README 组件区块校验:marker 之间的 id 集合必须与全部清单的 docs.components 完全一致(防文档漂移) ***/ +function checkReadme(pkgs) { + const issues = []; + const file = path.join(ROOT, 'README.md'); + if (!fs.existsSync(file)) return issues; // 卫语句:没有 README 就不校验 + const block = fs.readFileSync(file, 'utf8').match(/([\s\S]*?)/); + if (!block) { + issues.push('README.md 缺少组件区块标记( … )'); + return issues; + } + const listed = new Set(block[1].split('·').map((s) => s.trim()).filter(Boolean)); + const actual = new Set(pkgs.flatMap((p) => (p.docs?.components ?? []).map((c) => c.id))); + for (const id of actual) if (!listed.has(id)) issues.push(`README.md 组件区块缺少 ${id}`); + for (const id of listed) if (!actual.has(id)) issues.push(`README.md 组件区块多出 ${id}(清单里没有)`); + return issues; +} + /*** 清单一致性校验:组件文档块的 id / props 说明 / demo 锚点必须与代码和演示产物对齐 ***/ function cmdVerify() { const issues = []; + const pkgs = collectManifests(); // 一次收集,供组件校验与 README 区块校验共用 const demoPath = path.join(ROOT, 'apps', 'demo', 'main.ts'); // 演示区块定义在源码里(产物是打包后的 JS) const demoSource = fs.existsSync(demoPath) ? fs.readFileSync(demoPath, 'utf8') : ''; const anchors = new Set(demoSource.match(/sec-[a-z0-9-]+/g) ?? []); // 已定义的区块锚点 if (!demoSource) console.log('verify: 未找到 apps/demo/main.ts,跳过 demo 锚点校验'); - for (const pkg of collectManifests()) { + for (const pkg of pkgs) { const comps = pkg.docs?.components ?? []; // 清单已被 collectManifests 摊平 const exports = pkg.api?.exports ?? []; if (pkg.type === 'skin-dom') issues.push(...checkSkinCss(pkg)); @@ -610,12 +628,14 @@ function cmdVerify() { } } + issues.push(...checkReadme(pkgs)); + if (issues.length) { for (const it of issues) console.error('verify: ' + it); console.error(`${issues.length} violation(s)`); process.exit(1); } - console.log('verify OK (manifest ↔ docs ↔ demo 一致,皮肤无硬编码颜色)'); + console.log('verify OK (manifest ↔ docs ↔ demo 一致,皮肤无硬编码颜色,README 组件清单同步)'); } // ---- 入口 ----