From 6e52cc62068518c0023b7b603a83b15b954c100b Mon Sep 17 00:00:00 2001 From: HIsheR Date: Mon, 26 Oct 2026 21:58:58 +0800 Subject: [PATCH] =?UTF-8?q?iter(g245):=20=E8=A1=A5=E9=AB=98=E5=AF=B9?= =?UTF-8?q?=E6=AF=94=E5=81=8F=E5=A5=BD=E4=BB=A4=E7=89=8C=EF=BC=8C=E5=AF=B9?= =?UTF-8?q?=E6=AF=94=E5=BA=A6=E9=97=A8=E7=A6=81=E6=89=A9=E5=88=B0=E5=9B=9B?= =?UTF-8?q?=E5=A5=97=E4=B8=BB=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .ua/index.json | 6 +++--- AGENTS.md | 2 +- README.md | 1 + llms-full.txt | 6 +++--- llms.txt | 2 +- packages/design/tokens/tokens.css | 18 ++++++++++++++++++ packages/tools/cli/bin/ua.js | 3 ++- packages/tools/cli/lib/contrast-audit.mjs | 20 +++++++++++++++----- packages/tools/cli/ua.manifest.json | 4 ++-- 9 files changed, 46 insertions(+), 16 deletions(-) diff --git a/.ua/index.json b/.ua/index.json index b2964e1..5558413 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-26T13:57:11.667Z", + "generatedAt": "2026-10-26T13:58:42.557Z", "repoRoot": ".", "packages": [ { @@ -4119,7 +4119,7 @@ "ua verify:清单一致性门禁(id 唯一 / exports 对应 / props 必带 n t d / steps≥3 / demo 锚点存在 / 视觉回归抽查清单六组件各 ≥3 条且五维度齐全)", "ua commit-lint <文件|message>:提交信息门禁(type 枚举 / scope 规则 / subject 中文祈使 ≤50 字符),规范见 COMMITTING.md,违规退出码 1", "ua audit:visual [--json|--baseline]:视觉漂移门禁,统计皮肤包里各类硬编码视觉字面量(字号/动效时长/阴影/圆角/焦点环/内距/控件高度/字重/行高/层级/边框色)并与 .ua/visual-baseline.json 棘轮比对,超基线退出码 1;--baseline 重写基线,--json 出机器可读计数与每类命中行号(line / prop / value / hint,Agent 可直接定位)", - "ua audit:contrast [--json|--list]:对比度门禁,把令牌里 16 组「文字色 × 底色」配对按 WCAG 公式算成对比度(求值支持 var() 与 color-mix,半透明底先压到页面底色),亮暗分算,低于阈值(正文 7 / 常规 4.5 / 禁用 2.6)退出码 1;--json 出逐对明细,--list 只列配对表", + "ua audit:contrast [--json|--list]:对比度门禁,把令牌里 16 组「文字色 × 底色」配对按 WCAG 公式算成对比度(求值支持 var() 与 color-mix,半透明底先压到页面底色),亮/暗 × 常规/高对比四套分算,低于阈值(正文 7 / 常规 4.5 / 禁用 2.6)退出码 1;--json 出逐对明细,--list 只列配对表", "ua mcp:零依赖 stdio MCP server,暴露 list_components / get_component / search_components / get_tokens / list_recipes / get_recipe" ], "keyTypes": [ @@ -4142,7 +4142,7 @@ "查令牌:ua tokens 打印全部 --ua-* 变量(颜色带暗色值),--json 输出结构化数组;写样式前先查,禁止硬编码。令牌分十三组:颜色 / 混色浓度 / 圆角 / 间距 / 字号 / 字重行高 / 边框 / 阴影 / 动效 / 位移 / 焦点环 / 控件高度 / 层级,color-mix 混入浓度用 --ua-alpha-soft/tint/line/mid/strong/heavy,弹层阴影用 --ua-shadow-1..4,过渡用 --ua-duration-* + --ua-ease,transform 位移用 --ua-translate-*(负号 calc(-1 * var(...))),焦点环用 --ua-focus-ring(_inset)", "改完清单跑 npm run docs:同时刷新人类文档站与 llms-full.txt(Agent 首选喂料)", "改完样式跑 npm run audit:visual:各类硬编码计数不得高于 .ua/visual-baseline.json(棘轮只减不增);字面量可用 npm run visual:migrate 幂等改写成令牌", - "改完颜色令牌跑 npm run audit:contrast:16 组文字/底色配对在亮暗两套主题下都必须达标(正文 7 / 常规 4.5 / 禁用 2.6),跌破即退出码 1——调一个色值就能让全站辅助文字掉到 4.4,人眼看不出但门禁看得出", + "改完颜色令牌跑 npm run audit:contrast:16 组文字/底色配对在亮/暗 × 常规/高对比四套主题下都必须达标(正文 7 / 常规 4.5 / 禁用 2.6),跌破即退出码 1——调一个色值就能让全站辅助文字掉到 4.4,人眼看不出但门禁看得出", "在 ~/.workbuddy/mcp.json 注册 ua mcp 后,Agent 可直接调 list_components / get_component / search_components / get_tokens / list_recipes / get_recipe" ], "components": [] diff --git a/AGENTS.md b/AGENTS.md index 87bf7c5..4d80d86 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,提交前必过。其中 `smoke` 是两段内核断言:`@ui-arch/frame` 14 条(信号读写与同值短路、effect 注销与条件分支重追踪、computed 链式派生、多订阅者扇出)+ `@ui-arch/table-core` 21 条(排序 / 筛选 / 分页 / 选择的派生管线);改内核语义前先跑它,别靠组件 demo 肉眼验。`ua audit:visual` 统计皮肤包里十四类硬编码视觉字面量(字号 / 动效时长 / 阴影 / 圆角 / 焦点环 / 内距 / 控件高度 / 字重 / 行高 / 层级 / 边框色 / 混色浓度 / transform 位移 / 焦点描边)并与 `.ua/visual-baseline.json` 做棘轮:任一类超过基线即退出码 1 并指出疑似引入的提交;迁移脚本 `npm run visual:migrate` 可把字面量幂等改写成令牌。`ua audit:contrast` 走另一条线:把令牌里 16 组「文字色 × 底色」配对(正文/次级/辅助文字 × 页面底/表面/凹陷底、强调色与其反白、语义三色、禁用态豁免档)按 WCAG 公式算成对比度(支持 `color-mix` 与 `var()` 求值,半透明底先压到页面底色上),亮暗两套主题各算一遍,低于阈值(正文 7 / 常规 4.5 / 禁用 2.6)即退出码 1;`--json` 出逐对明细,`--list` 只列配对表。改任一颜色令牌后它会立刻告诉你「改完谁读不清了」,不必等人工目测暗色。`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.`),检查皮肤包样式表无硬编码颜色(`#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,提交前必过。其中 `smoke` 是两段内核断言:`@ui-arch/frame` 14 条(信号读写与同值短路、effect 注销与条件分支重追踪、computed 链式派生、多订阅者扇出)+ `@ui-arch/table-core` 21 条(排序 / 筛选 / 分页 / 选择的派生管线);改内核语义前先跑它,别靠组件 demo 肉眼验。`ua audit:visual` 统计皮肤包里十四类硬编码视觉字面量(字号 / 动效时长 / 阴影 / 圆角 / 焦点环 / 内距 / 控件高度 / 字重 / 行高 / 层级 / 边框色 / 混色浓度 / transform 位移 / 焦点描边)并与 `.ua/visual-baseline.json` 做棘轮:任一类超过基线即退出码 1 并指出疑似引入的提交;迁移脚本 `npm run visual:migrate` 可把字面量幂等改写成令牌。`ua audit:contrast` 走另一条线:把令牌里 16 组「文字色 × 底色」配对(正文/次级/辅助文字 × 页面底/表面/凹陷底、强调色与其反白、语义三色、禁用态豁免档)按 WCAG 公式算成对比度(支持 `color-mix` 与 `var()` 求值,半透明底先压到页面底色上),亮 / 暗 × 常规 / 高对比四套主题各算一遍,低于阈值(正文 7 / 常规 4.5 / 禁用 2.6)即退出码 1;`--json` 出逐对明细,`--list` 只列配对表。改任一颜色令牌后它会立刻告诉你「改完谁读不清了」,不必等人工目测暗色。`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.`),检查皮肤包样式表无硬编码颜色(`#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 六段各 ≥3 条可勾选要点、每条维度标签必须落在边框 / 阴影 / 过渡 / 焦点环 / 暗色五类内、合计 ≥18 条且五维度全覆盖——视觉类条目收尾前照单过一遍,改清单时别把条目删漏。 diff --git a/README.md b/README.md index 4d68c93..a24d12f 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ accordion · affix · alert · anchor · autoComplete · avatar · avatarGroup ## 可访问性约定 +- 对比度:令牌层 16 组「文字色 × 底色」在亮 / 暗 × 常规 / 高对比四套主题下都不低于 WCAG 阈值(正文 7、常规 4.5、禁用 2.6),`npm run audit:contrast` 是这条约定的门禁;系统开启「提高对比度」时边框与弱文字自动加深一档 - 键盘焦点:所有库内元素统一 `outline: 2px solid var(--ua-color-accent)`(focus-visible 专属,鼠标点击不出现),组件自绘的 box-shadow 光环与它叠加不冲突。 - 关键交互都有键盘等价:Tab 进焦点、Enter/空格 激活、↑↓/Home/End 在列表类组件内移动、Esc 逐层关闭(内层先关,不连带外层)。 - 语义与播报:modal/drawer 带 `role="dialog" aria-modal`、toast/notification/empty/result 带 `role="status"/"alert"`,切换类组件用 `aria-selected`/`aria-expanded` 同步状态。 diff --git a/llms-full.txt b/llms-full.txt index fd4c5ac..633999e 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-26T13:57:11.667Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-26T13:58:42.557Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -46,7 +46,7 @@ 4. 查令牌:ua tokens 打印全部 --ua-* 变量(颜色带暗色值),--json 输出结构化数组;写样式前先查,禁止硬编码。令牌分十三组:颜色 / 混色浓度 / 圆角 / 间距 / 字号 / 字重行高 / 边框 / 阴影 / 动效 / 位移 / 焦点环 / 控件高度 / 层级,color-mix 混入浓度用 --ua-alpha-soft/tint/line/mid/strong/heavy,弹层阴影用 --ua-shadow-1..4,过渡用 --ua-duration-* + --ua-ease,transform 位移用 --ua-translate-*(负号 calc(-1 * var(...))),焦点环用 --ua-focus-ring(_inset) 5. 改完清单跑 npm run docs:同时刷新人类文档站与 llms-full.txt(Agent 首选喂料) 6. 改完样式跑 npm run audit:visual:各类硬编码计数不得高于 .ua/visual-baseline.json(棘轮只减不增);字面量可用 npm run visual:migrate 幂等改写成令牌 -7. 改完颜色令牌跑 npm run audit:contrast:16 组文字/底色配对在亮暗两套主题下都必须达标(正文 7 / 常规 4.5 / 禁用 2.6),跌破即退出码 1——调一个色值就能让全站辅助文字掉到 4.4,人眼看不出但门禁看得出 +7. 改完颜色令牌跑 npm run audit:contrast:16 组文字/底色配对在亮/暗 × 常规/高对比四套主题下都必须达标(正文 7 / 常规 4.5 / 禁用 2.6),跌破即退出码 1——调一个色值就能让全站辅助文字掉到 4.4,人眼看不出但门禁看得出 8. 在 ~/.workbuddy/mcp.json 注册 ua mcp 后,Agent 可直接调 list_components / get_component / search_components / get_tokens / list_recipes / get_recipe ## @ui-arch/ui(skin-dom)v0.1.0 @@ -2349,7 +2349,7 @@ API 速览: - ua verify:清单一致性门禁(id 唯一 / exports 对应 / props 必带 n t d / steps≥3 / demo 锚点存在 / 视觉回归抽查清单六组件各 ≥3 条且五维度齐全) - ua commit-lint <文件|message>:提交信息门禁(type 枚举 / scope 规则 / subject 中文祈使 ≤50 字符),规范见 COMMITTING.md,违规退出码 1 - ua audit:visual [--json|--baseline]:视觉漂移门禁,统计皮肤包里各类硬编码视觉字面量(字号/动效时长/阴影/圆角/焦点环/内距/控件高度/字重/行高/层级/边框色)并与 .ua/visual-baseline.json 棘轮比对,超基线退出码 1;--baseline 重写基线,--json 出机器可读计数与每类命中行号(line / prop / value / hint,Agent 可直接定位) -- ua audit:contrast [--json|--list]:对比度门禁,把令牌里 16 组「文字色 × 底色」配对按 WCAG 公式算成对比度(求值支持 var() 与 color-mix,半透明底先压到页面底色),亮暗分算,低于阈值(正文 7 / 常规 4.5 / 禁用 2.6)退出码 1;--json 出逐对明细,--list 只列配对表 +- ua audit:contrast [--json|--list]:对比度门禁,把令牌里 16 组「文字色 × 底色」配对按 WCAG 公式算成对比度(求值支持 var() 与 color-mix,半透明底先压到页面底色),亮/暗 × 常规/高对比四套分算,低于阈值(正文 7 / 常规 4.5 / 禁用 2.6)退出码 1;--json 出逐对明细,--list 只列配对表 - ua mcp:零依赖 stdio MCP server,暴露 list_components / get_component / search_components / get_tokens / list_recipes / get_recipe 一行用法: diff --git a/llms.txt b/llms.txt index 471476c..312635f 100644 --- a/llms.txt +++ b/llms.txt @@ -144,7 +144,7 @@ - ua verify:清单一致性门禁(id 唯一 / exports 对应 / props 必带 n t d / steps≥3 / demo 锚点存在 / 视觉回归抽查清单六组件各 ≥3 条且五维度齐全) - ua commit-lint <文件|message>:提交信息门禁(type 枚举 / scope 规则 / subject 中文祈使 ≤50 字符),规范见 COMMITTING.md,违规退出码 1 - ua audit:visual [--json|--baseline]:视觉漂移门禁,统计皮肤包里各类硬编码视觉字面量(字号/动效时长/阴影/圆角/焦点环/内距/控件高度/字重/行高/层级/边框色)并与 .ua/visual-baseline.json 棘轮比对,超基线退出码 1;--baseline 重写基线,--json 出机器可读计数与每类命中行号(line / prop / value / hint,Agent 可直接定位) - - ua audit:contrast [--json|--list]:对比度门禁,把令牌里 16 组「文字色 × 底色」配对按 WCAG 公式算成对比度(求值支持 var() 与 color-mix,半透明底先压到页面底色),亮暗分算,低于阈值(正文 7 / 常规 4.5 / 禁用 2.6)退出码 1;--json 出逐对明细,--list 只列配对表 + - ua audit:contrast [--json|--list]:对比度门禁,把令牌里 16 组「文字色 × 底色」配对按 WCAG 公式算成对比度(求值支持 var() 与 color-mix,半透明底先压到页面底色),亮/暗 × 常规/高对比四套分算,低于阈值(正文 7 / 常规 4.5 / 禁用 2.6)退出码 1;--json 出逐对明细,--list 只列配对表 - ua mcp:零依赖 stdio MCP server,暴露 list_components / get_component / search_components / get_tokens / list_recipes / get_recipe - 用法: `node packages/tools/cli/bin/ua.js |docs|tokens|style|verify|commit-lint|audit:visual|audit:contrast|doctor|mcp>;仓库内等价于 npm run index / docs / style / verify / audit:visual / audit:contrast / doctor` - 约定: 零依赖:只用 node 标准库,Node >= 18 diff --git a/packages/design/tokens/tokens.css b/packages/design/tokens/tokens.css index 0f7e23c..8a57aba 100644 --- a/packages/design/tokens/tokens.css +++ b/packages/design/tokens/tokens.css @@ -189,3 +189,21 @@ --ua-focus-outline-inset: 1.5px solid var(--ua-color-accent-soft); /* 暗色内嵌焦点描边:accent-soft 随底色重混 */ --ua-focus-outline-soft: 2px solid color-mix(in srgb, var(--ua-color-accent) var(--ua-alpha-mid), transparent); /* 暗色柔和外描边:浓度档与亮色一致 */ } + +/* ---- 高对比偏好:系统设置「提高对比度」时加深边界与弱文字 ---- + 为什么放文件末尾:媒体查询里的同选择器规则要靠后置胜出,写在暗色块之前会被顶掉。 + 只重设已有变量、不新增名字:verify 的颜色令牌 ↔ tokens.json 双向核对保持一一对应。 */ +@media (prefers-contrast: more) { + :root { + --ua-color-border: rgba(0, 0, 0, 0.45); /* 边界加深:控件轮廓在强光 / 低视力下仍可辨(边框细分线随之加深,它是 border 的混色派生) */ + --ua-color-text-muted: #4b5158; /* 辅助文字升级为次级文字色,说明文字不再是浅灰 */ + --ua-color-text-faint: #6b7280; /* 三级文字升到原辅助档:禁用态也要读得清 */ + --ua-color-scrollbar: rgba(0, 0, 0, 0.45); /* 滚动条拇指加深,可拖拽区域更明确 */ + } + [data-theme='dark'] { + --ua-color-border: rgba(255, 255, 255, 0.45); /* 暗色同样加深:白边提亮而不是加深黑 */ + --ua-color-text-muted: #b4bac2; + --ua-color-text-faint: #888780; + --ua-color-scrollbar: rgba(255, 255, 255, 0.5); + } +} diff --git a/packages/tools/cli/bin/ua.js b/packages/tools/cli/bin/ua.js index 532e3fb..b85b9da 100644 --- a/packages/tools/cli/bin/ua.js +++ b/packages/tools/cli/bin/ua.js @@ -1592,7 +1592,8 @@ function cmdAuditContrast(args) { if (args.includes('--json')) { const pairs = result.rows.map((r) => ({ label: r.label, fg: r.fg, bg: r.bg, min: r.min, - light: Number(r.light.toFixed(2)), dark: Number(r.dark.toFixed(2)), bad: r.bad, + light: Number(r.light.toFixed(2)), dark: Number(r.dark.toFixed(2)), + contrastLight: Number(r.contrastLight.toFixed(2)), contrastDark: Number(r.contrastDark.toFixed(2)), bad: r.bad, })); console.log(JSON.stringify({ ok: result.failures.length === 0, total: pairs.length, failing: result.failures.length, pairs }, null, 2)); } else { diff --git a/packages/tools/cli/lib/contrast-audit.mjs b/packages/tools/cli/lib/contrast-audit.mjs index 78e1426..1af2235 100644 --- a/packages/tools/cli/lib/contrast-audit.mjs +++ b/packages/tools/cli/lib/contrast-audit.mjs @@ -171,12 +171,16 @@ function ratio(fg, bg) { return (l1 + 0.05) / (l2 + 0.05); } -/*** 作用:构建两套主题的变量表:亮色 = :root,暗色 = :root 叠加 [data-theme='dark'] ***/ +/*** 作用:构建四套主题的变量表:亮/暗 × 常规/高对比(高对比块在文件末尾的媒体查询里,同名变量覆盖即生效) ***/ export function loadThemes(root) { const css = readText(path.join(root, TOKENS_REL)); const light = declsOf(blockOf(css, ':root')); const dark = new Map([...light, ...declsOf(blockOf(css, "[data-theme='dark']"))]); - return { light, dark }; + // 系统「提高对比度」偏好:媒体查询里只覆盖部分变量,未覆盖的沿用常规档,因此以对应主题为底再叠加 + const media = blockOf(css, '@media (prefers-contrast: more)'); + const contrastLight = new Map([...light, ...declsOf(blockOf(media, ':root'))]); + const contrastDark = new Map([...dark, ...declsOf(blockOf(media, "[data-theme='dark']"))]); + return { light, dark, contrastLight, contrastDark }; } /*** 作用:跑一遍全部配对,返回每个主题的明细(纯数据,供 CLI 与测试共用) ***/ @@ -195,13 +199,19 @@ export function auditContrast(root) { }; const lightOf = binder(themes.light); const darkOf = binder(themes.dark); + const contrastLightOf = binder(themes.contrastLight); + const contrastDarkOf = binder(themes.contrastDark); const rows = PAIRS.map((pair) => { const lightRatio = ratio(lightOf(pair.fg), lightOf(pair.bg)); const darkRatio = ratio(darkOf(pair.fg), darkOf(pair.bg)); + const contrastLightRatio = ratio(contrastLightOf(pair.fg), contrastLightOf(pair.bg)); + const contrastDarkRatio = ratio(contrastDarkOf(pair.fg), contrastDarkOf(pair.bg)); const bad = []; if (lightRatio < pair.min) bad.push('亮色'); if (darkRatio < pair.min) bad.push('暗色'); - return { ...pair, light: lightRatio, dark: darkRatio, bad }; + if (contrastLightRatio < pair.min) bad.push('高对比亮'); + if (contrastDarkRatio < pair.min) bad.push('高对比暗'); + return { ...pair, light: lightRatio, dark: darkRatio, contrastLight: contrastLightRatio, contrastDark: contrastDarkRatio, bad }; }); return { rows, failures: rows.filter((r) => r.bad.length > 0) }; } @@ -209,10 +219,10 @@ export function auditContrast(root) { /*** 作用:把明细打成对齐的文本表(人读模式,CLI 默认输出) ***/ export function formatReport({ rows, failures }) { const fixed = (n) => n.toFixed(2).padStart(6); - const lines = ['配对 亮色 暗色 阈值 结论']; + const lines = ['配对 亮色 暗色 高亮 高暗 阈值 结论']; for (const r of rows) { const ok = r.bad.length === 0 ? 'OK' : `FAIL(${r.bad.join('+')})`; - lines.push(`${r.label.padEnd(26)}${fixed(r.light)} ${fixed(r.dark)} ${String(r.min).padStart(4)} ${ok}`); + lines.push(`${r.label.padEnd(24)}${fixed(r.light)} ${fixed(r.dark)} ${fixed(r.contrastLight)} ${fixed(r.contrastDark)} ${String(r.min).padStart(4)} ${ok}`); } lines.push(''); lines.push(failures.length === 0 ? '全部配对达标' : `不达标 ${failures.length} 条:${failures.map((f) => f.label).join(' / ')}`); diff --git a/packages/tools/cli/ua.manifest.json b/packages/tools/cli/ua.manifest.json index 42865a6..063baf2 100644 --- a/packages/tools/cli/ua.manifest.json +++ b/packages/tools/cli/ua.manifest.json @@ -20,7 +20,7 @@ "ua verify:清单一致性门禁(id 唯一 / exports 对应 / props 必带 n t d / steps≥3 / demo 锚点存在 / 视觉回归抽查清单六组件各 ≥3 条且五维度齐全)", "ua commit-lint <文件|message>:提交信息门禁(type 枚举 / scope 规则 / subject 中文祈使 ≤50 字符),规范见 COMMITTING.md,违规退出码 1", "ua audit:visual [--json|--baseline]:视觉漂移门禁,统计皮肤包里各类硬编码视觉字面量(字号/动效时长/阴影/圆角/焦点环/内距/控件高度/字重/行高/层级/边框色)并与 .ua/visual-baseline.json 棘轮比对,超基线退出码 1;--baseline 重写基线,--json 出机器可读计数与每类命中行号(line / prop / value / hint,Agent 可直接定位)", - "ua audit:contrast [--json|--list]:对比度门禁,把令牌里 16 组「文字色 × 底色」配对按 WCAG 公式算成对比度(求值支持 var() 与 color-mix,半透明底先压到页面底色),亮暗分算,低于阈值(正文 7 / 常规 4.5 / 禁用 2.6)退出码 1;--json 出逐对明细,--list 只列配对表", + "ua audit:contrast [--json|--list]:对比度门禁,把令牌里 16 组「文字色 × 底色」配对按 WCAG 公式算成对比度(求值支持 var() 与 color-mix,半透明底先压到页面底色),亮/暗 × 常规/高对比四套分算,低于阈值(正文 7 / 常规 4.5 / 禁用 2.6)退出码 1;--json 出逐对明细,--list 只列配对表", "ua mcp:零依赖 stdio MCP server,暴露 list_components / get_component / search_components / get_tokens / list_recipes / get_recipe" ], "keyTypes": [ @@ -43,7 +43,7 @@ "查令牌:ua tokens 打印全部 --ua-* 变量(颜色带暗色值),--json 输出结构化数组;写样式前先查,禁止硬编码。令牌分十三组:颜色 / 混色浓度 / 圆角 / 间距 / 字号 / 字重行高 / 边框 / 阴影 / 动效 / 位移 / 焦点环 / 控件高度 / 层级,color-mix 混入浓度用 --ua-alpha-soft/tint/line/mid/strong/heavy,弹层阴影用 --ua-shadow-1..4,过渡用 --ua-duration-* + --ua-ease,transform 位移用 --ua-translate-*(负号 calc(-1 * var(...))),焦点环用 --ua-focus-ring(_inset)", "改完清单跑 npm run docs:同时刷新人类文档站与 llms-full.txt(Agent 首选喂料)", "改完样式跑 npm run audit:visual:各类硬编码计数不得高于 .ua/visual-baseline.json(棘轮只减不增);字面量可用 npm run visual:migrate 幂等改写成令牌", - "改完颜色令牌跑 npm run audit:contrast:16 组文字/底色配对在亮暗两套主题下都必须达标(正文 7 / 常规 4.5 / 禁用 2.6),跌破即退出码 1——调一个色值就能让全站辅助文字掉到 4.4,人眼看不出但门禁看得出", + "改完颜色令牌跑 npm run audit:contrast:16 组文字/底色配对在亮/暗 × 常规/高对比四套主题下都必须达标(正文 7 / 常规 4.5 / 禁用 2.6),跌破即退出码 1——调一个色值就能让全站辅助文字掉到 4.4,人眼看不出但门禁看得出", "在 ~/.workbuddy/mcp.json 注册 ua mcp 后,Agent 可直接调 list_components / get_component / search_components / get_tokens / list_recipes / get_recipe" ], "components": []