iter(g65): verify 校验文档 props 落到实现,并补齐漏记字段

This commit is contained in:
HIsheR
2026-10-26 12:32:20 +08:00
parent 9e3d91493b
commit b7824f7e2a
5 changed files with 88 additions and 4 deletions
+24 -1
View File
@@ -1,6 +1,6 @@
{ {
"$schema": "ui-arch-registry-index/0.1", "$schema": "ui-arch-registry-index/0.1",
"generatedAt": "2026-10-26T04:28:43.453Z", "generatedAt": "2026-10-26T04:31:57.320Z",
"repoRoot": ".", "repoRoot": ".",
"packages": [ "packages": [
{ {
@@ -283,6 +283,12 @@
"n": "width", "n": "width",
"t": "number", "t": "number",
"d": "缩略图宽度像素,高度自适应;缺省走样式默认" "d": "缩略图宽度像素,高度自适应;缺省走样式默认"
},
{
"n": "fallback",
"t": "string",
"df": "'图片加载失败'",
"d": "加载失败时缩略图内显示的占位文案;缺省「图片加载失败」"
} }
], ],
"example": "image({ src: '/a.png', alt: '架构图', width: 160 })", "example": "image({ src: '/a.png', alt: '架构图', width: 160 })",
@@ -370,6 +376,12 @@
"n": "suffix", "n": "suffix",
"t": "string | HTMLElement", "t": "string | HTMLElement",
"d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染" "d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染"
},
{
"n": "clearable",
"t": "boolean",
"df": "false",
"d": "开启后有值即显示一键清空按钮;缺省 false"
} }
], ],
"example": "const name = signal(''); const el = textField({ value: name, placeholder: '输入名字…', onInput: (v) => console.log(v) })", "example": "const name = signal(''); const el = textField({ value: name, placeholder: '输入名字…', onInput: (v) => console.log(v) })",
@@ -478,6 +490,11 @@
"n": "suffix", "n": "suffix",
"t": "string | HTMLElement", "t": "string | HTMLElement",
"d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染" "d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染"
},
{
"n": "maxCount",
"t": "number",
"d": "字数上限:同时设原生 maxlength,并在下方附加块显示实时计数;缺省不限"
} }
], ],
"example": "const bio = signal(''); textarea({ value: bio, rows: 4, placeholder: '一句话介绍…' })", "example": "const bio = signal(''); textarea({ value: bio, rows: 4, placeholder: '一句话介绍…' })",
@@ -1400,6 +1417,12 @@
"k": 1, "k": 1,
"d": "翻页回调" "d": "翻页回调"
}, },
{
"n": "jumpable",
"t": "boolean",
"df": "false",
"d": "末尾显示跳页输入框(总页数 > 1 才出现);缺省 false"
},
{ {
"n": "pageSizes", "n": "pageSizes",
"t": "number[]", "t": "number[]",
+1 -1
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` = build + demo + style + typecheck + smoke + index + verify,提交前必过。`ua verify` 校验清单 ↔ 文档 ↔ 演示一致(docs.components 的 id 必须在 api.exports 有对应条目、props 必须带 n/t/d、demo 锚点必须在 `apps/demo/main.ts` 中存在,且该组件必须被 demo 真实调用 `id(` / `id.`),检查皮肤包样式表无硬编码颜色(`#hex` 一律禁止,必须走 `--ua-*` 令牌)与令牌引用有效(`var(--ua-*)` 必须是 tokens.css 已定义或组件自设的变量),核对 README 的 `<!-- ua:components -->` 区块与「分组:」行都覆盖清单组件集合(漏写/多写/重复都报错),并交叉校验 backlog 的 done 条目与 `.ua/iterations.log` 的 done 留痕一一对应。 **门禁一条龙**:`npm run check` = build + demo + style + typecheck + smoke + index + verify,提交前必过。`ua verify` 校验清单 ↔ 文档 ↔ 演示一致(docs.components 的 id 必须在 api.exports 有对应条目、props 必须带 n/t/d、每个文档字段名必须在组件实现源码里真实出现(改名后忘改文档会直接报错)、demo 锚点必须在 `apps/demo/main.ts` 中存在,且该组件必须被 demo 真实调用 `id(` / `id.`),检查皮肤包样式表无硬编码颜色(`#hex` 一律禁止,必须走 `--ua-*` 令牌)与令牌引用有效(`var(--ua-*)` 必须是 tokens.css 已定义或组件自设的变量),核对 README 的 `<!-- ua:components -->` 区块与「分组:」行都覆盖清单组件集合(漏写/多写/重复都报错),并交叉校验 backlog 的 done 条目与 `.ua/iterations.log` 的 done 留痕一一对应。
**变更日志**:`CHANGELOG.md` 由 git 提交史生成(`npm run changelog`),**不进 `npm run check`**——它包含每个提交自身,提交后必然与历史差一条,只能作为「发版前刷新」步骤;`npm run changelog:check` 用于确认是否漂移。 **变更日志**:`CHANGELOG.md` 由 git 提交史生成(`npm run changelog`),**不进 `npm run check`**——它包含每个提交自身,提交后必然与历史差一条,只能作为「发版前刷新」步骤;`npm run changelog:check` 用于确认是否漂移。
+5 -1
View File
@@ -1,6 +1,6 @@
# @ui-arch 组件库 Agent 文档(无图形版) # @ui-arch 组件库 Agent 文档(无图形版)
> 自动生成于 2026-10-26T04:28:43.453Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 自动生成于 2026-10-26T04:31:57.320Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
## 快速上手 ## 快速上手
@@ -58,6 +58,7 @@
- src(必填):string —— 图片地址(缩略图与大图共用) - src(必填):string —— 图片地址(缩略图与大图共用)
- alt(默认 ''):string —— 替代文本;缺省空串 - alt(默认 ''):string —— 替代文本;缺省空串
- width:number —— 缩略图宽度像素,高度自适应;缺省走样式默认 - width:number —— 缩略图宽度像素,高度自适应;缺省走样式默认
- fallback(默认 '图片加载失败'):string —— 加载失败时缩略图内显示的占位文案;缺省「图片加载失败」
示例: 示例:
@@ -109,6 +110,7 @@ button({ label: '提交', variant: 'primary', onClick: () => console.log('clicke
- onInput(回调):(value: string) => void —— 输入回调,每次按键触发 - onInput(回调):(value: string) => void —— 输入回调,每次按键触发
- prefix:string | HTMLElement —— 前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染 - prefix:string | HTMLElement —— 前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染
- suffix:string | HTMLElement —— 后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染 - suffix:string | HTMLElement —— 后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染
- clearable(默认 false):boolean —— 开启后有值即显示一键清空按钮;缺省 false
示例: 示例:
@@ -164,6 +166,7 @@ const qty = signal(1); const el = inputNumber({ value: qty, min: 1, max: 99, onC
- onInput(回调):(value: string) => void —— 输入回调 - onInput(回调):(value: string) => void —— 输入回调
- prefix:string | HTMLElement —— 前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染 - prefix:string | HTMLElement —— 前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染
- suffix:string | HTMLElement —— 后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染 - suffix:string | HTMLElement —— 后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染
- maxCount:number —— 字数上限:同时设原生 maxlength,并在下方附加块显示实时计数;缺省不限
示例: 示例:
@@ -773,6 +776,7 @@ divider({ label: '更多信息' }) / divider({ vertical: true })
- pageSize(必填):number —— 每页条数 - pageSize(必填):number —— 每页条数
- total(必填):number —— 总条数 - total(必填):number —— 总条数
- onChange(回调):(page: number) => void —— 翻页回调 - onChange(回调):(page: number) => void —— 翻页回调
- jumpable(默认 false):boolean —— 末尾显示跳页输入框(总页数 > 1 才出现);缺省 false
- pageSizes:number[] —— 每页条数候选(如 [10, 20, 50]);传了才渲染条数切换器 - pageSizes:number[] —— 每页条数候选(如 [10, 20, 50]);传了才渲染条数切换器
- onPageSizeChange(回调):(size: number) => void —— 切换每页条数回调,同时自动回到第 1 页 - onPageSizeChange(回调):(size: number) => void —— 切换每页条数回调,同时自动回到第 1 页
+23
View File
@@ -154,6 +154,12 @@
"n": "width", "n": "width",
"t": "number", "t": "number",
"d": "缩略图宽度像素,高度自适应;缺省走样式默认" "d": "缩略图宽度像素,高度自适应;缺省走样式默认"
},
{
"n": "fallback",
"t": "string",
"df": "'图片加载失败'",
"d": "加载失败时缩略图内显示的占位文案;缺省「图片加载失败」"
} }
], ],
"example": "image({ src: '/a.png', alt: '架构图', width: 160 })", "example": "image({ src: '/a.png', alt: '架构图', width: 160 })",
@@ -241,6 +247,12 @@
"n": "suffix", "n": "suffix",
"t": "string | HTMLElement", "t": "string | HTMLElement",
"d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染" "d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染"
},
{
"n": "clearable",
"t": "boolean",
"df": "false",
"d": "开启后有值即显示一键清空按钮;缺省 false"
} }
], ],
"example": "const name = signal(''); const el = textField({ value: name, placeholder: '输入名字…', onInput: (v) => console.log(v) })", "example": "const name = signal(''); const el = textField({ value: name, placeholder: '输入名字…', onInput: (v) => console.log(v) })",
@@ -349,6 +361,11 @@
"n": "suffix", "n": "suffix",
"t": "string | HTMLElement", "t": "string | HTMLElement",
"d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染" "d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染"
},
{
"n": "maxCount",
"t": "number",
"d": "字数上限:同时设原生 maxlength,并在下方附加块显示实时计数;缺省不限"
} }
], ],
"example": "const bio = signal(''); textarea({ value: bio, rows: 4, placeholder: '一句话介绍…' })", "example": "const bio = signal(''); textarea({ value: bio, rows: 4, placeholder: '一句话介绍…' })",
@@ -1271,6 +1288,12 @@
"k": 1, "k": 1,
"d": "翻页回调" "d": "翻页回调"
}, },
{
"n": "jumpable",
"t": "boolean",
"df": "false",
"d": "末尾显示跳页输入框(总页数 > 1 才出现);缺省 false"
},
{ {
"n": "pageSizes", "n": "pageSizes",
"t": "number[]", "t": "number[]",
+35 -1
View File
@@ -850,6 +850,39 @@ function checkDemoBundle(anchors) {
return issues; return issues;
} }
/*** 文档字段漂移校验:props 名必须在实现源码里真实出现(改了参数名忘了改文档就会命中) ***/
function checkPropsImpl(pkg, comps) {
const issues = [];
const srcDir = path.join(ROOT, pkg.dir, 'src'); // 实现源码目录
if (!fs.existsSync(srcDir)) return issues;
const sources = new Map(); // 文件名 → 源码文本
for (const name of fs.readdirSync(srcDir)) {
if (name.endsWith('.ts')) sources.set(name, fs.readFileSync(path.join(srcDir, name), 'utf8'));
}
const findImpl = (id) => {
const own = sources.get(`${id}.ts`);
if (own !== undefined) return own; // 同名文件优先
const decl = new RegExp(`export function ${id}\\(`); // 兜底:按导出函数名反查文件
for (const text of sources.values()) if (decl.test(text)) return text;
return null;
};
for (const c of comps) {
if (!c.id) continue; // 缺 id 由主循环报,这里跳过
const tag = `${pkg.name}#${c.id}`;
const impl = findImpl(c.id);
if (impl === null) {
issues.push(`${tag}: 在 ${pkg.dir}/src 下找不到实现(既无同名文件,也没有 export function ${c.id})`);
continue;
}
for (const p of c.props ?? []) {
const name = String(p.n ?? '');
if (!/^[A-Za-z_$][\w$]*$/.test(name)) continue; // 说明性条目(...children、带括号注记)不参与
if (!impl.includes(name)) issues.push(`${tag}: 文档声明了 props「${name}」,实现里找不到该名字(改名后文档未同步)`);
}
}
return issues;
}
/*** 清单一致性校验:组件文档块的 id / props 说明 / demo 锚点必须与代码和演示产物对齐 ***/ /*** 清单一致性校验:组件文档块的 id / props 说明 / demo 锚点必须与代码和演示产物对齐 ***/
function cmdVerify() { function cmdVerify() {
const issues = []; const issues = [];
@@ -867,6 +900,7 @@ function cmdVerify() {
if (pkg.type === 'skin-dom') { if (pkg.type === 'skin-dom') {
issues.push(...checkSkinCss(pkg)); issues.push(...checkSkinCss(pkg));
issues.push(...checkTokenRefs(pkg, tokenVars)); issues.push(...checkTokenRefs(pkg, tokenVars));
issues.push(...checkPropsImpl(pkg, comps));
} }
if (comps.length && !exports.length) issues.push(`${pkg.name}: docs.components 有 ${comps.length} 项但 api.exports 为空`); if (comps.length && !exports.length) issues.push(`${pkg.name}: docs.components 有 ${comps.length} 项但 api.exports 为空`);
@@ -906,7 +940,7 @@ function cmdVerify() {
console.error(`${issues.length} violation(s)`); console.error(`${issues.length} violation(s)`);
process.exit(1); process.exit(1);
} }
console.log('verify OK (manifest ↔ docs ↔ demo 一致,皮肤无硬编码颜色与未定义令牌,README 组件清单与分组同步,backlog ↔ 留痕一致)'); console.log('verify OK (manifest ↔ docs ↔ demo 一致,文档 props 均能落到实现,皮肤无硬编码颜色与未定义令牌,README 组件清单与分组同步,backlog ↔ 留痕一致)');
} }
/*** 校验 git 提交信息是否符合 COMMITTING.md 规范(参数为 message 文件路径或直接 message 文本),违规退出码 1 ***/ /*** 校验 git 提交信息是否符合 COMMITTING.md 规范(参数为 message 文件路径或直接 message 文本),违规退出码 1 ***/