@qilitt-mickey/vue3-temp-skill 1.1.48 → 1.1.49

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.
@@ -73,13 +73,26 @@
73
73
  },
74
74
  "admin.content.table.operation_bar": {
75
75
  "type": "element",
76
- "selector": ".vts-action-bar",
77
- "file": "条件通道:ReTableBar 内部工具栏行(标题/按钮/工具三件套所在行)是本条目的唯一合法挂载点;该行出厂**无语义类**,selector 命中前须按 structure_ops 给该行补挂 .vts-action-bar(属补通道,交付说明单列申报)。禁止挂到 ReTableBar 根节点(见 preconditions)。右侧工具三件套源码实证:刷新 svg-icon + el-dropdown 密度(宽松/默认/紧凑)+ el-popover 列设置",
76
+ "selector": ".vts-page-card .flex-between > .flex-end(ReTableBar 内部工具栏行右侧容器,bar.vue JSX 实证 div.flex-end)",
77
+ "file": "条件通道实证(2026-09-20 复核):.vts-action-bar 类**全项目无模板挂载**(仅 element-plus.scss 有样式定义、无消费者)——历史 selector 锚定 .vts-action-bar 属「应该在」而非「实际在」,导致设计层规则全部落空(工具钮裸排事故)。本 selector 改锚真实出厂 DOM;.vts-action-bar 作为可选增强通道保留在 element-plus.scss,页面按需自行挂载后设计层规则自动命中。右侧工具三件套源码实证:刷新 svg-icon + el-dropdown 密度(宽松/默认/紧凑)+ el-popover 列设置",
78
78
  "style_scope": ".vts-page-card",
79
79
  "forms": {
80
80
  "with_tools": "工具三件套(刷新/密度/列设置)齐全——设计规范 list-page §4 要求三件套缺一不可(ReTableBar 出厂已具备,此为默认形态)",
81
+ "tools_labeled": "边框文字钮形态:三件套从「纯图标 + 竖分隔线」换装为「图标 + 文案」的 1px 边框钮(高/圆角/字号走 tools_* 字段族,hover 文字与边框变主色)——出厂 svg-icon 结构保留,文案由业务侧 slots 或 AI 增量插入 span 补齐(结构增量,不删原节点);suppress 必含 tool_divider(出厂两条 el-divider direction=vertical 与边框钮观感互斥,不收口 = 图标钮与边框钮混排)",
81
82
  "left_batch": "左侧批量操作区(批量删除红底 + 批量导出白底,未选中置灰 opacity 0.4 + cursor not-allowed)——经 slots.buttons 插入,出厂多数 demo 未用"
82
83
  },
84
+ "suppress_rules": {
85
+ "tools_labeled": ["tool_divider"]
86
+ },
87
+ "field_forms": {
88
+ "tools_btn_height": ["tools_labeled"],
89
+ "tools_btn_radius": ["tools_labeled"],
90
+ "tools_font_size": ["tools_labeled"],
91
+ "tools_icon_size": ["tools_labeled"],
92
+ "tools_text_color": ["tools_labeled"],
93
+ "tools_hover_color": ["tools_labeled"],
94
+ "tools_btn_gap": ["tools_labeled"]
95
+ },
83
96
  "css_vars": {
84
97
  "gap": "--action-bar-gap",
85
98
  "padding_y": "--action-bar-padding-y"
@@ -89,24 +102,34 @@
89
102
  "btn_radius": "border-radius",
90
103
  "btn_font_size": "font-size",
91
104
  "batch_delete_bg": "background-color",
92
- "batch_disabled_opacity": "opacity"
105
+ "batch_disabled_opacity": "opacity",
106
+ "tools_btn_height": "height",
107
+ "tools_btn_radius": "border-radius",
108
+ "tools_font_size": "font-size",
109
+ "tools_btn_gap": "gap"
93
110
  },
94
111
  "anchors": [
95
- { "kind": "class", "layer": "inner", "target": ".vts-action-bar" },
96
- { "kind": "class", "layer": "inner", "target": ".vts-action-bar .el-button" },
112
+ { "kind": "class", "layer": "inner", "target": ".vts-page-card .flex-between > .flex-end" },
113
+ { "kind": "class", "layer": "inner", "target": ".vts-page-card .flex-end .el-button" },
114
+ { "kind": "class", "layer": "inner", "target": ".vts-page-card .flex-end .vts-tool-btn" },
97
115
  { "kind": "css_var", "target": "--action-bar-gap" },
98
116
  { "kind": "css_var", "target": "--action-bar-padding-y" }
99
117
  ],
118
+ "sub_selectors": {
119
+ "$comment": "工具三件套部件点位(bar.vue JSX 实证):刷新 svg-icon[name=refresh]、密度 el-dropdown>svg-icon[name=collapse]、列设置 el-popover reference 槽 svg-icon[name=tools];出厂右侧容器 = 工具栏行 .flex-between 的 .flex-end 子容器。form=tools_labeled 时按钮壳与文案按此定位增量插入",
120
+ "tools": ".vts-page-card .flex-between > .flex-end",
121
+ "tool_btn": ".vts-page-card .flex-end .vts-tool-btn"
122
+ },
100
123
  "preconditions": [
101
- "挂载点唯一合法位置 = ReTableBar 内部工具栏行(出厂已挂 .vts-action-bar)。禁止把 .vts-action-bar 挂到 ReTableBar 根节点:根节点是「工具栏行 + 表格 + 分页」的纵向复合容器,而 .vts-action-bar flex-direction: row —— 挂根节点会让工具栏与表格并排、表格被挤压、tableHeight 实测失准(已发生的事故,勿复发)。",
102
- "本条目 sid 锚点只能落在 ReTableBar 根节点(页面透传 attrs 到根),样式落点是内层工具栏行 —— anchors layer 必须区分 host / inner,verify 抽取按 anchors.target 而非 sid 所在节点。",
124
+ "挂载点实证(2026-09-20 复核修正):出厂右侧容器是 .flex-between > .flex-end(真实存在),历史锚点 .vts-action-bar 全项目无模板挂载(仅样式定义、零消费者)——设计层规则写到 .vts-action-bar 上全部落空(工具钮裸排事故根因)。词典 selector 必须锚定源码实证存在的 DOM;「应该在」的点位未经 structure_ops 补挂 = 不存在。",
125
+ "本条目样式落点是 ReTableBar 内部工具栏行内容器——禁止把容器级样式(flex-direction 等)写到 ReTableBar 根节点:根节点是「工具栏行 + 表格 + 分页」的纵向复合容器,写根节点会让工具栏与表格并排、表格被挤压、tableHeight 实测失准(已发生的事故,勿复发)。",
103
126
  "gap / padding 的 CSS 变量必须在项目样式中存在消费者(var() 引用),只定义不消费 = 死通道。--action-bar-padding-y 供 padding-block,最多两个值(如 20px 8px),给三个值整条声明即失效。"
104
127
  ],
105
128
  "priority": {
106
129
  "strategy": "scope_qualified",
107
- "reason": "出厂 .vts-action-bar 类位 1,作用域限定(html body .vts-table-area .vts-action-bar)即可覆盖;但不得把样式写到 ReTableBar 根节点上。"
130
+ "reason": "出厂 .flex-end 类位 1(uno 约定类,无 scoped 竞争),作用域限定到 .vts-page-card 前缀即可覆盖;但不得把容器级样式写到 ReTableBar 根节点上。"
108
131
  },
109
- "$note": "设计意图(list-page §4/§11.25-11.36):flex 两端对齐、pad 20px 20px 8px、左侧批量(未选中置灰 opacity 0.4 + not-allowed)、右侧工具三件套缺一不可。分工:本条目管容器布局与置灰;批量删除红色按钮本体走 component.button.danger。demo 噪音按钮去留属业务层,交付说明申报后由项目侧业务闭环移除"
132
+ "$note": "设计意图(list-page §4/§11.25-11.36):flex 两端对齐、pad 20px 20px 8px、左侧批量(未选中置灰 opacity 0.4 + not-allowed)、右侧工具三件套缺一不可。分工:本条目管容器布局与置灰;批量删除红色按钮本体走 component.button.danger。demo 噪音按钮去留属业务层,交付说明申报后由项目侧业务闭环移除。⚠tools_labeled 形态(效果图口径):三件套为「图标16px+文案」32px 边框钮(border 1px、radius 6px、gap 6px、hover 文字/边框主色),与出厂「纯图标20px+竖分隔线」是**形态互斥**——声明 tools_labeled 必须同时 suppress tool_divider(隐藏出厂 el-divider),并按结构增量给每个 svg-icon 套 .vts-tool-btn 按钮壳 + 文案 span(结构施工,design-apply 底线二豁免)。"
110
133
  },
111
134
  "admin.content.table.operation_column": {
112
135
  "type": "element",
@@ -123,6 +146,12 @@
123
146
  "divider_color": "--re-op-divider-color",
124
147
  "divider_gap": "--re-op-divider-gap"
125
148
  },
