iter: d06 新增 ua verify 门禁(清单/文档/演示锚点一致性)

This commit is contained in:
ui-arch-bot
2026-10-23 16:50:49 +08:00
parent 60c052439f
commit 582318adf1
8 changed files with 76 additions and 13 deletions
+8
View File
@@ -96,6 +96,14 @@
"acceptance": "新增 breadcrumb.ts:nav + ol 语义结构,末项自动为当前页(不可点、aria-current=page),条目有 href 渲染 <a>、无 href 渲染 button 走 onNavigate,分隔符可配;只消费 --ua-* 令牌;导出 / manifest / 文档 / llms-full.txt / demo 同步;npm run check 全绿", "acceptance": "新增 breadcrumb.ts:nav + ol 语义结构,末项自动为当前页(不可点、aria-current=page),条目有 href 渲染 <a>、无 href 渲染 button 走 onNavigate,分隔符可配;只消费 --ua-* 令牌;导出 / manifest / 文档 / llms-full.txt / demo 同步;npm run check 全绿",
"status": "done", "status": "done",
"notes": "API breadcrumb({ items: {label, href?}[], separator?, onNavigate? });分隔符 aria-hidden;reduced-motion 列表补 .ua-crumb-link;demo 新增 sec-breadcrumb('›' 分隔 + toast 反馈跳转);首轮 style 门禁拦下多行 /*** 注释(检查器只认紧邻单行块),已改单行" "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"
} }
] ]
} }
+7 -4
View File
@@ -1,6 +1,6 @@
{ {
"$schema": "ui-arch-registry-index/0.1", "$schema": "ui-arch-registry-index/0.1",
"generatedAt": "2026-10-23T08:49:44.245Z", "generatedAt": "2026-10-23T08:50:48.366Z",
"repoRoot": ".", "repoRoot": ".",
"packages": [ "packages": [
{ {
@@ -630,7 +630,8 @@
"example": "badge({ label: '成功', variant: 'success' })", "example": "badge({ label: '成功', variant: 'success' })",
"steps": [ "steps": [
"选择语义色:default 中性、success 成功、warning 警告、danger 危险", "选择语义色:default 中性、success 成功、warning 警告、danger 危险",
"传入 label 即可,纯展示组件无状态" "传入 label 即可,纯展示组件无状态",
"状态语义靠 variant 承载,不要自造颜色;纯展示不可交互,需要操作请配 button"
] ]
}, },
{ {
@@ -654,7 +655,8 @@
"example": "alert({ message: '操作可能存在风险', variant: 'warning' })", "example": "alert({ message: '操作可能存在风险', variant: 'warning' })",
"steps": [ "steps": [
"按信息级别选 variant:info 提示、success 成功、warning 警告、danger 错误", "按信息级别选 variant:info 提示、success 成功、warning 警告、danger 错误",
"传入 message 即可,入场自带下滑动画" "传入 message 即可,入场自带下滑动画",
"需要可关闭或带操作按钮时,外层用 h() 组合 button 自行拼装,alert 只负责语义底色"
] ]
}, },
{ {
@@ -770,7 +772,8 @@
"example": "spinner(); spinner({ size: 26 })", "example": "spinner(); spinner({ size: 26 })",
"steps": [ "steps": [
"数据加载时插入 spinner(),加载完成移除", "数据加载时插入 spinner(),加载完成移除",
"用 size 匹配上下文尺寸(按钮内用小号,整页 loading 用大号)" "用 size 匹配上下文尺寸(按钮内用小号,整页 loading 用大号)",
"与 skeleton 分工:已知内容结构用 skeleton 占位,只有『在跑』的短等待用 spinner"
] ]
}, },
{ {
+1
View File
@@ -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: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: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 走 <a>、无 href 走 onNavigate,分隔符可配;manifest/llms/demo 同步,check 全绿(中途被 style 门禁拦下多行注释,已改单行)"} {"time": "2026-10-23T16:49:45+08:00", "id": "d05", "result": "done", "summary": "新增面包屑 breadcrumb:nav/ol 语义结构 + 末项 aria-current,href 走 <a>、无 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 全绿"}
+2 -2
View File
@@ -27,7 +27,7 @@
- `apps/demo`(@ui-arch/demo):交互演示,产物 apps/demo/dist/demo.html - `apps/demo`(@ui-arch/demo):交互演示,产物 apps/demo/dist/demo.html
- `apps/docs`:文档站,纯产物(ua docs 生成) - `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 查询组件库的三种方式 ## Agent 查询组件库的三种方式
@@ -46,7 +46,7 @@
可用工具:`list_components` / `get_component` / `search_components`。 可用工具:`list_components` / `get_component` / `search_components`。
2. **CLI**:`node packages/tools/cli/bin/ua.js list | info <name> | index` 2. **CLI**:`node packages/tools/cli/bin/ua.js list | info <name> | index | docs | style | verify`
3. **静态索引**:`.ua/index.json`(机器可读索引)、`llms.txt`(速览)或 **`llms-full.txt`(全量 Agent 文档:逐组件参数 / 回调 / 手把手步骤 / 令牌,无图形纯文本,喂给 Agent 首选)**。 3. **静态索引**:`.ua/index.json`(机器可读索引)、`llms.txt`(速览)或 **`llms-full.txt`(全量 Agent 文档:逐组件参数 / 回调 / 手把手步骤 / 令牌,无图形纯文本,喂给 Agent 首选)**。
+4 -1
View File
@@ -1,6 +1,6 @@
# @ui-arch 组件库 Agent 文档(无图形版) # @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)。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
## 快速上手 ## 快速上手
@@ -319,6 +319,7 @@ badge({ label: '成功', variant: 'success' })
1. 选择语义色:default 中性、success 成功、warning 警告、danger 危险 1. 选择语义色:default 中性、success 成功、warning 警告、danger 危险
2. 传入 label 即可,纯展示组件无状态 2. 传入 label 即可,纯展示组件无状态
3. 状态语义靠 variant 承载,不要自造颜色;纯展示不可交互,需要操作请配 button
现场演示:apps/docs/demo.html#sec-feedback 现场演示:apps/docs/demo.html#sec-feedback
@@ -339,6 +340,7 @@ alert({ message: '操作可能存在风险', variant: 'warning' })
1. 按信息级别选 variant:info 提示、success 成功、warning 警告、danger 错误 1. 按信息级别选 variant:info 提示、success 成功、warning 警告、danger 错误
2. 传入 message 即可,入场自带下滑动画 2. 传入 message 即可,入场自带下滑动画
3. 需要可关闭或带操作按钮时,外层用 h() 组合 button 自行拼装,alert 只负责语义底色
现场演示:apps/docs/demo.html#sec-feedback 现场演示:apps/docs/demo.html#sec-feedback
@@ -429,6 +431,7 @@ spinner(); spinner({ size: 26 })
1. 数据加载时插入 spinner(),加载完成移除 1. 数据加载时插入 spinner(),加载完成移除
2. 用 size 匹配上下文尺寸(按钮内用小号,整页 loading 用大号) 2. 用 size 匹配上下文尺寸(按钮内用小号,整页 loading 用大号)
3. 与 skeleton 分工:已知内容结构用 skeleton 占位,只有『在跑』的短等待用 spinner
现场演示:apps/docs/demo.html#sec-feedback 现场演示:apps/docs/demo.html#sec-feedback
+3 -2
View File
@@ -12,8 +12,9 @@
"style": "node packages/tools/cli/bin/ua.js style", "style": "node packages/tools/cli/bin/ua.js style",
"index": "node packages/tools/cli/bin/ua.js index", "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", "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", "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" "ua": "node packages/tools/cli/bin/ua.js",
"verify": "node packages/tools/cli/bin/ua.js verify"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.6.0" "typescript": "^5.6.0"
+6 -3
View File
@@ -501,7 +501,8 @@
"example": "badge({ label: '成功', variant: 'success' })", "example": "badge({ label: '成功', variant: 'success' })",
"steps": [ "steps": [
"选择语义色:default 中性、success 成功、warning 警告、danger 危险", "选择语义色:default 中性、success 成功、warning 警告、danger 危险",
"传入 label 即可,纯展示组件无状态" "传入 label 即可,纯展示组件无状态",
"状态语义靠 variant 承载,不要自造颜色;纯展示不可交互,需要操作请配 button"
] ]
}, },
{ {
@@ -525,7 +526,8 @@
"example": "alert({ message: '操作可能存在风险', variant: 'warning' })", "example": "alert({ message: '操作可能存在风险', variant: 'warning' })",
"steps": [ "steps": [
"按信息级别选 variant:info 提示、success 成功、warning 警告、danger 错误", "按信息级别选 variant:info 提示、success 成功、warning 警告、danger 错误",
"传入 message 即可,入场自带下滑动画" "传入 message 即可,入场自带下滑动画",
"需要可关闭或带操作按钮时,外层用 h() 组合 button 自行拼装,alert 只负责语义底色"
] ]
}, },
{ {
@@ -641,7 +643,8 @@
"example": "spinner(); spinner({ size: 26 })", "example": "spinner(); spinner({ size: 26 })",
"steps": [ "steps": [
"数据加载时插入 spinner(),加载完成移除", "数据加载时插入 spinner(),加载完成移除",
"用 size 匹配上下文尺寸(按钮内用小号,整页 loading 用大号)" "用 size 匹配上下文尺寸(按钮内用小号,整页 loading 用大号)",
"与 skeleton 分工:已知内容结构用 skeleton 占位,只有『在跑』的短等待用 spinner"
] ]
}, },
{ {
+45 -1
View File
@@ -560,6 +560,47 @@ function cmdStyle() {
console.log(`style OK (${files.length} files)`); 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); const [cmd, ...args] = process.argv.slice(2);
@@ -583,10 +624,13 @@ switch (cmd) {
case 'style': case 'style':
cmdStyle(); cmdStyle();
break; break;
case 'verify':
cmdVerify();
break;
case 'mcp': case 'mcp':
cmdMcp(); cmdMcp();
break; break;
default: default:
console.log('usage: ua <index|list|info <name>|docs|style|mcp>'); console.log('usage: ua <index|list|info <name>|docs|style|verify|mcp>');
process.exit(cmd ? 1 : 0); process.exit(cmd ? 1 : 0);
} }