@qilitt-mickey/vue3-temp-skill 1.1.57 → 1.1.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qilitt-mickey/vue3-temp-skill",
3
- "version": "1.1.57",
3
+ "version": "1.1.58",
4
4
  "type": "module",
5
5
  "description": "Vue 3 企业级中后台项目开发规范技能包 — core-kernel 架构、按需功能模块",
6
6
  "bin": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$comment": "verify 夹具 · 项目实测(clean 全绿对照,与 oracle.json + actual.json 三件配套):全部属性命中、absent 点位已收口(display:none)、申报残差点位允许与效果图不同。供 U 步正例(exit 0)与 V 步快照基线段使用。",
2
+ "$comment": "verify 夹具 · 项目实测(clean 全绿对照,与 oracle.json + actual.json 三件配套):全部属性命中、absent 点位已收口(display:none)、states 状态期望命中(disabled 态 opacity 0.4 由浏览器脚本触发后抽取)、无孤儿点位(G15 对账通过)、申报残差点位允许与效果图不同。供 U 步正例(exit 0)与 V 步快照基线段使用。",
3
3
  "sids": {
4
4
  "admin.content.table.header_cell": {
5
5
  "padding": "16px",
@@ -25,5 +25,11 @@
25
25
  "background-color": "#10214d"
26
26
  },
27
27
  "$note": "logo_area 背景与效果图期望 #0B1A3C 不同 = 已申报固有残差(oracle.residual_accepted 命中后不计缺口,U 步正例演示)"
28
+ },
29
+ "states": {
30
+ "admin.content.list.batch_delete@disabled": {
31
+ "opacity": "0.4",
32
+ "cursor": "not-allowed"
33
+ }
28
34
  }