149
+ "field_forms": {
150
+ "slot_width": ["text_link", "icon_text"]
151
+ },
152
+ "data_channels": {
153
+ "slot_width": "等宽槽宽度通道(⚠2026-09-20 语义重写·动态优先):**槽宽默认由组件按按钮文案动态自适应**(ReTableOperate 的 --re-op-slot-w 在 slotMinWidth 省略时走 resolveOperateSlotWidthPx 自动推算),变更集**不声明 slot_width、业务页不传 slot-min-width** ——设计只声明 divider 几何(width/height/margin_x),槽宽随内容走、divider_margin_x 对称由槽内居中兑现(历史事故①:声明 60px 钉死槽宽违反动态优先;历史事故②:槽内 flex-start 使短文案按钮残白全堆到分割线前侧 → 线前 40px/线后 8px 视觉不等距,第五轮截图实证)。仅当设计效果图确实要求**统一槽宽**(如所有操作列等宽对齐网格)时例外:变更集显式声明 slot_width → 施工落业务页 prop(结构通道),禁 CSS 仿冒;无此明确设计意图 = 保持动态。等宽槽本体是对齐机制(槽边界保证行间第 N 项对齐),机制本身禁 CSS 收口 width:auto"
154
+ },
126
155
  "$note": "操作列分割线为**条件通道**:出厂组件当前**没有**分割线元素——声明 divider_color / divider_gap 时,必须先按 structure_ops 在 ReTableOperate 模板按钮间 add_node `<span class=\"re-table-operate__divider\">`(中性元素,几何与颜色全走令牌:width/height/gap/color 分别挂 --re-op-divider-width/height/gap/color,出厂缺省 0 不可见),并同步给 resolveOperateColumnWidth 公式补分割线占位((limit-1)×(width+2×gap)),否则最后按钮被裁——元素与公式缺一即申报补通道失败。列宽由组件公式推算,业务页禁止写死 width——列宽属业务层,设计侧不声明列宽(操作列除外)。",
127
156
  "custom_style_map": {
128
157
  "align": "text-align",
@@ -132,6 +161,7 @@
132
161
  "divider_width": "width",
133
162
  "divider_height": "height",
134
163
  "divider_margin_x": "margin-inline",
164
+ "divider_color": "background-color",
135
165
  "more_arrow_size": "font-size",
136
166
  "more_arrow_gap": "gap",
137
167
  "vertical_align": "vertical-align",
@@ -145,9 +175,17 @@
145
175
  { "kind": "class", "layer": "host", "target": ".vts-table-area .el-table__cell:has(.re-table-operate)" },
146
176
  { "kind": "class", "layer": "inner", "target": ".vts-table-area .el-table__cell:has(.re-table-operate) > .cell" }
147
177
  ],
178
+ "sub_selectors": {
179
+ "$comment": "divider_* 字段按前缀路由到分割线元素本体(出厂平铺按钮间 v-if=\"i > 0\" 已有 .re-table-operate__divider;「更多」槽前无元素须按 structure_ops 增量补挂)",
180
+ "divider": ".vts-page-card .re-table-operate .re-table-operate__divider"
181
+ },
148
182
  "preconditions": [
149
183
  "按钮之间的间距由分割线元素的左右 margin 承载,不是按钮自身 gap:必须先存在分割线元素(ReTableOperate 默认不渲染 divider,需按 structure_ops 补槽位),divider_width / divider_height / divider_margin_x 才有落点——这是「操作列按钮间距没还原」的根因。",
150
- "「纯文字无图标」是数据侧结论:图标来自业务页列定义的 operateButtons[].icon,只写 form=text_link 而不移除数据里的 icon = 未落地(改 CSS 不算)。"
184
+ "「纯文字无图标」是数据侧结论:图标来自业务页列定义的 operateButtons[].icon,只写 form=text_link 而不移除数据里的 icon = 未落地(改 CSS 不算)。",
185
+ "「更多」折叠槽前必须有分割线:组件出厂模板只在平铺按钮之间渲染 divider(v-if=\"i > 0\"),平铺末位与「更多」槽之间无分割线元素——效果图口径是每个按钮之间都要有且等距,施工时须按 structure_ops 给「更多」槽前补 divider(或把「更多」并入 v-for 让 i 计数覆盖),缺线 = 最后一段间距失真(历史缺口:查看│编辑 更多 之间无线)。",
186
+ "等宽槽(--re-op-slot-w)是行间纵向对齐的唯一机制:设计层禁止覆写 .re-table-operate__slot 宽度为 auto,CSS 收口 auto = 各行按钮漂移、竖线间距不一致(历史事故:孔雀绿 160px 紧凑型被 CSS 收口 auto)。",
187
+ "⚠槽宽三态判定(2026-09-20 通用编排,与 data_channels.slot_width 配套):①**默认=动态自适应**——组件按最长文案自动推算槽宽,变更集不声明 slot_width、业务页不传 slot-min-width(设计只管 divider 几何;钉死固定值 = 短文案按钮残白堆到分割线前侧、线距视觉不等,60px 事故实证);②**例外=统一槽宽**——设计效果图明确所有操作列等宽网格时才声明 slot_width,施工落业务页 prop 禁 CSS 仿冒;③**机制不可 CSS 化**——无论哪态,禁在设计层覆写 __slot 宽度。判据一句话:**槽宽是内容推算的机制值,不是设计声明值;设计声明divider,不声明槽宽**。",
188
+ "⚠对齐语义两级契约(2026-09-20 第五轮截图复核,替代第四轮单级结论):**容器级 flex-start + 槽内 center**。①容器级(.re-table-operate)必须 justify-content: flex-start——按钮数不同的行(2 项 vs 3 项)整组从左起点排布,行组不漂移;容器级 center 是历史误结论,禁用(各行各自居中 → 槽边界漂移、竖线竖向错位)。②槽内级(.re-table-operate__slot 与槽内 .el-button)必须 justify-content: center——等宽槽宽按最长文案推算(如「更多」3 字=60px),2 字按钮(28px)靠左会把 32px 残白全堆到分割线前侧 → 线前 40px/线后 8px 视觉不等距(第五轮截图实证:竖线贴右按钮);居中后 divider_margin_x 两侧对称才成立。行间第 N 项对齐由**等宽槽边界**保证(槽宽全行统一),不受槽内对齐影响——第四轮「槽内也 flex-start」是把容器级结论过度泛化到槽级,勿复发。"
151
189
  ],
152
190
  "priority": {
153
191
  "strategy": "scope_qualified",
@@ -215,6 +253,7 @@
215
253
  ],
216
254
  "preconditions": [
217
255
  "出厂 element-plus.scss 已给 .el-table .el-table__cell 设了 padding(var(--vts-space-xs) 0),本条目字段必须落 host 层才能覆盖它;写在 inner 层只改内层文本盒,观感上「改了没反应」。",
256
+ "几何语境随值迁移:padding/padding_x/padding_y 落宿主 td 时必须同步声明 suppress_inner(内层 .cell padding 清零口径)——出厂内层 .cell 自带 padding 0 12px,不清零则两层之和 ≠ 设计值(28px/侧 → 列挤坏)。",
218
257
  "行高由「单元格 padding + 内容行高」共同决定;声明行高档位时必须同时给出 padding 取值,只改其一会得到非预期行高。"
219
258
  ],
220
259
  "priority": {
@@ -356,6 +395,7 @@
356
395
  ],
357
396
  "preconditions": [
358
397
  "行高由「单元格 padding + 内容行高」共同决定(见 admin.content.table.cell)——声明行高档位时必须同时给出 padding 取值,只改其一会得到非预期行高。",
398
+ "几何语境随值迁移:padding_x 落宿主 th/td 时必须同步声明 suppress_inner(内层 .cell padding 清零口径)——出厂内层 .cell 自带 padding 0 12px,不清零则两层之和 ≠ 设计值。",
359
399
  "出厂密度档位由 ReTableBar 的 size(large/default/small,源码实证 bar.vue 的宽松/默认/紧凑三项)驱动;设计侧只声明档位意图(form.density),具体 padding 由本项目按常规取值落定,不得照抄效果图某实例数据的行高。"
360
400
  ],
