iter(g233): 位移量令牌化,transform 位移收敛为 --ua-translate-* 七档并纳入审计口径

This commit is contained in:
HIsheR
2026-10-26 21:16:22 +08:00
parent 39f53041e4
commit 0eb67c48fe
9 changed files with 106 additions and 44 deletions
+39 -1
View File
@@ -71,6 +71,11 @@ export const KIND_META = [
label: '混色浓度字面值',
hint: 'color-mix 的混入浓度写死百分比,应改用 --ua-alpha-* 六档(soft/tint/line/mid/strong/heavy)',
},
{
key: 'translate-literal',
label: '位移字面值',
hint: 'transform 的 translate 写死 px 位移,应改用 --ua-translate-* 档位(负号用 calc(-1 * var(...)),百分比居中豁免)',
},
];
/*** 作用:需要扫描的 CSS 属性白名单(其余属性本轮不动;用白名单顺带避开伪类选择器误判) ***/
@@ -117,6 +122,7 @@ const SCAN_PROPS = new Set([
'border-right-color',
'border-bottom-color',
'border-left-color',
'transform',
]);
/*** 作用:边框类属性集合(轮廓与分割线共用一套色值口径,禁用字面色) ***/
@@ -264,6 +270,18 @@ const MIX_TOKENS = new Map([
['80', 'var(--ua-alpha-heavy)'],
]);
/*** 作用:transform 位移字面值 → --ua-translate-* 档位(10px 就近收敛到 8 档,Δ≤2px 入场不可感;负号由迁移规则转 calc) ***/
const TRANSLATE_TOKENS = new Map([
['1px', 'var(--ua-translate-1)'],
['2px', 'var(--ua-translate-2)'],
['3px', 'var(--ua-translate-3)'],
['4px', 'var(--ua-translate-4)'],
['6px', 'var(--ua-translate-6)'],
['8px', 'var(--ua-translate-8)'],
['10px', 'var(--ua-translate-8)'],
['12px', 'var(--ua-translate-12)'],
]);
/*** 作用:提取值里所有 color-mix(...) 块(配平括号,嵌套 color-mix 逐层都算一条);返回全块文本与首色参(in srgb 之后的第一个顶层参数) ***/
function extractMixes(value) {
const out = [];
@@ -340,7 +358,7 @@ export function blankComments(src) {
/*** 作用:逐条产出「属性 + 值」及其在原文中的偏移;只认白名单属性且必须以分号收尾(避开伪类选择器) ***/
export function eachDeclaration(code) {
const re = /(border-top-color|border-right-color|border-bottom-color|border-left-color|border-color|border-top|border-right|border-bottom|border-left|border|padding-top|padding-right|padding-bottom|padding-left|padding|margin-top|margin-right|margin-bottom|margin-left|margin|row-gap|column-gap|gap|font-weight|line-height|z-index|min-height|max-height|height|transition-duration|animation-duration|transition|animation|font-size|box-shadow|border-radius|outline|background-image|background|color|fill|stroke)\s*:\s*([^;{}]*);/gi;
const re = /(transform|border-top-color|border-right-color|border-bottom-color|border-left-color|border-color|border-top|border-right|border-bottom|border-left|border|padding-top|padding-right|padding-bottom|padding-left|padding|margin-top|margin-right|margin-bottom|margin-left|margin|row-gap|column-gap|gap|font-weight|line-height|z-index|min-height|max-height|height|transition-duration|animation-duration|transition|animation|font-size|box-shadow|border-radius|outline|background-image|background|color|fill|stroke)\s*:\s*([^;{}]*);/gi;
const list = [];
for (const m of code.matchAll(re)) {
const prop = m[1].toLowerCase();
@@ -467,6 +485,19 @@ function migrateLeading(core) {
return { kind: 'leading-literal', value: token };
}
/*** 作用:迁移单条 transform:px 位移逐个换 --ua-translate-* 档位,负号转 calc(-1 * var(...));百分比居中与 0 天然不入表 ***/
function migrateTranslate(core) {
let hits = 0;
const next = String(core).replace(/(-?\d+(?:\.\d+)?)px\b/gi, (full, num) => {
const token = TRANSLATE_TOKENS.get(`${Math.abs(Number(num))}px`);
if (!token) return full; // 表外取值留给后续条目评估
hits++;
return Number(num) < 0 ? `calc(-1 * ${token})` : token;
});
if (!hits) return null;
return { kind: 'translate-literal', value: next };
}
/*** 作用:迁移单条层级:下拉类常见字面值(20/30/40)归 dropdown 档;其余取值层级语义需按选择器人工归档,留给门禁拦截 ***/
function migrateZ(core) {
const token = Z_DROPDOWN_LIKE.has(squash(core)) ? 'var(--ua-z-dropdown)' : null;
@@ -507,6 +538,8 @@ export function migrateDeclaration(prop, rawValue) {
result = migrateLeading(core);
} else if (prop === 'z-index') {
result = migrateZ(core);
} else if (prop === 'transform') {
result = migrateTranslate(core); // 位移量走独立档位,不与内距梯子混用
} else {
result = migrateMix(core) ?? migrateBorder(prop, core); // 其余属性:先收敛 color-mix 浓度,再收敛纯黑 rgba 边
}
@@ -585,6 +618,11 @@ function detect(prop, rawValue, key) {
if (key === 'mix-literal') {
return extractMixes(value).filter(({ arg1 }) => mixAmount(arg1) !== null).length; // 只认 color-mix 块内首色参的写死浓度;渐变停靠位与几何百分比天然不在口径内
}
if (key === 'translate-literal') {
if (prop !== 'transform') return 0;
const stripped = value.replace(/var\(--ua-translate-[\w-]+\)/g, ' '); // 已令牌化的位移先剔除
return (stripped.match(/-?\d+(\.\d+)?px\b/gi) ?? []).length; // 百分比(居中/轨道)与 0 不算漂移
}
return 0;
}
+1 -1
View File
@@ -39,7 +39,7 @@
"接手先跑 ua doctor(node 版本 / 依赖 / git 钩子 / .ua 元数据一次检完,秒级),再跑 npm run check 确认基线全绿(build → demo → style → typecheck → smoke → index → verify → lint-iterations → audit:visual)",
"查组件:ua list 看全量,ua search <关键词> 按 id/标题/参数名/参数说明/步骤五层 + 配方三层(标题/配方组件/要点)模糊找(命中非 id 标题会标出命中字段,配方命中行直接给 ua recipes <编号> 指路;拼错会回相近 id),ua info <组件id> 看 props/步骤/示例(ua info <包名> 看单包 api 与约定,加 --json 出原始清单)",
"查配方:ua recipes 列全量(编号 / 标题 / 用到的组件),ua recipes <编号> 看骨架代码与要点(--json 给脚本用;MCP 里对应 list_recipes / get_recipe)",
"查令牌:ua tokens 打印全部 --ua-* 变量(颜色带暗色值),--json 输出结构化数组;写样式前先查,禁止硬编码。令牌分十二组:颜色 / 混色浓度 / 圆角 / 间距 / 字号 / 字重行高 / 边框 / 阴影 / 动效 / 焦点环 / 控件高度 / 层级,color-mix 混入浓度用 --ua-alpha-soft/tint/line/mid/strong/heavy,弹层阴影用 --ua-shadow-1..4,过渡用 --ua-duration-* + --ua-ease,焦点环用 --ua-focus-ring(_inset)",
"查令牌: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 幂等改写成令牌",
"在 ~/.workbuddy/mcp.json 注册 ua mcp 后,Agent 可直接调 list_components / get_component / search_components / get_tokens / list_recipes / get_recipe"