From 003dae302cce4d62e32cbf7a1b2b85cc2e74a96d Mon Sep 17 00:00:00 2001 From: HIsheR Date: Mon, 26 Oct 2026 23:18:19 +0800 Subject: [PATCH] =?UTF-8?q?iter(g288):=20=E6=96=87=E6=A1=A3=E6=AD=A5?= =?UTF-8?q?=E9=AA=A4=E4=B8=8B=E9=99=90=E6=8F=90=E5=88=B0=204=20=E6=9D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .ua/index.json | 14 +++++++++----- AGENTS.md | 4 ++-- llms-full.txt | 6 +++++- packages/skin/ui/ua.manifest.json | 12 ++++++++---- packages/tools/cli/bin/ua.js | 2 +- 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/.ua/index.json b/.ua/index.json index 0c21f44..275fff3 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-26T15:17:13.022Z", + "generatedAt": "2026-10-26T15:18:04.340Z", "repoRoot": ".", "packages": [ { @@ -1051,7 +1051,8 @@ "steps": [ "纯状态文案直接传 label,配 variant 定语义色", "要显示数量传 count(如未读数),超过 max(缺省 99)自动渲染成 99+", - "只提示「有更新」用 dot: true,配合 label 给出读屏名(如「有未读消息」)" + "只提示「有更新」用 dot: true,配合 label 给出读屏名(如「有未读消息」)", + "不要给 badge 挂点击:它不是按钮(无 tabindex 也无键盘响应);「点角标进消息中心」应当把 badge 塞进 button 里,让按钮承担语义" ] }, { @@ -1658,7 +1659,8 @@ "steps": [ "在区块之间插入 divider 划分内容层次", "传 label 在水平线上显示居中文案,如「更多信息」", - "工具栏内用 vertical: true 的行内竖线,dashed: true 切虚线弱化分隔感" + "工具栏内用 vertical: true 的行内竖线,dashed: true 切虚线弱化分隔感", + "分割线是纯视觉件:没有 role 也没有焦点,读屏不会播报它。真要让读屏理解分组,用 heading 或 section,别指望横线" ] }, { @@ -4039,7 +4041,8 @@ "steps": [ "传字符串展示单键帽:kbd({ keys: \"Esc\" }),适合文档里说明单个按键", "传数组展示组合键:kbd({ keys: [\"Ctrl\", \"K\"] }),键帽之间以弱化的 + 连接(+ 对读屏隐藏)", - "常与提示文案混排:行内 baseline 对齐,键帽底边加厚一线出立体感" + "常与提示文案混排:行内 baseline 对齐,键帽底边加厚一线出立体感", + "组件只画键帽,不做平台映射:Ctrl 与 ⌘ 的替换由调用方按平台判断后传进来(写死 Ctrl 在 Mac 上就是错的)" ] }, { @@ -4064,7 +4067,8 @@ "steps": [ "传 text + keyword 即可,命中片段以淡黄 mark 底展示,适合搜索结果 / 补全列表的匹配说明", "两个入参都接受信号:搜索框输入实时联动高亮,无需手动重渲染", - "大小写不敏感且全部命中(非只第一处);关键词两侧空白自动忽略" + "大小写不敏感且全部命中(非只第一处);关键词两侧空白自动忽略", + "keyword 为空或纯空白时原样返回文本,既不抛错也不全高亮:搜索框清空那一刻不用特判" ] }, { diff --git a/AGENTS.md b/AGENTS.md index 3b72cb4..a82d55b 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 + demo + style + typecheck + smoke + index + verify + lint-iterations + audit:visual + audit:contrast + audit:motion,提交前必过。其中 `smoke` 是三段断言:`@ui-arch/frame` 14 条(信号读写与同值短路、effect 注销与条件分支重追踪、computed 链式派生、多订阅者扇出)+ `@ui-arch/table-core` 31 条(排序 / 筛选 / 分页 / 选择的派生管线 + setRows 换源与失效行剔除 + 越界页码回写)+ `@ui-arch/ui` 8 条(皮肤层纯逻辑:行级差异的剪前后缀 / LCS 对齐 / 忽略空白 / 超限退化,跑的是 dist/diffKit.js);改内核语义或纯逻辑前先跑它,别靠组件 demo 肉眼验。`ua audit:visual` 统计皮肤包里十四类硬编码视觉字面量(字号 / 动效时长 / 阴影 / 圆角 / 焦点环 / 内距 / 控件高度 / 字重 / 行高 / 层级 / 边框色 / 混色浓度 / transform 位移 / 焦点描边)并与 `.ua/visual-baseline.json` 做棘轮:任一类超过基线即退出码 1 并指出疑似引入的提交;迁移脚本 `npm run visual:migrate` 可把字面量幂等改写成令牌。`ua audit:contrast` 走另一条线:把令牌里 18 组「前景色 × 底色」配对(正文/次级/辅助文字 × 页面底/表面/凹陷底、强调色与其反白、语义三色、禁用态豁免档、焦点描边 × 页面底/表面)按 WCAG 公式算成对比度(支持 `color-mix` 与 `var()` 求值,半透明底先压到页面底色上),亮 / 暗 × 常规 / 高对比四套主题各算一遍,低于阈值(正文 7 / 常规 4.5 / 禁用 2.6 / 非文本 3)即退出码 1;`--json` 出逐对明细,`--list` 只列配对表。改任一颜色令牌后它会立刻告诉你「改完谁读不清了」,不必等人工目测暗色。`ua audit:motion` 守的是动效面,两类必查:① 带 `infinite` 的动画是否在 `prefers-reduced-motion: reduce` 块里被 `animation: none` 关掉——循环动画是前庭敏感用户的硬伤,漏一条整页就在抖(它抓出过转圈与不确定态进度两处漏网);② 悬停 / 按压的位移(`translate`)是否在减少动效下归零——位移最容易诱发前庭不适,且只提供手感、不表达状态(一条 `ua-` 类通配的 `transform: none !important` 兜住全站 14 处,新增组件不用各自补)。③ 旋转 / 缩放这类第三类空间动效(折叠箭头、按压缩放)是否有「只留颜色过渡」的收窄兜底——空间变换即时到位、状态仍看得见(一条通配 `transition-property` 收窄兜住全站 35 处)。`ua verify` 校验清单 ↔ 文档 ↔ 演示一致(docs.components 的 id 必须在 api.exports 有对应条目、props 必须带 n/t/d、每个文档字段名必须在组件实现源码里真实出现(改名后忘改文档会直接报错)、反向也校验 `src/types.ts` 里 `<组件>Props`(可用清单的 `propsType` 覆盖接口名,`false` 表示无入参)每个字段都在文档 props 表登记(新加字段忘了写文档会报错)、demo 锚点必须在 `apps/demo/main.ts` 中存在,且该组件必须被 demo 真实调用 `id(` / `id.`),反向也查孤儿演示(demo 里用 section() 定义的每个区块都必须被某个组件的 demo 字段认领,加了区块忘登记会直接报错),检查皮肤包样式表无硬编码颜色(`#hex` 一律禁止,必须走 `--ua-*` 令牌)与令牌引用有效(`var(--ua-*)` 必须是 tokens.css 已定义或组件自设的变量)、核对每个 `--ua-color-*` 都在 `[data-theme='dark']` 里有覆盖值且暗色块没有凭空多出的变量名(新增颜色令牌漏写暗色值直接报错)、核对 `tokens.json` 的 `color` 键与 tokens.css 的 `--ua-color-*` 同名同值(名字按 camelCase → kebab 折算,亮色值与暗色值都比,双向查漏——只改一处时文档色板会凭空多/少一个令牌,过去只能靠人眼发现)、核对 `@keyframes` 没有「帧体完全相同但名字不同」的复制粘贴(同一段动效只该有一份声明,改名复制会让方向 / 时长各写各的,翻转态就出现过「朝反方向入场」),核对 README 的 `` 区块与「分组:」行都覆盖清单组件集合(漏写/多写/重复都报错),并交叉校验 backlog 的 done 条目与 `.ua/iterations.log` 的 done 留痕一一对应(另外查 backlog id 不重复、g 编号严格递增、同一 id 只有一条 done 留痕,且留痕 ts 带 +08:00、时间线不回退、不落在未来——都是过去手工插条目/手写时间戳踩过的坑);配方还要求编号连续、每条都有代码块与要点,且代码块里至少认出一个已注册组件 id(拼错组件名时 `ua recipes` 会把它列成空组件表)。 +**门禁一条龙**:`npm run check` = build + demo + style + typecheck + smoke + index + verify + lint-iterations + audit:visual + audit:contrast + audit:motion,提交前必过。其中 `smoke` 是三段断言:`@ui-arch/frame` 14 条(信号读写与同值短路、effect 注销与条件分支重追踪、computed 链式派生、多订阅者扇出)+ `@ui-arch/table-core` 31 条(排序 / 筛选 / 分页 / 选择的派生管线 + setRows 换源与失效行剔除 + 越界页码回写)+ `@ui-arch/ui` 8 条(皮肤层纯逻辑:行级差异的剪前后缀 / LCS 对齐 / 忽略空白 / 超限退化,跑的是 dist/diffKit.js);改内核语义或纯逻辑前先跑它,别靠组件 demo 肉眼验。`ua audit:visual` 统计皮肤包里十四类硬编码视觉字面量(字号 / 动效时长 / 阴影 / 圆角 / 焦点环 / 内距 / 控件高度 / 字重 / 行高 / 层级 / 边框色 / 混色浓度 / transform 位移 / 焦点描边)并与 `.ua/visual-baseline.json` 做棘轮:任一类超过基线即退出码 1 并指出疑似引入的提交;迁移脚本 `npm run visual:migrate` 可把字面量幂等改写成令牌。`ua audit:contrast` 走另一条线:把令牌里 18 组「前景色 × 底色」配对(正文/次级/辅助文字 × 页面底/表面/凹陷底、强调色与其反白、语义三色、禁用态豁免档、焦点描边 × 页面底/表面)按 WCAG 公式算成对比度(支持 `color-mix` 与 `var()` 求值,半透明底先压到页面底色上),亮 / 暗 × 常规 / 高对比四套主题各算一遍,低于阈值(正文 7 / 常规 4.5 / 禁用 2.6 / 非文本 3)即退出码 1;`--json` 出逐对明细,`--list` 只列配对表。改任一颜色令牌后它会立刻告诉你「改完谁读不清了」,不必等人工目测暗色。`ua audit:motion` 守的是动效面,两类必查:① 带 `infinite` 的动画是否在 `prefers-reduced-motion: reduce` 块里被 `animation: none` 关掉——循环动画是前庭敏感用户的硬伤,漏一条整页就在抖(它抓出过转圈与不确定态进度两处漏网);② 悬停 / 按压的位移(`translate`)是否在减少动效下归零——位移最容易诱发前庭不适,且只提供手感、不表达状态(一条 `ua-` 类通配的 `transform: none !important` 兜住全站 14 处,新增组件不用各自补)。③ 旋转 / 缩放这类第三类空间动效(折叠箭头、按压缩放)是否有「只留颜色过渡」的收窄兜底——空间变换即时到位、状态仍看得见(一条通配 `transition-property` 收窄兜住全站 35 处)。`ua verify` 校验清单 ↔ 文档 ↔ 演示一致(docs.components 的 id 必须在 api.exports 有对应条目、props 必须带 n/t/d、steps 至少 4 条(少于 4 条说明这组件还没想清楚边界)、每个文档字段名必须在组件实现源码里真实出现(改名后忘改文档会直接报错)、反向也校验 `src/types.ts` 里 `<组件>Props`(可用清单的 `propsType` 覆盖接口名,`false` 表示无入参)每个字段都在文档 props 表登记(新加字段忘了写文档会报错)、demo 锚点必须在 `apps/demo/main.ts` 中存在,且该组件必须被 demo 真实调用 `id(` / `id.`),反向也查孤儿演示(demo 里用 section() 定义的每个区块都必须被某个组件的 demo 字段认领,加了区块忘登记会直接报错),检查皮肤包样式表无硬编码颜色(`#hex` 一律禁止,必须走 `--ua-*` 令牌)与令牌引用有效(`var(--ua-*)` 必须是 tokens.css 已定义或组件自设的变量)、核对每个 `--ua-color-*` 都在 `[data-theme='dark']` 里有覆盖值且暗色块没有凭空多出的变量名(新增颜色令牌漏写暗色值直接报错)、核对 `tokens.json` 的 `color` 键与 tokens.css 的 `--ua-color-*` 同名同值(名字按 camelCase → kebab 折算,亮色值与暗色值都比,双向查漏——只改一处时文档色板会凭空多/少一个令牌,过去只能靠人眼发现)、核对 `@keyframes` 没有「帧体完全相同但名字不同」的复制粘贴(同一段动效只该有一份声明,改名复制会让方向 / 时长各写各的,翻转态就出现过「朝反方向入场」),核对 README 的 `` 区块与「分组:」行都覆盖清单组件集合(漏写/多写/重复都报错),并交叉校验 backlog 的 done 条目与 `.ua/iterations.log` 的 done 留痕一一对应(另外查 backlog id 不重复、g 编号严格递增、同一 id 只有一条 done 留痕,且留痕 ts 带 +08:00、时间线不回退、不落在未来——都是过去手工插条目/手写时间戳踩过的坑);配方还要求编号连续、每条都有代码块与要点,且代码块里至少认出一个已注册组件 id(拼错组件名时 `ua recipes` 会把它列成空组件表)。 `ua verify` 另外校验 `ITERATION.md` 的《视觉回归抽查清单》:button / textField / select / modal / dataTable / toast / layout / jsonViewer / diffView / codeBlock / signaturePad / heatmap / sparkline 十三段各 ≥3 条可勾选要点、每条维度标签必须落在边框 / 阴影 / 过渡 / 焦点环 / 暗色五类内、合计 ≥39 条(下限由段数 × 3 推导,加抽查位不必手改)且五维度全覆盖——视觉类条目收尾前照单过一遍,改清单时别把条目删漏。 @@ -76,7 +76,7 @@ | 3 | `packages/skin/ui/src/index.ts` | 末尾追加 `export { xxx } from './xxx.js';` | | 4 | `packages/skin/ui/ui.css` | 样式只消费 `--ua-*`(禁 `#hex`),动效要带 `prefers-reduced-motion` 兜底;**禁止新增硬编码 `box-shadow` / 动效时长 / `font-size` 字面值**,一律用 elevation(`--ua-shadow-1..4`)/ motion(`--ua-duration-*` + `--ua-ease`)/ 字号(`--ua-font-size-*`)令牌,焦点环用 `--ua-focus-ring`,`npm run audit:visual` 会让计数上涨的提交直接红 | | 5 | `packages/skin/ui/ua.manifest.json` → `api.exports` | 一行 `xxx(props):一句话说明` | -| 6 | 同上 → `docs.components` | `{ id, title, demo, props[], example, steps[] }`:props 里每个字段名必须在实现源码里真实出现,且 `<组件>Props` 的字段一个都不能漏(verify 双向校验) | +| 6 | 同上 → `docs.components` | `{ id, title, demo, props[], example, steps[] }`:props 里每个字段名必须在实现源码里真实出现,且 `<组件>Props` 的字段一个都不能漏(verify 双向校验);`steps` 至少 4 条(怎么传 / 怎么联动 / 边界行为 / 什么时候别用它) | | 7 | `apps/demo/main.ts` | 新增 `section('', ...)` 并**真实调用**该组件(verify 会查) | | 8 | `README.md` | `` 区块补 id、「分组:」行补归组(数字交给下一步自动同步) | | 9 | `npm run sync-counts` | 自动把三份文档里的「N 个组件 / 共 N 个」改成清单实际数量(幂等;漏改时 verify 报行号) | diff --git a/llms-full.txt b/llms-full.txt index 1538e8f..2fdaa86 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-26T15:17:13.022Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-26T15:18:04.340Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -471,6 +471,7 @@ badge({ label: '成功', variant: 'success' }) 1. 纯状态文案直接传 label,配 variant 定语义色 2. 要显示数量传 count(如未读数),超过 max(缺省 99)自动渲染成 99+ 3. 只提示「有更新」用 dot: true,配合 label 给出读屏名(如「有未读消息」) +4. 不要给 badge 挂点击:它不是按钮(无 tabindex 也无键盘响应);「点角标进消息中心」应当把 badge 塞进 button 里,让按钮承担语义 现场演示:apps/docs/demo.html#sec-feedback @@ -833,6 +834,7 @@ divider({ label: '更多信息' }) / divider({ vertical: true }) 1. 在区块之间插入 divider 划分内容层次 2. 传 label 在水平线上显示居中文案,如「更多信息」 3. 工具栏内用 vertical: true 的行内竖线,dashed: true 切虚线弱化分隔感 +4. 分割线是纯视觉件:没有 role 也没有焦点,读屏不会播报它。真要让读屏理解分组,用 heading 或 section,别指望横线 现场演示:apps/docs/demo.html#sec-divider @@ -2127,6 +2129,7 @@ kbd({ keys: 'Esc' })、kbd({ keys: ['Ctrl', 'K'] }) 1. 传字符串展示单键帽:kbd({ keys: "Esc" }),适合文档里说明单个按键 2. 传数组展示组合键:kbd({ keys: ["Ctrl", "K"] }),键帽之间以弱化的 + 连接(+ 对读屏隐藏) 3. 常与提示文案混排:行内 baseline 对齐,键帽底边加厚一线出立体感 +4. 组件只画键帽,不做平台映射:Ctrl 与 ⌘ 的替换由调用方按平台判断后传进来(写死 Ctrl 在 Mac 上就是错的) 现场演示:apps/docs/demo.html#sec-kbd @@ -2148,6 +2151,7 @@ highlight({ text: 'Alpha Beta', keyword: 'beta' }) 1. 传 text + keyword 即可,命中片段以淡黄 mark 底展示,适合搜索结果 / 补全列表的匹配说明 2. 两个入参都接受信号:搜索框输入实时联动高亮,无需手动重渲染 3. 大小写不敏感且全部命中(非只第一处);关键词两侧空白自动忽略 +4. keyword 为空或纯空白时原样返回文本,既不抛错也不全高亮:搜索框清空那一刻不用特判 现场演示:apps/docs/demo.html#sec-highlight diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index a6ef27d..e5e7d89 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -922,7 +922,8 @@ "steps": [ "纯状态文案直接传 label,配 variant 定语义色", "要显示数量传 count(如未读数),超过 max(缺省 99)自动渲染成 99+", - "只提示「有更新」用 dot: true,配合 label 给出读屏名(如「有未读消息」)" + "只提示「有更新」用 dot: true,配合 label 给出读屏名(如「有未读消息」)", + "不要给 badge 挂点击:它不是按钮(无 tabindex 也无键盘响应);「点角标进消息中心」应当把 badge 塞进 button 里,让按钮承担语义" ] }, { @@ -1529,7 +1530,8 @@ "steps": [ "在区块之间插入 divider 划分内容层次", "传 label 在水平线上显示居中文案,如「更多信息」", - "工具栏内用 vertical: true 的行内竖线,dashed: true 切虚线弱化分隔感" + "工具栏内用 vertical: true 的行内竖线,dashed: true 切虚线弱化分隔感", + "分割线是纯视觉件:没有 role 也没有焦点,读屏不会播报它。真要让读屏理解分组,用 heading 或 section,别指望横线" ] }, { @@ -3910,7 +3912,8 @@ "steps": [ "传字符串展示单键帽:kbd({ keys: \"Esc\" }),适合文档里说明单个按键", "传数组展示组合键:kbd({ keys: [\"Ctrl\", \"K\"] }),键帽之间以弱化的 + 连接(+ 对读屏隐藏)", - "常与提示文案混排:行内 baseline 对齐,键帽底边加厚一线出立体感" + "常与提示文案混排:行内 baseline 对齐,键帽底边加厚一线出立体感", + "组件只画键帽,不做平台映射:Ctrl 与 ⌘ 的替换由调用方按平台判断后传进来(写死 Ctrl 在 Mac 上就是错的)" ] }, { @@ -3935,7 +3938,8 @@ "steps": [ "传 text + keyword 即可,命中片段以淡黄 mark 底展示,适合搜索结果 / 补全列表的匹配说明", "两个入参都接受信号:搜索框输入实时联动高亮,无需手动重渲染", - "大小写不敏感且全部命中(非只第一处);关键词两侧空白自动忽略" + "大小写不敏感且全部命中(非只第一处);关键词两侧空白自动忽略", + "keyword 为空或纯空白时原样返回文本,既不抛错也不全高亮:搜索框清空那一刻不用特判" ] }, { diff --git a/packages/tools/cli/bin/ua.js b/packages/tools/cli/bin/ua.js index 7e30248..ad58947 100644 --- a/packages/tools/cli/bin/ua.js +++ b/packages/tools/cli/bin/ua.js @@ -1591,7 +1591,7 @@ function cmdVerify() { 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 ((c.steps ?? []).length < 4) issues.push(`${tag}: steps 少于 4 条(每件至少给 4 步:怎么传 / 怎么联动 / 边界行为 / 什么时候别用它)`); if (!exports.some((e) => e.startsWith(`${c.id}(`) || e.startsWith(`${c.id}.`))) { issues.push(`${tag}: api.exports 中没有以「${c.id}(」或「${c.id}.」开头的条目`); }