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
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
> 让 AI 直接输出完整 schema 会浪费约 **4-5x** token(节点树实测压缩比 4.4x);若 AI 省略 `dictionary`(展开器从 `$` 绑定自动推断 `inputParams`),
|
|
7
7
|
> **整体 token 节省约 75-80%**。改「AI 输出紧凑 IR + 脚本展开」后,首次生成与每轮视觉闭环迭代都只传输 IR,链路 token 大幅下降。
|
|
8
8
|
|
|
9
|
+
> **两段式写法(2026-09-21 固化):先骨架、后样式**。第一版 IR 只求**要素铺全**(设计稿的每条文案 / 每张切图都有对应节点,位置与尺寸取审计值即可),
|
|
10
|
+
> 跑一轮 `iterate.mjs --spec <spec.json> --design <card-N.png>` 确认 `gate.stage !== 'structure'`(`elements.missing=0`);
|
|
11
|
+
> 之后每一轮的 `ir_patch` / `--set` **只动样式与布局**(`s.*`:尺寸 / 间距 / 颜色 / 字号 / flex / 位置),**不删节点、不换文案**。
|
|
12
|
+
> 丢要素会被门禁按 `structure` 段拦下,且「像素更高但要素更少」的轮次不算进步(不进 `ir.best`)—— 因为设计稿的要素是固定真值。
|
|
13
|
+
|
|
9
14
|
---
|
|
10
15
|
|
|
11
16
|
## 1. 节点字段契约
|
|
@@ -41,7 +46,7 @@
|
|
|
41
46
|
"id": "cardId", // pageId(可选)
|
|
42
47
|
"name": "卡片名", // 可选
|
|
43
48
|
"platform": "002", // 可选,默认 002
|
|
44
|
-
"width": "
|
|
49
|
+
"width": "343", // 卡片本体设计宽(卡面宽,非画板宽;省略默认 375。见 §2f 铁律 1)
|
|
45
50
|
"viewId": "view_xxx", // Root 的 componentId(可选,round-trip 用)
|
|
46
51
|
"dictionary": { ... }, // 可选;省略时展开器从 $ 绑定自动推断 inputParams + 默认 mock
|
|
47
52
|
"s": { ... }, // 可选;Root(view)自身的样式
|
|
@@ -69,7 +74,26 @@
|
|
|
69
74
|
| 动态值(金额 / 等级 / 日期 / 名称…) | `v: "$memberLevel"` | 绑定;`defaultValue` = 设计稿字面量 |
|
|
70
75
|
| 条件文案 | `v: "$showAll ? '收起' : '查看更多'"` | 表达式;分支字面量也参与 fidelity 对齐 |
|
|
71
76
|
| 金额展示 | 照 `recipe_snippets/money-format.json` | 格式化表达式,别把「¥7,000.00」拆成字面量 |
|
|
72
|
-
| 重复行 / tile 组 | 数组变量 + `listScope` 插槽,子树内
|
|
77
|
+
| 重复行 / tile 组 | 数组变量 + `listScope` 插槽,子树内 **`$<字段名>`** / `$_index` | 范式见 `recipe_snippets/list-expand-collapse.json` 与 `src/cards/account` 的 `account_details` |
|
|
78
|
+
|
|
79
|
+
> **列表插槽里的作用域**(运行时契约,见 `card-editor/runtime/evaluate.js` 的 `makeSlotScope`):
|
|
80
|
+
> `listScope` 的子作用域 = 外层作用域 ∪ **item 的字段逐个展开** ∪ `{$$: 整个 item, _index: 行索引}`。
|
|
81
|
+
> schema 里的**规范形**只有三种:
|
|
82
|
+
>
|
|
83
|
+
> | 你要取 | 规范形 | 也接受的别名(生成器自动改写) |
|
|
84
|
+
> |---|---|---|
|
|
85
|
+
> | item 的某个字段 | `$<字段名>` | `$item.<字段名>` |
|
|
86
|
+
> | 整个 item 对象 | `$$` | `$item` |
|
|
87
|
+
> | 行索引 | `$_index` | `$item._index` |
|
|
88
|
+
>
|
|
89
|
+
> 别名由 **`expand_ir.mjs` 在扩展开头自动归一化**(`lib/scope_alias.mjs`;schema 级则由
|
|
90
|
+
> `autofix_schema.py` 规则 5 兜底)。写别名不会被打回、也不会中止写盘 —— 生成器只负责把
|
|
91
|
+
> 规范形落到 schema,不需要你记住这条规则。**唯一的例外**:卡片真的声明了名为 `item` 的根变量
|
|
92
|
+
> (`dictionary.inputParams[].key === 'item'`)时 `$item.x` 是合法取值,一律不改写。
|
|
93
|
+
>
|
|
94
|
+
> 为什么必须归一化而不是「禁止 + 报错」:`$item.<字段>` 求值恒为 `undefined` 且**不抛错**
|
|
95
|
+
> (validate 绿、preflight 过、`/__render-status` 的 `ok=true`,只有它单列的 `bindingErrors`
|
|
96
|
+
> 会记一笔)。这类「静默失败」由生成器在入口消灭,比要求作者背规则可靠得多。
|
|
73
97
|
|
|
74
98
|
`dictionary` 形状(每项字段齐全,`type` 空串会被 validate 拒):
|
|
75
99
|
|
|
@@ -91,6 +115,180 @@
|
|
|
91
115
|
|
|
92
116
|
---
|
|
93
117
|
|
|
118
|
+
## 2c. 分片写 IR(大卡 / 被输出上限截断过时)
|
|
119
|
+
|
|
120
|
+
IR 的常规写法是**一次 `fs_write` 写完整份**(最省步骤)。但工具是在**流结束后**才执行的:整份 IR 压在
|
|
121
|
+
一段流里时,这段流中途被单次输出上限截断(`finish_reason=length`)/ 网关静默 / 墙钟到点 = 盘上**什么都
|
|
122
|
+
没有**,整轮白跑(实测一次建卡 `fs_write` 花 88s 纯吐参数,全程零落盘)。满足任一条就走分片:
|
|
123
|
+
|
|
124
|
+
- 紧凑 JSON 预计 > 10KB(约 8 个以上区块,或带大 `dictionary`);
|
|
125
|
+
- 本轮已被单次输出上限截断过一次(原样重写还会被截断);
|
|
126
|
+
- 上一轮写 IR 中途被打断过(片段还在盘上,可续跑)。
|
|
127
|
+
|
|
128
|
+
三步走:
|
|
129
|
+
|
|
130
|
+
1. **骨架**(小,一次 `fs_write`):顶层字段 + 根容器,容器数组留空
|
|
131
|
+
`{"id":"<id>","name":"…","width":"<卡面宽>","children":[{"c":"Column","children":[]}]}`
|
|
132
|
+
2. **一片一个步骤**:每片只放**一个区块**(一行 / 一组 tile / 一段自绘),`fs_write` 到
|
|
133
|
+
`.cache/ai-scratch/<id>.p01.json`(名字随意,合并时按 `--part` 顺序应用):
|
|
134
|
+
```json
|
|
135
|
+
{ "ops": [ { "path": "children.0.children", "append": [ <区块 IR> ] } ] }
|
|
136
|
+
```
|
|
137
|
+
`path` 是点路径(数字段=数组下标),**中间段必须已存在**(容器建在骨架里;拼错的键不会自动创建,
|
|
138
|
+
会报错并列出该级实际可用的键)。往已有对象上补字段用 `merge`:
|
|
139
|
+
`{ "path": "children.0.s", "merge": { "paddingType": "1" } }`。
|
|
140
|
+
3. **最后合并一次**:`node skills/dsl-card-generator/scripts/ir_merge.mjs --ir <ir> --part <p01.json> --part <p02.json> …`
|
|
141
|
+
- **幂等**:合并过的片段名记在 IR 的 `_parts` 里,重复合并直接跳过(重发同一次调用不会把区块加两遍);
|
|
142
|
+
- **原子**:任何一条 op 失败则整份不写,盘上保留上一次成功合并的状态;
|
|
143
|
+
- **形状不对也整批不写**:片段里出现缺 `c` 的节点 / 不是对象的项,报错并保持 IR 原样(盘上永远只有
|
|
144
|
+
能进管线的内容);改好这一片可以原样重跑(片段名没进 `_parts`,不会被当成已合并跳过)。
|
|
145
|
+
- 返回的 `nodes` 是当前节点总数(据此判断结构是否完整)。
|
|
146
|
+
|
|
147
|
+
合并完成后照常跑 `expand_ir.mjs`。**要改已合并区块的内容**:细节用 `iterate.mjs --set`;整块结构推翻就
|
|
148
|
+
写一片**新名字**的片段,用 `merge` 覆盖容器键(`merge` 是浅合并,数组键会被整体替换,如
|
|
149
|
+
`{"path":"children.0","merge":{"children":[<重写后的全部区块>]}}`)。
|
|
150
|
+
|
|
151
|
+
代价要认:**每片一个步骤**,实测每个 LLM 步骤 40–140s —— 小卡(一次能写完 < ~6KB)拆开只会更慢。
|
|
152
|
+
分片是给「一次写不进去 / 写了会丢」兜底,不是提速手段。
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
## 2d. 标题 + 装饰条/下划线(重叠样式:照抄,不要临场发明)
|
|
156
|
+
|
|
157
|
+
设计稿里标题底部压着一条彩色装饰条时,**整段照抄下面这个片段**塞进所在容器的 `children`,
|
|
158
|
+
只把 `$title` 换成标题变量、字号/颜色/宽高换成标注值。字段契约:容器属性走 `p`(组件属性,如 `align`)、
|
|
159
|
+
定位走 `s.position`(样式组,itemId 由 style-meta 给出,写成 `s.rect.top` 会得到空 itemId 被静默丢弃)。
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"c": "AbsoluteLayout",
|
|
164
|
+
"p": {
|
|
165
|
+
"align": "BottomLeft"
|
|
166
|
+
},
|
|
167
|
+
"s": {
|
|
168
|
+
"rect": {
|
|
169
|
+
"width": "auto",
|
|
170
|
+
"height": "24"
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
"children": [
|
|
174
|
+
{
|
|
175
|
+
"c": "Row",
|
|
176
|
+
"s": {
|
|
177
|
+
"rect": {
|
|
178
|
+
"width": "$title.length * 17",
|
|
179
|
+
"height": "8"
|
|
180
|
+
},
|
|
181
|
+
"background": {
|
|
182
|
+
"backgroundType": "01",
|
|
183
|
+
"backgroundColor": "rgba(0,164,109,0.2)"
|
|
184
|
+
},
|
|
185
|
+
"position": {
|
|
186
|
+
"left": "0",
|
|
187
|
+
"top": "16"
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"c": "Text",
|
|
193
|
+
"v": "$title",
|
|
194
|
+
"s": {
|
|
195
|
+
"font": {
|
|
196
|
+
"fontSize": "17",
|
|
197
|
+
"fontWeight": "500",
|
|
198
|
+
"color": "#111111",
|
|
199
|
+
"lineHeight": "24"
|
|
200
|
+
},
|
|
201
|
+
"position": {
|
|
202
|
+
"left": "0",
|
|
203
|
+
"top": "0"
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**为什么必须照抄**(三条都是实测踩出来的,不是洁癖):
|
|
212
|
+
|
|
213
|
+
1. **条子绝不能排在流式布局里**。`Column > [Text, Row(h5, marginTop3)]` 这种写法看着「只差一点点」,
|
|
214
|
+
但条子会占掉自己的高度把后续兄弟整体下推,且拿不到「压在标题底部」的重叠关系 ——
|
|
215
|
+
实测这么写的卡 lint 报 0 error / 0 warning(R42 只在已经是 AbsoluteLayout 时才校验,整条绕过去了)。
|
|
216
|
+
现在 `r64-title-underline-flow` 硬拦这个形态。
|
|
217
|
+
2. **几何读标注差值,别套公式**。`position.top = 标注条.y − 标注标题.y`。
|
|
218
|
+
例:标题 rect(32,250,68,24) + 条子 rect(32,266,68,8) → 容器 `height:24`、条子 `top:16`、`w:68`、`h:8`
|
|
219
|
+
(条子底边 274 = 标题底边 274,压在标题最后 8px)。myLoan 那张的 `top:28` 是「条子挂在线框下方」的另一种设计,
|
|
220
|
+
照抄到「压在标题底部」的稿子上会偏 12px。
|
|
221
|
+
3. **子节点顺序是 `[装饰条, 标题]`**:装饰条先绘制,标题后绘制压在上面;顺序反了文字会被条子盖住。
|
|
222
|
+
|
|
223
|
+
范式全文(适用场景、颜色/宽度绑定、存量样例)见 `recipe_snippets/title-underline-overlay.json` ——
|
|
224
|
+
本节的片段与它同源,改动必须同步(`tools/tests/title-underline-recipe.test.mjs` 会校验两处一致)。
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
## 2e. 列表数组化与数据绑定完备(binding-* 门禁)
|
|
228
|
+
|
|
229
|
+
> 判定实现单一来源 `scripts/lib/rules/bindings.mjs`(lint 与 readiness 共享);本节是规则的权威文档锚点。
|
|
230
|
+
|
|
231
|
+
**设计稿里重复出现的数据行(同构行 / tile,值逐行不同)必须在视觉分析阶段就明确用数组绑定**——
|
|
232
|
+
S2 审计的 `listCandidates[].{fields,values}` 就是现成的字段与值矩阵。禁止把列表逐行静态展开:
|
|
233
|
+
像素分可以很高,但数据维度缺失(列表数据变了视图不动),且行数错了没有任何门禁能兜住。
|
|
234
|
+
|
|
235
|
+
**三条门禁规则(2026-09-25)**:
|
|
236
|
+
|
|
237
|
+
| code | severity | 判定 |
|
|
238
|
+
| --- | --- | --- |
|
|
239
|
+
| `binding-repeater-no-scope` | **error** | ListLayout/GridLayout 的 value 没绑数组表达式(渲染 0 项),或有 value 但没有带 item 模板的 `slotScope` 插槽(渲染 N 个空壳) |
|
|
240
|
+
| `binding-array-unconsumed` | warn | data.json / inputParams 里的数组变量没有任何列表消费(复现器 value / slotScope 模板 / 事件代码 / 表达式都找不到引用)。名字带列表语义(list/items/rows/records/details/rules/entries)时 readiness 升级为 **blocker**(拦像素循环);Canvas 图表数据(radarData/donutData 类)可忽略 |
|
|
241
|
+
| `binding-list-static-expansion` | warn | 复现器外 ≥3 个结构同构的兄弟子树、全静态、且每行 ≥2 条文本且值逐行互异 → 疑似列表静态展开。评分星 / tab 标签 / 固定按钮组(单内容节点或值全同)是合法静态形态,不会被此规则命中 |
|
|
242
|
+
|
|
243
|
+
**合法消费途径(宽收口防误报)**:复现器 value 的 `$数组` 引用(含三元/切片复合式)、
|
|
244
|
+
slotScope 插槽内的 `$<字段>`(字段名 ∈ 数组元素键集合)、事件代码文本里的变量名
|
|
245
|
+
(`setData` 转造源属合法——monthlyBill 的 `$renderListData` 即此形态)、任意属性/样式/指令表达式引用。
|
|
246
|
+
|
|
247
|
+
**readiness 新增门**(内容就绪段,像素循环之前):
|
|
248
|
+
- `checks.bindings.arraysUnconsumed`:数组消费对账结果(hard = 列表语义未消费 ⇒ blocker)。
|
|
249
|
+
- `checks.lists`(传 `--audit` 时):S2 审计 `listCandidates` 提名 N 个列表、schema 里数组化列表数为 0 ⇒ **blocker**
|
|
250
|
+
(视觉分析已明确是列表,静态展开就是疏漏);提名 > 数组化只记 note(同构列表合并 / 提名假阳性合法)。
|
|
251
|
+
|
|
252
|
+
**全类型要素对账**(check_fidelity `elements.symbols` / `elements.shapes`,warning 级):
|
|
253
|
+
设计稿 symbol 数 vs schema Icon 组件数、细长条 shape(min(w,h)≤2 且 max(w,h)≥40)数 vs Divider 组件数。
|
|
254
|
+
缺口进迭代报告 `gate.elementHints` 供每轮自查(symbol 走 slice 位图实现也合法,故不硬拦)。
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
## 2f. 宽度自适应契约(占满语义禁写死 px,r66 门禁)
|
|
258
|
+
|
|
259
|
+
> 判定实现单一来源 `scripts/lib/rules/adaptive-width.mjs`(lint 消费);本节是规则的权威文档锚点。
|
|
260
|
+
|
|
261
|
+
**渲染契约(component-meta.json / components.css 实证)**:全部容器组件(Row/Column/MultiColumn/
|
|
262
|
+
ListLayout/GridLayout/AbsoluteLayout/ViewContainer)的 meta 默认 `rect.width` 就是 `"100%"`;
|
|
263
|
+
`adapter.doSize` 对 `%` 值原样透传;纵向流(Root/Column/AbsoluteLayout)里子项交叉轴默认 stretch。
|
|
264
|
+
因此**容器写死 px 宽 = 显式覆盖默认的自适应行为**,宿主容器宽度一旦偏离设计基准,内容层锁死。
|
|
265
|
+
|
|
266
|
+
**三条铁律**:
|
|
267
|
+
|
|
268
|
+
1. **顶层 `width` = 卡片本体设计宽(卡面宽),不是画板宽**。设计稿「画板 375 + 卡面两侧各 16 留白」
|
|
269
|
+
时,IR 顶层写 `"width":"343"`,**不要**写 375 再给主容器定宽 343 复刻画板留白——留白是宿主布局
|
|
270
|
+
职责,卡片只对卡面负责。
|
|
271
|
+
2. **占满语义禁写死 px(r66,error)**:纵向流父级(Root/Column/AbsoluteLayout)下,容器子节点
|
|
272
|
+
rect.width 占满父内容盒(98%~103%)必须改 `width:"100%"`(或不写 width,meta 默认即 100%)。
|
|
273
|
+
与设计宽逐像素等价(父内容盒宽 == 原 px 值),且随容器自适应。**真固定宽元素保留 px**:
|
|
274
|
+
图标 / 头像 / 徽章 / 按钮 / 图表画布 / hug 宽标题壳。横向流(Row/MultiColumn)主轴里并列子项
|
|
275
|
+
定宽是 space-between 等范式的合法组成,不受此限(R-FIXEDW / R-COMPARE)。
|
|
276
|
+
3. **`width:"100%"` 禁叠水平 margin**(rules.md §十六 R63):100% 已等于父内容盒宽,再叠
|
|
277
|
+
marginLeft/Right 是往外框上加宽度 → 溢出被裁。水平内缩写**父容器 padding** 或改用小于 100% 的
|
|
278
|
+
具体语义宽度。
|
|
279
|
+
|
|
280
|
+
**多行文本钉宽说明**(`textFromSpec`):多行文本 rect.width 钉 spec 框宽是为了换行折点与设计稿
|
|
281
|
+
一致;当文本框宽 ≈ 父内容盒宽时应写 `100%`(折点在基准宽下不变、容器变宽时行自然跟随);
|
|
282
|
+
窄于父内容盒(双列格 / 右侧留金额位)时保留 px——那是「局部定宽」语义,不属于占满。
|
|
283
|
+
|
|
284
|
+
**图表要素前置(步骤 2 下发 IR 前必须完成)**:要素盘点识别出图表(雷达 / 环形 / 仪表盘 / 柱状 /
|
|
285
|
+
折线 / 散点)时,每个图表必须在 IR 下发前落定「范式来源」三件套,缺一视为要素盘点未完成:
|
|
286
|
+
① `references/recipes.md` 对应图表范式章节(§13-§19);② 对应 `references/recipe_snippets/canvas-*.json`
|
|
287
|
+
完整代码片段;③ `references/card_index.json` 近邻图表卡的 Canvas 实现作对照。
|
|
288
|
+
图表节点一律 **Canvas + onMeasured 自绘**(数据绑定走 state 数组,范式里 dataContractNote 有契约),
|
|
289
|
+
**禁止**用 Text/Image 堆砌模拟图表形态——那是像素与 token 双输的垃圾结构。
|
|
290
|
+
|
|
291
|
+
---
|
|
94
292
|
## 3. 完整示例(list 卡)
|
|
95
293
|
|
|
96
294
|
```json
|
|
@@ -162,7 +360,7 @@
|
|
|
162
360
|
|
|
163
361
|
## 4.9 收敛门禁(iterate.mjs 报告)
|
|
164
362
|
|
|
165
|
-
`iterate.mjs` 每轮把报告落盘 `.cache/ai-scratch/<cardId>.iterate.json`,含:`gate`(机器判定:validate/lint/preflight 零 error ∧ match≥95 ∧ heightDelta=0 ∧ widthDelta=0 ∧ |contentOffset1x|≤2)、`history`(逐轮 match/热区)、`stalled
|
|
363
|
+
`iterate.mjs` 每轮把报告落盘 `.cache/ai-scratch/<cardId>.iterate.json`,含:`gate`(机器判定:validate/lint/preflight 零 error ∧ match≥95 ∧ heightDelta=0 ∧ widthDelta=0 ∧ |contentOffset1x|≤2)、`history`(逐轮 match/热区)、`stalled`(**连续 3 轮未刷新历史最好成绩**——震荡下滑同样触发——时给出可执行 hint:先用 `ir_restore.mjs` 回退到 best 快照,再一轮只改一个分项)、`best`(持久化的最好成绩 `{match,round,irSnapshot}`,history 只留最近 10 条、best 不会被挤掉)、`contentOffset1x` + `contentOffsetHint`(内容整体平移)、`renderImage`(渲染原图路径)、`lint.warningDetails`、`preflight`(机械化 Pre-flight 10 条)。收尾用 `node iterate.mjs --gate --out src/cards/<卡>/` 单独判收敛(schema 在报告之后被改过 ⇒ 判 FAIL)。正文只引用 gate 的 verdict,不得自述达标。
|
|
166
364
|
|
|
167
365
|
## 5. CLI
|
|
168
366
|
|
|
@@ -187,7 +385,7 @@ IR 只表达语义,**lint 规则仍需在 IR 里显式写出**,展开器不
|
|
|
187
385
|
- **R35**(受外宽约束的 Row/Column 多并列子节点全 `flexShrink:0` 溢出):至少一个子节点 `s.flex.flexShrink: "1"`;`width:"auto"` 的内容自适应容器可保留固定子元素。
|
|
188
386
|
- **R9/R10**(Root 设非零 padding/margin):IR 顶层 / Root 不写 padding/margin(裁切后间距归零铁律)。
|
|
189
387
|
|
|
190
|
-
- **R25**(Image itemValueFrom 三态精确映射):Image 静态路径用 `pi: {value: "/slices/xxx.png"}`(展开器自动 `itemValueFrom="2"`),变量绑定用 `p: {value: "$img"}`(自动 `"1"
|
|
388
|
+
- **R25**(Image itemValueFrom 三态精确映射):Image 静态路径用 `pi: {value: "/slices/xxx.png"}`(展开器自动 `itemValueFrom="2"`),变量绑定用 `p: {value: "$img"}`(自动 `"1"`)。**`pi` 里给 `$` 表达式展开器直接抛错**(`"2"` 是原样透传,会渲染成 `url($icon)` 空白);反向也别把静态路径写进 `p`——它会被判成 `"0"` 常量,Image 拿不到图片分支同样空白(preflight/lint `r25-image-from-0` 拦)。
|
|
191
389
|
- **R31**(切图文件名含空格必须重命名):IR 中的 `pi.value` 路径不含空格;展开器会自动把被引用切图汇聚进 `src/slices/`,来源优先 `--slices-dir <要素包>/slices`(设计稿要素包就在该目录,免拷贝),未指定时回落 IR 同目录 `slices/`。
|
|
192
390
|
- **切图语义名 + 同内容去重**(`slices_convention.md §1.1 / §3.1`,lint `r59` / `r60`):`pi.value` 直接写**按功能/作用取的语义名**(`/slices/iconArrowRight@3x.png`、`/slices/bgCardHeaderDiffuse@3x.png`),不要沿用设计稿图层名 / 导出目录前缀 / 中文名 / `_alt2` 副本后缀。展开器汇聚时按**内容 sha256** 判重:`src/slices/` 已有同内容文件 → **不拷贝**,并把 IR 展开后的引用改写到既有文件名(日志 `[slices] 内容已存在 → 复用 …`,schema/data 自动回写)——即同内容时最终以既有语义名为准,不要指望靠换个名字多存一份。
|
|
193
391
|
- **R32**(Image 必须设显式 rect.height):Image 节点 `s.rect.height` 必须写设计稿标注高度(如 `"110"`),否则被 CSS `aspect-ratio:16/9` 压缩。
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# 视觉闭环迭代协议(iterate.mjs 多轮收敛纪律)
|
|
2
|
+
|
|
3
|
+
> 从 SKILL.md Workflow A 步骤 5 外迁(2026-09-21 优化方案 1.1)。生成时按需查阅:第一轮迭代前速览「要素线」与「停滞回退」,其余按报告字段名定位本文件对应小节。
|
|
4
|
+
|
|
5
|
+
## 1. 多轮节奏
|
|
6
|
+
|
|
7
|
+
第一轮修系统性问题(整卡偏移、缺失区域、主色错误);第二轮修细节(间距、字号、对齐);第三轮验收(逐元素比对)。通常 2-3 轮收敛。
|
|
8
|
+
|
|
9
|
+
**批量迭代 keep-alive**:连续多卡用 `iterate.mjs --keep-alive` 复用 Chrome 进程,每轮 ~0.6s(stdin 逐行 `{"ir":"...","out":"...","design":"...","width":343}`)。
|
|
10
|
+
|
|
11
|
+
## 2. 预算硬纪律(2026-09-21 起,硬性)
|
|
12
|
+
|
|
13
|
+
- **每卡 iterate ≤ 15 轮**;连续 3 轮未刷新最好成绩(`stalled`)立即停止微调,按 §3 回退。
|
|
14
|
+
- 触顶(轮数或 stalled)后**禁止**继续 --set 盲调、**禁止**全量重写 IR:走 `ir_restore.mjs --best` 回退,或带证据链(`diff_bands.mjs` + 按 lint code 查 `scripts/lib/rules/registry.mjs` 登记的文档锚点)换思路。
|
|
15
|
+
- `ir_merge` 分片每片多付一个 LLM 步骤(实测 40-140s),**小卡禁止拆分**;仅当单次输出被截断/写不进时用。
|
|
16
|
+
- 改已存在 IR 优先 `ir_patch`(MCP)或 `iterate.mjs --set`,**一轮只改一个分项**(同时改多项无法归因)。
|
|
17
|
+
|
|
18
|
+
## 3. 停滞与回退
|
|
19
|
+
|
|
20
|
+
`stalled` = 连续 3 轮没有刷新历史最好成绩(`round - best.round >= 3`),**震荡下滑同样触发**(实测:r10 做到 92.42 后 13 轮 11 轮更差,旧"几乎不变"判据 23 份报告 0 次触发,模型盲调 707 万 token 被人工中止;新判据第 13 轮即触发)。
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
node scripts/ir_restore.mjs --list --id <卡id> # 列轮次快照与各轮 match(只读)
|
|
24
|
+
node scripts/ir_restore.mjs --best .cache/ai-scratch/<卡id>.ir.best.json --ir .cache/ai-scratch/<卡id>.ir.json
|
|
25
|
+
node scripts/ir_restore.mjs --round 10 --id <卡id> --ir .cache/ai-scratch/<卡id>.ir.json
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
回退后必须重跑一轮 iterate 拿新报告(旧报告 schemaHash 已失效)。快照由 `iterate.mjs` 自动落:每轮 `.cache/ai-scratch/<id>.ir.rNN.json`,刷新最好成绩另存 `.ir.best.json`;报告持久化 `best:{match,round,irSnapshot}`(history 留最近 10 条,best 不被窗口挤掉);快照留最近 12 轮 + best,超出自动清理。
|
|
29
|
+
|
|
30
|
+
## 4. 内容线(先内容、后骨架、最后样式 —— 三段式门禁)
|
|
31
|
+
|
|
32
|
+
### 4.0 S4.9 内容就绪门禁(**像素循环的前置条件**)
|
|
33
|
+
|
|
34
|
+
`readiness.mjs` 做七道**声明性**判定(不需要渲染,所以挡在最贵的一步之前):**A** 组件契约(属性键/样式组/样式子键必须在元数据白名单内)· **B** 要素齐全(文本逐条对账;图标/分割线数量对账进 `gate.elementHints` 每轮自查)· **C** 变量绑定完备(`$var` 根变量必须真存在;列表语义数组零消费 = blocker)· **G** 列表提名闭环(`--audit` 时 listCandidates 提名了列表而 schema 数组化列表为 0 = blocker)· **D** 图片地址已配置 · **E** 切图路径已配置(引用在 `src/slices` 有对应文件)。数据绑定完备细则(数组消费对账 / 静态展开判据 / 合法消费途径)见 `ir-format.md` §2e。
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# 单跑(退出码 0=就绪 / 3=未就绪 / 1=用法错误;3 不是脚本报错)
|
|
38
|
+
# 退出码语义全域权威:skills/shared/exit-codes.md(本文件不再复述其它脚本的码)
|
|
39
|
+
node scripts/readiness.mjs --id <卡id> --spec <bundle>/spec.json --design <bundle>/card-N.png --json
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`iterate.mjs` **每轮自动跑这道门禁**,未就绪时:不 render、不 pixelDiff、**不消耗迭代轮次**(不写 IR 快照、不进 history),报告里 `gate.stage=content`、`gate.blocked=true`、`conclusion='blocked-content'`,`blockedAt.{stage,blockers,hint}` 给出可执行清单。补齐内容后**直接重跑 iterate**。
|
|
43
|
+
|
|
44
|
+
为什么必须:实测(2026-09-24 gen-1)跳过这步,在 Image 的图片地址都没配(属性键写成 `image`,合法键只有 `value` ⇒ 运行时整条静默丢弃 ⇒ 破图)的情况下连刷 10 轮像素,`match` 卡在 87-90.46,两个回合 623 万 prompt token 全部作废。
|
|
45
|
+
|
|
46
|
+
逃生门 `--no-readiness`(或 `DSL_CARD_SKIP_READINESS=1`)只在排查「门禁本身误判」时用。
|
|
47
|
+
|
|
48
|
+
### 4.1 要素是固定真值
|
|
49
|
+
|
|
50
|
+
设计稿里每条文案/每张切图都是**既定要素**,迭代只许改样式与布局,**不许丢要素、不许自造内容**——图标(symbol)与分割线(细长条 shape)同样算既定要素:报告 `fidelity.symbols/shapes` 的数量缺口会进 `gate.elementHints`(warning 级每轮自查;symbol 走 slice 位图实现也合法)。报告 `fidelity`(`total/matched/missing/invented/slices/symbols/shapes`)与 `gate.stage`:
|
|
51
|
+
|
|
52
|
+
- `gate.stage=content`(`gate.blocked=true`)⇒ 内容层没齐,**本轮根本没 render**。照 `gate.verdict` 里的 blockers 补内容,别调像素、别重跑空转。
|
|
53
|
+
- `gate.stage=structure`(`elements.missing>0` 或 validate/lint/preflight 未过)⇒ **先补要素,别调像素**:把设计稿文案补回对应节点;绑定变量的把 `defaultValue` 改成设计稿真值(`check_fidelity.mjs` 的 `bindHints`)。改样式不会把丢掉的文案变回来。
|
|
54
|
+
- `gate.stage=pixel` ⇒ 内容与结构都齐了,才进入像素/布局收敛(一轮只改一个分项)。
|
|
55
|
+
|
|
56
|
+
**最好成绩判据是加权分 `score = match − 6 × 缺要素数`(同量纲相加,`MISSING_PENALTY = 6`)**,不是字典序、也不是纯 `match`。三种判据的演进理由(前两版都在真轨迹上翻车)见 `iterate.mjs` 里 `MISSING_PENALTY` 的注释块;`best.score` 与 `isBetterThan` 必须同口径。像素更高但要素更少的一轮**不算进步**、不存 `ir.best`。实测事故:模型把产品名/利率/期限整批换成自编占位值,`match` 照样刷到 89.56%、门禁全绿,内容真值整批丢失。`elements.invented` 会逐条点名自造文案;要素下滑时 `stalled.hint` 直接写明"这是在删要素凑像素"。
|
|
57
|
+
|
|
58
|
+
## 5. 分项归因 contributors
|
|
59
|
+
|
|
60
|
+
报告 `contributors[]` 回答「差在哪个节点」:每个差异热区给一组具体节点(最内层优先),字段 `cid / cls / text / rect / h / hSibling(同类中位高)/ designH / dH(与设计稿高差,需 --spec)/ flags / hotCover`。
|
|
61
|
+
|
|
62
|
+
用法:**一轮只改 `contributors[0].elements[0]` 这一个节点的一个属性**,改完立刻重跑 iterate 看是否刷新最好成绩。
|
|
63
|
+
|
|
64
|
+
flags 读法:
|
|
65
|
+
- `text-clipped` ⇒ 文本被裁剪(查 isMaxLine / maxLine / lineHeight)
|
|
66
|
+
- `vs-design ±N` ⇒ 与设计稿高度差 N(查它自己的 paddingTop/Bottom 与 lineHeight)
|
|
67
|
+
- `vs-siblings ±N` ⇒ **同类元素里只有它高度不同**(查它自己的 padding/margin,不是整层 gap)
|
|
68
|
+
- `bottom-edge` / `right-edge` ⇒ 差异贴边界外(查下边距 / 圆角 / 阴影)
|
|
69
|
+
|
|
70
|
+
`designH`/`dH` 为 null 表示没给 `--spec` 或文本匹配不上——只按 flags 与同类中位判断,**不要自己编设计稿高度**。
|
|
71
|
+
|
|
72
|
+
背景:实测 23 次 iterate 的报告只说「差 2.58 分、高度差 2px」,模型只能整份重写 IR 去试(16 次全量重写、12.4 万字符 ≈ 全部输出的一半)。`widthMismatch` 出现时先修 `--width`(宽度不对,match 与 dy 全都不可信)——**它现在是 gate check(`render.widthMismatch`),会出现在 gate.failed 首列**,别再忽略。传 `--spec` 才有设计稿卡面宽基准:没传时宽度校验与 designH 都拿不到口径(gen-1 实测:--width 375 跑了两轮才被发觉,best 分数全是错基准产物)。
|
|
73
|
+
|
|
74
|
+
## 5.5 结构性缺口(structuralGaps):先结构后微调
|
|
75
|
+
|
|
76
|
+
报告 `pixelDiff.structuralGaps[]`(1x 卡内坐标)= 「逐行差异率 ≥85%」连续 ≥40pt 的带区:该带要么整块内容漏建(图表 / 分区 / 列表段),要么背景/底色整层错涂。`gate.structuralHint` 在 match 未达标且存在缺口时给出带区清单。
|
|
77
|
+
|
|
78
|
+
用法:**缺口存在且 match<40 时,contributors / hotspots 的局部微调线索全部忽略**——那是「在 80% 缺失面前做 1% 微调」(gen-1 实测:雷达图整块漏建,模型照 contributors 调了 8 轮「10分/分」的 padding 后熔断)。修法:对照 spec_audit 的 `bands[]` 找到缺口带对应的元素群,把整块内容建出来(图表先查 `recipes.md` + `recipe_snippets/canvas-*.json` + `card_index` 近邻卡),或改对背景,缺口消失后再回到逐项微调。
|
|
79
|
+
|
|
80
|
+
## 5.6 环境注意
|
|
81
|
+
|
|
82
|
+
- **Windows 环境无 jq**(实测 exit=127 连烧 3 步):读 audit / iterate JSON 用 `node -e "const a=require('./<path>.json'); ..."` 或 fs_read,不要用 jq。
|
|
83
|
+
|
|
84
|
+
## 6. IR 修改通道
|
|
85
|
+
|
|
86
|
+
- 首次写 IR:`fs_write`(同轮第 2 次全量写同文件收提醒、第 3 次起被拒;确需重排结构传 `rewrite:"true"`;与盘上逐字节相同的重写直接跳过)。
|
|
87
|
+
- 迭代修改:`ir_patch`(MCP,`{file, ops:[{path, op, value}]}`,点路径 + 原子写 + 回旧值→新值摘要,不吃引号转义)或 `iterate.mjs --set '<path>=<value>'`(点路径数字段=数组下标)。
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
## 2. 绝对定位模型
|
|
18
18
|
|
|
19
19
|
- **position 样式组(left/top)预览器不支持**,写了被忽略。
|
|
20
|
-
- AbsoluteLayout 子节点定位 = **`align` 停靠 + margin 偏移**(margin 在 abspos
|
|
20
|
+
- AbsoluteLayout 子节点定位 = **`align` 停靠 + margin 偏移**(margin 在 abspos 子节点上有效,实测可用)。**边界**:margin 偏移只对 `width:auto` / 定宽子节点成立——abspos 子节点 `width:100%` 已等于容器 padding box 宽,再叠水平 margin 会顶出容器右缘(交叉轴不可缩,lint `r63-width100-with-hmargin`)。
|
|
21
21
|
- **禁负 margin**:重叠效果(标题文本+下划线/高亮条)用 AbsoluteLayout 两子节点同 `align` 停靠实现(装饰条排前,lint R42),不用负 margin 拉叠。
|
|
22
22
|
- 绝对布局容器自身高度必须 ≥ 子内容最大下沿,否则裁切。
|
|
23
23
|
|
|
@@ -23,13 +23,16 @@
|
|
|
23
23
|
| 目的 | 命令 |
|
|
24
24
|
|---|---|
|
|
25
25
|
| **输入路由(建卡第一步)** | `node scripts/route_input.mjs <输入路径> [--json]`:判输入形态并直接给出下一步命令。`meaxure-export`(Sketch MeaXure 标注导出:index.html + preview/ + assets/)→ **必须先跑 sketch-spec-to-card 前处理**,工具会打印 `parse_meaxure.py --src --draft --build` + `cluster_states.py --out UI/<稿名>` 两条命令(若 `UI/*/manifest.json` 的 `export` 已匹配该导出则跳过前处理、直接给存量产物路径);`ui-bundle` → Workflow A 逐卡;`image` → Workflow A 纯视觉;`card-dir` → Workflow B 改卡;`unknown`(exit 3) → 先问用户别猜。见 SKILL.md §8 Workflow 0 |
|
|
26
|
-
|
|
|
27
|
-
| **列画板 + 选卡门禁(多画板)** | 取清单:未前处理 `python3 skills/sketch-spec-to-card/scripts/parse_meaxure.py --src <导出目录> --list`(**展示 1 起**,传 `--indices` 时 -1);已前处理 `cat UI/<稿名>/manifest.json`(`canvases[].order` **1 起**/`name`/`artboardType`/`status`)+ `clusters.json`(`cards[].cardKey`/`pageId`/`stateNames`/`recommendedBase`)。交互:按 **`1..n` 列出 + 末尾 `全部`** → 一句纯文本提问 → **停止输出等用户回序号**(**没有** `request_user_input`,不要去找弹窗工具)。**清单逐行配图**(每画板行下 `图:
|
|
26
|
+
| 秒出合法骨架(**仅 blank 调试 / 模板克隆**) | `node scripts/scaffold_card.mjs --intent <blank\|list\|detail\|form\|dialog\|tab\|canvas-head> --id <卡id> --name <名称>`。**常规建卡走 IR 主路径**(SKILL.md §8 Workflow A:整树 IR → `expand_ir.mjs`);`--from <模板卡id>` 整卡克隆仍可用 |
|
|
27
|
+
| **列画板 + 选卡门禁(多画板)** | 取清单:未前处理 `python3 skills/sketch-spec-to-card/scripts/parse_meaxure.py --src <导出目录> --list`(**展示 1 起**,传 `--indices` 时 -1);已前处理 `cat UI/<稿名>/manifest.json`(`canvases[].order` **1 起**/`name`/`artboardType`/`status`)+ `clusters.json`(`cards[].cardKey`/`pageId`/`stateNames`/`recommendedBase`)。交互:按 **`1..n` 列出 + 末尾 `全部`** → 一句纯文本提问 → **停止输出等用户回序号**(**没有** `request_user_input`,不要去找弹窗工具)。**清单逐行配图**(每画板行下 `图: <绝对路径>`,缺图标缺失,禁止堆末尾;**路径必须完整绝对路径**——禁止 `…`/`...` 缩写前缀、禁止反引号或代码块包裹);**机械卡点**:`parse_meaxure.py` 不带范围参数的生成运行直接 **exit 4** 并打印清单;用户回复序号 k → `--indices <k-1>`、「全部」→ `--scope-all`(「全部」只能是用户的回答,Agent 不得默认全量)。**UI 产物可回 `全部` 全量生成;产卡默认单画板 / 单 cardKey,回 `全部` 才算显式批量授权**;禁止自行挑画板。见 `rules.md §十三` |
|
|
28
28
|
| **设计稿层级树(AI 读图)** | `node scripts/spec_tree.mjs --spec UI/<画板>/spec.json [--card 1] [--max-depth 3] [--compact]`:把 `elements` 按矩形包含关系几何重建为 AI 可读的缩进树(类型+名称+尺寸+样式+文案)。Workflow A 步骤 1 用 |
|
|
29
|
-
| **设计稿 vs
|
|
30
|
-
| **一条龙迭代(首选)** | `node scripts/iterate.mjs --ir <IR> --out src/cards/<卡id>/ --design <card-N.png> --width 343 [--spec <spec.json>] [--elements] [--slices-dir <要素包 slices 目录>]`:单进程 expand+lint+preflight+render+pixel-diff(含切图汇聚 + 同内容去重)。结果 JSON 带 `gate`(机器门禁判定)、`pixelDiff.hotspots`(>3% 差异热区,1× 设计 px
|
|
31
|
-
| **收敛判定(收尾必跑)** | `node scripts/iterate.mjs --gate --out src/cards/<卡id>/`:读 `.cache/ai-scratch/<卡id>.iterate.json` 做机器判定(match≥95 ∧ heightDelta=0 ∧ widthDelta=0 ∧ \|contentOffset1x\|≤2 ∧ 零 error,且 schema 未在报告后变更),未达标 exit 1
|
|
32
|
-
| **IR
|
|
29
|
+
| **设计稿 vs 渲染对比(可选,出并排图给人看)** | `node scripts/render_diff.mjs --card <卡id> --design UI/<画板>/card-1.png [--out diff.png] [--json]`:并排合成对比图(左=设计稿,右=渲染);省略 `--width` 自动读 `schema.view.width`。**视觉闭环主工具是 `iterate.mjs`(量化 match/hotspots/contributors)+ `diff_bands.mjs`(dy/dh/主色证据)**;render_diff 只在需要一张并排图给用户看时用 |
|
|
30
|
+
| **一条龙迭代(首选)** | `node scripts/iterate.mjs --ir <IR> --out src/cards/<卡id>/ --design <card-N.png> --width 343 [--spec <spec.json>] [--elements] [--slices-dir <要素包 slices 目录>]`:单进程 expand+lint+preflight+render+pixel-diff(含切图汇聚 + 同内容去重)。结果 JSON 带 `gate`(机器门禁判定)、`pixelDiff.hotspots`(>3% 差异热区,1× 设计 px)、**`contributors`(分项归因:每个热区 → 最内层具体节点 `cid/cls/text/rect/h/hSibling/designH/dH/flags/hotCover`;**一轮只改 `contributors[0].elements[0]` 的一个属性**,flags 见 SKILL.md §S5)**、`widthMismatch`(`--width` 与设计稿卡面宽不符 ⇒ 先改 width,否则 match 与 dy 全不可信)、`contentOffset1x`+`contentOffsetHint`(内容整体平移)、`renderImage`(渲染原图)、`lint.warningDetails`、`preflight`、**`fidelity`(要素线:`total/matched/missing/invented/slices` —— 设计稿有而 schema 里没有的文案、schema 里自造的文案;`elements.missing=0` 是门禁项)**、`stalled`(**连续 3 轮未刷新最好成绩,含震荡下滑形态 ⇒ 照 hint 先 `ir_restore.mjs` 回退 best、再单分项改,禁止继续盲调)。省略 `--width` 时自动取设计稿卡面宽。多卡连生成用 `--keep-alive`(stdin 逐行 JSON) |
|
|
31
|
+
| **收敛判定(收尾必跑)** | `node scripts/iterate.mjs --gate --out src/cards/<卡id>/`:读 `.cache/ai-scratch/<卡id>.iterate.json` 做机器判定(match≥95 ∧ heightDelta=0 ∧ widthDelta=0 ∧ \|contentOffset1x\|≤2 ∧ **elements.missing=0** ∧ 零 error,且 schema 未在报告后变更),未达标 exit 2(gate-fail,与「脚本报错 exit 1」区分开)。**`gate.stage` 给两段式节奏**:`structure`(要素缺失 / validate / lint / preflight 未过)⇒ 先补要素别调像素;`pixel` ⇒ 要素齐了才谈像素收敛。正文只引用它的 verdict,禁止自述「门禁全绿」 |
|
|
32
|
+
| **IR 微调(首选 `ir_patch`,别整份重写)** | MCP 工具 `ir_patch({file, ops:[{path, op, value}]})`:点路径最小写(数字段=数组下标)、`op ∈ set/merge/push/delete`、原子(任一条失败整份不写)、返回旧值→新值摘要、不吃引号转义。**改已存在的 IR 就用它**;`fs_write` 整份重写只在首次写时用 —— 同轮内第 2 次全量写会收到提醒、**第 3 次起被拒**(确实要重排结构才传 `rewrite:"true"`),与盘上逐字节相同则直接跳过不写。跑完仍要跑 iterate 才有门禁结论(ir_patch 不渲染、不校验) |
|
|
33
|
+
| **IR 微调随迭代下发** | `iterate.mjs` 加 `--set 'children.0.children.2.children.1.s.margin.marginTop="7"'`(可重复):点路径(数字段=数组下标)定位 + 原子写回 IR(tmp+rename,崩溃不留半状态),随后照常 expand→lint→render→diff 一次完成。中间路径不存在会响亮报错且不写文件。注意这是**字符串字面量语法**(引号/转义易错),结构化改 IR 优先用 `ir_patch` |
|
|
34
|
+
| **回退到最好成绩(停滞时第一步)** | `node scripts/ir_restore.mjs --best .cache/ai-scratch/<卡id>.ir.best.json --ir .cache/ai-scratch/<卡id>.ir.json`:把工作 IR 原子写回历史最好成绩那一轮的结构(写回前自动备份当前 IR 为 `<IR>.pre-restore`);`--list --id <卡id>` 只读列出各轮 match 与快照文件,`--round 10 --id <卡id>` 按轮次回退(history 窗口外也能按 `.ir.rNN.json` 文件名直取)。快照由 `iterate.mjs` 自动落(`<卡id>.ir.rNN.json` + 刷新最好成绩时另存 `.ir.best.json`);目标不是合法 JSON 时 exit 3 拒绝写坏工作 IR。**回退后必须重跑一轮 iterate**(旧报告 schemaHash 已失效、`--gate` 会判 FAIL) |
|
|
35
|
+
| **分片写 IR(大卡 / 被输出上限截断过)** | 触发条件三条之一:预计紧凑 JSON **>10KB**、本轮 `finish_reason=length` 截断过、上一轮写 IR 中途被打断过。做法:`fs_write` 骨架(区块数组留空)→ 每区块一片 `.cache/ai-scratch/<id>.pNN.json`,内容 `{"ops":[{"path":"children.0.children","append":[<区块 IR>]}]}` → `node scripts/ir_merge.mjs --ir <IR> --part <片>…`(或 `--parts-dir <目录>`,按文件名排序)合并一次 → 再 `expand_ir.mjs`。**幂等**:合并过的片段名记进 IR 的 `_parts`,重发同一调用不会把区块加两遍;**原子**:任一 op 失败或节点缺 `c` 就整份不写,盘上只有合法状态。**不是提速手段**:每片多付一个 LLM 步骤(实测 40-140s/步),小卡别拆。契约见 `ir-format.md §2c` |
|
|
33
36
|
| **量化偏差比对(防幻影调查)** | `node scripts/diff_bands.mjs --id <卡id> --design <card-N.png> [--tol 2] [--out report.json]`:输出文本带逐条 dy/dh、总高/内容底边/尾部留白、上中下分区主色直方图(背景色错误直接定位分区)。凡「渲染与设计不一致」、或 iterate 报告出现 `stalled` / `heightDelta≠0` / match 卡住,**先跑它拿证据再改**,不要肉眼下结论、不要手写 node -e 像素脚本;已有渲染图可 `--render <png>`(iterate 报告的 `renderImage` 就是现成渲染原图)跳过重渲 |
|
|
34
37
|
| **切图判重三层(强制)** | 落盘/裁切前依次:① sha256 字节;② **像素容差**(裁切图 ≡ 导出资产/已重命名共用切图,`lib/slice_pixels.mjs`);③ 都不命中才写新文件。审计缺口按指纹**分组**派生(同组一张图),`_altN` 仅限真不同内容组;按名查找走 manifest 别名(`renamedFrom`)。见 `slices_convention.md §3.1`、`pitfalls §15` |
|
|
35
38
|
| **数字语义变量提取(中文+数字混排,强制)** | 落盘文本先过 **AI 语义解析**:含**运行期数字**(金额含千分号 / 笔数 / 利率 / 期限;判定看数字是否随数据变化,版本号 / 固定话术不提取)→ 数字抽 `inputParams`(金额 `type:"number"` + 原始数字 defaultValue),混排串用表达式拼接 `'静态文案' + <expr> + '静态文案'`(`itemValueFrom:"1"` + `itemDefaultValue` 兜底);**带千分号金额 = 原始数字变量 + 格式化表达式**(`amount_formatting.md §3.4b`,中文前后缀拼表达式外)。禁止整串写死 / 整串绑 string / 格式化串进字典 / 正则见数字就拆。lint `r61` 提醒级。见 `rules.md §十四` |
|
|
@@ -50,8 +53,8 @@
|
|
|
50
53
|
| **建骨架(MCP)** | `card_scaffold({intent,id,name,outBase,platform,force})`:等价于 scaffold_card.mjs,无需 dev server |
|
|
51
54
|
| **插组件(MCP)** | `card_add_component({id,parent,className,text,click})`:等价于 add_component.mjs,插入后自带离线硬校验 |
|
|
52
55
|
| **渲染回归基线** | `node scripts/render_baseline.mjs --snapshot`(建基线)/ `--diff`(对比基线像素差异,超阈值 exit 70)。已改为离线独立渲染器,不依赖 dev server |
|
|
53
|
-
| **批量产线(spec→多卡粗骨架)** | `node scripts/generate_cards.mjs --ui <UI目录> --out <输出目录> [--
|
|
54
|
-
| **手动单卡(批量产线工具)** | `node scripts/spec_to_schema_v3.mjs --spec <spec.json> --id <卡id> [--name <名称>] [--force]`:从 spec.json
|
|
56
|
+
| **批量产线(spec→多卡粗骨架)** | `node scripts/generate_cards.mjs --ui <UI目录> --out <输出目录> [--only <子串>] [--popups] [--force]`(走 v3 引擎,无 `--strategy` 开关)。生成粗骨架后逐卡走 `iterate.mjs` 视觉闭环精修(见 Workflow A 批量产线段) |
|
|
57
|
+
| **手动单卡(批量产线工具)** | `node scripts/spec_to_schema_v3.mjs --spec <spec.json> --id <卡id> [--name <名称>] [--force]`:从 spec.json 直组装组件树(v3 CLI),产出粗骨架后走 `iterate.mjs` 精修 |
|
|
55
58
|
|
|
56
59
|
## 1. 最常用组件最小骨架
|
|
57
60
|
|
|
@@ -76,6 +79,11 @@
|
|
|
76
79
|
{ "className": "Image", "properties": [ { "itemKey":"value","itemValueFrom":"1","itemValue":"$img","itemDefaultValue":"" } ] }
|
|
77
80
|
{ "className": "Icon", "properties": [ { "itemKey":"icon", "itemValueFrom":"1","itemValue":"$icon","itemDefaultValue":"angle_right" } ] }
|
|
78
81
|
|
|
82
|
+
// ⚠ Image/backgroundImage 的两个来源槽不能串:`"1"`=变量(值**必须**以 $ 开头)、`"2"`=静态图片(值是
|
|
83
|
+
// `/slices/…` 或 `@filestore/…` 字面量)。把 $ 表达式塞进 `"2"` → 运行时对 "2" 是**原样透传** →
|
|
84
|
+
// `background-image: url($icon)` → 画布那张图静默空白,而 validate 全绿、切图 HTTP 全 200(R25 反向陷阱)。
|
|
85
|
+
// IR 侧对应:变量写 `p:{value:"$img"}`(自动判成 "1"),静态写 `pi:{value:"/slices/x.png"}`(自动 "2",给 $ 值直接抛错)。
|
|
86
|
+
|
|
79
87
|
// 缺素材占位:Image 的 value 留空 → 自动显示打包内 default-image.png 占位图(renderer-proven);Icon 缺 glyph 时改用占位 Image 顶位。详见 references/generation_pitfalls.md §3.1
|
|
80
88
|
|
|
81
89
|
// ListLayout(列表;slotScope 下子项:表达式用 $field / $$ / $_index,事件脚本用 this.state.<field> 直读、禁 $$)
|
|
@@ -113,7 +121,7 @@ $title.length * 16 // 按文字长度算宽度
|
|
|
113
121
|
- **铁律 r56(JSAPI 裸调)**:事件里直接 `ynet.callJSAPI(name, params, cb)`;禁止 `window.ynet` 前缀与 `typeof window` 存在性守卫,lint `r56` ERROR 拦截。
|
|
114
122
|
- **甜甜圈/环形比例图**:UI 出现环形占比图 → DSL 无图表组件,用 **Canvas 自绘**:`onMeasured` 事件里用 `event.canvas` 画;环形段 = `arc(外)` 正向 + `arc(内, true)` 反向 + `closePath()`,内半径取外半径 1/3 出镂空;先 `canvas.width=width` 防糊;占比由 `onReady` 从原始值算 `ratio` 写回。中心文字用另叠 Text。范式见 `references/recipes.md §10` + 片段 `canvas-donut.json`。
|
|
115
123
|
- **角标 / 角标签**:内容块角落的小标签(『付款账户』『收款账户』『NEW』折扣标)→ DSL 无 badge 组件,用 **AbsoluteLayout 叠加**:外层包 AbsoluteLayout(`align` 选角,留空=TopLeft),内放 ① 基础内容(满尺寸子元素)+ ② 角标(小尺寸 Text/Image 子元素)重叠;角标用非对称 `borderRadius`(左上+右下=8)+ `backgroundColor` + 橙字做成胶囊。角标必须作为 AbsoluteLayout 的**第二个小尺寸子元素**,别塞进基础内容 Column。范式见 `references/recipes.md §11` + 片段 `absolute-badge.json`。
|
|
116
|
-
- **标题+下划线/高亮条(强制配方)**:标题底部紧贴或重叠彩色条时,禁止只写 AbsoluteLayout 后手调 top
|
|
124
|
+
- **标题+下划线/高亮条(强制配方)**:标题底部紧贴或重叠彩色条时,禁止只写 AbsoluteLayout 后手调 top。**整段照抄 `ir-format.md §2d` 的 IR 片段**(与 `recipe_snippets/title-underline-overlay.json` 同源):`AbsoluteLayout(align:"BottomLeft") > [装饰条 Row(s.position.top/left), 标题 Text(s.position.top/left)]`;装饰条先绘制、标题后绘制;几何读标注差值(`top = 标注条.y − 标注标题.y`,别套「行高 + gap」公式),宽度可绑 `$titleVar.length * fontSize`。**条子排在流式里、或用正/负 margin 顶位 → lint `r64-title-underline-flow` ERROR**;缺 `BottomLeft`、错误 z-order、写死宽度由 R42 拦。
|
|
117
125
|
|
|
118
126
|
## 3. 4 个事件范式(actionType 仅 4 种合法值)
|
|
119
127
|
|
|
@@ -171,41 +179,42 @@ $title.length * 16 // 按文字长度算宽度
|
|
|
171
179
|
|
|
172
180
|
## 7. 生成避坑速查(精简版——自动化规则由 lint/preflight 拦截,此处仅保留设计原则)
|
|
173
181
|
|
|
174
|
-
> **已自动化(lint/preflight 自动拦截,AI 无需记忆)**:itemValueFrom 必填、style itemId 映射、flexShrink 分流(R17/R22
|
|
182
|
+
> **已自动化(lint/preflight 自动拦截,AI 无需记忆)**:itemValueFrom 必填、style itemId 映射、flexShrink 分流(R17/R22/**R22b**/R24/R35)、Image itemValueFrom(R25)、Image rect.height(R32)、切图文件名空格(R31)、**切图引用含 URL 保留字符(R31b:`# ? % & +`,改写=重命名不是转义)**、**切图引用在 src/slices 不存在(R31c:引用必须与磁盘名同源)**、百分比 Math.round(R23)、Icon eventaction(R30)、分割线用 Divider(R28)、元素计数(R29)、**透明度表达式(R57,机械改写 `apply_opacity_pattern.mjs`)**。生成后跑 `preflight.mjs` + `lint_card.mjs` 即可,报错信息含修复指引。
|
|
175
183
|
|
|
176
184
|
**AI 必须主动判断的设计原则**:
|
|
177
185
|
|
|
178
|
-
-
|
|
179
|
-
- **元素间距用 margin 不用 padding**:
|
|
180
|
-
- **卡片根样式全量提取**:spec 卡片 bg shape
|
|
181
|
-
-
|
|
182
|
-
-
|
|
183
|
-
- **运行期数据必须绑定**:金额/日期/姓名/枚举 Tab/利率行等一律 inputParams;金额
|
|
184
|
-
- **水平 margin 不叠 width:100
|
|
185
|
-
- **space-between 三要素公式**:父 Row
|
|
186
|
-
-
|
|
187
|
-
- **语义组件由 AI 裁决**:脚本给信号,AI
|
|
188
|
-
- **垂直节奏用 padding 不用 lineHeight
|
|
189
|
-
- **background 必须含 backgroundType:"01"
|
|
190
|
-
-
|
|
191
|
-
-
|
|
192
|
-
- **borderRadius
|
|
193
|
-
- **分割线用 Divider 组件不用 Row
|
|
194
|
-
- **AbsoluteLayout height 必须显式设值**:
|
|
195
|
-
-
|
|
196
|
-
- **space-between Row
|
|
197
|
-
- **对比表行范式**:左值(强调色
|
|
198
|
-
- **分段标题范式(Divider 居中夹 Text
|
|
199
|
-
- **柱状图/网格线/轴标签 = Canvas
|
|
186
|
+
- **标注可信度分层**:属性值(圆角/阴影/字体/边框/背景色)读标注、禁抄存量卡;几何(rect/间距)按实图/容器边界分析(pitfalls §9-R1/R10)。
|
|
187
|
+
- **元素间距用 margin 不用 padding**:padding 污染 box 高(§9-R9)。
|
|
188
|
+
- **卡片根样式全量提取**:spec 卡片 bg shape 的背景+圆角+边框+阴影+透明度逐项提取到根容器,不可只取两项(§9-R33)。
|
|
189
|
+
- **根节点零间距**:聚焦裁切后卡片独立渲染,Root 与首层容器 padding/margin 归零,内部留白下沉到 content 容器 padding16(§9-R10)。
|
|
190
|
+
- **渲染器默认样式显式覆盖**:Row/Column 默认 padding:2px+gap:4px+居中、ListLayout 默认 edge-insets{2,2,2,2} 须归零/指定(§9-R11)。
|
|
191
|
+
- **运行期数据必须绑定**:金额/日期/姓名/枚举 Tab/利率行等一律 inputParams;金额 type:"number" + 千分号表达式(§9-R12 + amount_formatting.md)。
|
|
192
|
+
- **水平 margin 不叠 width:100%(R63)**:叶子设左右 margin 用 width:auto 靠 stretch 补宽;100%+marginLeft/Right 溢出。分栏:左 100%+shrink:1,右 auto+shrink:0。lint r63 只拦交叉轴父容器(Root/Column/SingleColumn/flexDirection:column/AbsoluteLayout abspos 子);Row/MultiColumn 主轴不报。补宽须显式 rect.width:"auto"(Text/MultiText 的 fit-content 顶掉 stretch)(rules.md §十六)。
|
|
193
|
+
- **space-between 三要素公式**:父 Row 100%+shrink:1+space-between;左 100%+shrink:1;右 auto+shrink:0(§9-R27)。
|
|
194
|
+
- **预览器行为契约**:容器默认 width:100%(自适应须显式 auto)、组件默认 overflow:hidden(有意溢出须 visible)、abspos 用 align+margin(position 组无效)、Canvas 禁 save/scale/transform/drawImage、fontWeight 只用 400/500/600、font 组无 fontFamily、预览默认 line-height:1.5(单行须显式行高)。完整见 previewer_contract.md。
|
|
195
|
+
- **语义组件由 AI 裁决**:脚本给信号,AI 结合截图+层级树定语义(§9-R36)。
|
|
196
|
+
- **垂直节奏用 padding 不用 lineHeight**:控制垂直位置用容器 paddingTop/Bottom,不设 lineHeight(§9-R51)。
|
|
197
|
+
- **background 必须含 backgroundType:"01"**:写纯色背景须同时写 backgroundType:"01",否则渲染器按默认 "00"(透明)静默丢弃 backgroundColor ——「设了背景但渲染透明」最常见原因。
|
|
198
|
+
- **文本水平居中需包裹层**:Column 中单 Text 居中仅设 textAlign:"center" 不生效,用 Row(width:100%, flexShrink:1, justifyContent:center)包裹。
|
|
199
|
+
- **券项/卡片项白色填充**:列表项即使无切图也须 background:{backgroundType:"01", backgroundColor:"rgba(255,255,255,1)"},否则与卡底融为一体失层次。
|
|
200
|
+
- **borderRadius 必须用对象形式**:borderRadius:"12" 标量展开器不生成 itemChildren、渲染器读不到;须写 {borderRadiusType:"0", borderRadius:"12"}。
|
|
201
|
+
- **分割线用 Divider 组件不用 Row+背景**:列表项间须 c:"Divider",禁 Row+backgroundColor 模拟;末项隐藏用 v-show 对象形式(showType:"01"+condition.expression,裸字符串被编译成 itemValueFrom:"1" 静默丢弃)+ condition.name 必填(缺名 validate ERROR、命名不合规 lint r52 WARN)。
|
|
202
|
+
- **AbsoluteLayout height 必须显式设值**:abspos 子元素脱标不撑高父容器,height:"auto" 导致容器塌缩为 0;须设 height=最高子元素已知高(如 "75")。
|
|
203
|
+
- **简单几何图形用样式实现不切图**:圆/方+文字(VS 徽章/数字角标)用 Column(borderRadius=宽/2 + backgroundColor)+ 内层 Text,无需切图。
|
|
204
|
+
- **space-between Row 子元素需显式像素宽**:两并列卡在 space-between 中须各设显式 width(从 spec 读像素如 "152"),不可用 width:"100%";至少一侧 flexShrink:"1" 满足 R35。
|
|
205
|
+
- **对比表行范式**:左值(强调色 textAlign:right width:100% flexShrink:1)+ 中间标签(灰底圆角徽章 width:"72" bg:rgba(245,245,245,1) radius:"6" flexShrink:0)+ 右值(对比色 textAlign:left width:100% flexShrink:1),父 Row justifyContent:space-between。
|
|
206
|
+
- **分段标题范式(Divider 居中夹 Text)**:Row(justifyContent:center alignItems:center)包 Divider(flexShrink:1)+Text(flexShrink:0, margin:左右8)+Divider(flexShrink:1),两线自适应居中。
|
|
207
|
+
- **柱状图/网格线/轴标签 = flex 原生优先、Canvas 仅兜底**:DSL 无图表组件,矢量柱体此前被静默丢弃。生成器已按纯几何识别柱状图簇(同基线窄柱 ≥4 + 网格线/标签佐证)——先出 flex 原生组件树(轴列 + 槽位柱列 + 硬停渐变网格线),表达不了才回退 Canvas 回放;手写 Canvas 兜底时记住 `event.canvas.getContext('2d')` 才是 ctx、白名单无 roundRect(四角 arc 拼路径)、簇高要登记语义边界。详见 `generation_pitfalls.md §16`。
|
|
200
208
|
- **列表行壳/行右图标认领**:行壳 = 圆角≥4 或渐变或宽≤卡宽-8(满宽无圆角平填充=分区背景板,禁认领);壳间有间隙 → 不发 Divider、row-spacing 承间隙、列表按壳宽内缩;逐行壳色不同绑 $rowBg;行右 32-72px 图标 = $rightIcon;icon 内嵌徽标不是 affordance(按 rightIcon 盒排除)。详见 `generation_pitfalls.md §17`。
|
|
201
209
|
- **图表绘制层序(铁律 R62)**:出码顺序 = 底 → 顶——坐标系/参考线(网格线、轴线、雷达环与辐射线、环形轨道)最底层 → 数据标记(柱/折线/多边形/扇区/高亮弧)→ 文字标签最上层;禁止「先填充后画网格」。半透明数据标记把 `opacity` 乘进颜色/渐变(代理 ctx 无 `globalAlpha`)。spec 元素顺序不可照抄。详见 `rules.md §十五`。
|
|
202
210
|
- **重复行并表前先比模板签名**:行图标/复合金额对/行右沿/行内操作不同的行不能进同一个 ListLayout(半宽对比面板混进全宽产品行 → 字段错位竖排换行 + 占位文案 + 双份渲染)。
|
|
203
211
|
- **卡底色看左右 margin**:采样卡底色只信左右边缘条带(内缩壳盖不到),别采底部中条(会采到最后一行彩色行壳)。
|
|
204
212
|
|
|
205
213
|
- 对比面板并排壳:预抽取每壳一行(壳内标签行+值行+圆点+tag),y 带重叠合成一条带;连接件末尾子节点+负 marginLeft;见 pitfalls §18。
|
|
206
|
-
- 柱状图走 flex 原生(轴列+槽位柱列+硬停渐变网格线),Canvas 仅兜底;值标签列宽=标签宽+4 防折行;见 pitfalls §
|
|
214
|
+
- 柱状图走 flex 原生(轴列+槽位柱列+硬停渐变网格线),Canvas 仅兜底;值标签列宽=标签宽+4 防折行;见 pitfalls §16。
|
|
207
215
|
- 渲染器契约:position 组被忽略(无绝对定位)/overflow 默认 hidden/负 margin 可用(**逐边负值必伴 marginType=1**,否则静默失效)/style 组同名子项唯一(绑定改写不追加);见 pitfalls §20/§23。
|
|
208
216
|
- 散点带(≥4 pill 分 ≥2 行 + 中心近方媒体):三列 Column + 列间负 margin 还原重叠,列内行 paddingTop=spec y 差;胶囊=Row 壳+文本+行尾箭头;金额/标签走变量绑定;见 pitfalls §21 / recipes §18。
|
|
217
|
+
- 顶点标签绝对定位(胶囊空间散布围绕多边形顶点,≥3 pill y 跨度>40px x 跨度>80px):胶囊+雷达图同一 AbsoluteLayout + 逐元素 margin 偏移到 spec 坐标(不嵌套容器),height 显式设值(auto 塌缩),overflow:visible;消费元素从 texts/shapes/slices/orphanSlices 统一过滤防重影;见 recipes §18a。
|
|
209
218
|
- 半环评分仪表盘(大号数字+「分」单位+下标签,均未被消费)→ Canvas recipe 出码,零流高叠放(marginTop+负 marginBottom,marginType=1);见 pitfalls §24 / recipes §14。
|
|
210
219
|
- 段落壳(灰底圆角含富文本碎片)归 richBlock:panelRows 不认领;尾部链接行(查看详细建议+箭头)随段落壳内补行;见 pitfalls §22。
|
|
211
220
|
- aux 行锚点认 spec x 几何(textAlign=right 但几何在左 1/3 → 左锚);行尾 8-32px 小切片随行消费不孤立成行;见 pitfalls §23。
|