cardbuddy 0.14.3 → 0.16.0
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/dist/cli.js +0 -0
- package/dist/preview-editor/assets/index-BkbBxDTT.css +1 -0
- package/dist/preview-editor/assets/index-Bro07DTa.js +313 -0
- package/dist/preview-editor/index.html +2 -2
- package/dist/standalone.js +3 -0
- package/dist/vendored/card-editor/runtime/adapter.js +2 -2
- package/dist/vendored/mcp/client.mjs +9 -2
- package/dist/vendored/server/core/ai-chat.js +1702 -134
- package/dist/vendored/server/core/browser.js +12 -3
- package/dist/vendored/server/core/cardFiles.js +3 -0
- package/dist/vendored/server/core/cardHistory.js +1 -0
- package/dist/vendored/server/core/cardImportMeta.js +7 -6
- package/dist/vendored/server/core/cardOpenFolder.js +1 -0
- package/dist/vendored/server/core/cardSave.js +1 -0
- package/dist/vendored/server/core/cardSync.js +3 -0
- package/dist/vendored/server/core/cardValidate.js +16 -5
- package/dist/vendored/server/core/codeBridge.js +5 -1
- package/dist/vendored/server/core/logStream.js +1 -0
- package/dist/vendored/server/core/sliceStatic.js +24 -4
- package/dist/vendored/server/core/sliceUpload.js +1 -0
- package/dist/vendored/server/core/turnJournal.js +6 -0
- package/dist/vendored/skills/card-skill-trainer/SKILL.md +257 -201
- package/dist/vendored/skills/card-skill-trainer/scripts/diagnose_case.mjs +14 -82
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/diagnosis.mjs +207 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/layout-correction-sim.mjs +62 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/paradigm-router.mjs +76 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/pixel-worker-runner.mjs +31 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/pixel-worker.mjs +138 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/scoring.mjs +24 -20
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/telemetry.mjs +46 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/plan_next_round.mjs +4 -1
- package/dist/vendored/skills/card-skill-trainer/scripts/run_round.mjs +279 -90
- package/dist/vendored/skills/card-skill-trainer/scripts/score_round.mjs +74 -2
- package/dist/vendored/skills/card-skill-trainer/scripts/sink_rules.mjs +127 -0
- package/dist/vendored/skills/dsl-card-ai/SKILL.md +1 -1
- package/dist/vendored/skills/dsl-card-generator/SKILL.md +208 -380
- package/dist/vendored/skills/dsl-card-generator/references/dsl_structure.md +1 -15
- package/dist/vendored/skills/dsl-card-generator/references/expression_engine.md +11 -20
- package/dist/vendored/skills/dsl-card-generator/references/generation_pitfalls.md +233 -61
- package/dist/vendored/skills/dsl-card-generator/references/ir-format.md +202 -4
- package/dist/vendored/skills/dsl-card-generator/references/iteration_protocol.md +87 -0
- package/dist/vendored/skills/dsl-card-generator/references/previewer_contract.md +1 -1
- package/dist/vendored/skills/dsl-card-generator/references/quickref.md +42 -33
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/title-underline-overlay.json +59 -11
- package/dist/vendored/skills/dsl-card-generator/references/recipes.md +44 -8
- package/dist/vendored/skills/dsl-card-generator/references/rules.md +26 -6
- package/dist/vendored/skills/dsl-card-generator/references/scripts_reference.md +54 -0
- package/dist/vendored/skills/dsl-card-generator/references/semantic_adjudication.md +23 -0
- package/dist/vendored/skills/dsl-card-generator/references/slices_convention.md +6 -6
- package/dist/vendored/skills/dsl-card-generator/references/spec_to_schema_patterns.md +33 -4
- package/dist/vendored/skills/dsl-card-generator/references/ui_fidelity_rules.md +13 -1
- package/dist/vendored/skills/dsl-card-generator/scripts/add_component.mjs +5 -14
- package/dist/vendored/skills/dsl-card-generator/scripts/autofix_schema.py +133 -2
- package/dist/vendored/skills/dsl-card-generator/scripts/check_fidelity.mjs +226 -19
- package/dist/vendored/skills/dsl-card-generator/scripts/check_magic_values.mjs +6 -1
- package/dist/vendored/skills/dsl-card-generator/scripts/check_topology.py +0 -35
- package/dist/vendored/skills/dsl-card-generator/scripts/crop_slices.mjs +92 -6
- package/dist/vendored/skills/dsl-card-generator/scripts/expand_ir.mjs +277 -27
- package/dist/vendored/skills/dsl-card-generator/scripts/gate.mjs +147 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/generate_cards.mjs +12 -8
- package/dist/vendored/skills/dsl-card-generator/scripts/ir_merge.mjs +288 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/ir_restore.mjs +175 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/iterate.mjs +1015 -54
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/component_contract.mjs +315 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/geometry_audit.mjs +131 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/layout-tokens.mjs +1 -1
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/layout_rules.mjs +160 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/paths.mjs +23 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/paths.py +0 -37
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/python.mjs +99 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/adaptive-width.mjs +105 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/bindings.mjs +260 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/expr.mjs +52 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/image.mjs +96 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/registry.mjs +166 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/structure.mjs +127 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/walk.mjs +61 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/scope_alias.mjs +125 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/slicePath.mjs +17 -5
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/slice_dedupe.mjs +102 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_assemble.mjs +5727 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_extract.mjs +4214 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_shared.mjs +1110 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lint_card.mjs +322 -71
- package/dist/vendored/skills/dsl-card-generator/scripts/preflight.mjs +87 -43
- package/dist/vendored/skills/dsl-card-generator/scripts/readiness.mjs +634 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/register_card_template.mjs +5 -8
- package/dist/vendored/skills/dsl-card-generator/scripts/render_card.mjs +91 -43
- package/dist/vendored/skills/dsl-card-generator/scripts/route_input.mjs +7 -11
- package/dist/vendored/skills/dsl-card-generator/scripts/spec_audit.mjs +272 -46
- package/dist/vendored/skills/dsl-card-generator/scripts/spec_to_schema_v3.mjs +43 -10499
- package/dist/vendored/skills/dsl-card-generator/scripts/v3_regression.mjs +188 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/validate_schema.py +23 -15
- package/dist/vendored/skills/shared/exit-codes.md +79 -0
- package/dist/vendored/skills/shared/spec-contract.md +118 -0
- package/dist/vendored/skills/sketch-spec-to-card/SKILL.md +43 -20
- package/dist/vendored/skills/sketch-spec-to-card/references/spec-schema.md +4 -4
- package/dist/vendored/skills/sketch-spec-to-card/scripts/build_prompt.py +47 -9
- package/dist/vendored/skills/sketch-spec-to-card/scripts/cluster_states.py +14 -25
- package/dist/vendored/skills/sketch-spec-to-card/scripts/parse_meaxure.py +546 -91
- package/dist/vendored/skills/sketch-spec-to-card/scripts/png_utils.py +51 -5
- package/dist/vendored/skills/sketch-spec-to-card/scripts/test_text_only_artboard.py +217 -0
- package/dist/vendored/skills/sketch-spec-to-card/scripts/vision.py +64 -34
- package/package.json +1 -1
- package/dist/preview-editor/assets/index-Cm0USikc.js +0 -307
- package/dist/preview-editor/assets/index-cbTCHp8J.css +0 -1
- package/dist/vendored/skills/card-skill-trainer/scripts/test_missing_slice.py +0 -122
- package/dist/vendored/skills/dsl-card-generator/scripts/analyze_component_usage.mjs +0 -153
|
@@ -169,21 +169,7 @@ showType:00 始终显示;01 满足条件显示;02 满足条件隐藏。con
|
|
|
169
169
|
|
|
170
170
|
## 事件 eventactions
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
{
|
|
174
|
-
"eventactions": [
|
|
175
|
-
{
|
|
176
|
-
"event": { "eventName": "点击事件", "eventKey": "click" },
|
|
177
|
-
"action": {
|
|
178
|
-
"actionType": "customAction",
|
|
179
|
-
"properties": [
|
|
180
|
-
{ "itemKey": "function", "itemValue": { "code": "var that = this;\n...", "compiled": "var that = this;\n..." } }
|
|
181
|
-
]
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
]
|
|
185
|
-
}
|
|
186
|
-
```
|
|
172
|
+
**形状与 code/compiled 同步规则见 `event_patterns.md §eventaction 形状`**。
|
|
187
173
|
|
|
188
174
|
eventKey:click 高频、onReady/onMeasured 中频。actionType:customAction 高频、其余低频。**完整频次表见 event_patterns.md §频次统计**(不在本节重复数字)。
|
|
189
175
|
|
|
@@ -27,7 +27,15 @@
|
|
|
27
27
|
|
|
28
28
|
`slotScope:true` 的插槽,子作用域 = 父作用域 ∪ 当前 item 展开 ∪ $$ (整个 item) ∪ _index (索引)。
|
|
29
29
|
|
|
30
|
-
-
|
|
30
|
+
- 列表项内的**规范形**:`$fieldName`(item 的字段)/ `$$`(整个 item)/ `$_index`(索引)。
|
|
31
|
+
- **别名 `$item.<字段>` / `$item` / `$item._index` 会被生成器自动归一化**,不是需要你规避的坑:
|
|
32
|
+
schema 里只存规范形,改写由 `expand_ir.mjs`(IR→schema 入口,实现 `lib/scope_alias.mjs`)与
|
|
33
|
+
`autofix_schema.py` 规则 5(schema 级兜底)完成;写别名不会中止写盘、也不会被打回。
|
|
34
|
+
为什么归一化而不是禁止:作用域里本来**没有 `item` 这个键**(它是「字段逐个展开 ∪ {$$, _index}」),
|
|
35
|
+
所以 `$item.<字段>` 求值恒为 `undefined`,而求值器用 Proxy 兜底(见 §4 的逐项 try/catch)**不抛错**
|
|
36
|
+
—— validate / preflight / render 全绿,只有 `/__render-status` 单列的 `bindingErrors` 会记一笔。
|
|
37
|
+
这类静默失败在入口消灭比让作者背规则可靠。唯一例外:卡片声明了名为 `item` 的根变量时不改写。
|
|
38
|
+
(CI 侧 `lint_card.mjs` 的 `r50b-unnormalized-scope-alias` 只作为「该 schema 绕过了归一化管线」的哨兵。)
|
|
31
39
|
- **以上 $ 作用域只适用于绑定表达式 / v-show 条件**。**事件脚本(customAction)里没有 `$$`**:slotScope 当前 item 的字段已展开到 `this.state`,直接 `this.state.<field>` 读取(铁律 r55,禁 `$$` / `this.state.$$` / 「$$ 兜底判断」,lint ERROR)。
|
|
32
40
|
- ListLayout.value = "$payeeList",item = { acctName:"张三", amount:"100.00" },子 Text.value = "$acctName" 渲染当前项 acctName。
|
|
33
41
|
- 常见表达式:
|
|
@@ -65,25 +73,8 @@
|
|
|
65
73
|
|
|
66
74
|
## 6. v-show 指令
|
|
67
75
|
|
|
68
|
-
组件级显隐,在 `directives[]`
|
|
69
|
-
|
|
70
|
-
```
|
|
71
|
-
{
|
|
72
|
-
"itemId": "001",
|
|
73
|
-
"itemKey": "v-show",
|
|
74
|
-
"itemValueFrom": "0",
|
|
75
|
-
"itemValue": {},
|
|
76
|
-
"itemChildren": {
|
|
77
|
-
"properties": [
|
|
78
|
-
{ "itemId": "001", "itemKey": "showType", "itemValueFrom": "0", "itemValue": "00" },
|
|
79
|
-
{ "itemId": "002", "itemKey": "condition", "itemValueFrom": "0", "itemValue": { "name": "", "expression": "$flag === '1'" } }
|
|
80
|
-
]
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
- showType:00 始终显示;01 满足条件显示;02 满足条件隐藏。
|
|
86
|
-
- condition.expression 是 $ 表达式,渲染期 evalExpr 实时算。无表达式时兜底:01 显 / 02 隐。
|
|
76
|
+
组件级显隐,在 `directives[]` 里。**形状与语义(showType 00/01/02、condition.name 必填、lint `r52`/`r53` 铁律)见 `dsl_structure.md §指令 directives`**,本节只讲表达式陷阱。
|
|
77
|
+
|
|
87
78
|
- **关键陷阱(tab↔header)**:`activeTab` 默认值(如 "1")不随 billMode 变。依赖 `$activeTab` 的面板可见性必须 AND `$billMode === '0'` 再 OR 强制模式,否则首个面板泄漏到其他模式:
|
|
88
79
|
- 错:`$activeTab === '1' || $billMode === '1'`(billMode='2' 时 activeTab 仍是 '1' → 首面板错显)。
|
|
89
80
|
- 对:`($billMode === '0' && $activeTab === '1') || $billMode === '1'`。
|
|
@@ -113,7 +113,7 @@ if (this.itemVisible && this.itemKey) {
|
|
|
113
113
|
## 4. 布局陷阱(反复中招)
|
|
114
114
|
|
|
115
115
|
0. **卡片根节点绝不用画布/页面灰底;外圆角只画在卡片本体上;绝不从截图四角采样背景。**(rules.md §九)
|
|
116
|
-
→ 聚焦图 `card-N.png` 是 RGBA,卡片几何(`spec.cards[].mask`,`type:"geometry"`)之外已透明,背景可能是非均匀/非固定色,禁止取四角像素当 Root 背景。纯色卡:Root 背景=`spec.cards[].cardFill`(卡片自身 fill),外圆角放 Root,第一层容器透明且不重复圆角(
|
|
116
|
+
→ 聚焦图 `card-N.png` 是 RGBA,卡片几何(`spec.cards[].mask`,`type:"geometry"`)之外已透明,背景可能是非均匀/非固定色,禁止取四角像素当 Root 背景。纯色卡:Root 背景=`spec.cards[].cardFill`(卡片自身 fill),外圆角放 Root,第一层容器透明且不重复圆角(v3 `whiteCard` 已按此约束)。分层卡:Root `backgroundType=00` 透明,圆角与背景放内部独立卡容器(v3 的 `whiteCard`)。`cardFill` 为 null 时 Root 必须透明。**禁止**把画布灰底(`#F5F6F8` 等)或 `TOKEN.pageBg/fallbackBg` 写进 Root 背景。
|
|
117
117
|
1. **根节点(Root)不要在 style 里放 rect 组(width)。**
|
|
118
118
|
→ 根节点宽度由顶层 `view.width="375"` 承载,`view.style.properties` 里**不要**加 `rect` 组(含 `width:375`)——冗余且与 view.width 冲突(导出实证:49+ 张卡根 style 均无 rect)。误加时 `check_topology.py` 会报 `Root_style_rect_forbidden`(hard 级)提示删除。铁律见 `rules.md §九`。
|
|
119
119
|
1. **Column 默认 `width:100%`,同 Row 内两个 100% 宽 Column 会换行。**
|
|
@@ -260,26 +260,58 @@ gen-card-test 第一轮就把收支占比条写成了静态 `43%` / `57%` 的 `D
|
|
|
260
260
|
|
|
261
261
|
### 自动化覆盖索引(哪些规则已由工具兜底)
|
|
262
262
|
|
|
263
|
+
> 2026-09-25 全量盘点刷新(原表 2026-08-19 版已过期:R23/R25/R29/R30/R31/R32/R33/R35/R42/R43/R44/R50-R57/R59/R60/R61/R63/binding-\*/§10/§20.4/§27 均已陆续落入门禁)。逐条 code 的唯一权威 = `scripts/lib/rules/registry.mjs`。
|
|
264
|
+
|
|
263
265
|
| 规则 | 工具 | 自动校验内容 |
|
|
264
266
|
|---|---|---|
|
|
265
|
-
|
|
|
266
|
-
| **
|
|
267
|
-
| **
|
|
268
|
-
| **
|
|
269
|
-
| **
|
|
270
|
-
| **
|
|
271
|
-
| **
|
|
272
|
-
| **
|
|
273
|
-
| **
|
|
274
|
-
| **
|
|
275
|
-
| **
|
|
276
|
-
| **
|
|
277
|
-
| **
|
|
278
|
-
| **
|
|
279
|
-
| **
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
267
|
+
| **§1 itemValueFrom 必填** | `validate_schema.py` + `autofix_schema.py` + lint `missing-itemValueFrom` | 缺失/空串 → ERROR;一键补 "0" |
|
|
268
|
+
| **§2 style itemId** | `validate_schema.py` + lint `style-itemId-mismatch` | flexDirection 002/006、font lineHeight 007 等错配 → ERROR |
|
|
269
|
+
| **R4 列表数据驱动** ✅ | lint `binding-repeater-no-scope`(ERROR) / `binding-array-unconsumed`(WARN) / `binding-list-static-expansion`(WARN) + readiness bindings/lists 门 | 复现器缺 value / slotScope 模板 → ERROR;数组零消费、同构静态 tile 展开 → WARN;有视觉提名(listCandidates)而零数组化 → readiness blocker |
|
|
270
|
+
| **R8 圆角归属** ✅ | lint `r8-double-border-radius` | Root 与一级子容器双重圆角 → INFO |
|
|
271
|
+
| **R9/R10 Root 零间距** ✅ | lint `r9-root-nonzero-padding` | view 设非零 padding/margin → **ERROR** |
|
|
272
|
+
| **R14/R28 分割线 = Divider** | preflight `r28-row-as-divider`(WARN) + fidelity `elements.shapes` | Row 模拟分割线 → WARN;spec 细长 shape 缺 Divider 对应 → 迭代报告 `gate.elementHints` |
|
|
273
|
+
| **R16 slice 必须 Image** | fidelity `elements.slices`(切片覆盖)+ lint `r31c`(悬空引用 → ERROR) | 源缺失静默降级由生成前 WARN 与引用悬空双拦 |
|
|
274
|
+
| **R17 叶子节点防压缩** ✅ | lint `r17-leaf-no-flexshrink` | Text/Icon/Image/MultiText 缺 flexShrink:0(单子容器)→ **ERROR** |
|
|
275
|
+
| **R19 space-between 宽度** ✅ | lint `r19-space-between-no-width` | Row space-between 且宽度不确定又未被父容器拉伸 → WARN |
|
|
276
|
+
| **R20 Icon 显式 rect** ✅ | lint `r20-icon-no-rect` | Icon 缺 rect width/height(按默认 32×32 渲染)→ WARN |
|
|
277
|
+
| **R22 width:100% 可压缩** ✅ | lint `r22-width100-flexshrink0` / `r22b` | → **ERROR** |
|
|
278
|
+
| **R23 百分比 Math.round** ✅ | lint `r23-no-math-round` | → **ERROR** |
|
|
279
|
+
| **R25 Image itemValueFrom 三态** ✅ | lint `r25-image-*` 四向 + preflight Check 3 | 静态路径 from≠2 / 表达式 from≠1 / `$` 进图片槽 → **ERROR** |
|
|
280
|
+
| **R29 元素遗漏** | preflight `r29-element-count` | schema 节点数 vs spec 关键元素数 → WARN |
|
|
281
|
+
| **R30 Icon 事件格式** | lint `icon-no-click`(**ERROR**) / preflight `r30-icon-no-action`(WARN) | 缺 click / 扁平格式 |
|
|
282
|
+
| **R31 切图「引用 ↔ 磁盘名」同源** ✅ | lint `r31`(空格) / `r31b`(保留字符) / `r31c`(悬空) | → **ERROR** |
|
|
283
|
+
| **R32 Image 显式 rect.height** ✅ | lint `r32-image-no-height` | → **ERROR** |
|
|
284
|
+
| **R33 根样式全量提取** | lint `r33-root-missing-border` / `r33-root-missing-shadow` | spec 有 border/shadow 而根缺 → WARN |
|
|
285
|
+
| **R34 渲染契约归一化** ✅ | lint `r34-null-child`(null 子节点 → ERROR)+ `r17` / `icon-no-click` 覆盖其余契约 | 生成器出口 `normalizeRenderContract` 已实现,lint 防手写回归 |
|
|
286
|
+
| **R35 多子容器全刚性** ✅ | lint `r35-multi-child-all-shrink0` | → **ERROR** |
|
|
287
|
+
| **R37/R41 源文本单一消费** | trainer `duplicationScore`(语义重复渲染 → 门禁) | 训练链路承载 |
|
|
288
|
+
| **R38 容器高度** | lint `r38-negative-margin`(负 marginTop → ERROR,对应 R42)+ `r38-root-fixed-height`(Root 定高 → WARN) | |
|
|
289
|
+
| **R42 标题+装饰条** ✅ | lint `r42-title-underline-align/z-order/fixed-width` + `r64-title-underline-flow` | → ERROR/WARN |
|
|
290
|
+
| **R43 卡背景禁全透明** ✅ | lint `r43-card-transparent-background` | 背景声明缺失/Type 00 → **ERROR** |
|
|
291
|
+
| **R44 类型+值成对** ✅ | lint `r44-*` 七向(borderRadius/border/shadow/margin/padding/background) | 有值缺类型开关 → **ERROR** |
|
|
292
|
+
| **R49 inputParams 完整性** | `validate_schema.py` + expand_ir `normalizeInputParams` | 7 必填字段缺失 → ERROR(展开期自动补) |
|
|
293
|
+
| **R50 列表循环 $$** ✅ | lint `r50-dollar-dollar-field` / `r50b-unnormalized-scope-alias` | → **ERROR** |
|
|
294
|
+
| **R51 事件 this.data** ✅ | lint `r51-event-this-data-misuse` | 读非 Input 绑定键 → **ERROR** |
|
|
295
|
+
| **R52/R53 v-show** | lint `r52-vshow-condition-name-style`(WARN) / `r53-vshow-needs-wrapper`(**ERROR**) | |
|
|
296
|
+
| **R54/R55/R56 事件契约** ✅ | lint `r54-list-item-must-postmessage` / `r54-globalset-no-root-listener` / `r55-event-no-dollar-dollar` / `r56-jsapi-no-window-guard` | → **ERROR** |
|
|
297
|
+
| **R57 透明度不支持表达式** ✅ | lint `r57-opacity-expression` | → **ERROR** |
|
|
298
|
+
| **R61 数字语义** | lint `r61-text-numeric-literal`(千分号/货币/单位词/日期四形态) | 疑似运行期数字/日期静态文案 → WARN |
|
|
299
|
+
| **R63 width:100% 撞水平 margin** ✅ | lint `r63-width100-with-hmargin` | 交叉轴父容器 → **ERROR**(主轴靠 shrink 吸收,不报) |
|
|
300
|
+
| **§10 子卡回传裁剪** ✅ | lint `r50c-alertdata-whole-state` | `data: this.state` 整体回传 / `state.x = res.data` 整体赋值 → WARN |
|
|
301
|
+
| **§14/§15 切图命名与判重** | lint `r59-slice-semantic-name` / `r60-slice-duplicate-content` | 非语义名 / 同内容多副本 → WARN |
|
|
302
|
+
| **§20.4 style 同名子项唯一** ✅ | lint `style-duplicate-child` | 组内重复 itemKey → **ERROR** |
|
|
303
|
+
| **§26 要素线(两段式门禁)** | `check_fidelity.mjs` + `iterate --gate`(`stage=structure` → `stage=pixel`) | 文案 missing/invented + symbols/shapes `elementHints`;丢要素的轮次不算进步 |
|
|
304
|
+
| **§27 循环插槽单容器** ✅ | lint `r65-slot-multi-children` | slotScope 插槽平铺 ≥2 子节点 → **ERROR** |
|
|
305
|
+
| **R-FMT1-3 元组格式** ✅ | lint `fmt-border-radius-tuple` / `fmt-padding-tuple` / `fmt-margin-tuple` | → **ERROR** |
|
|
306
|
+
| **R-FMT4 MultiText 默认值** ✅ | lint `multitext-item-no-default` | → **ERROR** |
|
|
307
|
+
| **Button/Icon click / Canvas** | lint `button-no-click`(WARN) / `button-click-by-ancestor`(INFO) / `canvas-no-onmeasured`(WARN) | |
|
|
308
|
+
|
|
309
|
+
> **用法**:Workflow A 步骤 4 中,`validate_schema.py`(结构合法性)+ `lint_card.mjs`(行为规则)**双门禁全过 0 ERROR** 才能进入步骤 5 渲染。lint 已把渲染相关的关键规则升级为 **ERROR**,在渲染前拦截。
|
|
310
|
+
>
|
|
311
|
+
> **未被工具覆盖的规则(2026-09-25 归类,三类)**——机器判据在这些规则上要么依赖设计稿语义(硬编码业务词违反 R36),要么属于流程纪律,**仍需 AI 生成时遵守**:
|
|
312
|
+
> 1. **几何/标注消费纪律**(判定依赖「读设计稿」的语义能力):R2(间距来源)、R10(标注可信度分层)、R15(flex 对齐心智模型)、R18(分割线配套隐藏)、R24(auto 宽紧邻定宽)、R27(space-between 配方,R17/R22/R19 组合已分别有 gate)、R45(宽度实测)、R46(装饰条表达式化)、R55/R56/R80-R87(语义装配纪律);
|
|
313
|
+
> 2. **生成器内部已实现**(v3 训练链路的实现逻辑,不进 schema lint):R34(出口归一化)、R36(Agent 裁决)、R39-R41、R50-R58(语义边界/行高纪律)、§11/§16-§24(几何装配与图表回放);
|
|
314
|
+
> 3. **流程纪律**(由 iterate 门禁与 Workflow 编排承载):R1/R5/R6/R7/R12(AI 语义复核,r61 提醒)、R13/R21/R47/§13 四红线。
|
|
283
315
|
|
|
284
316
|
### R1 属性值来源 = spec 标注 + meta,禁止凭记忆、禁止抄存量卡
|
|
285
317
|
- **属性值类**(color/fontSize/lineHeight/radius/shadow/border/背景色)**直接读 spec 标注**(见 R10);组名 / 子项 key / 类型开关 / itemId 读 `vendor/preview/meta/style-meta.json` + `vendor/preview/meta/component-meta.json`。
|
|
@@ -293,9 +325,9 @@ gen-card-test 第一轮就把收支占比条写成了静态 `43%` / `57%` 的 `D
|
|
|
293
325
|
- 验收:渲染后 Playwright `getBoundingClientRect` 逐区块对照 spec 容器边界,±2px 内。
|
|
294
326
|
|
|
295
327
|
### R3 切图唯一路径 = slicePath.mjs;源缺失必须显式告警,禁止手写 @Nx
|
|
296
|
-
- 图标一律 `resolveSliceExport(spec.slices, sliceElName)` + `sliceRefPath`(导出路径自带 @3x → **3x 自动优先**);被引用切片必须拷到 `src/slices/<扁平名>`(仅引用才拷,卡片主体外的 chrome
|
|
328
|
+
- 图标一律 `resolveSliceExport(spec.slices, sliceElName)` + `sliceRefPath`(导出路径自带 @3x → **3x 自动优先**);被引用切片必须拷到 `src/slices/<扁平名>`(仅引用才拷,卡片主体外的 chrome 图标不拷)。`sliceRefPath` 已内置 `sanitizeSliceFileName()`,**产出即消毒名 = 落盘名**,不要在外层再手动拼 `/slices/` 路径。
|
|
297
329
|
- **`collectSlices` 返回 `missing` 清单**(spec slice 元素 + rect + reason),生成器**显式 WARN 告警**——禁止静默跳过。
|
|
298
|
-
- 源缺失兜底:**从 card-N.png 按 spec slice rect 精确抠图占位**(spec rect 在画布上是精确坐标,注意 card-N.png 通常 @2x 需按画布→图内坐标换算),存 `src/slices/<flatname>.png
|
|
330
|
+
- 源缺失兜底:**从 card-N.png 按 spec slice rect 精确抠图占位**(spec rect 在画布上是精确坐标,注意 card-N.png 通常 @2x 需按画布→图内坐标换算),存 `src/slices/<flatname>.png`;**落盘名必须先过 `sanitizeSliceFileName()` 消毒为纯 ASCII 名**(`# ? % & +` 一律替换,如 `警示-#999` → `iconWarning@2x`),schema 引用写**消毒后**的名字——引用与磁盘名必须同源(lint `r31b`/`r31c` 硬拦)。**不要用 `%23` 转义**:文件名带字面 `%` 会在任何一层 URL 编码处二次编码成 `%2523`,且 `r31b` 会把 `%` 直接判 ERROR。
|
|
299
331
|
- 已踩坑:手写 `@2x`(src/slices 只有 @3x → 404)且违反 3x 优先约定;上游导出遗漏(spec.slices/源目录都缺)时静默降级为 Text "?" 掩盖问题。
|
|
300
332
|
- 验收:Image value 全部 @3x 且可访问(curl /slices/<扁平名> 200);missing 清单为空(Agent/上游修图后)。
|
|
301
333
|
|
|
@@ -435,7 +467,7 @@ gen-card-test 第一轮就把收支占比条写成了静态 `43%` / `57%` 的 `D
|
|
|
435
467
|
- 图标视觉简洁(单色线条 + 语义明确)→ 优先找 sprite 字形。
|
|
436
468
|
- 图标视觉复杂(多色、阴影、品牌细节)→ 切片优先。
|
|
437
469
|
- sprite 字形清单:`grep -oE 'id="[^"]*"' vendor/preview/assets/svg-symbols.svg | sort -u`。
|
|
438
|
-
- 验收:选择 Icon 时 sprite 字形存在;Icon 节点 style.rect 有 width/height
|
|
470
|
+
- 验收:选择 Icon 时 sprite 字形存在;Icon 节点 style.rect 有 width/height 且等于设计稿尺寸(**lint `r20-icon-no-rect` WARN 已拦**);DOM 中 Icon 元素 `getBoundingClientRect` 与设计稿标注一致。
|
|
439
471
|
|
|
440
472
|
### R21 程序化构建器陷阱(Python/JS 手写 schema 生成器,2026-08-20 固化)
|
|
441
473
|
- 当卡片需要用脚本批量/半自动构建 schema(如 Python 构建器辅助生成)时,以下三类陷阱反复出现:
|
|
@@ -497,7 +529,8 @@ gen-card-test 第一轮就把收支占比条写成了静态 `43%` / `57%` 的 `D
|
|
|
497
529
|
- **绝不可能用 `"0"`**:Image 的 `itemTypeRange` 没有 `const` 项,`"0"` 不在合法枚举内。
|
|
498
530
|
- **根因**:R25 初版写「`"0"` 或 `"2"` 均可」,但渲染器逻辑为 `if (itemValueFrom === '2') { /* 设置背景图 */ }`——`"0"` 不进入该分支,图片永远空白。已收紧为只允许 `"2"`。
|
|
499
531
|
- **itemValueFrom 全局三态**(`dsl_structure.md`):`"0"`=常量(用于 Text 文案、数字等 `const` 类型字段)、`"1"`=变量/表达式、`"2"`=图片/文件。Image 字段 `itemTypeRange: "image,var"` 对应 `"2"` 和 `"1"`。
|
|
500
|
-
-
|
|
532
|
+
- **反向陷阱:把 `$` 表达式塞进 `"2"` 图片槽**(2026-09-21 补,实际事故):`"2"` 槽在运行时是**原样透传**——`property.js onPreview` 的 `"2"` 分支就一句 `out[itemKey] = itemValue`,不做任何求值。所以 `{"itemValueFrom":"2","itemValue":"$icon"}` 会把字符串 `"$icon"` 直接当图片地址,渲染成 `background-image: url($icon)` ⇒ **画布空白**,而且 `validate_schema.py` 全绿、控制台不报错、HTTP 也不 404(DOM 里那个 background-image 就是错值本身)。典型形态:**列表行内的图标 Image** 绑当前项字段(`$icon`/`$avatar`),却被误标成 `"2"`——同一行里旁边的 Text 用 `"1"`(`$name`/`$time`)全部正常,只有 Image 那一张空着,极易被误判成「切图没托管 / 服务没起」。正解:图片槽放表达式一律改 `"1"`(并补 `itemDefaultValue`)。
|
|
533
|
+
- **验收**:① schema 中所有 `className: "Image"` 节点的 `value` 属性 `itemValueFrom` 为 `"2"`(静态路径)或 `"1"`(变量),**且 `"2"` 的 `itemValue` 不以 `$` 开头、`"1"` 的 `itemValue` 以 `$` 开头**(`preflight.mjs` Check 3 / `lint_card.mjs` R25 三个方向都拦);② 渲染后 DOM 中 `.ce_image_fill` 的 `background-image` 不为 `none`,且**不含未求值的 `$` 变量名**;③ `python3 scripts/autofix_schema.py <schema.json>` 可机械修复该错槽(日志 `itemValueFrom "2"→"1"(图片槽放了表达式)`)。
|
|
501
534
|
|
|
502
535
|
### R26 font lineHeight/textAlign itemId 写错(2026-08-21 固化)
|
|
503
536
|
- **铁律**:font 组的 lineHeight itemId 是 **007**(不是 005),textAlign itemId 是 **003**(不是 006)。005/006 在 font 组里分别是 isMaxLine/maxLine,写错会导致行高/对齐失效甚至属性串位。
|
|
@@ -535,11 +568,14 @@ gen-card-test 第一轮就把收支占比条写成了静态 `43%` / `57%` 的 `D
|
|
|
535
568
|
- **根因**:平台 eventaction 是 event + action 分离的嵌套结构,AI 从通用 JSON 经验写成扁平 key。lint 检查的是 ea.event.eventKey === click,扁平格式没有 event 字段。
|
|
536
569
|
- **验收**:lint_card.mjs 无 icon-no-click ERROR。
|
|
537
570
|
|
|
538
|
-
### R31
|
|
539
|
-
- **铁律**:复制切图到 `src/slices/`
|
|
540
|
-
-
|
|
541
|
-
-
|
|
542
|
-
-
|
|
571
|
+
### R31 切图「引用 ↔ 磁盘名」必须同源:空格 / 保留字符 / 悬空(2026-08-21 起,2026-09-21 补齐 b/c)
|
|
572
|
+
- **铁律**:复制切图到 `src/slices/` 后,检查文件名是否含**空格、中文括号、特殊符号、URL 保留字符**(如 `矩形 4.png`、`icon (2).png`、`警示-#999.png`)。含则**必须重命名为纯 ASCII 安全名**(如 `矩形 4.png` → `btn_exchange.png`、`警示-#999.png` → `iconWarning@2x.png`)。schema 中 Image `value` 引用的路径必须与重命名后的文件名**一致**。
|
|
573
|
+
- **根因(R31,空格)**:渲染器将 `value` 拼接进 CSS `background-image: url(...)` 时,空格导致 URL 断裂(浏览器解析 `url(/slices/矩形` 然后把 `4.png)` 当非法值丢弃)→ `background-image: none` → 图片空白。即使后端 HTTP server 做了 URL decode,前端 CSS url() 拼接时未编码空格。
|
|
574
|
+
- **根因(R31b,URL 保留字符 `# ? % & +`)**:`#` 之后被浏览器当 fragment、`?` 之后当 query,**请求根本没发到那个文件**。实测引用 `/slices/警示-#999.png` 时只请求到 `/slices/警示-`;又因切片未命中曾落到 SPA 兜底返回 `200 + HTML` ⇒ 图空白、网络面板 200、控制台零报错、门禁全绿,**静默失败**。**正解是重命名,不是转义**:文件名带字面 `%` 会被任何一层 URL 编码二次编码成 `%2523`;`sliceNameFromRef()` 还会先 `decodeURIComponent`,写 `%23` 解码回 `#` 照样被 `r31b` 拦。(旧版本本条曾写「`#` 必须 `%23` 编码」——**已废弃,别照做**。)
|
|
575
|
+
- **根因(R31c,悬空引用)**:引用的名字在 `src/slices/` 里**根本不存在**(审计给出的名字与落盘名不同源、只改了一边、切图漏拷)→ 渲染 404、图片空白。R31 拦「名字带空格」、R31b 拦「名字带保留字符」,都是「名字写了但用不了」;R31c 补「这个名字磁盘上就没有」。`@filestore` / `http(s)://` / `data:` 远端引用天然无本地文件,不参与判定。
|
|
576
|
+
- **处理时机**:`cp slices/*.png src/slices/` 后、写 schema 前,逐个检查文件名。落盘名统一走 `lib/slice_dedupe.mjs → sanitizeSliceFileName()`(单一来源,`crop_slices.mjs` 已内置);安全命名规则:纯 ASCII、`小写字母_数字.png`,去掉空格/括号/保留字符。
|
|
577
|
+
- **验收**:`lint_card.mjs` 无 `r31` / `r31b` / `r31c` ERROR;渲染后 DOM 中 `.ce_image_fill` 的 `background-image` 不为 `none`。
|
|
578
|
+
- **兜底**:`crop_slices.mjs --from-audit` 会「收养」审计判定存在的资产(原字节复制 + 同源消毒 + 像素判重),补齐「审计给了正确名字、文件还在 bundle 里」这最后一环。
|
|
543
579
|
|
|
544
580
|
### R32 Image 组件必须设显式 rect.height(否则被 CSS aspect-ratio:16/9 压缩)(2026-08-21 固化)
|
|
545
581
|
- **铁律**:Image 组件的 `rect` 必须显式设 `height`(从 spec 标注读取),**不可省略**。仅设 `width:100%` 不够——渲染器 CSS 硬编码 `.ce_image { aspect-ratio:16/9 }`,不设高度时浏览器按 16:9 比例自动计算高度(如 width:162px → height:91px,远小于设计稿 110px),导致图片被压扁。
|
|
@@ -577,7 +613,7 @@ gen-card-test 第一轮就把收支占比条写成了静态 `43%` / `57%` 的 `D
|
|
|
577
613
|
- **铁律**:`spec_to_schema_v3.mjs` 的 `generate()` 返回前调用 `normalizeRenderContract()`:`Text` / `Icon` / `Image` / `MultiText` 必须有 `flex.flexShrink=0`;`Icon` 必须有嵌套 `click` eventaction;组件树中禁止出现 `null` 子节点。
|
|
578
614
|
- **根因**:逐段组装时容易漏掉通用契约;尤其是列表箭头 slice 命中但 PNG 未导出时,`imageFromSlice()` 返回 `null` 仍被放入插槽,导致运行期渲染挂起。
|
|
579
615
|
- **处理**:契约归一化放在生成器出口,而不是依赖训练器或人工修复;可选 slice 为空时必须提供可见占位或 Icon 兜底。
|
|
580
|
-
- **验收**:`validate_schema.py` 0 ERROR、`lint_card.mjs` 0 ERROR、递归扫描 schema 无 `null`
|
|
616
|
+
- **验收**:`validate_schema.py` 0 ERROR、`lint_card.mjs` 0 ERROR、递归扫描 schema 无 `null` 子节点(**lint `r34-null-child` ERROR 已拦**),独立 Chrome 渲染能进入 `data-card-ready`。
|
|
581
617
|
|
|
582
618
|
### R36 语义组件由 Agent 裁决,脚本不得业务硬编码(2026-08-25 固化)
|
|
583
619
|
- **铁律**:脚本只输出几何、标注和渲染证据;`display-value/input/action/list/media` 等组件语义由 Agent 结合截图与层级树裁决。禁止用“还款”“账单”等业务词触发组件选型。
|
|
@@ -609,42 +645,44 @@ gen-card-test 第一轮就把收支占比条写成了静态 `43%` / `57%` 的 `D
|
|
|
609
645
|
- **根因**:`¥` 被同时消费为主金额前缀和选项组标签,造成金额重影。
|
|
610
646
|
- **验收**:主金额、组标签、选项成员在 schema 中可追溯到不同源元素;渲染文本无重复。
|
|
611
647
|
|
|
612
|
-
|
|
648
|
+
> **R80–R87 = 2026-08-26/27 训练系列**(原编号 R42–R49,与下文 2026-09-02 系列撞号;后者被 lint / SKILL / quickref / rules 按号引用,故此处改为 R80–R87 消歧义,**内容未变**)。
|
|
649
|
+
|
|
650
|
+
### R80 列表带内分隔线不提升为顶层块(2026-08-26 训练固化)
|
|
613
651
|
- **铁律**:细长分隔线若落在重复媒体行或列表项的纵向带内,由重复项模板或列表容器承载;只有带外通栏分隔线才作为独立 divider 渲染。
|
|
614
652
|
- **根因**:列表项之间的 divider 被提升为顶层块后,把列表项撑散、箭头插错位置,甚至挤掉后续内容。
|
|
615
653
|
- **验收**:重复媒体行/ListLayout 的项间距来自模板;独立 divider 只出现在列表或重复带外。
|
|
616
654
|
|
|
617
|
-
###
|
|
655
|
+
### R81 重复行控件必须归入模板(2026-08-26 r039n 固化)
|
|
618
656
|
- **铁律**:同一右缘、同尺寸、等距重复的小控件是重复行模板的一部分;ListLayout/重复媒体模板消费后,原始控件不得再进入孤立媒体块。已消费的副金额 label/value 也必须从列表标题候选互斥排除。
|
|
619
657
|
- **根因**:列表模板已复用右缘箭头后,三个源箭头又被兜底成孤立媒体块;副金额值因字号和下方文本数量误判成列表标题,分别造成纵向膨胀和重影。
|
|
620
658
|
- **验收**:ListLayout 渲染 N 行时只出现 N 个模板控件;schema 中同一源元素只映射到一个语义块;按钮后不得出现被面板消费的金额文本。
|
|
621
659
|
|
|
622
|
-
###
|
|
660
|
+
### R82 语义间距只计一次(2026-08-26 r039n 固化)
|
|
623
661
|
- **铁律**:相邻语义块的标注间距由统一边界表推导,不得在上一块底部和下一块顶部重复添加;列表标题、列表项、查看更多必须注册边界。列表行内边距用“标注行高 - 内容高”推导,ListLayout 行间距显式归零。
|
|
624
662
|
- **根因**:列表语义缺失边界时,按钮误算到卡底的大留白;列表模板默认行距与项内 padding 叠加,导致内容撑出设计稿高度。
|
|
625
663
|
- **验收**:DOM 顶层块顺序与标注 y 一致;内容变化时容器继续 auto 撑开;渲染总高不因固定行距或重复间距超过标注比例门禁。
|
|
626
664
|
|
|
627
|
-
###
|
|
665
|
+
### R83 分割线必须是原生 Divider(2026-08-26 r050n 固化)
|
|
628
666
|
- **铁律**:独立分割线用 `Divider` 组件承载,逻辑高度固定为 `1px`,颜色优先读标注填充;重叠去重阈值使用 `>=0.4` 的纵向覆盖比例。
|
|
629
667
|
- **根因**:细线被兜底成普通 Image 后按倍率放大或与半像素线重复输出,视觉上变成粗线/重影。
|
|
630
668
|
- **验收**:schema 中带外独立细线只有一个 `Divider`;渲染 DOM 高度为 `1px`;同位置无第二条可见线。
|
|
631
669
|
|
|
632
|
-
###
|
|
670
|
+
### R84 缺失切图按画板裁切优先(2026-08-26 r050n 固化)
|
|
633
671
|
- **铁律**:缺失导出的媒体优先从原画板按标注矩形裁切为 `cropSlice`;裁切失败或切片视觉与画板局部不一致时才使用 `http://iph.href.lu/{w}x{h}?text=缺切图|切图冲突` 占位图。禁止退化为纯色块冒充图片。
|
|
634
672
|
- **根因**:设计稿可能漏导切图,纯色会丢失图像结构;直接占位又浪费画板中已有的真实视觉证据。
|
|
635
673
|
- **验收**:`spec.json` 缺导出项携带 `annotationDefects.resolution=artboard-crop` 与 `cropSlice`;生成器优先引用裁切文件;小切片用 alpha mask IoU 校验,低于 `0.35` 记录 `slice-visual-mismatch`。
|
|
636
674
|
|
|
637
|
-
###
|
|
675
|
+
### R85 面板流高裁剪必须有界(2026-08-26 r050n 固化)
|
|
638
676
|
- **铁律**:只有轻微重叠才允许把面板流式高度截到下一个重叠顶边,阈值为 `max(16px, 面板高度 * 25%)`;列表面板和大幅重叠保持完整高度。
|
|
639
677
|
- **根因**:无界裁剪会让头部面板压低主金额;完全不裁剪又会保留跨面板的幽灵留白。
|
|
640
678
|
- **验收**:轻微重叠卡的主金额 y 回到标注范围;列表面板不被压缩;整卡高度继续由内容撑开。
|
|
641
679
|
|
|
642
|
-
###
|
|
680
|
+
### R86 文本禁止压缩,辅助留白随对齐变化(2026-08-26 r050n 固化)
|
|
643
681
|
- **铁律**:所有 `Text` 节点统一设置 `flexShrink:0`;居中文本的辅助 padding 只加上下,不加左右;右对齐加右侧,左对齐加左侧。
|
|
644
682
|
- **根因**:文本被挤压换行或截断后还原度失真;对称侧向 padding 又会把右值和左标签推出标注列。
|
|
645
683
|
- **验收**:递归 schema 无可压缩 Text;长文案不横向压缩;居中、左对齐和右对齐辅助文本的偏移方向与标注一致。
|
|
646
684
|
|
|
647
|
-
###
|
|
685
|
+
### R87 列表行 padding 禁止 NaN(2026-08-27 r052 固化)
|
|
648
686
|
- **铁律**:`T.pad('micro')` 等 token 引用必须确认键存在于对应 token 表(`PADDING` 表有 `micro`,`GAP` 表也有 `micro`);消费端用 `parseInt(val, 10) || 0` 防御性兜底。`Math.max(NaN, x)` 恒为 `NaN`,会导致 padding 静默失效。
|
|
649
687
|
- **根因**:`PADDING` 表缺少 `micro` 键,`T.pad('micro')` 返回 `'undefined'`,`Number('undefined')` = `NaN`,使列表行 padding 全部为 `NaN`,行高坍塌。
|
|
650
688
|
- **验收**:所有 `T.pad/T.gap` 调用的键在对应 token 表中存在;`parseInt` 兜底确保非负整数;列表行 padding 不为 `NaN` 或 `undefined`。
|
|
@@ -721,15 +759,15 @@ gen-card-test 第一轮就把收支占比条写成了静态 `43%` / `57%` 的 `D
|
|
|
721
759
|
- **lint 实现**:`lint_card.mjs` 新增 `r38-negative-margin` 检查——遍历所有节点的 marginTop,值为负数 → ERROR;同时检查标题+装饰条拓扑:缺 `align:"BottomLeft"` 或装饰条排在标题之后 → ERROR,装饰条宽度未绑定标题长度表达式 → WARN。lint 编号 R38 对应 pitfalls R42。
|
|
722
760
|
- **验收**:① schema 中无任何节点 marginTop 为负值;② 标题+装饰条用 `AbsoluteLayout(align:"BottomLeft")` 包裹;③ 子节点顺序为 [装饰条, 标题];④ 装饰条宽度绑定标题长度表达式;⑤ lint_card.mjs 0 ERROR。
|
|
723
761
|
|
|
724
|
-
### R43
|
|
725
|
-
-
|
|
726
|
-
-
|
|
762
|
+
### R43 卡片背景禁完全透明(半透明允许)(2026-09-02 固化,2026-09-21 修订语义)
|
|
763
|
+
- **铁律**:卡片背景**不允许完全透明**——Root(view)或首层容器必须有明确的背景声明(backgroundColor + backgroundType:"01")。**带 alpha 的半透明底色/渐变允许**,按设计稿标注原样还原(含 alpha,不丢透明度)。
|
|
764
|
+
- **根因**:卡片完全透明时,设计器预览背景(浅灰)与实际运行环境背景不一致,导致文字在某些场景不可读、视觉对比度不足;半透明底色(弹层遮罩、毛玻璃风等)是明确的设计意图,正常还原即可。
|
|
727
765
|
- **取色优先级**:
|
|
728
|
-
1. **首选**:从设计稿标注 `spec.cards[].cardFill`
|
|
729
|
-
2. **兜底**:标注缺失时,从聚焦截图(`card-1.png
|
|
730
|
-
3.
|
|
731
|
-
- **lint 实现**:`lint_card.mjs`
|
|
732
|
-
- **验收**:① lint_card.mjs 对该规则 0 ERROR;②
|
|
766
|
+
1. **首选**:从设计稿标注 `spec.cards[].cardFill` 获取背景色(**含 alpha 原样保留**,写入 Root/首层容器的 `background.backgroundColor` + `backgroundType:"01"`)。
|
|
767
|
+
2. **兜底**:标注缺失时,从聚焦截图(`card-1.png`)的卡片主体区域**中心采样**取色(截图采样只能得实色,此时按实色处理)。
|
|
768
|
+
3. **禁止**:使用完全透明(`backgroundType:"00"` 或缺省);禁止用画布灰底;禁止从透明化截图四角/边缘采样(聚焦图几何外已透明,见 rules.md §九)。
|
|
769
|
+
- **lint 实现**:`lint_card.mjs` 的 `r43-card-transparent-background` 检查——Root 的 background 组缺失、或 backgroundType 为 "00"/空、或有 backgroundType 但缺 backgroundColor → ERROR(只判「背景声明存在性」,**不判色值 alpha**——半透明色值合法)。
|
|
770
|
+
- **验收**:① lint_card.mjs 对该规则 0 ERROR;② 渲染截图卡片背景非全透明(实色或半透明均可)。
|
|
733
771
|
|
|
734
772
|
### R44 类型+值成对检查(borderRadius/border/shadow 的类型开关必须配值)(2026-09-02 固化)
|
|
735
773
|
- **铁律**:样式系统中存在「类型开关 + 值」成对出现的样式组——设了值但缺类型开关,渲染器默认类型为空,值被**静默丢弃**。
|
|
@@ -766,7 +804,7 @@ gen-card-test 第一轮就把收支占比条写成了静态 `43%` / `57%` 的 `D
|
|
|
766
804
|
1. 渲染截图 vs 设计稿截图 → 像素级 band diff 分析(哪些 y 区间差异最大)。
|
|
767
805
|
2. 对关键元素采样颜色(D=设计稿 RGB vs R=渲染 RGB),标注 Δ 差值。
|
|
768
806
|
3. 检测元素 y 坐标偏移(渐变区域 top 差、按钮 top 差),找出级联偏移根因。
|
|
769
|
-
4. 针对性修改 → 重渲染 → 再对比,直到 match
|
|
807
|
+
4. 针对性修改 → 重渲染 → 再对比,直到 `pixelDiff.match` 达到收敛门禁(唯一权威 = `iterate.mjs` 的 `GATE.match`,以脚本为准,本文档不复制数字)。
|
|
770
808
|
- **工具**:`iterate.mjs` 一条龙输出 pixelDiff.match 百分比 + lint.errors 计数;像素分析用 PIL/numpy 扫描。
|
|
771
809
|
- **验收**:每轮迭代有量化指标(match% / Δy / Δcolor),不做盲改。
|
|
772
810
|
|
|
@@ -776,7 +814,7 @@ gen-card-test 第一轮就把收支占比条写成了静态 `43%` / `57%` 的 `D
|
|
|
776
814
|
| 属性 | 默认值 | 含义 |
|
|
777
815
|
|---|---|---|
|
|
778
816
|
| flex.gap | "0" | 子元素间距为 0 |
|
|
779
|
-
| background | 透明(backgroundType:"00") | 无背景色(R43
|
|
817
|
+
| background | 透明(backgroundType:"00") | 无背景色(R43 要求根节点显式给底色,半透明色值合法) |
|
|
780
818
|
| padding | 全 0(paddingType:"1" + 四边=0) | 内边距为 0 |
|
|
781
819
|
| borderRadius | 无(borderRadiusType:"0" + borderRadius:"0") | 无圆角 |
|
|
782
820
|
- **expand_ir 实现**:`ensureRootDefaults(schema.view)` 在展开后只对根节点自动补入缺失的核心样式默认值。
|
|
@@ -838,6 +876,7 @@ gen-card-test 第一轮就把收支占比条写成了静态 `43%` / `57%` 的 `D
|
|
|
838
876
|
|
|
839
877
|
### 验收
|
|
840
878
|
- 子卡 `closeAlertCard` 回传对象无 `list`/`title`/`parentInstanceId` 等父卡注入字段;父卡回调里找不到 `that.state.xxx = res.data.currCard` 这类整体赋值。
|
|
879
|
+
- **lint `r50c-alertdata-whole-state`(WARN,2026-09-25 沉淀)**:`data: this.state` 整体回传 / `state.x = res.data` 整体赋值自动暴露(`res.data.<字段>` 形态无法机械区分字段提取与子对象整体赋值,不拦、交 AI 语义复核)。
|
|
841
880
|
- 选中后父卡 `$currCard.xxx` 显示绑定字段齐全且值正确(事件面板用 `JSON.stringify` 核对)。
|
|
842
881
|
- 关联范本:`references/event_patterns.md` alertCard/closeAlertCard 段、`references/recipes.md` §2。
|
|
843
882
|
|
|
@@ -911,7 +950,7 @@ gen-card-test 第一轮就把收支占比条写成了静态 `43%` / `57%` 的 `D
|
|
|
911
950
|
## 13. 建卡收敛四红线(CardBuddy 黄金跑固化,2026-09-16)
|
|
912
951
|
|
|
913
952
|
- **背景**:CardBuddy 内建助手与 Codex 用同一条 query(07 画板建卡)各跑一遍:助手 26 步、4 次整份重写 IR(单次 40-110s)、终产物 validate 3 ERROR、match 靠删内容刷到 89.96(渲染高 260 vs 设计 464);黄金跑 1 次写 IR + 5 轮 `--set` 微调收敛到 **match 98.82 / heightDelta 0 / validate 0 error / lint 0 error**。差距全部落在下列红线。
|
|
914
|
-
- **R-a iterate.mjs 是唯一门禁**:单份报告内含 autofix + 真 `validate_schema.py` + lint + render + 像素对比。历史上 iterate 硬编码 `validate:{errors:0}`、expand_ir 又因 autofix 的 exit 1 误报「validate 未执行(未找到 python)」,两个假信号叠加让模型 ship 了 flexEnd
|
|
953
|
+
- **R-a iterate.mjs 是唯一门禁**:单份报告内含 autofix + 真 `validate_schema.py` + lint + render + 像素对比。历史上 iterate 硬编码 `validate:{errors:0}`、expand_ir 又因 autofix 的 exit 1 误报「validate 未执行(未找到 python)」,两个假信号叠加让模型 ship 了 flexEnd 硬错误。现在两处都已修:非零退出码是正常结果;python 缺失改按「真跑一次 --version」判定(Windows 的 `python3` 常是应用执行别名——能启动、立刻 9009 退出,不是 ENOENT,只认 ENOENT 会钉死这个假解释器),且缺 python 时报告带 `envError` ⇒ 门禁判 `BLOCKED(环境问题,不是卡片的错)`,明确让模型停手告知用户,而不是接着 --set 盲调。
|
|
915
954
|
- **R-b 并集像素对比**:`pixelDiff` 按 max(渲染, 设计) 画布采样,超出重叠区一律计差异 → 删内容 / 高度塌缩只会降分(旧坏卡从「89.96」被打回 50.41)。收敛条件:`pixelDiff.match ≥ 95` 且 `pixelDiff.size.heightDelta = 0` 且 validate/lint 0 error。match 不升反降或 hotspots 集中在已有内容区 = 在删内容凑分,回退该轮 `--set`。
|
|
916
955
|
- **R-c IR 只写一次**:首写 `fs_write .cache/ai-scratch/<id>.ir.json`,之后一律 `iterate.mjs --set '点路径=值'` 原子微调(一轮可多条;中段不存在响亮报错)。整份重写 IR 是纯输出浪费,只有结构级推翻才允许。
|
|
917
956
|
- **R-d 默认值三陷阱**:① Column/Row 默认 `gap=4`,不需要间距就显式 `gap:"0"`;② Text 不写 `lineHeight` 时渲染器按 1.5×fontSize 撑高(fs15→22.5、fs12→18),整卡累计漂移 +5~6px,标注的 lineHeight 必须逐条抄;③ Row 父级里的 Row / AbsoluteLayout 子节点默认 `width:100%`,space-between 行里会互相挤出画布——定宽子簇显式 `rect.width`,且并列子节点至少一个 `flexShrink:"1"`(lint R19 / R35)。flex 枚举 kebab-case(`flex-end` 不是 `flexEnd`)。
|
|
@@ -947,16 +986,19 @@ gen-card-test 第一轮就把收支占比条写成了静态 `43%` / `57%` 的 `D
|
|
|
947
986
|
**纪律**:`_altN` 只允许表示「同名切图在设计稿里确实标到了内容不同的多组位置」;**任何**派生/拷贝动作落盘前必须先过内容判重(sha → 像素容差 → 都不命中才写新文件)。看到 `src/slices/` 出现同内容多份 = 链路缺陷复发,先修链路再清存量(`slices_normalize.mjs --dedupe --write`)。
|
|
948
987
|
|
|
949
988
|
|
|
950
|
-
## 16.
|
|
989
|
+
## 16. 柱状图回放:flex 原生优先、Canvas 仅兜底(2026-09-18,r213-r221 沉淀;r242-r247 flex 原生化)
|
|
951
990
|
|
|
952
991
|
**事故**:综合推荐/保险保障卡的柱状图(窄柱 + 网格线 + 轴标签/值标签)在 spec 里是纯矢量
|
|
953
992
|
shape/text,DSL 无图表组件,生成器此前把柱体静默丢弃、网格线当 Divider 错排——整块图表区
|
|
954
993
|
(约占卡高 25%)像素全错,position 分崩塌。
|
|
955
994
|
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
-
|
|
995
|
+
**形态判定(`lib/v3_shared.mjs` 已实现,纯几何、两路共用)**:`detectChartClusters` —— 同基线(底沿 ±3)窄柱(有纯色/渐变 fill、圆角 <8、宽 4-48 / 高 4-260)≥4 根 + 横向近等距 + (网格线(h≤2 / 宽 ≥60 / 与柱区横向重叠 ≥60%)≥3 **或** 簇内标签 ≥4)→ 判为柱状图簇;不满足则不识别,留在通用流。
|
|
996
|
+
|
|
997
|
+
**首选路径:flex 原生组件树(r242-r247,`buildFlexBarChart`)** —— Canvas 自绘会让标注文本在组件计数中**消失**(schemaComponents 只数 Text 节点,textComponents 被系统性压低,实测 0.4375),且 canvas 字体渲染与设计稿偏差大,故图簇优先 flex 原生:
|
|
998
|
+
- 轴列:Column(space-between, align flex-end) 承刻度文本;绘图区 Row(align flex-end) 按「类目标签中心中点」切槽位;槽内柱列宽=max(柱宽, 值标签宽+4)(渲染字形比标注 rect 略宽,不留余量会折行 h16→30 整列上移 13px),相邻标签水平重叠用负 marginLeft 拉回柱中心;值标签在柱顶之上(Column justify flex-end 流内自然落位)。
|
|
999
|
+
- 网格线=绘图区**硬停线性渐变背景**(坐标系线条在绘制层最底部,与 R62 同旨);类目行按槽位居中、比柱对宽时撑开 + 负 margin 锚柱对中心 + 槽位列 overflow visible。
|
|
1000
|
+
|
|
1001
|
+
**兜底路径:Canvas 回放(r213-r221,`chartBarCanvasCode`)** —— 已识别为图簇但 `buildFlexBarChart` 返回 null 时(柱<4 / 网格线<2 / 无簇内文本 / 缺轴刻度或类目标签 / 切槽后空槽),整簇(柱 + 网格线 + 簇 bbox 扩边内的文本标签)从通用文本/形状流摘出,改由一个 Canvas
|
|
960
1002
|
(`rect` = 簇 bbox,`flexShrink:0`)在 onMeasured 里按簇内相对坐标回放:柱 = 四角 arc 拼路径
|
|
961
1003
|
(预览器白名单**无 roundRect**)+ `fill()`;网格线 = `moveTo/lineTo/stroke`;标签 = `fillText`
|
|
962
1004
|
(`textAlign/textBaseline` 按标注);渐变柱用 `createLinearGradient` + 原生 `addColorStop`。
|
|
@@ -964,12 +1006,12 @@ shape/text,DSL 无图表组件,生成器此前把柱体静默丢弃、网格
|
|
|
964
1006
|
- 簇必须登记 `addSemanticBound(y, y+height, container=true)`:Canvas 是流内真实高度,不登记则
|
|
965
1007
|
下文所有 section 的顶距从更早的底沿起算(实测下一节被推偏 200+px)。
|
|
966
1008
|
|
|
967
|
-
**预览器 ctx
|
|
1009
|
+
**预览器 ctx 铁律(Canvas 路径最大坑)**:`event.canvas` 是**画布包装对象**而不是 ctx!必须先
|
|
968
1010
|
`var cv = event.canvas.getContext('2d')` 拿代理 ctx(export-proven 范式见 assetAllocRadar 的
|
|
969
1011
|
gaugeCanvas/radarCanvas)。直接把 `event.canvas` 当 ctx 调 `fillRect/arc` 会**静默无输出**
|
|
970
1012
|
(不抛错、画布全空白),排查成本极高。另:`canvas.width = event.width` 是 no-op 但无害。
|
|
971
1013
|
|
|
972
|
-
|
|
1014
|
+
**同轮沉淀的连带护栏(非图表,2026-09-18)**:
|
|
973
1015
|
- 列表模板签名护栏:重复行并表前按结构签名(行图标/复合金额对/行右沿/行内操作)取多数签名,
|
|
974
1016
|
少数签名行退出 ListLayout——半宽对比面板与全宽产品行曾被并成一表,模板字段错位
|
|
975
1017
|
(desc 落 title 槽竖排换行 + 缺键渲染「这是一个文本」占位)且对比块双份渲染。
|
|
@@ -1008,19 +1050,16 @@ padRight 被算成 303px 把左栏压没。
|
|
|
1008
1050
|
- 值行与**标签文本**左对齐(圆点只缩进标签行):缺这段 line2 内缩金额会顶到壳左沿。
|
|
1009
1051
|
- 状态 tag 壳 fill 常报成描边同色(实为浅底):fill 与字色亮度差<0.08 时按描边色 8% tint 还原,否则彩字隐入同色底。
|
|
1010
1052
|
|
|
1011
|
-
## 19. 柱状图 flex
|
|
1053
|
+
## 19. 柱状图 flex 原生回放(已并入 §16)
|
|
1012
1054
|
|
|
1013
|
-
|
|
1014
|
-
- 轴列:Column(space-between, align flex-end) 承刻度文本;绘图区 Row(align flex-end) 按「类目标签中心中点」切槽位;槽内柱列宽=max(柱宽, 值标签宽+4)(渲染字形比标注 rect 略宽,不留余量会折行 h16→30 整列上移 13px),相邻标签水平重叠用负 marginLeft 拉回柱中心;值标签在柱顶之上(Column justify flex-end 流内自然落位)。
|
|
1015
|
-
- 网格线=绘图区**硬停线性渐变背景**(坐标系线条在绘制层最底部,与 R62 同旨);类目行按槽位居中、比柱对宽时撑开 + 负 margin 锚柱对中心 + 槽位列 overflow visible。
|
|
1016
|
-
- 不满足柱状图形态(柱<4 / 网格线<2 / 缺轴或类目标签)才回退 chartBarCanvasCode。
|
|
1055
|
+
> 本节(flex 原生柱状图回放 + Canvas 兜底判据)已全量合并进 **§16 柱状图回放:flex 原生优先、Canvas 仅兜底**。保留本节号与标题作重定向锚点,避免既有引用断链。
|
|
1017
1056
|
|
|
1018
1057
|
## 20. 预览渲染器契约四铁律(2026-09-18 实测固化,vendor/preview standalone)
|
|
1019
1058
|
|
|
1020
1059
|
1. **无绝对定位**:style 解析器只认 rect/flex/background/padding/margin/borderRadius/border/shadow/transform/other;`position` 组被**整体忽略**(AbsoluteLayout 子节点 left/top 全失效、堆在 0,0)。二维散布只能「行 + 负 margin」或「列 + paddingTop」组合表达。
|
|
1021
1060
|
2. **overflow 默认 hidden**(parseOther:`t.overflow = e.overflow || 'hidden'`):任何「溢出可见」必须显式 other.overflow=visible;靠溢出叠图的方案默认被裁。
|
|
1022
1061
|
3. **负 margin 可用**(margin 组 marginType=1 逐边):flex 行内唯一可重叠/回拉手段;人力卡(holdFinancialProducts 等)亦在用。
|
|
1023
|
-
4. **style 组同名子项唯一**:makeStyle 已带空 backgroundColor/backgroundGradient 子项,绑定值必须**改写**该子项(itemValue=$x + itemValueFrom=1);追加第二个同名子项会让渲染器取到空值/绑定串原样进 doColor → 背景透明(列表逐行壳色 $rowBg 曾因此整片消失,pix 0.237→0.085 即修复此 bug
|
|
1062
|
+
4. **style 组同名子项唯一**:makeStyle 已带空 backgroundColor/backgroundGradient 子项,绑定值必须**改写**该子项(itemValue=$x + itemValueFrom=1);追加第二个同名子项会让渲染器取到空值/绑定串原样进 doColor → 背景透明(列表逐行壳色 $rowBg 曾因此整片消失,pix 0.237→0.085 即修复此 bug)。**lint `style-duplicate-child`(ERROR,2026-09-25 沉淀)已拦**。
|
|
1024
1063
|
|
|
1025
1064
|
## 21. 散点带:中心媒体 + 环绕胶囊簇按三列重建(2026-09-18,r250-r255 训练沉淀)
|
|
1026
1065
|
- **形态**:≥4 个 pill 按钮(radius≥10、h≤40、有底色/渐变)分 ≥2 行环绕一个近方形中心媒体(雷达/环图裁切图,orphanBlocks 60-200 近方)。「五笔钱」类布局即此。
|
|
@@ -1046,3 +1085,136 @@ Canvas 自绘柱状图会让标注文本在组件计数中**消失**(schemaCom
|
|
|
1046
1085
|
- **识别兼容拆分标注**:allTexts 里「70分」常被拆成 大号纯数字(fs≥20) + 同基线右邻「分」单位两个条目;判据 = 数字 + 单位 + 下方 fs≤13 标签,三者均未被任何通道消费。allTexts 条目是归一化形状(顶层 fontSize/color + sourceEl),几何/样式必须双源回退(anyRect/anyFs/anyColor),只认 rect()/textStyle 会全 null 静默跳过。
|
|
1047
1086
|
- **零流高叠放**:仪表盘与金额行竖直重叠 → 行 marginTop=semanticTopGap(gy)、marginBottom=−(84+顶距)(marginType=1),流式高度归零不推偏头部;addSemanticBound(container=true) 让带内行忽略该边界、带下行以其底沿为间距基准。
|
|
1048
1087
|
- Canvas 出码按 `self.state.score/scoreLabel` 字面读:变量被 uniqueKey 重命名(命名冲突)时放弃仪表盘,禁改出码。
|
|
1088
|
+
|
|
1089
|
+
## 25. width:100% 撞水平 margin:交叉轴定宽不可缩(lint r63,2026-09-20 固化)
|
|
1090
|
+
- **现象**:节点右侧内容被切掉/整块消失。模型想表达「父容器里水平内缩 6/12px」,写成 `rect.width:"100%"` + `margin.marginLeft:6`。
|
|
1091
|
+
- **根因(确证,读渲染源码)**:`StyleParser.parseRect` 把 `100%` 交给 `adapter.doSize`,doSize 见 `%` 原样返回 → CSS `width:100%`,**已等于父容器内容盒宽**;`parseMargin` 再把 `margin-left/right` 加上去 = 元素外框超出父容器。父容器按列排布时水平方向是**交叉轴**,定宽不可缩 → 必溢出,被 `overflow:hidden`(Root / 组件默认 hidden)裁掉。
|
|
1092
|
+
- **为什么不报主轴**:父容器 `flex-direction:row` 时 `flexShrink` 默认 1,节点会被缩回「容器宽 − margin」,margin 被吸收、不溢出。全仓 205 份 schema 实测 4 处 `width:100% + marginLeft`(accountCreditedlist `column_m` / memberLevelNone `comp_8` / pointsDetail `comp_19` / assetOverview `text_55`)**全在 Row 父容器**,全靠 shrink 吃掉间距——故 R63 只盯交叉轴(`Root`/`Column`/`SingleColumn`/`flexDirection:"column"`/`AbsoluteLayout` 的 abspos 子节点),主轴不报,零误报。
|
|
1093
|
+
- **正路**:① 水平间距挪到父容器 `paddingLeft/Right`(或 `gap`);② 流式父容器(`Column`/`Root` 且 `alignItems` 为空/`stretch`)把 `rect.width` 改成 `"auto"`(**必须显式写**),靠 stretch 自动补宽、保留 margin;③ 并排分栏用「左 `width:100%`+`flexShrink:1`,右 `auto`+`flexShrink:0`」。详见 `rules.md §十六`。
|
|
1094
|
+
- **实测(375 宽卡,`render_card.mjs` + `elementBoxes`,2026-09-20)**——父容器 375,子节点 `marginLeft:6`:
|
|
1095
|
+
|
|
1096
|
+
| 子节点形态 | 父容器 | 实测 w / 右缘 | 结论 |
|
|
1097
|
+
|---|---|---|---|
|
|
1098
|
+
| `rect.width:"100%"` | Column | 375 / 381 | **溢出 6px**(被裁)|
|
|
1099
|
+
| `rect.width:"100%"` | Row | 369 / 375 | shrink 吸收 margin,不溢出(故主轴不报)|
|
|
1100
|
+
| `rect.width:"100%"` | AbsoluteLayout | 375 / 381 | **溢出 6px**(abspos 无 shrink)|
|
|
1101
|
+
| `rect.width:"auto"` | Column | 369 / 375 | 正路② 成立 |
|
|
1102
|
+
| 无 `rect.width`(Text) | Column | **86.67** / 92.67 | **只删 width 无效**——`.ce_text{width:fit-content}` 顶掉 stretch |
|
|
1103
|
+
| 无 `rect.width`(Column) | Column | 375 / 375 | 容器类无组件 CSS width,靠 stretch 撑满 |
|
|
1104
|
+
|
|
1105
|
+
> 结论:正路② 一律**显式**写 `rect.width:"auto"`;AbsoluteLayout 的 abspos 子节点无 stretch,只能用 ① 或显式像素宽。
|
|
1106
|
+
- **护栏**:lint `r63-width100-with-hmargin`(ERROR,渲染前拦截,iterate `--gate` 要求 lint.errors=0)+ `tools/tests/card-gen-gates.test.mjs` 正反用例。
|
|
1107
|
+
|
|
1108
|
+
## 26. 要素线:设计稿的文案是固定真值,迭代只许改样式布局(2026-09-21 demo 回合固化)
|
|
1109
|
+
|
|
1110
|
+
一次 61 步 / 15′31″ / 460 万 input 的低效建卡回合,逐项复盘出的**七条**可机械拦截的根因(按代价排序):
|
|
1111
|
+
|
|
1112
|
+
| # | 形态 | 代价 | 现在被什么拦住 |
|
|
1113
|
+
|---|---|---|---|
|
|
1114
|
+
| 1 | **要素真值整批丢失**:设计稿「金钱包40号E款 / 1.37 / 每日申赎」被换成自编占位值「理财优选1号 / 2.45 / 每日申购」 | 卡片内容真值全错,人工比对才发现 | `check_fidelity.mjs` 的 `elements.missing` / `elements.invented` + `iterate.mjs --gate` 的 `elements.missing=0`;`gate.stage=structure` 明说「先补要素再调像素」;最好成绩改判**字典序(先要素后像素)**,丢要素的轮次不算进步也不进 `ir.best` |
|
|
1115
|
+
| 2 | **像素分靠删节点凑**:为消一条 lint ERROR 把 Row 改 `width:auto`,match 87.5→61.95,再改回 | 40+ 步来回震荡 | `r35-multi-child-all-shrink0` 只有在「子定宽之和 > 父定宽」时才 ERROR,其余降 WARN 且文案写明「不要为此改 width:auto 或删节点」;`stalled.hint` 在要素下滑时点名「这是在删要素凑像素」 |
|
|
1116
|
+
| 3 | **「改 IR → 原样重跑检查」被当成打转**:调用指纹只含脚本 + 参数,`ir_patch` 改了 IR 再跑同一条 iterate 判定为「同一调用在最近 8 轮出现 3 次」→ 强制收尾 | 回合在第 61 步被误熔断(当时正在收敛轨道上) | `skillInputFingerprint()`:指纹带上 `--ir` 文件的大小 + mtime,**输入变了就不是打转**(`--out` 刻意排除:每轮都写 schema.json,算进去等于永不触发) |
|
|
1117
|
+
| 4 | **退避短路的 key 不含可变输入** | 2 次「该调用已连续失败 2 次,本次不再执行」,每次白付 ~11 万 token | 同上,`skillCallKey` 走同一指纹 |
|
|
1118
|
+
| 5 | **`iterate --gate` 用 exit 1**,与「脚本报错收尾」同码 → 被记账成连续失败 | 误导模型去改别的地方 | `--gate` 未达标改 **exit 2**(gate-fail ≠ 脚本报错);quickref / SKILL.md 同步 |
|
|
1119
|
+
| 6 | **`card_screenshot` 指向 5180**(服务在 8787)`ERR_CONNECTION_REFUSED` ×2 | 纯噪音 2 步 | `browser.js` 的 `BASE_URL` 改懒取 `baseUrl()`;`standalone.js` 在 preview 模式把 `CARD_SCREENSHOT_BASE` 设为实际 apiBase |
|
|
1120
|
+
| 7 | **正文暴走**:单步正文 24754 字 / 2 分 38 秒,超上限的 23254 字被丢弃(output token 照付) | 一步烧掉 2.6 分钟 | `BODY_BURST_ABORT_CHARS=8000` + `BODY_BURST_MAX=2`:截断后仍在吐就**掐断本步**并注入「正文只写结论」提醒,本步零副作用可安全重跑 |
|
|
1121
|
+
|
|
1122
|
+
> **总纲**:要素是设计稿给定的**真值**,UI 差异终究只是样式与布局的差异。可以调节点结构与位置,不能丢要素、不能自造内容 —— 门禁因此是**两段式**:`stage=structure`(要素齐全 + 结构合法)→ `stage=pixel`(像素 / 布局收敛)。
|
|
1123
|
+
|
|
1124
|
+
### 26.1 顺带修掉的静默数据损坏:HTTP 请求体逐块 `toString`
|
|
1125
|
+
|
|
1126
|
+
`req.on('data', (c) => (body += c))` 在 `c` 是 Buffer 时**逐块**解码,跨块的多字节字符(中文)会被劈成 `U+FFFD`。
|
|
1127
|
+
影响:`/__ai/chat`(整轮对话历史 + 卡片片段,动辄几百 KB 中文)每次错一个字符 ⇒ 下一轮前端回传的历史与服务端上一轮实际发送的 prompt **不再逐字节一致**,
|
|
1128
|
+
**前缀缓存从该处起整段失效**(这是「同一张卡每轮都吃满 input token」的一个隐藏放大器);`/__save` 更糟 —— 被劈坏的字符直接写进 `schema.json`。
|
|
1129
|
+
修法:所有请求体读取前 `req.setEncoding('utf8')`(StringDecoder 会正确拼接跨块序列)。已覆盖 `ai-chat / cardFiles / cardSave / cardHistory / cardSync / sliceUpload / codeBridge / logStream / cardOpenFolder` 与 e2e 脚本。
|
|
1130
|
+
|
|
1131
|
+
## 27. 列表循环插槽:每个循环项必须由【单一容器】包裹,slot 下禁止平铺多个子节点(2026-09-24 gen-1 汇率卡事故固化)
|
|
1132
|
+
|
|
1133
|
+
- **现象**:把写死的重复行改成 ListLayout 绑定 `$exchangeRows` 时,在 slotScope=true 的 slot 里平铺放了两个平级子节点 —— 数据行 `comp_8:Row` 和分隔线 `comp_19:Row`。结果每个「汇率条目」被拆成两段错位重复,行与分隔线互相穿插、最后一条没有分隔线。
|
|
1134
|
+
- **根因(确证 ListLayout 语义)**:`slotScope=true` 的 slot 是**循环模板**,其 `components[]` 数组是**一个整体模板**,会**原样整体**套到 value 数组的每一项上重复渲染。slot 里放几个平级子节点,这几个就会在每个 item 下都平铺出现 —— **不是**「各自独立显示一次」。所以把「数据行 + 分隔线」分开放成两个平级子节点,等价于每个 item 都渲染「数据行,分隔线」两个并列块,二者无法构成「一行内容 + 一条底边线」的纵向整体,互相穿插错位。
|
|
1135
|
+
- **正路**:每个循环 item 的**全部**元素(数据行、分隔线、角标、标签、插拔箭头…)必须包进**一个**容器(`Column` 或 `Row`),这个容器是 slot children 数组里的**唯一元素**。形如:ListLayout.slots[0].slotScope=true,children[0] = Column「item」,其内 slots[0] = [数据行 Row, 分隔线 Row, ...任意数量]。该 Column 承担 item 骨架(`rect.height` 约束、`gap` 控制行与分隔线间距、`flexShrink`),内层自由排布。**禁止**在 slot 直接平铺 ≥2 个顶层子节点。
|
|
1136
|
+
- **识别要点**:分隔线、角标这类「非数据主行」元素若与主行平级,会被当成 items 的一部分铺开;只有包进一个容器的 slot,才能用 flex 竖直方向把「行内容 + 分隔线」固定成一个不可拆散的整体,循环时作为一个条目对齐滚动与间距。
|
|
1137
|
+
- **护栏**:lint `r65-slot-multi-children`(**ERROR**,2026-09-25 沉淀:slotScope 插槽平铺 ≥2 子节点直接拦);迭代阶段 validate/lint/preflight 零 error + `renderOk` 不代表 slot 平铺多子节点不产生视觉错位 —— 必须用 `card_screenshot`(渲染原图)核对条目边界。
|
|
1138
|
+
|
|
1139
|
+
|
|
1140
|
+
## 28. midCluster 固定 padding 漂移 + optionGroup 侧标签 + 像素排除缺陷(2026-09-26 转账计划确认卡训练固化)
|
|
1141
|
+
|
|
1142
|
+
### 28.1 midCluster paddingTop 必须用 semanticTopGap
|
|
1143
|
+
- **现象**:midCluster 行(如「付款账户/收款账户」标签)恒定用 `mcPad=4` 作 paddingTop,与 spec 顶距(如 12px)不符,上半部所有元素系统性高 12px,逐元素累积成 IoU=0.31。
|
|
1144
|
+
- **根因**:midCluster 组装路径独立于通用 auxText 行,padding 写死 `mcPad`(=T.pad('mini')=4),不走 `semanticTopGap()`。
|
|
1145
|
+
- **正路**:`const mcTopGap = semanticTopGap(sem.midCluster.y) ?? mcPad`,paddingTop 用 `mcTopGap`。**所有**语义行的 paddingTop 都必须走 `semanticTopGap()` —— 固定常量会让该行与 spec 顶距脱钩,下游所有行跟着漂。
|
|
1146
|
+
- **代码**:`v3_assemble.mjs` ~L2197。
|
|
1147
|
+
|
|
1148
|
+
### 28.2 optionGroup 侧标签(label 与按钮同 y 在左)
|
|
1149
|
+
- **现象**:表单标签「执行日期」位于切换按钮(仅一次/按月/按年)的**左侧同 y**,被当成独立 auxText 行竖排堆叠,凭空多出 ~28px 高度,下半部全部下移。
|
|
1150
|
+
- **根因**:optionGroup 标签检测只查「按钮正上方」(`bottom <= top && top - bottom <= 48`),不查「同 y 左侧」。侧标签漏检 → 进 auxTexts → 独立成行。
|
|
1151
|
+
- **正路**:标签检测增加侧标签路径——垂直中心重叠(`|tcY - grpCY| <= max(15, groupHeight*0.6)`)且文本右沿 ≤ 按钮组左沿 +4px。命中后标记 `sideLabel: true`,组装时标签与 chips 同行 `space-between`。
|
|
1152
|
+
- **anchorY 陷阱**:侧标签的 `anchorY` 必须用 `group.y`(不能用 `labelY`),否则落入 optionGroup 自身注册的 semanticBound 内 → `semanticTopGap` 归零 → 顶距丢失。
|
|
1153
|
+
- **代码**:`v3_extract.mjs` ~L327 侧标签检测;`v3_assemble.mjs` ~L1635 anchorY + ~L1705 内联渲染。
|
|
1154
|
+
|
|
1155
|
+
### 28.3 像素排除:slice-visual-mismatch + cropFile 空格归一
|
|
1156
|
+
- **现象**:大按钮切片(`矩形 4`,有 `slice-visual-mismatch` 缺陷,走 artboard-crop 兜底)渲染为空白占位图,像素差全计分(89% 热区);但排除逻辑只认 `missing-slice` 类型 → 不排除。
|
|
1157
|
+
- **根因①**:排除条件 `defect.type !== 'missing-slice'` 漏了 `slice-visual-mismatch`(同一条 artboard-crop 兜底路径)。
|
|
1158
|
+
- **根因②**:cropFile 匹配用 spec 原始名(空格 `矩形 4`)对 schema JSON(消毒后 `矩形-4`),`includes()` 恒 false。
|
|
1159
|
+
- **正路**:排除条件加 `slice-visual-mismatch`;cropFile 匹配增加 `.replace(/\s+/g, '-')` 归一兜底。
|
|
1160
|
+
- **代码**:`run_round.mjs` ~L1118。
|
|
1161
|
+
|
|
1162
|
+
### 28.4 pairDeviations 重复文本交叉匹配
|
|
1163
|
+
- **现象**:诊断输出出现 `dy=-202` 幻影偏差(两处「转账计划」交叉配对),误导修复方向。
|
|
1164
|
+
- **根因**:`pairDeviations` 按 score 取首个匹配,无几何 tiebreaker —— 相同文本按数组顺序配对,而非最近距离。
|
|
1165
|
+
- **正路**:score 并列时取几何中心最近者(与 `positionScore` 同款 tiebreaker)。
|
|
1166
|
+
- **代码**:`diagnosis.mjs` ~L64。
|
|
1167
|
+
|
|
1168
|
+
## auto. generated leaves lacked flexShrink and Icon lacked click con(r002 训练回灌)
|
|
1169
|
+
> lesson: `LESSON-20260824-002` status: `promoted`
|
|
1170
|
+
|
|
1171
|
+
v3 generate 返回前统一执行渲染契约归一化:Text/Icon/Image/MultiText 设置 flex.flexShrink=0,Icon 补嵌套 click eventaction。
|
|
1172
|
+
|
|
1173
|
+
|
|
1174
|
+
## auto. large currency value with right edit affordance rendered as (r018n 训练回灌)
|
|
1175
|
+
> lesson: `LESSON-20260825-036` status: `promoted`
|
|
1176
|
+
|
|
1177
|
+
货币符号 + 数值 + 右侧编辑/箭头控件是金额输入候选;Agent 复核后用 Input + 独立货币前缀,控件高度读标注或行高推导。禁止用业务词触发该规则。
|
|
1178
|
+
|
|
1179
|
+
|
|
1180
|
+
## auto. button text consumed again as list title caused duplicate gh(r018n 训练回灌)
|
|
1181
|
+
> lesson: `LESSON-20260825-037` status: `promoted`
|
|
1182
|
+
|
|
1183
|
+
源文本单一消费:按钮文案必须从列表标题候选中互斥排除;列表标题必须绑定完整业务容器,而不是按钮壳。
|
|
1184
|
+
|
|
1185
|
+
|
|
1186
|
+
## auto. fixed root height clipped variable-length content(r018n 训练回灌)
|
|
1187
|
+
> lesson: `LESSON-20260825-038` status: `promoted`
|
|
1188
|
+
|
|
1189
|
+
Root/普通容器高度必须 auto,由子内容、gap 和标注 padding 撑开;只有叶子控件可读标注高度。
|
|
1190
|
+
|
|
1191
|
+
|
|
1192
|
+
## auto. auxiliary texts with different y values were grouped into on(r038n 训练回灌)
|
|
1193
|
+
> lesson: `LESSON-20260826-039` status: `promoted`
|
|
1194
|
+
|
|
1195
|
+
sameRow 只能消费带 rect/sourceEl 的原始标注对象;语义投影对象必须显式携带 sourceEl 或 y。分组循环禁止对丢失 rect 的投影对象重复调用几何函数。
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
## auto. a composed primary amount fragment was reused as an option-g(r038n 训练回灌)
|
|
1199
|
+
> lesson: `LESSON-20260826-041` status: `promoted`
|
|
1200
|
+
|
|
1201
|
+
主金额合成片段不得同时成为 option-group label;label 候选必须位于主金额矩形外且不是主金额合成文本的组成部分。源文本继续执行单一消费。
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
## auto. standalone divider rendering split repeated media rows and l(r038n 训练回灌)
|
|
1205
|
+
> lesson: `LESSON-20260826-042` status: `promoted`
|
|
1206
|
+
|
|
1207
|
+
细长分隔线只有在重复媒体行或列表项带外才是独立语义块;带内分隔线由 ListLayout/重复项模板承载,避免把项间 divider 提升成顶层块。
|
|
1208
|
+
|
|
1209
|
+
|
|
1210
|
+
## auto. panel value and repeated right affordance leaked into other (r039n 训练回灌)
|
|
1211
|
+
> lesson: `LESSON-20260826-044` status: `promoted`
|
|
1212
|
+
|
|
1213
|
+
源文本单一消费扩展到副金额:面板 label/value 必须从列表标题候选互斥排除;同一右缘、同尺寸、等距重复的小控件由列表或重复行模板唯一消费,不得再作为孤立媒体块渲染。
|
|
1214
|
+
|
|
1215
|
+
|
|
1216
|
+
## auto. semantic gaps were double-counted and list spacing overflowe(r039n 训练回灌)
|
|
1217
|
+
> lesson: `LESSON-20260826-045` status: `promoted`
|
|
1218
|
+
|
|
1219
|
+
列表标题、列表项和查看更多必须注册语义边界;相邻块间距只计一次。列表行内边距由标注行高减内容高推导,ListLayout 行间距显式归零,容器高度继续由内容撑开。
|
|
1220
|
+
|