361
401
  "priority": {
@@ -55,10 +55,25 @@
55
55
  "style_scope": ".sidebar-container",
56
56
  "forms": {
57
57
  "pill": "整块胶囊底色高亮(sidebar.scss 消费 --vts-theme-menu-active-bg,带 !important)。suppress 必含 right_indicator_legacy——出厂 element-plus.scss 的 .el-menu-item.is-active{border-right:var(--menu-active-border-right)} 是活通道,不收口会出现左右两根竖条并存",
58
- "indicator_left": "左侧竖条指示:先在本作用域覆写 --vts-theme-menu-active-bg: transparent 关胶囊,再于设计层新建 ::after 竖条(left:0)消费 --vts-theme-menu-active-before。suppress 必含 right_indicator_legacy",
59
- "indicator_right": "右侧竖条指示:同上关胶囊后 ::after 挂 right:0。出厂右竖条通道 --menu-active-border-right(element-plus.scss 消费、design-tokens.scss 定义为 3px solid 品牌色)是活通道,可复用也可收口后自建,二选一必须显式声明",
60
- "indicator_underline": "底部横线指示:关胶囊后 ::after 挂 bottom:0(宽取 100% 或指定宽度,厚取 indicator_thickness)。suppress 必含 right_indicator_legacy",
61
- "none": "无指示:胶囊与竖条均关闭,仅文字变色。suppress 必含 right_indicator_legacy"
58
+ "indicator_left": "仅左竖条指示、无胶囊:先在本作用域覆写 --vts-theme-menu-active-bg: transparent 关胶囊,再于设计层新建 ::after 竖条消费 --vts-theme-menu-active-before。suppress 必含 right_indicator_legacy",
59
+ "indicator_right": "仅右竖条指示、无胶囊:同上关胶囊后 ::after 挂 right:0。出厂右竖条通道 --menu-active-border-right(element-plus.scss 消费、design-tokens.scss 定义为 3px solid 品牌色)是活通道,可复用也可收口后自建,二选一必须显式声明",
60
+ "indicator_underline": "仅底部横线指示、无胶囊:关胶囊后 ::after 挂 bottom:0(宽取 100% 或指定宽度,厚取 indicator_thickness)。suppress 必含 right_indicator_legacy",
61
+ "none": "无指示:胶囊与竖条均关闭,仅文字变色。suppress 必含 right_indicator_legacy",
62
+ "pill_indicator_left": "胶囊底色 + 左竖条**并存**复合形态(设计实证:效果图 .menu-item.active 同时声明 background:var(--primary) 与 ::before 竖条):胶囊照 pill 走 --vts-theme-menu-active-bg;竖条照 indicator_left 走 --vts-theme-menu-active-before(设计层新建 ::after/::before 消费者)。suppress 必含 right_indicator_legacy(出厂右竖条必须收口)。⚠菜单项宿主盒必须声明 margin 横向内缩(custom_style_map margin_x),否则竖条负偏移落进容器外被 overflow 裁掉——竖条偏移值以「容器 padding − 竖条 left 偏移」为语境换算,禁止照搬外部效果图常量"
63
+ },
64
+ "suppress_rules": {
65
+ "pill": ["right_indicator_legacy"],
66
+ "indicator_left": ["right_indicator_legacy"],
67
+ "indicator_right": ["right_indicator_legacy"],
68
+ "indicator_underline": ["right_indicator_legacy"],
69
+ "none": ["right_indicator_legacy"],
70
+ "pill_indicator_left": ["right_indicator_legacy"]
71
+ },
72
+ "field_forms": {
73
+ "active_indicator_width": ["indicator_left", "indicator_right", "indicator_underline", "pill_indicator_left"],
74
+ "active_indicator_thickness": ["indicator_left", "indicator_right", "indicator_underline", "pill_indicator_left"],
75
+ "active_indicator_style": ["indicator_left", "indicator_right", "indicator_underline", "pill_indicator_left"],
76
+ "menu_active_bg": ["pill", "pill_indicator_left"]
62
77
  },
63
78
  "css_vars": {
64
79
  "menu_bg": "--vts-theme-menu-bg",
@@ -78,6 +93,7 @@
78
93
  "active_indicator_thickness": "height",
79
94
  "active_indicator_style": "border-radius",
80
95
  "padding_y": "padding-block",
96
+ "margin_x": "margin-inline",
81
97
  "font_size": "font-size",
82
98
  "font_weight": "font-weight",
83
99
  "text_color": "color"
@@ -96,7 +112,7 @@
96
112
  "strategy": "deep_qualified",
97
113
  "reason": "出厂 sidebar.scss 的胶囊规则用 !important,设计层需以同等或更高权重覆盖。"
98
114
  },
99
- "$note": "侧栏配色挂载点为 html[data-theme='xxx'](theme.scss 8 套预设);设计输出深色侧栏 → Theme=default + Version++(useTheme 规则),浅色 → Theme=light。⚠文本完整性红线:菜单项名称必须完整显示(el-menu 自带 ellipsis 折叠溢出菜单),禁止改 menu 文本截断/隐藏策略;item_height 加大时确认行高不挤压文字。宽度类改动(--sidebar-width)后必须目视验证长菜单名显示。⚠激活态出厂中性基线(禁止把某张图的结论写进源码):出厂形态 = pill 胶囊高亮;--vts-theme-menu-active-before 出厂默认 transparent 且**全项目无消费者**(死通道)。因此 form=indicator_* 时必须在本条目 style_scope 内成对落地两件事才算施工完成——① 覆写 --vts-theme-menu-active-bg: transparent 关胶囊;② 在设计层文件(不可写出厂 sidebar.scss)新建 ::after 消费者引用 var(--vts-theme-menu-active-before)。缺任一项 = 「映射了却没效果」。所有几何参数(方位/粗细/长度/圆角/内缩)取自变更集 style 字段,禁止写死 left:0 / 3px 等常量。"
115
+ "$note": "侧栏配色挂载点为 html[data-theme='xxx'](theme.scss 8 套预设);设计输出深色侧栏 → Theme=default + Version++(useTheme 规则),浅色 → Theme=light。⚠文本完整性红线:菜单项名称必须完整显示(el-menu 自带 ellipsis 折叠溢出菜单),禁止改 menu 文本截断/隐藏策略;item_height 加大时确认行高不挤压文字。宽度类改动(--sidebar-width)后必须目视验证长菜单名显示。⚠激活态出厂中性基线(禁止把某张图的结论写进源码):出厂形态 = pill 胶囊高亮;--vts-theme-menu-active-before 出厂默认 transparent 且**全项目无消费者**(死通道)。因此 form=indicator_* / pill_indicator_left 时必须在本条目 style_scope 内成对落地两件事才算施工完成——① 竖条消费者(form 不含 pill 时另需覆写 --vts-theme-menu-active-bg: transparent 关胶囊);② 在设计层文件(不可写出厂 sidebar.scss)新建 ::after/::before 消费者引用 var(--vts-theme-menu-active-before)。缺任一项 = 「映射了却没效果」。所有几何参数(方位/粗细/长度/圆角/内缩)取自变更集 style 字段,禁止写死 left:0 / 3px 等常量。⚠field_forms 硬闸(parser G7 机检):style 声明的字段必须被当前 form 消费——如 active_indicator_* 只在 indicator_* / pill_indicator_left 形态下有消费者,声明 pill 却带竖条字段 = 字段被静默丢弃(历史事故:孔雀绿变更集声明 pill + active_indicator_*,设计层按 pill 施工把竖条令牌置透明,效果图要求的左竖条消失)。"
100
116
  },
101
117
  "admin.layout.sidebar.collapse_trigger": {
102
118
  "type": "element",
@@ -107,6 +123,10 @@
107
123
  "bottom_bar": "SidebarLeftCollapse(底部条,出厂默认)",
108
124
  "center_edge": "SidebarCenterCollapse(侧缘中点,折叠/悬停时显示)"
109
125
  },
126
+ "suppress_rules": {
127
+ "center_edge": ["bottom_bar"],
128
+ "bottom_bar": ["center_edge"]
129
+ },
110
130
  "css_vars": {
111
131
  "bar_height": "--sidebar-collapse-bar-height"
112
132
  },
@@ -190,6 +210,9 @@
190
210
  "icon_only": "出厂现状:纯图标触发器(.language-trigger > .language-icon),下拉菜单选语言",
191
211
  "inline_switch": "并排切换形态(下拉项平铺为触发器旁的切换组)"
192
212
  },
213
+ "suppress_rules": {
214
+ "inline_switch": ["icon_only"]
215
+ },
193
216
  "css_vars": {
194
217
  "icon_color": "--el-text-color-regular",
195
218
  "hover_bg": "--el-fill-color-light"
@@ -216,6 +239,9 @@
216
239
  "sidebar_bottom": "出厂现状:侧栏底部折叠条(SidebarLeftCollapse)",
217
240
  "sidebar_center": "侧栏侧缘中点(SidebarCenterCollapse)"
218
241
  },
242
+ "suppress_rules": {
243
+ "header_inline": ["sidebar_bottom", "sidebar_center"]
244
+ },
219
245
  "css_vars": {
220
246
  "icon_color": "--el-text-color-regular",
221
247
  "hover_bg": "--el-fill-color-light"
@@ -273,6 +299,11 @@
273
299
  "avatar_only": "仅头像",
274
300
  "name_only": "仅用户名"
275
301
  },
302
+ "suppress_rules": {
303
+ "greeting_name": ["avatar_name"],
304
+ "avatar_only": ["avatar_name"],
305
+ "name_only": ["avatar_name"]
306
+ },
276
307
  "sub_selectors": {
277
308
  "$comment": "avatar/name 为点位内部部件,不设独立语义 ID(符合 4 段 ID 上限);样式字段按前缀路由到对应选择器。form 含 greeting 时新增节点统一用 class 前缀 vts-header-greeting / vts-header-username(插入前先查重名)",
278
309
  "avatar": ".navbar-right .el-dropdown-link img",
@@ -315,6 +346,18 @@
315
346
  "card": "卡片页签(ShowModel=card):圆角卡片底 + --vts-tag-card-* 整组令牌 + 关闭钮 hover 才显现(出厂 opacity:0)",
316
347
  "flat": "平面页签(设计规范口径,出厂无此形态):ShowModel 仍设 card(复用 card 结构 = 图标槽 + 关闭钮 + resolveCardTabIcon 数据驱动),平面观感全部由设计层覆写达成 —— ① --vts-tag-card-idle-bg/hover-bg/active-bg 置 transparent;② --vts-tag-card-radius 与 --vts-tag-card-gap 置 0;③ 激活态仅文字/图标变主色 + font-weight 500,不加 border-bottom / box-shadow;④ 关闭钮 opacity 覆写为常显值(出厂 card 为 0);⑤ 首页页签项宽=高=--vts-tag-card-height 居中呈方块;⑥ 页签间 1px×100% 贯穿分割线(card 无该元素,需设计层 ::after/::before 新建)。几何与色值一律取自变更集 style 字段,禁止写死常量"
317
348
  },
349
+ "suppress_rules": {
350
+ "flat": [
351
+ "card_idle_bg",
352
+ "card_active_bg",
353
+ "card_hover_bg",
354
+ "card_radius",
355
+ "card_gap",
356
+ "card_active_text",
357
+ "card_idle_text",
358
+ "card_close_color"
359
+ ]
360
+ },
318
361
  "css_vars": {
319
362
  "height": "--tabs-height",
320
363
  "chrome_inset_top": "--vts-tag-chrome-inset-top",
@@ -339,13 +382,16 @@
339
382
  { "kind": "class", "layer": "inner", "target": ".tags-view .scroll-container" }
340
383
  ],
