diff --git a/.ua/backlog.json b/.ua/backlog.json
index d11d8c7..79871d5 100644
--- a/.ua/backlog.json
+++ b/.ua/backlog.json
@@ -96,6 +96,14 @@
"acceptance": "新增 breadcrumb.ts:nav + ol 语义结构,末项自动为当前页(不可点、aria-current=page),条目有 href 渲染 、无 href 渲染 button 走 onNavigate,分隔符可配;只消费 --ua-* 令牌;导出 / manifest / 文档 / llms-full.txt / demo 同步;npm run check 全绿",
"status": "done",
"notes": "API breadcrumb({ items: {label, href?}[], separator?, onNavigate? });分隔符 aria-hidden;reduced-motion 列表补 .ua-crumb-link;demo 新增 sec-breadcrumb('›' 分隔 + toast 反馈跳转);首轮 style 门禁拦下多行 /*** 注释(检查器只认紧邻单行块),已改单行"
+ },
+ {
+ "id": "d06",
+ "title": "门禁新增 ua verify:清单 ↔ 文档 ↔ 演示锚点一致性校验",
+ "motivation": "新增组件靠人工同步 manifest/docs/demo,易漏(本轮已出现 badge/alert/spinner 手把手只有 2 步);把不变量机器化,防回归",
+ "acceptance": "CLI 新增 verify 子命令:docs.components 的 id 唯一且 api.exports 有 `${id}(` 或以 `${id}.` 开头条目、props 条目必备 n/t/d、steps ≥3、example 必填、demo 锚点必须存在于 apps/demo/main.ts;纳入 npm run check 链条;AGENTS.md 同步;npm run check 全绿",
+ "status": "done",
+ "notes": "锚点校验改看演示源码而非打包产物(demo.html 是 esbuild 产物,锚点在运行时生成);顺带补齐 badge/alert/spinner 的第 3 条手把手(说明与 button/skeleton 的分工);check 链变为 build→style→typecheck→smoke→index→verify"
}
]
}
diff --git a/.ua/index.json b/.ua/index.json
index 169713c..7f3ec4d 100644
--- a/.ua/index.json
+++ b/.ua/index.json
@@ -1,6 +1,6 @@
{
"$schema": "ui-arch-registry-index/0.1",
- "generatedAt": "2026-10-23T08:49:44.245Z",
+ "generatedAt": "2026-10-23T08:50:48.366Z",
"repoRoot": ".",
"packages": [
{
@@ -630,7 +630,8 @@
"example": "badge({ label: '成功', variant: 'success' })",
"steps": [
"选择语义色:default 中性、success 成功、warning 警告、danger 危险",
- "传入 label 即可,纯展示组件无状态"
+ "传入 label 即可,纯展示组件无状态",
+ "状态语义靠 variant 承载,不要自造颜色;纯展示不可交互,需要操作请配 button"
]
},
{
@@ -654,7 +655,8 @@
"example": "alert({ message: '操作可能存在风险', variant: 'warning' })",
"steps": [
"按信息级别选 variant:info 提示、success 成功、warning 警告、danger 错误",
- "传入 message 即可,入场自带下滑动画"
+ "传入 message 即可,入场自带下滑动画",
+ "需要可关闭或带操作按钮时,外层用 h() 组合 button 自行拼装,alert 只负责语义底色"
]
},
{
@@ -770,7 +772,8 @@
"example": "spinner(); spinner({ size: 26 })",
"steps": [
"数据加载时插入 spinner(),加载完成移除",
- "用 size 匹配上下文尺寸(按钮内用小号,整页 loading 用大号)"
+ "用 size 匹配上下文尺寸(按钮内用小号,整页 loading 用大号)",
+ "与 skeleton 分工:已知内容结构用 skeleton 占位,只有『在跑』的短等待用 spinner"
]
},
{
diff --git a/.ua/iterations.log b/.ua/iterations.log
index 0ab4210..941a2d5 100644
--- a/.ua/iterations.log
+++ b/.ua/iterations.log
@@ -10,3 +10,4 @@
{"time": "2026-10-23T16:47:56+08:00", "id": "d03", "result": "done", "summary": "新增折叠面板 accordion:单开/多开 + active 受控 + 禁用面板,grid 行高 0fr→1fr 过渡免测量,aria 关联完整;manifest/llms/demo 同步,check 全绿"}
{"time": "2026-10-23T16:48:36+08:00", "id": "d04", "result": "done", "summary": "dataTable 新增 loading(布尔|信号):表体换骨架行、筛选与翻页禁用、aria-busy;demo 加开关,manifest/llms 同步,check 全绿"}
{"time": "2026-10-23T16:49:45+08:00", "id": "d05", "result": "done", "summary": "新增面包屑 breadcrumb:nav/ol 语义结构 + 末项 aria-current,href 走 、无 href 走 onNavigate,分隔符可配;manifest/llms/demo 同步,check 全绿(中途被 style 门禁拦下多行注释,已改单行)"}
+{"time": "2026-10-23T16:50:49+08:00", "id": "d06", "result": "done", "summary": "新增 ua verify 门禁:清单 id/exports/props/steps/example 与 demo 锚点一致性,纳入 npm run check;顺带补齐 badge/alert/spinner 手把手第三条,check 全绿"}
diff --git a/AGENTS.md b/AGENTS.md
index b9072b0..efae679 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` = style + typecheck + smoke + index,提交前必过。
+**门禁一条龙**:`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` 中存在)。
## Agent 查询组件库的三种方式
@@ -46,7 +46,7 @@
可用工具:`list_components` / `get_component` / `search_components`。
-2. **CLI**:`node packages/tools/cli/bin/ua.js list | info | index`
+2. **CLI**:`node packages/tools/cli/bin/ua.js list | info | index | docs | style | verify`
3. **静态索引**:`.ua/index.json`(机器可读索引)、`llms.txt`(速览)或 **`llms-full.txt`(全量 Agent 文档:逐组件参数 / 回调 / 手把手步骤 / 令牌,无图形纯文本,喂给 Agent 首选)**。
diff --git a/llms-full.txt b/llms-full.txt
index f4fab03..427f5a3 100644
--- a/llms-full.txt
+++ b/llms-full.txt
@@ -1,6 +1,6 @@
# @ui-arch 组件库 Agent 文档(无图形版)
-> 自动生成于 2026-10-23T08:49:44.245Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
+> 自动生成于 2026-10-23T08:50:48.366Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
## 快速上手
@@ -319,6 +319,7 @@ badge({ label: '成功', variant: 'success' })
1. 选择语义色:default 中性、success 成功、warning 警告、danger 危险
2. 传入 label 即可,纯展示组件无状态
+3. 状态语义靠 variant 承载,不要自造颜色;纯展示不可交互,需要操作请配 button
现场演示:apps/docs/demo.html#sec-feedback
@@ -339,6 +340,7 @@ alert({ message: '操作可能存在风险', variant: 'warning' })
1. 按信息级别选 variant:info 提示、success 成功、warning 警告、danger 错误
2. 传入 message 即可,入场自带下滑动画
+3. 需要可关闭或带操作按钮时,外层用 h() 组合 button 自行拼装,alert 只负责语义底色
现场演示:apps/docs/demo.html#sec-feedback
@@ -429,6 +431,7 @@ spinner(); spinner({ size: 26 })
1. 数据加载时插入 spinner(),加载完成移除
2. 用 size 匹配上下文尺寸(按钮内用小号,整页 loading 用大号)
+3. 与 skeleton 分工:已知内容结构用 skeleton 占位,只有『在跑』的短等待用 spinner
现场演示:apps/docs/demo.html#sec-feedback
diff --git a/package.json b/package.json
index aaad274..7696a19 100644
--- a/package.json
+++ b/package.json
@@ -12,8 +12,9 @@
"style": "node packages/tools/cli/bin/ua.js style",
"index": "node packages/tools/cli/bin/ua.js index",
"docs": "node packages/tools/cli/bin/ua.js index && node packages/tools/cli/bin/ua.js docs",
- "check": "npm run build && npm run style && npm run typecheck && npm run smoke && npm run index",
- "ua": "node packages/tools/cli/bin/ua.js"
+ "check": "npm run build && npm run style && npm run typecheck && npm run smoke && npm run index && npm run verify",
+ "ua": "node packages/tools/cli/bin/ua.js",
+ "verify": "node packages/tools/cli/bin/ua.js verify"
},
"devDependencies": {
"typescript": "^5.6.0"
diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json
index 5fd5859..ec0bcfb 100644
--- a/packages/skin/ui/ua.manifest.json
+++ b/packages/skin/ui/ua.manifest.json
@@ -501,7 +501,8 @@
"example": "badge({ label: '成功', variant: 'success' })",
"steps": [
"选择语义色:default 中性、success 成功、warning 警告、danger 危险",
- "传入 label 即可,纯展示组件无状态"
+ "传入 label 即可,纯展示组件无状态",
+ "状态语义靠 variant 承载,不要自造颜色;纯展示不可交互,需要操作请配 button"
]
},
{
@@ -525,7 +526,8 @@
"example": "alert({ message: '操作可能存在风险', variant: 'warning' })",
"steps": [
"按信息级别选 variant:info 提示、success 成功、warning 警告、danger 错误",
- "传入 message 即可,入场自带下滑动画"
+ "传入 message 即可,入场自带下滑动画",
+ "需要可关闭或带操作按钮时,外层用 h() 组合 button 自行拼装,alert 只负责语义底色"
]
},
{
@@ -641,7 +643,8 @@
"example": "spinner(); spinner({ size: 26 })",
"steps": [
"数据加载时插入 spinner(),加载完成移除",
- "用 size 匹配上下文尺寸(按钮内用小号,整页 loading 用大号)"
+ "用 size 匹配上下文尺寸(按钮内用小号,整页 loading 用大号)",
+ "与 skeleton 分工:已知内容结构用 skeleton 占位,只有『在跑』的短等待用 spinner"
]
},
{
diff --git a/packages/tools/cli/bin/ua.js b/packages/tools/cli/bin/ua.js
index 6ff5502..e7c5b0b 100644
--- a/packages/tools/cli/bin/ua.js
+++ b/packages/tools/cli/bin/ua.js
@@ -560,6 +560,47 @@ function cmdStyle() {
console.log(`style OK (${files.length} files)`);
}
+/*** 清单一致性校验:组件文档块的 id / props 说明 / demo 锚点必须与代码和演示产物对齐 ***/
+function cmdVerify() {
+ const issues = [];
+ 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()) {
+ const comps = pkg.docs?.components ?? []; // 清单已被 collectManifests 摊平
+ const exports = pkg.api?.exports ?? [];
+ if (comps.length && !exports.length) issues.push(`${pkg.name}: docs.components 有 ${comps.length} 项但 api.exports 为空`);
+
+ const seen = new Set(); // 组件 id 去重(同一清单内不允许重复)
+ for (const c of comps) {
+ const tag = `${pkg.name}#${c.id ?? '?'}`;
+ if (!c.id || !c.title) issues.push(`${tag}: 缺少 id 或 title`);
+ if (seen.has(c.id)) issues.push(`${tag}: id 重复`);
+ seen.add(c.id);
+ if (!c.example) issues.push(`${tag}: 缺少 example`);
+ if ((c.steps ?? []).length < 3) issues.push(`${tag}: steps 少于 3 条`);
+ if (!exports.some((e) => e.startsWith(`${c.id}(`) || e.startsWith(`${c.id}.`))) {
+ issues.push(`${tag}: api.exports 中没有以「${c.id}(」或「${c.id}.」开头的条目`);
+ }
+ for (const prop of c.props ?? []) {
+ if (!prop.n || !prop.t || !prop.d) issues.push(`${tag}: props 条目 ${prop.n ?? '?'} 缺 n/t/d`);
+ }
+ if (c.demo && demoSource && !anchors.has(c.demo)) {
+ issues.push(`${tag}: demo 锚点 ${c.demo} 在 apps/demo/main.ts 中不存在(缺对应 section)`);
+ }
+ }
+ }
+
+ 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 一致)');
+}
+
// ---- 入口 ----
const [cmd, ...args] = process.argv.slice(2);
@@ -583,10 +624,13 @@ switch (cmd) {
case 'style':
cmdStyle();
break;
+ case 'verify':
+ cmdVerify();
+ break;
case 'mcp':
cmdMcp();
break;
default:
- console.log('usage: ua |docs|style|mcp>');
+ console.log('usage: ua |docs|style|verify|mcp>');
process.exit(cmd ? 1 : 0);
}