29
35
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "$comment": "verify 夹具 · 项目实测(含 3 类故意缺口 + 1 类存在性缺口,用于回归):header_cell 字重被压 + 对齐语境错位 + white-space 未取到(期望值加密后暴露「属性所在规则未命中」新形态);cell_amount 选择器落空;user_profile gap 语境错位;breadcrumb 仍渲染可见(G12 存在性缺口故意展示)。logo_area 命中 = L 夹具不制造残差缺口、保持归因计数稳定;残差演示见 actual-clean.json 的 U 步。",
2
+ "$comment": "verify 夹具 · 项目实测(故意缺口全家桶,用于 L 步回归):header_cell 字重被压 + 对齐语境错位 + white-space 未取到;cell_amount 选择器落空;user_profile gap 语境错位;breadcrumb 仍渲染可见(G12 存在性缺口);batch_delete@disabled 状态值与期望不符(G14 状态缺口——静态快照取的是激活态 opacity 1,证明状态断言闸必要性);extra_banner 为 oracle 全集未申报的孤儿点位(G15 孤儿缺口——效果图没有、验收范围外、页面却渲染)。logo_area 命中 = L 夹具不制造残差缺口、保持归因计数稳定。",
3
3
  "sids": {
4
4
  "admin.content.table.header_cell": {
5
5
  "padding": "16px",
@@ -20,5 +20,14 @@
20
20
  "admin.layout.sidebar.logo_area": {
21
21
  "background-color": "#0b1a3c"
22
22
  }
23
+ },
24
+ "states": {
25
+ "admin.content.list.batch_delete@disabled": {
26
+ "opacity": "1",
27
+ "cursor": "default"
28
+ }
29
+ },
30
+ "extra_banner": {
31
+ "display": "flex"
23
32
  }
24
33
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "$comment": "verify 夹具 · 效果图期望(computed style 转储外形)。真实抽取由浏览器链路完成,本文件仅用于回归。顶层扩展字段:absent=效果图上不存在的出厂点位(G12 存在性闸断言目标,2026-09-21 面包屑事故机制化);residual_accepted=申报过的固有残差(命中豁免不计缺口,reason 必填)。header_cell 增补 white-space 期望 = 列头截断防线的期望值加密(oracle 无期望值的属性验了等于没验)。",
2
+ "$comment": "verify 夹具 · 效果图期望(computed style 转储外形)。真实抽取由浏览器链路完成,本文件仅用于回归。顶层扩展字段:absent=效果图上不存在的出厂点位(G12 存在性闸断言目标,2026-09-21 面包屑事故机制化);residual_accepted=申报过的固有残差(命中豁免不计缺口,reason 必填);states=交互态期望(G14 状态断言闸,键 <sid>@<state>,2026-09-22 批量按钮禁用态事故机制化——静态快照取不到交互态,逐点 diff 永远验不到)。header_cell 增补 white-space 期望 = 列头截断防线的期望值加密(oracle 无期望值的属性验了等于没验)。",
3
3
  "sids": {
4
4
  "admin.layout.sidebar.logo_area": {
5
5
  "background-color": "#0B1A3C"
@@ -25,6 +25,12 @@
25
25
  "absent": [
26
26
  "admin.layout.header.breadcrumb"
27
27
  ],
28
+ "states": {
29
+ "admin.content.list.batch_delete@disabled": {
30
+ "opacity": "0.4",
31
+ "cursor": "not-allowed"
32
+ }
33
+ },
28
34
  "residual_accepted": [
29
35
  { "sid": "admin.layout.sidebar.logo_area", "reason": "出厂 logo 图形属业务资产,效果图占位图形与实际 logo 不逐像素比对(restore-checklist §三:只还原载体模块)" }
30
36
  ]
@@ -1,5 +1,5 @@
1
1
  {
2
- "generated_at": "2026-09-22T05:33:53.887Z",
2
+ "generated_at": "2026-09-22T09:06:58.540Z",
3
3
  "oracle": "E:\\work\\Project\\Template\\skills\\vue3-temp-skill\\parser\\fixtures\\verify\\oracle.json",
4
4
  "actual": "E:\\work\\Project\\Template\\skills\\vue3-temp-skill\\parser\\fixtures\\verify\\actual-clean.json",
5
5
  "points": {
@@ -82,6 +82,22 @@
82
82
  "admin.layout.header.breadcrumb": {
83
83
  "verdict": "absent-hit",
84
84
  "evidence": "display=none visibility=-"
85
+ },
86
+ "admin.content.list.batch_delete@disabled": {
87
+ "verdict": "hit",
88
+ "state": "disabled",
89
+ "props": {
90
+ "opacity": {
91
+ "expected": "0.4",
92
+ "actual": "0.4",
93
+ "verdict": "hit"
94
+ },
95
+ "cursor": {
96
+ "expected": "not-allowed",
97
+ "actual": "not-allowed",
98
+ "verdict": "hit"
99
+ }
100
+ }
85
101
  }
86
102
  }
87
103
  }
package/parser/index.js CHANGED
@@ -110,7 +110,7 @@ function printHelp() {
110
110
  node parser/index.js match --batch <dir> 项目级批量匹配:目录下全部变更集 + 跨文件汇总
111
111
  node parser/index.js audit --project <dir> [<cs.json…>] 挂载点存在性 + 死通道静态审计(需目标项目源码)
112
112
  node parser/index.js verify --oracle <o.json> --actual <a.json> [--sids a,b]
113
- 渲染态逐点验收:computed style 期望 vs 实测 diff(oracle.absent 存在性闸 + residual_accepted 残差豁免)
113
+ 渲染态逐点验收:computed style 期望 vs 实测 diff(oracle.absent 存在性闸 G12 + residual_accepted 残差豁免 + oracle.states 状态断言闸 G14 + 孤儿点位对账闸 G15)
114
114
  node parser/index.js verify --oracle <o.json> --actual <a.json> --baseline <snap.json> [--snapshot <out.json>]
115
115
  快照回归:与上轮快照比对基线漂移(上轮命中本轮退步 = 红);--snapshot 导出本轮快照
116
116
  node parser/index.js verify --oracle <o.json> --resolve [--sids a,b]
@@ -1910,6 +1910,17 @@ function findStructEvidence(probe, sources) {
1910
1910
  * 列入 oracle.absent: ["sid", …],verify 逐个断言「实测转储中不存在,或 display:none / visibility:hidden」。
1911
1911
  * 治「该消失的没消失」——这类缺口在效果图上无可见元素 → 无 data-sid → 无期望值,逐点 diff 永远验不到。
1912
1912
  *
1913
+ * G14 状态断言闸(oracle.states):交互态(disabled / active / hover…)在静态页面快照上取不到——
1914
+ * 效果图上「批量按钮置灰」标注了 opacity:.4,页面静态抽取却是激活态值 → 状态性差异逐点 diff 永远验不到。
1915
+ * 设计侧在 oracle.json 顶层声明 states: { "<sid>@<state>": { "<css属性>": "<期望值>" } }(state 取值见
1916
+ * 设计侧 mockup-extraction-spec §3.1:disabled / active / hover / focus / checked),
1917
+ * verify 逐键断言;actual 侧同键形态抽取(sid 查表 + state 属性覆盖/筛选,见下文 G14 段)。
1918
+ *
1919
+ * G15 孤儿点位对账闸:actual 中存在、oracle 全集(sids ∪ absent ∪ residual_accepted ∪ states)均无申报
1920
+ * 的点位 = 孤儿 ——「效果图没有、验收范围外」的页面元素。G12 只断言「已申报的该消失」,孤儿闸补齐
1921
+ * 「未申报的出现了」——这是效果图级完备性检查(申报缺一个 = 漏网一个),存在孤儿 → exit 1,逼迫设计侧
1922
+ * 把该点位收进 absent(该消失)或补基线(该还原)。
1923
+ *
1913
1924
  * 残差豁免(oracle.residual_accepted):机制推算值 / 业务资产等**申报过的固有残差**点位不计缺口,
1914
1925
  * 每条 { "sid": …, "reason": … },reason 必填——防止把真缺口装进接受清单蒙混过关。
1915
1926
  */
@@ -1983,6 +1994,22 @@ function cmdVerify(args, dataset) {
1983
1994
  .map(r => (typeof r === "string" ? { sid: r, reason: "(未填 reason)" } : r))
1984
1995
  .filter(r => r && typeof r.sid === "string" && r.sid.trim())
1985
1996
  : [];
1997
+ // G14 状态断言闸:oracle.states = { "<sid>@<state>": { 属性: 期望值 } }(state ∈ disabled/active/hover/focus/checked)
1998
+ const stateEntries = [];
1999
+ if (oracleRaw.states && typeof oracleRaw.states === "object" && !Array.isArray(oracleRaw.states)) {
2000
+ for (const [key, exp] of Object.entries(oracleRaw.states)) {
2001
+ const m = /^(.+)@(disabled|active|hover|focus|checked)$/.exec(String(key).trim());
2002
+ if (!m || !exp || typeof exp !== "object" || Array.isArray(exp)) {
2003
+ console.error(`✖ oracle.states 键非法:「${key}」——应为 "<sid>@<state>"(state ∈ disabled/active/hover/focus/checked),值为属性对象`);
2004
+ process.exit(1);
2005
+ }
2006
+ stateEntries.push({ key: String(key).trim(), sid: m[1].trim(), state: m[2], expected: exp });
2007
+ }
2008
+ }
2009
+ else if (oracleRaw.states !== undefined) {
2010
+ console.error(`✖ oracle.states 应为对象 { "<sid>@<state>": { 属性: 期望值 } },实际类型:${Array.isArray(oracleRaw.states) ? "array" : typeof oracleRaw.states}`);
2011
+ process.exit(1);
2012
+ }
1986
2013
 
1987
2014
  let sids = Object.keys(oracle);
1988
2015
  if (sidFilter)
@@ -2029,6 +2056,11 @@ function cmdVerify(args, dataset) {
2029
2056
  }
2030
2057
 
2031
2058
  const actual = readDump(actualPath, "项目实测");
2059
+ // actual 顶层扩展字段:states 子对象(G14 实测端状态值,键形态 <sid>@<state>,与 oracle.states 对称)
2060
+ const actualRawFile = JSON.parse(readFileSync(path.resolve(actualPath), "utf-8"));
2061
+ const actualStatesMap = actualRawFile && actualRawFile.states && typeof actualRawFile.states === "object" && !Array.isArray(actualRawFile.states)
2062
+ ? actualRawFile.states
2063
+ : null;
2032
2064
 
2033
2065
  const normProp = p => String(p).trim().toLowerCase().replace(/_/g, "-");
2034
2066
  const normValue = v => {
@@ -2080,9 +2112,22 @@ function cmdVerify(args, dataset) {
2080
2112
  console.log(`比对点位:${sids.length}${sidFilter ? `(--sids 过滤后)` : "(oracle 全量)"}`);
2081
2113
  if (absentSids.length > 0)
2082
2114
  console.log(`存在性闸(G12):${absentSids.length} 点位应已收口 —— ${absentSids.join("、")}`);
2115
+ if (stateEntries.length > 0)
2116
+ console.log(`状态断言闸(G14):${stateEntries.length} 个交互态期望 —— ${stateEntries.map(s => s.key).join("、")}`);
2083
2117
  if (residualAccepted.length > 0)
2084
2118
  console.log(`残差豁免:${residualAccepted.length} 点位已申报接受 —— ${residualAccepted.map(r => r.sid).join("、")}`);
2085
2119
 
2120
+ // G15 孤儿点位对账:actual 顶层(原始文件外形)中存在、oracle 全集(sids ∪ absent ∪ residual ∪ states)均无
2121
+ // 申报的点位 = 孤儿。readDump 会把 { sids: {…} } 外形摊平成 sids 子对象,所以孤儿必须从原始文件算,
2122
+ // 排除结构性键($ 前缀注释 / sids / states 子对象)。G12 断言「已申报的该消失」;孤儿闸补齐「未申报的出现了」。
2123
+ const oracleCovered = new Set([
2124
+ ...Object.keys(oracle),
2125
+ ...absentSids,
2126
+ ...residualAccepted.map(r => r.sid.trim()),
2127
+ ...stateEntries.map(s => s.sid),
2128
+ ]);
2129
+ const orphans = Object.keys(actualRawFile).filter(k => !k.startsWith("$") && k !== "sids" && k !== "states" && !oracleCovered.has(k));
2130
+
2086
2131
  // 残差豁免索引:sid → reason。命中豁免的属性缺口降级为「已申报残差」提示行,不进 missProps。
2087
2132
  const residualMap = new Map(residualAccepted.map(r => [r.sid.trim(), r.reason || "(未填 reason)"]));
2088
2133
 
@@ -2193,6 +2238,101 @@ function cmdVerify(args, dataset) {
2193
2238
  }
2194
2239
  }
2195
2240
 
2241
+ // ---- G14 状态断言闸:oracle.states(<sid>@<state>)逐键比对交互态 computed style ----
2242
+ // actual 侧两种合法形态:① states 子对象同名键(推荐,浏览器脚本对 hover/focus 等态主动触发后抽取);
2243
+ // ② sid 主条目属性覆盖(仅 disabled/checked 等「属性可静态反映」的态——设计侧未给 states 子对象时的降级口径)。
2244
+ // 计数口径:stateMiss = 键级(每个状态键最多计 1 次缺口,供「状态断言 X/Y 命中」);
2245
+ // 属性级归因仍进统一四类(选择器落空 / 权重被压 / 语境错位),处置口径不因状态位分裂。
2246
+ let stateMiss = 0;
2247
+ if (stateEntries.length > 0) {
2248
+ console.log(`\n── 状态断言闸(G14):交互态期望逐键比对 ──`);
2249
+ for (const st of stateEntries) {
2250
+ if (sidFilter && !sidFilter.has(st.sid))
2251
+ continue;
2252
+ let actVal = null;
2253
+ const stDump = actualStatesMap ? actualStatesMap[st.key] : null;
2254
+ if (stDump && typeof stDump === "object" && !Array.isArray(stDump)) {
2255
+ actVal = stDump; // 形态①:该状态下的完整属性转储
2256
+ }
2257
+ else {
2258
+ // 形态②降级:sid 主条目里的属性值(disabled/checked 可静态反映;hover/active 等瞬态在此口径下取不到)
2259
+ const main = actual[st.sid];
2260
+ if (main && typeof main === "object") {
2261
+ const covered = {};
2262
+ let any = false;
2263
+ for (const [p, e] of Object.entries(st.expected)) {
2264
+ const np = normProp(p);
2265
+ if (np in main) {
2266
+ covered[p] = main[np];
2267
+ any = true;
2268
+ }
2269
+ }
2270
+ if (any)
2271
+ actVal = covered;
2272
+ }
2273
+ }
2274
+ if (!actVal) {
2275
+ stateMiss++;
2276
+ const why = (st.state === "hover" || st.state === "focus" || st.state === "active")
2277
+ ? `瞬态(${st.state})须由浏览器脚本触发后抽取到 actual.states["${st.key}"]——主条目静态值不能代表交互态`
2278
+ : `actual 未抽取到该状态(缺 actual.states["${st.key}"] 或 sid 主条目)`;
2279
+ console.log(` ✘ ${st.key} —— 状态期望未取到实测值:${why}`);
2280
+ snapshot.points[st.key] = { verdict: "state-miss", state: st.state };
2281
+ continue;
2282
+ }
2283
+ const stLines = [];
2284
+ const snapProps = {};
2285
+ let stKeyMiss = false;
2286
+ for (const [propRaw, expVal] of Object.entries(st.expected)) {
2287
+ const prop = normProp(propRaw);
2288
+ const actV = actVal[propRaw] ?? actVal[prop];
2289
+ if (actV === undefined || actV === null) {
2290
+ stLines.push(` - ${prop}: 期望 ${expVal} / 实测未取到 —— 状态抽取缺属性`);
2291
+ attr.selector_miss++;
2292
+ stKeyMiss = true;
2293
+ snapProps[prop] = { expected: expVal, actual: null, verdict: "selector-miss" };
2294
+ continue;
2295
+ }
2296
+ const expNorm = themeAlias ? themeAlias(expVal) : expVal;
2297
+ if (normValue(expNorm) === normValue(actV)) {
2298
+ snapProps[prop] = { expected: expVal, actual: actV, verdict: "hit" };
2299
+ }
2300
+ else {
2301
+ const isGeom = GEOMETRY_PROPS.has(prop);
2302
+ stLines.push(` - ${prop}: 期望 ${expVal} / 实得 ${actV} —— ${isGeom ? "语境错位" : "权重被压"}(${st.state} 态)`);
2303
+ if (isGeom)
2304
+ attr.context_mismatch++;
2305
+ else
2306
+ attr.weight_override++;
2307
+ stKeyMiss = true;
2308
+ snapProps[prop] = { expected: expVal, actual: actV, verdict: "miss", kind: "状态缺口" };
2309
+ }
2310
+ }
2311
+ if (stKeyMiss) {
2312
+ stateMiss++;
2313
+ console.log(`\n ✘ ${st.key}`);
2314
+ for (const l of stLines)
2315
+ console.log(l);
2316
+ snapshot.points[st.key] = { verdict: "miss", state: st.state, props: snapProps };
2317
+ }
2318
+ else {
2319
+ console.log(` ✔ ${st.key} —— 状态期望全部命中`);
2320
+ snapshot.points[st.key] = { verdict: "hit", state: st.state, props: snapProps };
2321
+ }
2322
+ }
2323
+ }
2324
+
2325
+ // ---- G15 孤儿点位对账:actual 有、oracle 全集无申报 = 效果图级完备性缺口 ----
2326
+ let orphanMiss = 0;
2327
+ if (orphans.length > 0) {
2328
+ console.log(`\n── 孤儿点位对账(G15):以下点位页面渲染了、oracle 全集(sids/absent/residual/states)均无申报 ──`);
2329
+ for (const sid of orphans) {
2330
+ orphanMiss++;
2331
+ console.log(` ✘ ${sid} —— 效果图上无此角色坐标:要么应 visible:false 收口(补进 oracle.absent),要么该还原(回流设计侧补基线 oracle.sids + 效果图 data-sid)`);
2332
+ snapshot.points[sid] = { verdict: "orphan" };
2333
+ }
2334
+ }
2335
+
2196
2336
  if (clean.length > 0)
2197
2337
  console.log(`\n✔ 逐点命中:${clean.join("、")}`);
2198
2338
 
@@ -2213,9 +2353,10 @@ function cmdVerify(args, dataset) {
2213
2353
  const prev = basePoints[sid];
2214
2354
  if (!prev)
2215
2355
  continue;
2216
- // 点位级漂移:上轮命中/收口、本轮缺口
2217
- const prevOk = prev.verdict === "hit" || prev.verdict === "absent-hit";
2218
- const curOk = cur.verdict === "hit" || cur.verdict === "absent-hit";
2356
+ // 点位级漂移:上轮命中/收口/状态命中、本轮缺口(G12/G14/G15 各 verdict 均纳入红绿判定)
2357
+ const okVerdicts = new Set(["hit", "absent-hit", "orphan-absolved"]);
2358
+ const prevOk = okVerdicts.has(prev.verdict);
2359
+ const curOk = okVerdicts.has(cur.verdict);
2219
2360
  if (prevOk && !curOk) {
2220
2361
  drifts.push(`${sid}(${prev.verdict} → ${cur.verdict})`);
2221
2362
  }
@@ -2247,10 +2388,10 @@ function cmdVerify(args, dataset) {
2247
2388
  }
2248
2389
  }
2249
2390
 
2250
- console.log(`\n汇总:${sids.length} 点位 · ${hitProps} 属性命中 · ${missProps} 缺口${residualProps > 0 ? ` · ${residualProps} 已申报残差` : ""}${absentSids.length > 0 ? ` · 存在性 ${absentSids.length - absentMiss}/${absentSids.length} 收口` : ""}`);
2251
- console.log(`归因:选择器落空 ${attr.selector_miss} / 权重被压 ${attr.weight_override} / 语境错位 ${attr.context_mismatch}${absentSids.length > 0 ? ` / 存在性缺口 ${absentMiss}` : ""}${driftCount > 0 ? ` / 基线漂移 ${driftCount}` : ""}`);
2252
- if (missProps > 0 || absentMiss > 0 || driftCount > 0) {
2253
- console.log(`处置口径:按归因修正 —— 选择器落空补挂载点(属能力缺口,走 structure_ops / 补通道并申报);权重被压按 priority 声明提权(禁止 !important 掩盖);语境错位校准 layer(host / inner);存在性缺口补 visible:false 收口并按词典 priority 提权;基线漂移逐条回归上轮修法。`);
2391
+ console.log(`\n汇总:${sids.length} 点位 · ${hitProps} 属性命中 · ${missProps} 缺口${residualProps > 0 ? ` · ${residualProps} 已申报残差` : ""}${absentSids.length > 0 ? ` · 存在性 ${absentSids.length - absentMiss}/${absentSids.length} 收口` : ""}${stateEntries.length > 0 ? ` · 状态断言 ${stateEntries.length - stateMiss}/${stateEntries.length} 命中` : ""}${orphans.length > 0 ? ` · 孤儿 ${orphans.length}` : ""}`);
2392
+ console.log(`归因:选择器落空 ${attr.selector_miss} / 权重被压 ${attr.weight_override} / 语境错位 ${attr.context_mismatch}${absentSids.length > 0 ? ` / 存在性缺口 ${absentMiss}` : ""}${stateEntries.length > 0 ? ` / 状态缺口 ${stateMiss}` : ""}${orphanMiss > 0 ? ` / 孤儿点位 ${orphanMiss}` : ""}${driftCount > 0 ? ` / 基线漂移 ${driftCount}` : ""}`);
2393
+ if (missProps > 0 || absentMiss > 0 || stateMiss > 0 || orphanMiss > 0 || driftCount > 0) {
2394
+ console.log(`处置口径:按归因修正 —— 选择器落空补挂载点(属能力缺口,走 structure_ops / 补通道并申报);权重被压按 priority 声明提权(禁止 !important 掩盖);语境错位校准 layer(host / inner);存在性缺口补 visible:false 收口并按词典 priority 提权;状态缺口由浏览器脚本触发交互态后抽到 actual.states(G14);孤儿点位回流设计侧收进 oracle.absent 或补基线(G15);基线漂移逐条回归上轮修法。`);
2254
2395
  process.exitCode = 1;
2255
2396
  }
2256
2397
  else {
@@ -24,11 +24,11 @@
24
24
  * I build --dry 产物链自检 → exit 0 且输出含 dry-run
25
25
  * J match 三声明维度 → exit 0 且输出含「结构施工 structure_ops」
26
26
  * K audit 挂载点 / 死通道审计 → exit 1(夹具项目故意存在挂载点缺失 / 死通道)
27
- * L verify 渲染态逐点验收 → exit 1 且归因各计其数(夹具含故意缺口:三类渲染缺口 + G12 存在性缺口)
27
+ * L verify 渲染态逐点验收 → exit 1 且归因各计其数(夹具含故意缺口:三类渲染缺口 + G12 存在性 + G14 状态 + G15 孤儿)
28
28
  * M match 登记表覆盖率细闸 → exit 1(夹具故意欠声明两条恒存在子项)
29
29
  * N registry 登记表通道核验 → exit 0 且输出含「登记表通道核验」
30
30
  * O registry 硬缺口负例 → exit 1(引用了不存在且未标 pending 的点位)
31
- * U verify 残差豁免 + 收口正例(clean 夹具)→ exit 0 且输出含「已申报残差」「已收口」(申报点位不计缺口、收口点位绿灯)
31
+ * U verify 残差豁免 + 收口 + 状态 + 无孤儿正例(clean 夹具)→ exit 0 且输出含「已申报残差」「已收口」「状态断言 1/1 命中」(申报点位不计缺口、收口/状态绿灯、无孤儿)
32
32
  * V verify 快照回归(snapshot → baseline)→ clean 快照为基线、缺口夹具为实测 → exit 1 且输出含「基线漂移」(上轮命中本轮退步必红)
33
33
  *
34
34
  * 任一步骤失败 → 本脚本 exit 1,并回显该步骤完整输出便于定位。
@@ -133,10 +133,10 @@ const steps = [
133
133
  },
134
134
  {
135
135
  key: "L",
136
- name: "verify 渲染态逐点验收(夹具含三类渲染缺口 + G12 存在性缺口 → exit 1)",
136
+ name: "verify 渲染态逐点验收(夹具含四类缺口 + G12 存在性 + G14 状态 + G15 孤儿 → exit 1)",
137
137
  script: INDEX,
138
138
  args: ["verify", "--oracle", path.join(FIXTURES, "verify", "oracle.json"), "--actual", path.join(FIXTURES, "verify", "actual.json")],
139
- expect: { code: 1, includes: "选择器落空 2 / 权重被压 1 / 语境错位 2 / 存在性缺口 1" },
139
+ expect: { code: 1, includes: ["选择器落空 2 / 权重被压 3 / 语境错位 2", "存在性缺口 1", "状态缺口 1", "孤儿点位 1"] },
140
140
  },
141
141
  {
142
142
  key: "M",
@@ -195,10 +195,10 @@ const steps = [
195
195
  },
196
196
  {
197
197
  key: "U",
198
- name: "verify 残差豁免 + 收口正例(clean 夹具:申报点位不计缺口、absent 已收口 → exit 0)",
198
+ name: "verify 残差豁免 + 收口 + 状态 + 无孤儿正例(clean 夹具:申报不计缺口、absent 已收口、G14 状态命中、G15 无孤儿 → exit 0)",
199
199
  script: INDEX,
200
200
  args: ["verify", "--oracle", path.join(FIXTURES, "verify", "oracle.json"), "--actual", path.join(FIXTURES, "verify", "actual-clean.json")],
201
- expect: { code: 0, includes: ["已申报固有残差", "已收口", "验收通过"] },
201
+ expect: { code: 0, includes: ["已申报固有残差", "已收口", "状态断言 1/1 命中", "验收通过"] },
202
202
  },
203
203
  {
204
204
  key: "V",
@@ -25,7 +25,7 @@
25
25
  - 顶栏未声明点位(设置按钮等)→ 已收口不显示;用户区 → 按 `form` 核对组合形态(`avatar_name` / `greeting_avatar_name` / `greeting_name` / `avatar_only` / `name_only`),其中 greeting 为结构增量节点(出厂无此 DOM,需 AI 按 form 插入后再验)
26
26
  - 页签图标 → 按 `form` 核对(item 的 `text_only`/`with_icon`,home_tab 的 `icon_only`/`text`),出处是路由 `meta.icon` 数据或本次隐藏规则,不是源码常量
27
27
  - 内容区 form=scroll/fixed → `data-content-mode` 属性落在**页面根节点**(不是样式文件、不是壳层组件),列表页自动定高未被压掉;填满链逐层带 `data-fill`(详见下方「高度链存活检查」)
28
- - 操作栏 form=with_tools/left_batch → 刷新/密度/列设置三件套齐全;批量删除红底/导出白底/未选中置灰
28
+ - 操作栏 form=with_tools/left_batch → 刷新/密度/列设置三件套齐全;批量删除红底/导出白底/未选中置灰;**效果图上有状态性表现(禁用置灰/选中态)的控件必须已申报 `oracle.states`(G14)**——交付前用浏览器脚本触发对应交互态核对,静态目视激活态不算验证通过
29
29
  - 操作列 form=text_link → **业务列定义的 icon 字段已移除**(纯文字无图标,只改 CSS 保留图标 = 未还原);按钮单行不换行(列宽 ≥160px + nowrap);删除项红色;≥3 个折叠「更多」带箭头
30
30
  - tabs form → 页签形态切换生效(chrome/card);激活态表现(有无下划线 / 是否仅文字变色 / 字重)按当次变更集 style 字段核对,**不以历史设计稿口径为准**
31
31
  - [ ] **高度链存活检查(结构层不被设计改动压垮,也不被"保高度"绑架)**:定高页(`form=fixed` 或列表页自动定高)还原后逐项确认——
@@ -36,7 +36,7 @@
36
36
  - [ ] **映射通道活性检查(杂必查)**:样式落地引用的每个 CSS 变量必须在项目样式中**存在消费者**(`变量名` 有 `var(--xxx)` 引用点);只定义不消费的变量 = 通道死路,翻译再准也不生效(menu_active_indicator 曾因 --vts-theme-menu-active-before 无消费者而「映射了却没效果」)。施工时 grep 核对,无消费者时改走消费者存在的作用域或补挂载点并申报。
37
37
  - [ ] **结构施工兑现检查(structure_ops 逐条对账,与红线同级)**:变更集 `structure_ops[]` 的**每一条 op** 都必须在交付说明给出「op → 落在哪个文件 / 哪个节点 / 怎么落的」;`add_node` 必须真的新增了元素或渲染分支(模板 diff 可验),`bind_source` 必须真的改了绑定(`class-name` / `align` / `formatter`)。出现「声明了 op、代码里找不到对应改动」= 交付不完整,禁止只改 CSS 冒充结构施工(历史缺口:操作列分割线 `add_node` 声明了但从未施工 → 按钮间竖线一直缺失)
38
38
  - [ ] **同权重失效检查( · 专查「写了不生效」)**:逐条核对设计层新增规则是否真能取胜——① 对手是组件 `<style scoped>` 时,本条特异度必须**严格高于**它(**平手即错**,因为组件样式在全局之后注入);② 对手带 `!important` 时本条必须同样 `!important`,或改走通道变量。**判定方法(必须真数,不许目测)**:对每条新规则与其对手分别写出特异度三元组 `(a,b,c)` 并**逐位比较(先 b 后 c)**,本条的 `b` 不大于对手即判为「失效」;`html` / `body` 只加 `c` 位,**对手 `b` 更高时加 `html body` 无效**,必须靠**加类**(祖先链补稳定语义类,如 `.el-menu`)把 `b` 抬到相等。同 `b` 时,`c` 更高者胜。③ **出厂用语义令牌间接设置该属性时(`var(--vts-theme-*)`),必须先走「令牌重映射」而不是抢属性**——在令牌宿主作用域重声明令牌即生效、零权重战争;只有无令牌或令牌语义不符时才抢属性。历史缺口全部命中本项:面包屑 `display:none`(与组件 scoped 同为 0,2,0)→ 照旧显示;页签关闭钮 `opacity`(同为 0,5,0)→ 仍要 hover 才显;页签项 `padding`(对手带 `!important`)→ 无论怎么抬特异度都改不动;**侧栏菜单 `color`(`b=2` 输给出厂 `b=3` + `!important`)→ 整栏菜单文字用浅色主题的黑色落在深色侧栏上、字全部消失**
39
- - [ ] **oracle 基线可用性检查**:变更集带 `oracle` 时——① `oracle.mockup` 必须与**本次主题、本次页面**一致;**同构异色复用除外**:变更集声明 `oracle.theme_mapping: { "from": "<效果图主题>", "to": "<本次主题>" }`(同场景 / 同页面 / 同角色集合、仅主题色板不同)时,允许复用 from 主题效果图,verify 必须带 `--from-theme/--to-theme` 做色值归一,**不得因换主题色就判死闭环**;结构 / 形态有差异时禁止复用;② `oracle.sids` 与 `changes[]` **双向闭合**:效果图里有、changes[] 未声明 = **欠声明**(该点位永远不可能被还原);changes[] 已声明、效果图无 sid = **无基线**——**仅 `visible:false` 的隐藏点位豁免**(效果图上无可见元素可标 `data-sid`,parser 已自动豁免并打印清单)。其余两类出现即回流设计侧补齐,**不得直接开工**(`parser match` 已自动报出)
39
+ - [ ] **oracle 基线可用性检查**:变更集带 `oracle` 时——① `oracle.mockup` 必须与**本次主题、本次页面**一致;**同构异色复用除外**:变更集声明 `oracle.theme_mapping: { "from": "<效果图主题>", "to": "<本次主题>" }`(同场景 / 同页面 / 同角色集合、仅主题色板不同)时,允许复用 from 主题效果图,verify 必须带 `--from-theme/--to-theme` 做色值归一,**不得因换主题色就判死闭环**;结构 / 形态有差异时禁止复用;② `oracle.sids` 与 `changes[]` **双向闭合**:效果图里有、changes[] 未声明 = **欠声明**(该点位永远不可能被还原);changes[] 已声明、效果图无 sid = **无基线**——**仅 `visible:false` 的隐藏点位豁免**(效果图上无可见元素可标 `data-sid`,parser 已自动豁免并打印清单)。其余两类出现即回流设计侧补齐,**不得直接开工**(`parser match` 已自动报出);③ **状态性期望与孤儿对账(G14/G15,抽取产物层)**:效果图上有可见状态性表现的点位(禁用置灰 / 激活 / 选中)→ `oracle.json.states` 必须有对应 `<sid>@<state>` 期望(缺失 = 状态维度放弃验收);交付前核对项目页渲染的每个区块都在 oracle 全集(sids ∪ absent ∪ residual ∪ states)内——孤儿点位 verify 当场 exit 1,提前自查可省一轮返工
40
40
  - [ ] **dark.scss 暗色同步**:新增视觉规则在 `html.dark` 有对应覆盖
41
41
  - [ ] **缺口清单只含意图缺口**:清单每一项都必须是「设计侧未给值 / 值非法」且已注明「回流设计侧补齐」;能力缺口(词典缺 ID / 缺字段枚举 / 缺 form)不得出现在清单里——出现即把可落地项搁置
42
42
  - [ ] **台账逐行兑现对账(对账闸门)**:把设计侧交付的提取台账逐行过一遍,每行必须有落地结论——已落地(写明落在哪个文件/选择器)或已入缺口清单(写明缺失字段)。存在「台账有行、落地无结论」的行 = 交付不完整,与红线同级
@@ -26,7 +26,7 @@
26
26
  ```
27
27
  退出码:存在缺口(含存在性缺口 / 基线漂移)→ `1`。
28
28
 
29
- **归因与处置(四类,禁止用 `!important` 掩盖)**:
29
+ **归因与处置(四类 + 状态/孤儿两类扩展,禁止用 `!important` 掩盖)**:
30
30
 
31
31
  | 归因 | 触发 | 处置 |
32
32
  |---|---|---|
@@ -34,9 +34,26 @@
34
34
  | **权重被压** | 颜色 / 字重 / 透明度 / 阴影类属性值不符 | 按词典 `priority` 声明的策略提权(`scope_qualified` / `self_plus_important` / `deep_qualified`) |
35
35
  | **语境错位** | 几何 / 盒模型属性(padding / 尺寸 / 对齐 / 行高)值不符 | 校准 `layer`(`host` vs `inner`)——值对了落错层 |
36
36
  | **存在性缺口(G12)** | `oracle.absent` 点位在页面仍渲染可见(`display` 非 `none`) | 补 `visible:false` 收口并按词典 `priority` 提权(历史缺口:面包屑 `display:none` 被同权重 scoped 规则反压——写了不生效也是它) |
37
+ | **状态缺口(G14)** | `oracle.states` 的 `<sid>@<state>` 交互态期望与实测不符 / 实测未取到 | 浏览器脚本**触发交互态**(`.click()` / `hover()` / `dispatchEvent`)后抽取到 `actual.states` 同名键;禁用态走结构通道(`disabled` 属性 / `:disabled` 类)+ 设计层样式(历史缺口:效果图「批量按钮置灰 opacity:.4」在静态快照上永远取不到——页面静态抽的是激活态值,逐点 diff 验不到,2026-09-22 机制化) |
38
+ | **孤儿点位(G15)** | actual 中存在、oracle 全集(sids ∪ absent ∪ residual ∪ states)均无申报 | 两选一回流设计侧:应消失 → 收进 `oracle.absent`;该还原 → 补 `oracle.sids` + 效果图 `data-sid`。**G12 只断言「已申报的该消失」,孤儿闸补齐「未申报的出现了」——申报缺一个 = 漏网一个** |
37
39
 
38
40
  **存在性闸(`oracle.absent`)**:效果图上不存在的出厂点位(面包屑 / 设置铃铛等)逐条列入变更集 `oracle.absent`,verify 逐个断言「actual 不存在或 `display:none` / `visibility:hidden`」。**这类缺口无期望值,逐点 diff 永远验不到——不进 absent 就不在验收范围内**(2026-09-21 面包屑事故机制化)。
39
41
 
42
+ **状态断言闸(`oracle.states`,G14)**:交互态(disabled / active / hover / focus / checked)在静态页面快照上取不到——效果图标注的状态性样式(批量按钮禁用置灰、页签激活字重)与页面静态抽取值**永远对不上或永远碰巧对上**,逐点 diff 均验不到。设计侧在 `oracle.json` 顶层声明:
43
+
44
+ ```json
45
+ {
46
+ "sids": { "…": { "…": "…" } },
47
+ "states": {
48
+ "admin.content.list.batch_delete@disabled": { "opacity": "0.4", "cursor": "not-allowed" }
49
+ }
50
+ }
51
+ ```
52
+
53
+ `changes[] oracle.sids` **保持纯 sid**(不带 `@state` 后缀——G8 双向闭合与效果图 `data-sid` 对账不感知后缀);状态期望只落 `oracle.json.states` 抽取产物层。实测端对称:`actual.json` 顶层 `states` 子对象,浏览器脚本对每键触发交互态后抽取。verify 逐键断言、计入缺口与归因(处置见上表);`hover/focus/active` 等瞬态**必须**走 states 子对象(主条目静态值不能代表交互态),`disabled/checked` 可降级用 sid 主条目属性。**效果图上可见的状态性差异(禁用置灰 / 激活态)必须申报 states——不申报 = 状态维度放弃验收**。
54
+
55
+ **孤儿点位对账闸(G15)**:verify 自动对账 actual 顶层点位与 oracle 全集(`sids ∪ absent ∪ residual_accepted ∪ states` 的 sid 部分),**未申报即出现 = 孤儿,exit 1**——效果图是验收的完备性基准,「图上没有、清单也没说」的页面元素不允许静默存活(它是下一轮「图上怎么多了个东西」体感的来源)。结构性键(`$` 前缀注释 / `sids` / `states`)不参与对账。
56
+
40
57
  **残差豁免(`oracle.residual_accepted`)**:`[{ "sid": …, "reason": … }]`(reason 必填)。命中豁免的属性差异降级为「已申报固有残差」提示行,不进缺口计数;验收报告单列豁免明细,逐条可审计。**机制推算值(等宽槽宽)、业务资产(logo 图形)必须申报**——不申报 = 每轮验收重新报缺口 = 「改了很多版始终不行」的体感来源。
41
58
 
42
59
  **快照回归(`--snapshot` / `--baseline`,治「上一轮改对的这一轮漂回」)**:
@@ -48,7 +65,7 @@ node parser/index.js verify --oracle oracle.json --actual actual.json --snapshot
48
65
  node parser/index.js verify --oracle oracle.json --actual actual.json --baseline snapshot-r1.json
49
66
  ```
50
67
 
51
- - 上轮 `hit` / `absent-hit`、本轮退步的点位 / 属性 = **基线漂移**,逐条列出且 `exit 1`——先回归上轮修法,再谈新缺口。
68
+ - 上轮 `hit` / `absent-hit`、本轮退步的点位 / 属性 = **基线漂移**,逐条列出且 `exit 1`——先回归上轮修法,再谈新缺口。(G14 状态键命中、G15 对账通过同样纳入上轮「命中」口径;上轮 `state-miss` / `orphan` 修复后本轮变 `hit` 属正常改善,不算漂移。)
52
69
  - 项目还原到出厂(design-shell 回空壳)后第一轮验证**不带** `--baseline`(基线已失真),改以 clean 夹具重新起拍。
53
70
 
54
71
  **夹具与设计资产的边界(职责单侧沉淀红线)**:`「页面名-主题」`命名法(如 `运单管理-钴蓝.html`)是设计侧效果图库的专属资产命名——**项目侧(含 parser/fixtures 回归夹具)禁止出现与设计侧资产同名的文件**。回归需要 mockup 实物时,一律用「回归夹具-」前缀合成名(如 `回归夹具-列表页-钴蓝.html`),点集为回归机制定制、与真基线**不同且不可互替**;真实效果图基线唯一沉淀在设计侧 `page-specs/styles/<方案>/scenes/<场景>/mockups/`,项目侧 `oracle.mockup` 指向它靠 `resolveFirstExisting` 跨库解析(变更集同目录 → cwd → 技能根 → 设计侧兄弟目录),不做本地拷贝。