341
384
  "preconditions": [
342
- "容器内边距出厂由组件样式给出(非 0),设计要求「内边距归零、首页页签与折叠按钮左对齐」时必须显式声明 padding_inline,不声明即保留出厂值(这是「首页页签不贴左缘」的根因)。"
385
+ "容器内边距出厂由组件样式给出(非 0),设计要求「内边距归零、首页页签与折叠按钮左对齐」时必须显式声明 padding_inline,不声明即保留出厂值(这是「首页页签不贴左缘」的根因)。",
386
+ "⚠ShowModel 运行时缓存坑:showModel 经 responsive-storage 持久化于 localStorage(命名空间 responsive-),platform-config.json 改 ShowModel 后**旧缓存仍生效**——项目侧施工 ShowModel 时必须同步清 localStorage(或 Version++ 触发响应式存储失效),否则模板代码已换、运行时仍渲染旧形态,页签「样式完全不对」多半是缓存残留而非映射失败。施工后验证口径:浏览器 DevTools Application→Local Storage 清 responsive-* 键后刷新再比对。",
387
+ "⚠ShowModel 三分支运行时兜底(2026-09-20 截图复核):useTags.ts 实证 showModel 读取链 = localStorage → fallback **'smart'**(platform-config 的 ShowModel 只在 storage 初始化时生效);而模板 DOM 只实现 chrome / card 两分支(v-if / v-else-if),smart 落入 v-else 裸分支——**无 .card-item / .card-tab 类**,锚定 card 结构的设计层规则全部不命中,仅锚定 .scroll-item 的规则(分割线等)命中 → 同一页签栏「半新半旧」混合观感(截图实证:底色块残留 + 分割线在)。因此 form=flat 施工必须**三分支全覆盖**:① 锚定 .card-item / .card-tab 的规则之外,同步给出锚定 .scroll-item 直系(不限形态类)的兜底规则(盒几何/底色透明/激活态),或在词典 preconditions 声明「ShowModel 须确保运行时为 card」(清缓存 + platform-config 双写)——二选一必须做,只写 card 锚点 = smart 残余分支裸奔。",
388
+ "⚠ShowModel 三件套硬闸(2026-09-20 复盘升级,G4 T 通道施工必须项):仅改 platform-config.ShowModel **不生效**——showModel 读 localStorage,而 localStorage 只在 platform-config.Version 变化时经 responsive.ts invalidateStorageIfConfigVersionChanged 重灌(历史事故:孔雀绿还原只改了 ShowModel=card,Version 停留 0.0.5,浏览器残留 chrome 形态缓存 → card/smart 兜底规则双双不命中、页签零变化)。form 涉及页签形态时施工三件套缺一不可:① platform-config.ShowModel = form 对应值(flat→card);② platform-config.Version 自增一位(0.0.5→0.0.6,触发缓存失效重灌);③ 渲染取证断言 .scroll-item.card-item 实际挂载(DevTools 或 DOM 断言),不许只看代码改没改。漏②=代码对但运行时仍旧形态(本轮实证);漏③=无渲染证据自嗨绿。"
343
389
  ],
344
390
  "priority": {
345
391
  "strategy": "deep_qualified",
346
392
  "reason": "容器内边距规则写在组件 scoped 内,与设计层同权重,需显式提权。"
347
393
  },
348
- "$note": "showModel 由 platform-config.json ShowModel 字段切换;smart/line 模板未实现,映射层按 chrome/card 两形态处理"
394
+ "$note": "showModel 由 platform-config.json ShowModel 字段声明、但运行时实值由 localStorage 决定(Version++ 才重灌)——施工按 preconditions 三件套执行;smart/line 模板未实现,映射层按 chrome/card 两形态处理"
349
395
  },
350
396
  "admin.layout.tabs.home_tab": {
351
397
  "type": "element",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qilitt-mickey/vue3-temp-skill",
3
- "version": "1.1.48",
3
+ "version": "1.1.49",
4
4
  "type": "module",
5
5
  "description": "Vue 3 企业级中后台项目开发规范技能包 — core-kernel 架构、按需功能模块",
6
6
  "bin": {
@@ -26,7 +26,7 @@
26
26
  }
27
27
  },
28
28
  { "id": "admin.layout.header", "style": { "bg": "#FFFFFF" } },
29
- { "id": "admin.layout.header.lang_switch", "form": "inline_switch" },
29
+ { "id": "admin.layout.header.lang_switch", "form": "inline_switch", "suppress": ["icon_only"] },
30
30
  {
31
31
  "id": "admin.layout.header.user_profile",
32
32
  "style": { "avatar_width": "28px", "avatar_height": "28px", "avatar_radius": "全圆角", "name_font_size": "14px", "name_font_weight": "中等" }
@@ -1,5 +1,5 @@
1
1
  {
2
- "$comment": "回归夹具 · 登记表样例(不是真实交付件)。6 条登记项示范粒度与字段口径:4 条 always(恒存在,必登)、1 条 pending(点位待补词典)、1 条条件项。用于 M/N 两步:M 校验覆盖率重算能报出欠声明并 exit 1,N 校验通道核验能跑通。",
2
+ "$comment": "回归夹具 · 登记表样例(不是真实交付件)。6 条登记项示范粒度与字段口径:4 条 always(恒存在,必登)、2 条条件项。用于 M/N 两步:M 校验覆盖率重算能报出欠声明并 exit 1,N 校验通道核验能跑通。⚠ selection_column / cell 等点位词典已补建通道,本夹具不再示范 pending 标注(pending 演示见 registry-bad.json 的硬缺口反例)。",
3
3
  "schema": "design.restore-registry/1.0",
4
4
  "design_scheme": "default",
5
5
  "scene": "admin",
@@ -9,7 +9,7 @@
9
9
  { "rule": "sample#2", "group": "样例·表格区", "sid": "admin.content.table.cell", "dims": ["padding", "padding_y"], "channel": ["style", "layer"], "source": "both", "always": true },
10
10
  { "rule": "sample#3", "group": "样例·金额列", "sid": "admin.content.table.cell_amount", "dims": ["align"], "channel": ["style", "structure_ops"], "source": "spec", "always": true },
11
11
  { "rule": "sample#4", "group": "样例·状态列", "sid": "admin.content.table.cell_status", "dims": ["form", "bg"], "channel": ["form", "structure_ops"], "source": "spec", "always": true },
12
- { "rule": "sample#5", "group": "样例·选择列", "sid": "admin.content.table.selection_column", "dims": ["column_width"], "channel": ["style"], "source": "spec", "always": false, "pending": true },
12
+ { "rule": "sample#5", "group": "样例·选择列", "sid": "admin.content.table.selection_column", "dims": ["text_align", "padding"], "channel": ["style"], "source": "spec", "always": false },
13
13
  { "rule": "sample#6", "group": "样例·状态列", "sid": "component.tag.status", "dims": ["bg_color"], "channel": ["style"], "source": "spec", "always": false }
14
14
  ]
15
15
  }
package/parser/index.js CHANGED
@@ -45,6 +45,13 @@ const COMPOSITE_VALUE_RE = /\s/;
45
45
  const LAYER_VALUES = ["host", "inner"];
46
46
  /** 语义 ID 严格格式(apply_via 锚点名须符合) */
47
47
  const ID_RE_STRICT = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*){1,3}$/;
48
+ /** 内层盒属性族(changeset-schema §layer):这些属性落在 inner 才生效(G5 几何语境闸使用) */
49
+ const INNER_ONLY_PROPS = new Set(["white_space", "text_overflow", "overflow", "word_break", "text_align", "line_height", "vertical_align", "nowrap", "text_align_last"]);
50
+ /** 几何字段(宿主内边距/外边距族):落 host 时与出厂内层盒自带 padding 叠加,必须同步清零内层(G5) */
51
+ const GEOMETRY_PAD_RE = /^(padding|padding_x|padding_y|padding_inline|padding_block|margin|margin_x|margin_y|margin_inline|margin_block)$/;
52
+ /** 登记表里唯一允许的「非词典点位」:全局令牌通道(对应变更集顶层 global 节点,无 css selector 故不入词典)。
53
+ * ⚠ 必须在 main() 调用前初始化(registry/match 命令运行时即访问;下移会触发 TDZ ReferenceError) */
54
+ const GLOBAL_PSEUDO_ID = "global";
48
55
 
49
56
  /** audit:审计时跳过的目录(依赖 / 产物 / 缓存) */
50
57
  const AUDIT_SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", ".output", ".vite", ".nuxt", "coverage", "public"]);
@@ -271,6 +278,7 @@ function matchOne(file, dataset, batchSummary, opts = {}) {
271
278
  const plan = [];
272
279
  const pending = [];
273
280
  const fieldPending = []; // 字段级待补充:ID 命中但 style 字段两层映射均未命中
281
+ const gateViolations = []; // 词典驱动硬闸:suppress 收口(G1)+ 几何语境(G5),违规 = 协议违规同级阻断
274
282
 
275
283
  // global 令牌翻译(design-apply 第 3 步:走 basic/ 词典,主色另需 setEpThemeColor 链路)
276
284
  if (changeset.global && Object.keys(changeset.global).length > 0) {
@@ -349,12 +357,18 @@ function matchOne(file, dataset, batchSummary, opts = {}) {
349
357
  if (p.level === 1) {
350
358
  console.log(`\n[L1 完整命中] ${p.id}`);
351
359
  checkFormEnum(p.entry, p.change);
360
+ checkSuppress(p.change, p.entry, p.id, gateViolations);
361
+ checkGeometryContext(p.change, p.entry, p.id, gateViolations);
362
+ checkFieldForms(p.change, p.entry, p.id, gateViolations);
352
363
  printApplyPlan(p.entry, p.change, p.id, index);
353
364
  printStyleTranslation(p.entry, p.change, p.id, fieldPending, valueDict);
354
365
  }
355
366
  else if (p.level === 2) {
356
367
  console.log(`\n[L2 父模块命中] ${p.id} → 继承 ${p.parent}(${p.entry.selector ?? p.entry.component ?? "?"})`);
357
368
  console.log(` 结构:定位父容器按相对位置增量插入;样式:复用父模块映射 + 全局组件映射`);
369
+ checkSuppress(p.change, p.entry, p.id, gateViolations);
370
+ checkGeometryContext(p.change, p.entry, p.id, gateViolations);
371
+ checkFieldForms(p.change, p.entry, p.id, gateViolations);
358
372
  printApplyPlan(p.entry, p.change, `${p.id}(继承 ${p.parent})`, index);
359
373
  printStyleTranslation(p.entry, p.change, `${p.id}(继承 ${p.parent})`, fieldPending, valueDict);
360
374
  }
@@ -383,6 +397,18 @@ function matchOne(file, dataset, batchSummary, opts = {}) {
383
397
  const l3 = plan.filter(p => p.level === 3).length;
384
398
  console.log(`\n汇总:L1=${l1}(还原度 100%) L2=${l2}(85-90%) L3=${l3}(70-80%,补映射后可达 100%)`);
385
399
 
400
+ // 词典驱动硬闸(suppress 收口 / 几何语境):违规 = 交付不完整,与禁词同级阻断
401
+ if (gateViolations.length > 0) {
402
+ console.error(`\n✖ 词典硬闸违规(共 ${gateViolations.length} 处 → 拒绝执行):`);
403
+ for (const v of gateViolations)
404
+ console.error(` ✘ ${v}`);
405
+ console.error(` 处置:回变更集补齐 suppress / suppress_inner / layer 声明后重跑;判据均来自词典条目自身,不接受「我观感上对了」。`);
406
+ batchSummary?.invalid.push(path.basename(file));
407
+ if (!batchSummary)
408
+ process.exit(1);
409
+ return;
410
+ }
411
+
386
412
  // 汇入跨文件统计(项目级批量模式)
387
413
  if (batchSummary) {
388
414
  batchSummary.l1 += l1;
@@ -534,6 +560,10 @@ function printStyleTranslation(entry, change, idLabel, fieldPending, valueDict)
534
560
  const layerLabel = spot.layer ? ` · ${spot.layer} 层` : "";
535
561
  lines.push(` . ${field}: ${valueLabel} → 自定义 ${entry.custom_style_map[field]}(@ ${spot.target}${layerLabel})`);
536
562
  }
563
+ else if (entry.data_channels && entry.data_channels[field]) {
564
+ // 数据/结构通道:值走业务页 prop / 数据 / 模板(禁 CSS 仿冒),非缺口
565
+ lines.push(` . ${field}: ${valueLabel} → 数据/结构通道(${entry.data_channels[field].slice(0, 60)}…)`);
566
+ }
537
567
  else {
538
568
  unmatched.push(field);
539
569
  continue;
@@ -705,6 +735,25 @@ function checkOracle(changeset, plan, valueDict) {
705
735
  const hiddenOnly = new Set(
706
736
  plan.filter(p => p.change && p.change.visible === false).map(p => p.id),
707
737
  );
738
+ // 实物闭合兜底(G3):带登记表时由 checkRegistryCoverage 做权威实物对账;
739
+ // 未带登记表时此处直接打开 mockup 数 data-sid,防止「sids 自报全绿、实物基线为空」。
740
+ if (!changeset.coverage?.registry && mockup) {
741
+ const cands = [path.resolve(mockup), path.resolve(ROOT, mockup), path.resolve(process.cwd(), mockup)];
742
+ let physical = null;
743
+ for (const c of cands) {
744
+ try {
745
+ const stripped = readFileSync(c, "utf-8").replace(/<!--[\s\S]*?-->/g, "");
746
+ physical = new Set([...stripped.matchAll(/data-sid\s*=\s*["']([^"']+)["']/g)].map(m => m[1]));
747
+ break;
748
+ }
749
+ catch { /* 试下一个候选路径 */ }
750
+ }
751
+ if (physical) {
752
+ const sidNoPhysical = sids.filter(s => !physical.has(s) && !hiddenOnly.has(s));
753
+ if (sidNoPhysical.length > 0)
754
+ issues.push(`实物缺口 ${sidNoPhysical.length} 处:oracle.sids 声明了、效果图 HTML 实物里无对应 data-sid(剥注释后数点)→ 基线抽不出来。\n ${sidNoPhysical.join("、")}`);
755
+ }
756
+ }
708
757
  const sidNotDeclared = sids.filter(s => !declaredIds.includes(s));
709
758
  const declaredWithoutSid = declaredIds.filter(id => !sidSet.has(id) && !hiddenOnly.has(id));
710
759
  if (hiddenOnly.size > 0)
@@ -796,9 +845,6 @@ function registrySids(item) {
796
845
  return item?.sid ? [item.sid] : [];
797
846
  }
798
847
 
799
- /** 登记表里唯一允许的「非词典点位」:全局令牌通道(对应变更集顶层 global 节点,无 css selector 故不入词典) */
800
- const GLOBAL_PSEUDO_ID = "global";
801
-
802
848
  /**
803
849
  * 登记表覆盖率(子项级细闸,)
804
850
  * 组级台账(restore-checklist)只能防「整组忘光」;组内子项是否被静默丢失,必须由登记表
@@ -1131,7 +1177,125 @@ function checkApplyFields(change, label, violations) {
1131
1177
  }
1132
1178
  }
1133
1179
 
1134
- // ---- audit:挂载点存在性 + 死通道(,静态审计,需目标项目源码) ----
1180
+ // ---- 词典驱动硬闸:suppress 收口(G1)+ 几何语境(G5) ----
1181
+ // 与 checkApplyFields(协议自洽性校验)不同,这两个闸的判据来自**词典条目自身**:
1182
+ // · suppress_rules:条目声明某 form 时必须收口的出厂互斥通道(声明 form = 换掉旧形态,不是叠加)
1183
+ // · preconditions 含「几何语境随值迁移」:几何值落宿主时必须同步清零内层盒同向属性
1184
+ // 缺失即「新旧形态叠加 / 两层 padding 相加」——值都对、观感错,是「还原度连乘公式」里
1185
+ // 通道正确率的天花板级杀手,因此与禁词同级(阻断,不是提示)。
1186
+
1187
+ /** 提取条目 suppress_rules:优先结构化字段;回落解析 forms 描述与 $note 中的「suppress 必含 xxx」内联标记 */
1188
+ function extractSuppressRules(entry) {
1189
+ const rules = {};
1190
+ if (entry && typeof entry.suppress_rules === "object" && entry.suppress_rules !== null) {
1191
+ for (const [form, channels] of Object.entries(entry.suppress_rules)) {
1192
+ rules[form] = Array.isArray(channels) ? channels.filter(c => typeof c === "string") : [];
1193
+ }
1194
+ }
1195
+ // 兜底:从 forms 描述文本提取「suppress 必含 名1/名2」标记(历史条目未结构化时仍能机检)
1196
+ const forms = entry?.forms;
1197
+ if (forms && typeof forms === "object" && !Array.isArray(forms)) {
1198
+ for (const [form, desc] of Object.entries(forms)) {
1199
+ if (typeof desc !== "string")
1200
+ continue;
1201
+ // 通道名 = 小写下划线标识符(可多项、以 、/, 分隔);遇非标识符字符(——、。等)即止
1202
+ for (const m of desc.matchAll(/suppress\s*必含\s*([a-z][a-z0-9_]*(?:[、,/][a-z][a-z0-9_]*)*)/gi)) {
1203
+ const channels = m[1].split(/[、,/]/).map(s => s.trim()).filter(Boolean);
1204
+ rules[form] = [...new Set([...(rules[form] || []), ...channels])];
1205
+ }
1206
+ }
1207
+ }
1208
+ return rules;
1209
+ }
1210
+
1211
+ /** G1:声明 form 时,词典 suppress_rules 要求的出厂互斥通道必须逐项声明在条目 suppress 数组 */
1212
+ function checkSuppress(change, entry, idLabel, violations) {
1213
+ if (typeof change.form !== "string" || !change.form || !entry)
1214
+ return;
1215
+ const rules = extractSuppressRules(entry);
1216
+ const required = rules[change.form];
1217
+ if (!required || required.length === 0)
1218
+ return;
1219
+ const declared = Array.isArray(change.suppress) ? change.suppress : [];
1220
+ const missing = required.filter(ch => !declared.includes(ch));
1221
+ if (missing.length > 0) {
1222
+ violations.push(
1223
+ `${idLabel}.suppress 缺口:词典要求 form="${change.form}" 必须收口出厂互斥通道 [${required.join("、")}],`
1224
+ + `实际声明 [${declared.join("、") || "无"}],缺 [${missing.join("、")}]`
1225
+ + ` —— 声明 form = 换掉旧形态而不是叠加;不收口 = 出厂旧装饰残留(历史事故:侧栏 pill 未收口右竖条 → 双竖条并存)。`
1226
+ + `通道名取该条目 forms/css_vars 的键名。`,
1227
+ );
1228
+ }
1229
+ }
1230
+
1231
+ /**
1232
+ * G5:几何语境自检 —— 宿主 + 内层之和必须等于设计值。
1233
+ * 词典 preconditions 含「几何语境随值迁移」标记的条目:style 声明几何字段且 layer 未显式
1234
+ * 指到 inner(即默认落 host)时,必须同步声明 suppress_inner(内层清零口径,纯语义描述),
1235
+ * 否则实际内边距 = 设计值 + 出厂内层自带值(历史事故:th 16px + EP .cell 0 12px = 28px/侧 → 列挤坏)。
1236
+ */
1237
+ function checkGeometryContext(change, entry, idLabel, violations) {
1238
+ if (!entry || !Array.isArray(entry.preconditions))
1239
+ return;
1240
+ if (!entry.preconditions.some(p => typeof p === "string" && p.includes("几何语境随值迁移")))
1241
+ return;
1242
+ const style = change.style && typeof change.style === "object" ? change.style : null;
1243
+ if (!style)
1244
+ return;
1245
+ const layer = change.layer && typeof change.layer === "object" && !Array.isArray(change.layer) ? change.layer : {};
1246
+ const geometryFields = Object.keys(style).filter(k => GEOMETRY_PAD_RE.test(k));
1247
+ if (geometryFields.length === 0)
1248
+ return;
1249
+ // 内层属性族字段(white-space/text-align 等)已逐字段声明 inner → 施工方已知内层结构,不重复拦截
1250
+ const hasInnerAware = Object.keys(layer).some(f => layer[f] === "inner") || geometryFields.every(f => layer[f] === "inner");
1251
+ if (hasInnerAware)
1252
+ return;
1253
+ const hostGeometry = geometryFields.filter(f => layer[f] !== "inner");
1254
+ if (hostGeometry.length === 0)
1255
+ return;
1256
+ const declaredInner = typeof change.suppress_inner === "string" ? change.suppress_inner.trim() : "";
1257
+ if (!declaredInner) {
1258
+ violations.push(
1259
+ `${idLabel}.suppress_inner 缺口:条目 preconditions 标记「几何语境随值迁移」(出厂内层文本盒自带内边距),`
1260
+ + `style 几何字段 [${hostGeometry.join("、")}] 落宿主时必须同步声明 suppress_inner(内层清零口径)`
1261
+ + ` —— 否则实际间距 = 设计值 + 出厂内层值,两层之和 ≠ 设计值(历史事故:表头 16px + 出厂内层 12px/侧 = 28px/侧 → 列宽被挤、标题截断)。`,
1262
+ );
1263
+ }
1264
+ }
1265
+
1266
+ /**
1267
+ * G7:field_forms 声明-消费闭合 —— style 字段必须被当前 form 消费。
1268
+ * 词典条目声明 field_forms(字段 → 合法 form 枚举)后:变更集 style 里的字段若不在
1269
+ * 当前 form 的合法集内 = 字段无消费者、将被静默丢弃(历史事故:变更集声明 pill +
1270
+ * active_indicator_* 三字段,设计层按 pill 施工把竖条令牌置透明,效果图要求的左竖条消失——
1271
+ * 「声明了却不消费」此前无任何闸门拦截)。修复路径:换 form(含组合形态)或删字段。
1272
+ */
1273
+ function checkFieldForms(change, entry, idLabel, violations) {
1274
+ if (!entry || typeof entry.field_forms !== "object" || entry.field_forms === null)
1275
+ return;
1276
+ const style = change.style && typeof change.style === "object" ? change.style : null;
1277
+ if (!style)
1278
+ return;
1279
+ const form = typeof change.form === "string" && change.form ? change.form : null;
1280
+ // 无 form 声明时跳过(纯样式条目,字段消费不依赖形态);无该字段映射时跳过
1281
+ if (!form)
1282
+ return;
1283
+ for (const [field, legalForms] of Object.entries(entry.field_forms)) {
1284
+ if (!(field in style))
1285
+ continue;
1286
+ if (!Array.isArray(legalForms) || legalForms.length === 0)
1287
+ continue;
1288
+ if (!legalForms.includes(form)) {
1289
+ violations.push(
1290
+ `${idLabel}.field_forms 闭合违规:style 字段「${field}」仅在 form [${legalForms.join("、")}] 下有消费者,`
1291
+ + `当前 form="${form}" 不消费该字段 → 声明将被静默丢弃`
1292
+ + ` —— 修复:换用消费该字段的 form(含组合形态),或删除该字段。禁止「声明字段 + 互斥 form」的组合(历史事故:pill + active_indicator_* → 设计层置透明,左竖条消失)。`,
1293
+ );
1294
+ }
1295
+ }
1296
+ }
1297
+
1298
+
1135
1299
  // 本节的模块级常量(AUDIT_SKIP_DIRS / STRUCT_EXT / STYLE_EXT / DESIGN_LAYER_RE /
1136
1300
  // BUILTIN_BARE_CLASSES)已在文件顶部声明——模块级常量必须在 main() 调用前初始化,勿下移。
1137
1301
 
@@ -1219,8 +1383,9 @@ function findVariableConsumer(varName, sources) {
1219
1383
  function judgeMount(token, where, hit) {
1220
1384
  if (hit.struct.length > 0)
1221
1385
  return [];
1386
+ const fmt = arr => arr.map(h => (typeof h === "string" ? h : `${h.file}(${(h.reasons || []).join(";")})`)).slice(0, 2).join("、");
1222
1387
  if (hit.style.length > 0)
1223
- return [`挂载点可疑:${where} 的「${token}」只出现在样式文件(${hit.style.slice(0, 2).join("、")}),未在模板 / 脚本里挂载 —— 规则可能永不命中(历史缺口:列未绑 class-name,金额列右对齐 / 缩略图尺寸失效)`];
1388
+ return [`挂载点可疑:${where} 的「${token}」只出现在样式文件(${fmt(hit.style)}),未在模板 / 脚本里挂载 —— 规则可能永不命中(历史缺口:列未绑 class-name,金额列右对齐 / 缩略图尺寸失效)`];
1224
1389
  if (hit.designLayer.length > 0)
1225
1390
  return [`挂载点缺失:${where} 的「${token}」只出现在设计层样式(${hit.designLayer.slice(0, 2).join("、")}),项目里既无结构挂载也无出厂对应 —— 规则不会生效`];
1226
1391
  return [`挂载点缺失:${where} 的「${token}」在项目中未找到任何出现位置(选择器永不命中)`];
@@ -1229,8 +1394,16 @@ function judgeMount(token, where, hit) {
1229
1394
  /** 单条目审计:selector / anchors 挂载点 + css_var 消费者 */
1230
1395
  function auditEntry(entry, sources) {
1231
1396
  const issues = [];
1232
- for (const t of extractMountTokens(entry.selector))
1233
- issues.push(...judgeMount(t, "selector", findMount(t, sources)));
1397
+ const miss = (token) => {
1398
+ // 设计层已承载同类名 挂载点可疑降级为提示(不阻断):C 通道由 design-shell.scss 兜底
1399
+ if (hasDesignLayerTakeover(token, sources))
1400
+ return null;
1401
+ return true;
1402
+ };
1403
+ for (const t of extractMountTokens(entry.selector)) {
1404
+ if (miss(t))
1405
+ issues.push(...judgeMount(t, "selector", findMount(t, sources)));
1406
+ }
1234
1407
 
1235
1408
  const anchors = Array.isArray(entry.anchors) ? entry.anchors : [];
1236
1409
  for (const a of anchors) {
@@ -1238,17 +1411,47 @@ function auditEntry(entry, sources) {
1238
1411
  continue;
1239
1412
  if (a.kind === "css_var") {
1240
1413
  const consumers = findVariableConsumer(a.target, sources);
1241
- if (consumers.length === 0)
1242
- issues.push(`死通道:CSS 变量 ${a.target} 在项目中无 var() 消费者(只定义不消费 = 通道死路,翻译再准也不生效)`);
1414
+ if (consumers.length === 0) {
1415
+ // 两类合法豁免(防误报):
1416
+ // ① --el-* 组件库变量:消费者在 element-plus 主题库(node_modules 不在扫描范围),
1417
+ // 项目源码无 var() 属正常 —— EP runtime 必然消费。
1418
+ // ② 设计层已接管变量定义(design-shell 显式赋值):suppress / 接管属故意行为
1419
+ // (词典 preconditions 有明示的条目,如 --vts-theme-menu-active-before)。
1420
+ if (a.target.startsWith("--el-")) {
1421
+ console.log(` ℹ ${a.target} 项目源码无 var() 消费者 —— 由组件库运行时消费(node_modules 不在扫描范围),不按死通道处理`);
1422
+ }
1423
+ else if (designLayerDefines(a.target, sources)) {
1424
+ console.log(` ℹ ${a.target} 项目源码无 var() 消费者 —— 设计层已接管该变量定义(suppress / 接管属故意行为),不按死通道处理`);
1425
+ }
1426
+ else {
1427
+ issues.push(`死通道:CSS 变量 ${a.target} 在项目中无 var() 消费者(只定义不消费 = 通道死路,翻译再准也不生效)`);
1428
+ }
1429
+ }
1243
1430
  }
1244
1431
  else {
1245
- for (const t of extractMountTokens(a.target))
1246
- issues.push(...judgeMount(t, `anchors(${a.layer || "未标层级"})`, findMount(t, sources)));
1432
+ for (const t of extractMountTokens(a.target)) {
1433
+ if (miss(t))
1434
+ issues.push(...judgeMount(t, `anchors(${a.layer || "未标层级"})`, findMount(t, sources)));
1435
+ }
1247
1436
  }
1248
1437
  }
1249
1438
  return [...new Set(issues)];
1250
1439
  }
1251
1440
 
1441
+ /** 判定「设计层已承载」的豁免:条目 anchors/selector 的类词元在设计层文件出现时,
1442
+ * 挂载点可疑可降级为提示 —— 设计层覆写本身就是合法落点(design-shell.scss 是 C 通道终点)。 */
1443
+ function hasDesignLayerTakeover(token, sources) {
1444
+ const needle = token.startsWith("[") ? token.slice(1, -1) : token;
1445
+ const re = new RegExp(`(^|[^\\w-])${escapeRe(needle)}([^\\w-]|$)`);
1446
+ return sources.some(s => DESIGN_LAYER_RE.test(s.file) && re.test(s.text));
1447
+ }
1448
+
1449
+ /** 设计层是否显式定义了该 CSS 变量(suppress / 接管动作),定义在非设计层的令牌宿主不算 */
1450
+ function designLayerDefines(varName, sources) {
1451
+ const re = new RegExp(`${escapeRe(varName)}\\s*:`);
1452
+ return sources.some(s => DESIGN_LAYER_RE.test(s.file) && re.test(s.text));
1453
+ }
1454
+
1252
1455
  function cmdAudit(args, dataset) {
1253
1456
  const projIdx = args.indexOf("--project");
1254
1457
  const projectRoot = projIdx > -1 ? args[projIdx + 1] : null;
@@ -1276,6 +1479,7 @@ function cmdAudit(args, dataset) {
1276
1479
 
1277
1480
  // 给了变更集 → 只审被引用的点位(含 apply_via 指向的锚点来源);否则审词典全量
1278
1481
  let targetIds;
1482
+ const loadedChangesets = []; // G2/G4:保留完整变更集供施工证据与 T 通道对账
1279
1483
  if (files.length > 0) {
1280
1484
  const wanted = new Set();
1281
1485
  for (const f of files) {
@@ -1287,6 +1491,7 @@ function cmdAudit(args, dataset) {
1287
1491
  console.error(`✖ 变更集读取失败 ${f}:${e.message}`);
1288
1492
  process.exit(1);
1289
1493
  }
1494
+ loadedChangesets.push(cs);
1290
1495
  for (const c of cs.changes || []) {
1291
1496
  if (c.id)
1292
1497
  wanted.add(c.id);
@@ -1304,6 +1509,113 @@ function cmdAudit(args, dataset) {
1304
1509
  console.log(`源码文件:${sources.length}(已剔除 node_modules / dist 等产物目录)`);
1305
1510
  console.log(`检查条目:${targetIds.length}${files.length > 0 ? "(仅变更集引用点位)" : "(词典全量)"}`);
1306
1511
 
1512
+ // ---- G2:structure_ops 施工证据(台账落地断言)----
1513
+ // 数据 / 结构驱动点位必须在项目源码里找到施工痕迹,纯 CSS 落点声明 structure_ops = 冒充施工:
1514
+ // add_node/remove_node/move_node/render_branch/bind_source → 按条目 selector/anchors 的语义类名
1515
+ // 在**非设计层**源码(含模板与数据脚本)里查证该节点/绑定的存在性(结构文件命中才算兑现)。
1516
+ // 历史事故:操作列分割线 add_node 声明了多轮从未施工,CSS 覆写冒充结构改动 → 按钮间距 / 列宽永不达标。
1517
+ let structIssues = 0;
1518
+ if (loadedChangesets.length > 0) {
1519
+ console.log(`\n=== structure_ops 施工证据(G2 · 模板 diff 取证)===`);
1520
+ for (const cs of loadedChangesets) {
1521
+ for (const [i, c] of (cs.changes || []).entries()) {
1522
+ const ops = Array.isArray(c.structure_ops) ? c.structure_ops : [];
1523
+ if (ops.length === 0 || !c.id)
1524
+ continue;
1525
+ const entry = index[c.id];
1526
+ const probes = collectStructProbes(entry, c);
1527
+ const evidence = probes
1528
+ .map(p => ({ probe: p, hits: findStructEvidence(p, sources) }))
1529
+ .filter(x => x.hits.length > 0);
1530
+ if (probes.length === 0) {
1531
+ console.log(` ⚠ changes[${i}] ${c.id}:structure_ops ${ops.length} 项,但词典条目无 selector/anchors 可提取证据探针 → 人工核验施工台账(design-apply §施工台账)`);
1532
+ continue;
1533
+ }
1534
+ if (evidence.length === 0) {
1535
+ console.log(` ✘ ${c.id}:structure_ops 声明 [${ops.map(o => o.op).join("/")}],但源码中找不到任何施工证据探针 [${probes.join("、")}](非设计层结构文件零命中)`);
1536
+ console.log(` → 纯 CSS 覆写不能兑现结构操作;须改模板/数据并在交付说明附施工台账,或从变更集移除该声明`);
1537
+ structIssues++;
1538
+ }
1539
+ else {
1540
+ const files1 = [...new Set(evidence.flatMap(x => x.hits.map(h => h.file)))];
1541
+ console.log(` ✔ ${c.id}:structure_ops 证据命中 ${evidence.length}/${probes.length} 探针(${files1.slice(0, 3).join("、")})`);
1542
+ }
1543
+ }
1544
+ }
1545
+ if (structIssues === 0)
1546
+ console.log(` ✔ 全部 structure_ops 均有源码施工证据`);
1547
+ }
1548
+
1549
+ // ---- G4:T 通道对账(运行时令牌 ↔ 变更集主题/形态声明)----
1550
+ // T 通道(platform-config.json / 运行时令牌)优先级最高、也最易被遗忘:
1551
+ // 主题色不一致 → 全局主色错;页签 ShowModel 与 form 叠加 → 新旧形态并存。
1552
+ let tIssues = 0;
1553
+ if (loadedChangesets.length > 0) {
1554
+ console.log(`\n=== T 通道对账(G4 · platform-config ↔ 变更集声明)===`);
1555
+ const pcPath = path.join(root, "public", "platform-config.json");
1556
+ let pc = null;
1557
+ try {
1558
+ pc = JSON.parse(readFileSync(pcPath, "utf-8"));
1559
+ console.log(` platform-config:${pcPath}`);
1560
+ }
1561
+ catch {
1562
+ console.log(` ⚠ 未能读取 ${pcPath}(无 public/platform-config.json)→ 跳过 T 通道对账`);
1563
+ }
1564
+ if (pc) {
1565
+ const tChecks = [];
1566
+ // ① 主题色:global.brand_primary(语义值经译表解析为 hex)↔ EpThemeColor
1567
+ const declaredBrand = loadedChangesets.map(cs => cs.global?.brand_primary).find(Boolean);
1568
+ if (declaredBrand && typeof declaredBrand === "string") {
1569
+ const allDict = buildSemanticValueIndex(dataset);
1570
+ const hex = resolveSemanticValue(declaredBrand, allDict) || (/^#/.test(declaredBrand) ? declaredBrand : null);
1571
+ if (hex && pc.EpThemeColor && String(pc.EpThemeColor).toLowerCase() !== String(hex).toLowerCase()) {
1572
+ tChecks.push(`✘ 主题色不一致:变更集 brand_primary=${declaredBrand}(解析 ${hex}),platform-config.EpThemeColor=${pc.EpThemeColor} → 改 platform-config 或运行时令牌持久化(T 通道先于 C 通道)`);
1573
+ }
1574
+ }
1575
+ // ② 页签形态:tabs form ↔ ShowModel(出厂 flat/chrome 形态都运行在 ShowModel=card 结构上)
1576
+ const tabsForm = loadedChangesets
1577
+ .flatMap(cs => (cs.changes || []).filter(c => c.id === "admin.layout.tabs" && typeof c.form === "string").map(c => c.form))
1578
+ .find(Boolean);
1579
+ if (tabsForm && pc.ShowModel && !["flat", "chrome", "card"].includes(pc.ShowModel) === false) {
1580
+ // flat 与 card 共用 card 结构,chrome 形态要求 ShowModel=chrome
1581
+ if (tabsForm === "chrome" && pc.ShowModel !== "chrome") {
1582
+ tChecks.push(`✘ 页签形态不一致:变更集 form=chrome 需 ShowModel=chrome,platform-config.ShowModel=${pc.ShowModel}`);
1583
+ }
1584
+ else if (tabsForm !== "chrome" && pc.ShowModel === "chrome") {
1585
+ tChecks.push(`✘ 页签形态不一致:变更集 form=${tabsForm}(card/flat 结构),platform-config.ShowModel=chrome → 运行时仍是梯形页签,设计层覆写不改变结构形态`);
1586
+ }
1587
+ }
1588
+ // ③ 显隐类 D 通道开关:变更集 visible:false 的点位若词典标了 visible_prop,必须核对开关
1589
+ const visibleProps = [];
1590
+ for (const cs of loadedChangesets) {
1591
+ for (const c of (cs.changes || [])) {
1592
+ if (c.visible !== false || !c.id)
1593
+ continue;
1594
+ const entry = index[c.id] || index[String(c.id).split(".").slice(0, -1).join(".")];
1595
+ if (entry?.visible_prop)
1596
+ visibleProps.push({ id: c.id, prop: entry.visible_prop });
1597
+ }
1598
+ }
1599
+ for (const vp of visibleProps) {
1600
+ const m = /^(\w+)(platform-config\.json)$/.exec(vp.prop) || /^(\w+)$/.exec(vp.prop.split("(")[0].trim());
1601
+ const key = m ? m[1] : null;
1602
+ if (key && key in pc && pc[key] === true) {
1603
+ tChecks.push(`✘ 显隐不一致:${vp.id} 声明 visible:false,但词典 visible_prop=${vp.prop} 且 platform-config.${key}=${pc[key]} → 开关未关,点位仍渲染(D 通道先于 S/C 通道)`);
1604
+ }
1605
+ }
1606
+ if (tChecks.length === 0) {
1607
+ console.log(` ✔ T/D 通道一致:主题色、页签形态、显隐开关均与变更集声明对齐`);
1608
+ }
1609
+ else {
1610
+ for (const t of tChecks) {
1611
+ console.log(` ${t}`);
1612
+ tIssues++;
1613
+ }
1614
+ console.log(` 处置:T 通道(platform-config / 运行时令牌持久化)优先级最高,先清 T 再谈 S/C;存量 localStorage 持久化值须一并断言清零。`);
1615
+ }
1616
+ }
1617
+ }
1618
+
1307
1619
  let problems = 0;
1308
1620
  const clean = [];
1309
1621
  for (const id of targetIds) {
@@ -1334,6 +1646,48 @@ function cmdAudit(args, dataset) {
1334
1646
  else {
1335
1647
  console.log(`✔ 无挂载 / 通道问题(渲染态是否一致仍需 §验收比对确认:静态审计查不出「值对了、位置不对」)`);
1336
1648
  }
1649
+ // G2/G4 违规同样置退出码(与挂载/通道问题同级 —— 施工没兑现、T 通道没清零 = 交付不完整)
1650
+ if (structIssues > 0) {
1651
+ console.log(`✖ structure_ops 施工证据缺失 ${structIssues} 处(G2)`);
1652
+ process.exitCode = 1;
1653
+ }
1654
+ if (tIssues > 0) {
1655
+ console.log(`✖ T 通道对账不一致 ${tIssues} 处(G4)`);
1656
+ process.exitCode = 1;
1657
+ }
1658
+ }
1659
+
1660
+ /** G2 辅助:从词典条目与 structure_ops 声明中提取「施工证据探针」(语义类名 / 节点名,不含技术词)。
1661
+ * 探针来源优先级:条目 anchors 的 class target 词元 → selector 词元 → op.node 语义名。 */
1662
+ function collectStructProbes(entry, change) {
1663
+ const probes = new Set();
1664
+ const pushTokens = (sel) => {
1665
+ if (typeof sel !== "string")
1666
+ return;
1667
+ for (const m of sel.matchAll(/\.([a-zA-Z][\w-]*__(?:divider|tag|icon|close|title|badge|arrow)[\w-]*|[a-zA-Z][\w-]*-divider[\w-]*|vts-[\w-]+|re-table-operate__[\w-]+)/g))
1668
+ probes.add(m[1]);
1669
+ };
1670
+ if (entry) {
1671
+ for (const a of (Array.isArray(entry.anchors) ? entry.anchors : [])) {
1672
+ if (a && a.kind === "class" && typeof a.target === "string")
1673
+ pushTokens(a.target);
1674
+ }
1675
+ pushTokens(entry.selector);
1676
+ }
1677
+ // op.node 语义名(如 tab_divider):既可能是设计层伪元素名(tab_divider → ::before 不可探测),
1678
+ // 也可能是真实类名(status_tag);转词元时保留下划线形式,匹配源码任意出现(类名/常量名/注释均可)
1679
+ for (const op of (Array.isArray(change.structure_ops) ? change.structure_ops : [])) {
1680
+ if (op && typeof op.node === "string" && op.node.trim())
1681
+ probes.add(op.node.trim());
1682
+ }
1683
+ return [...probes];
1684
+ }
1685
+
1686
+ /** G2 辅助:在非设计层源码中查找探针出现(结构文件命中 = 施工痕迹;仅样式文件命中不算) */
1687
+ function findStructEvidence(probe, sources) {
1688
+ const re = new RegExp(`(?:class|className|classList|node|name|id|label|key|prop)["']?\\s*[:=]\\s*["']${escapeRe(probe)}["']|${escapeRe(probe)}`);
1689
+ return sources.filter(h => h.kind === "struct" && !DESIGN_LAYER_RE.test(h.file) && re.test(h.text))
1690
+ .map(h => ({ file: h.file }));
1337
1691
  }
1338
1692
 
1339
1693
  // ---- verify:渲染态逐点验收( 补丁 3 · 闭环验收回路) ----
@@ -137,6 +137,17 @@ parser match(确定性翻译)→ 施工坐标 → AI 按坐标改代码 →
137
137
  - 落地前先读 localStorage(键 `responsive-layout`)的 `theme` / `epThemeColor`,与本变更集主题**不一致时清 key 或显式重设**;
138
138
  - 清场动作与结果写入交付说明(可回溯);
139
139
  - **不清场则颜色类差异全部失真**——归因前必须先确认起点已对齐,否则会把「环境残留」误判成「落地失败」。
140
+ - **机检对账(G4)**:`parser audit` 会自动比对 `global.brand_primary` ↔ `platform-config.EpThemeColor`、页签 form ↔ `ShowModel`、`visible:false` ↔ 显隐开关,不一致即 `exit 1`——人工清场 + 机检对账双保险。
141
+
142
+ **0.3b T 通道持久化三件套(页签形态 / 主题类字段施工必做)**
143
+
144
+ 运行时配置读 localStorage,而 localStorage 只在 `platform-config.Version` 变化时经 `responsive.ts invalidateStorageIfConfigVersionChanged` 重灌。凡施工涉及 `ShowModel` / `Theme` / `EpThemeColor` 等 T 通道字段,**三件套缺一不可**:
145
+
146
+ 1. 改 `platform-config.json` 实值(如 `ShowModel: "card"`);
147
+ 2. **`Version` 自增一位**(如 0.0.6 → 0.0.7)——漏此步 = 代码对但运行时仍旧形态(历史事故:只改 ShowModel 未动 Version,页签零变化);
148
+ 3. **渲染取证**:断言新形态类实际挂载(如 `.scroll-item.card-item` 出现在 DOM)——不许只看代码改没改。
149
+
150
+ 词典 `admin.layout.tabs.preconditions` 有同款硬闸描述;本三件套对任意 T 通道字段通用。
140
151
 
141
152
  ### 第 1 步 · parser 确定性匹配
142
153
 
@@ -175,6 +186,15 @@ node parser/index.js match --batch <changesets-dir>
175
186
  | `▸ 前置条件 preconditions` | 值成立依赖的宿主前提 | 施工前必须满足,否则「值对了也不生效」 |
176
187
  | `▸ 覆写优先级 priority` | 该点位的提权策略 | 按策略取胜,禁止逐点硬堆 `!important` |
177
188
 
189
+ **词典硬闸(match 期机检,违规 = 拒绝执行)**:
190
+
191
+ | 闸 | 触发条件 | 判据来源 | 违规输出 |
192
+ |---|---|---|---|
193
+ | **G1 suppress 收口** | 条目声明某 `form`,词典 `suppress_rules[form]`(或 forms 描述「suppress 必含 x」内联标记)要求收口的出厂互斥通道未全部出现在该条目 `suppress[]` | 词典条目 `suppress_rules` | `suppress 缺口:…必须收口出厂互斥通道 [x],缺 [x]` |
194
+ | **G5 几何语境** | 词典 preconditions 含「几何语境随值迁移」标记 ∧ `style` 含 padding/margin 族几何字段落宿主 ∧ 未声明 `suppress_inner` | 词典条目 preconditions 标记句 | `suppress_inner 缺口:…两层之和 ≠ 设计值` |
195
+
196
+ 两闸与禁词校验同级阻断(`exit 1`)——「新旧形态叠加」「两层 padding 相加」都是值全对、观感全错的历史事故源(双竖条、28px 列挤压),不允许靠观感兜底。
197
+
178
198
  **验收基线输出**:转换计划末尾另打印 `=== 验收基线 oracle ===`(效果图路径 + 待比对点位)与 `=== 覆盖率 coverage ===`(登记项 R / 适用 A / 已兑现 C)。有 `oracle` → 落地后必须跑第 6 步 `verify`;缺 `oracle` / `coverage` → 输出提示,把「补基线 / 补登记覆盖率」回流设计侧(场景还原应给)。
179
199
  **覆盖率校验**:`coverage.covered` 必须等于 `coverage.applicable`、`gaps` 为空,否则打印缺口清单——`C < A` 即**欠声明**(有规范子项未落进 `changes[]`/`visible`/`pending`),属设计侧交付不完整,必须回流,不得在项目侧"猜着补"。
180
200
 
@@ -189,10 +209,12 @@ node parser/index.js audit --project <目标项目根目录> [<changeset.json…
189
209
  | 检查 | 抓什么 | 对应历史缺口 |
190
210
  |---|---|---|
191
211
  | **挂载点缺失 / 可疑** | `selector` / `anchors.target` 里的类名在项目里**只在样式文件出现、模板没挂**,或**完全找不到** | 列未绑 `class-name` → 金额列右对齐 / 缩略图尺寸永不生效;操作栏规则有定义无引用 |
192
- | **死通道** | `anchors` 里的 CSS 变量在项目中**无 `var()` 消费者** | 变量有定义无引用,翻译再准也不生效 |
212
+ | **死通道** | `anchors` 里的 CSS 变量在项目中**无 `var()` 消费者**(两类豁免:`--el-*` 组件库变量由 EP 运行时消费;设计层已显式接管定义的变量属故意 suppress) | 变量有定义无引用,翻译再准也不生效 |
193
213
  | **词典无此条目** | 变更集引用了词典未建的 ID | L3 新增点位须当场落地并回写临时映射 |
214
+ | **G2 structure_ops 施工证据** | 变更集声明 `structure_ops` 的点位,按词典 anchors/selector 语义类名 + `op.node` 提取探针,在**非设计层结构源码**中查证施工痕迹;零命中 = 纯 CSS 冒充结构施工 | 操作列分割线 `add_node` 声明多轮从未施工 |
215
+ | **G4 T 通道对账** | `global.brand_primary`(语义值解析 hex)↔ `platform-config.EpThemeColor`;页签 `form` ↔ `ShowModel`;`visible:false` 点位的 `visible_prop` 开关 ↔ platform-config 实值 | 变更集孔雀绿、platform-config 残留旧主题;flat 页签叠加 chrome 结构 |
194
216
 
195
- 审计报错 = **能力缺口**:当场按 `structure_ops` 补挂载 / 补消费者,交付说明单列申报;不得以「词典没建」为由跳过。审计通过 ≠ 还原达标——**静态审计查不出「值对了、位置不对」**,那要靠第 6 步渲染态验收。
217
+ 挂载点疑似但**设计层文件已承载该类名** → 降级不阻断(C 通道由 design-shell 兜底是合法落点)。G2 / G4 违规与挂载点问题同置 `exitCode 1`。审计报错 = **能力缺口**:当场按 `structure_ops` 补挂载 / 补消费者,交付说明单列申报;不得以「词典没建」为由跳过。审计通过 ≠ 还原达标——**静态审计查不出「值对了、位置不对」**,那要靠第 6 步渲染态验收。
196
218
 
197
219
  ### 第 1 步附加(之二)· 登记表覆盖率(子项级细闸 · )
198
220