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
|
@@ -2,20 +2,68 @@
|
|
|
2
2
|
"name": "标题+下划线重叠(AbsoluteLayout 绝对定位范式)",
|
|
3
3
|
"when": "标题文本下方紧贴一条彩色装饰条/下划线(如蓝条高亮),两者在视觉上部分重叠或紧贴,设计稿常表现为标题底部与装饰条零间距或负间距",
|
|
4
4
|
"source": "export-proven:myLoan(用户手动确认)、loanCompare、pointsDetail、memberLevelNone、integralCouponList",
|
|
5
|
-
"diffFrom": "
|
|
6
|
-
"structure": "AbsoluteLayout(align:BottomLeft, height
|
|
7
|
-
"
|
|
5
|
+
"diffFrom": "禁止用 margin 实现标题与装饰条的位置关系——**负 margin 与正 margin 都不行**。负外边距会让 flex 流式布局错乱;正 margin(实测 Row h5 + marginTop3)更隐蔽:装饰条一旦排在流式里就占掉自己的高度,把后续兄弟整体下推,而且它的位置永远由「上一个兄弟的高度」决定,拿不到设计稿那种「条子压在标题底部」的与标题内部对齐的关系。改 AbsoluteLayout 绝对定位,标题与装饰条各自独立定位。lint:R38 拦负 margin,`r64-title-underline-flow` 拦流式写法。",
|
|
6
|
+
"structure": "AbsoluteLayout(align:BottomLeft, rect.height=标题行高) > [Row#underline(position.top/left), Text#title(position.top/left)]",
|
|
7
|
+
"geometry": "**几何读标注,不要套公式**:容器 rect.height = round(标题fontSize × 1.4)(渲染器行高);条子 rect.width / rect.height 直接抄标注;position.left = 标注条.x − 标注标题.x;position.top = 标注条.y − 标注标题.y。例(transferRecord 实测稿):标题 rect(32,250,68,24) + 条子 rect(32,266,68,8) → 容器 height=24、条子 top=266−250=**16**、left=0、w=68、h=8(条子底边 274 = 标题底边 274,即压在标题最后 8px 上)。**不要照抄 myLoan 的 top=28**:那是「条子挂在线框下方」的另一种设计(top = 行高 + 4),套到「压在标题底部」的稿子上会偏 12px。",
|
|
8
|
+
"bindingNote": "条子宽度在标题可变时才需绑定 `$title.length × fontSize`(示例 4 字 × 17 = 68,与标注 68 对得上);标注给死值就用死值。颜色读标注:实色写实色,标注 opacity<1 写同色 rgba(如 rgba(0,164,109,0.2))。",
|
|
9
|
+
"ir": {
|
|
10
|
+
"c": "AbsoluteLayout",
|
|
11
|
+
"p": {
|
|
12
|
+
"align": "BottomLeft"
|
|
13
|
+
},
|
|
14
|
+
"s": {
|
|
15
|
+
"rect": {
|
|
16
|
+
"width": "auto",
|
|
17
|
+
"height": "24"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"children": [
|
|
21
|
+
{
|
|
22
|
+
"c": "Row",
|
|
23
|
+
"s": {
|
|
24
|
+
"rect": {
|
|
25
|
+
"width": "$title.length * 17",
|
|
26
|
+
"height": "8"
|
|
27
|
+
},
|
|
28
|
+
"background": {
|
|
29
|
+
"backgroundType": "01",
|
|
30
|
+
"backgroundColor": "rgba(0,164,109,0.2)"
|
|
31
|
+
},
|
|
32
|
+
"position": {
|
|
33
|
+
"left": "0",
|
|
34
|
+
"top": "16"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"c": "Text",
|
|
40
|
+
"v": "$title",
|
|
41
|
+
"s": {
|
|
42
|
+
"font": {
|
|
43
|
+
"fontSize": "17",
|
|
44
|
+
"fontWeight": "500",
|
|
45
|
+
"color": "#111111",
|
|
46
|
+
"lineHeight": "24"
|
|
47
|
+
},
|
|
48
|
+
"position": {
|
|
49
|
+
"left": "0",
|
|
50
|
+
"top": "0"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
"irNote": "上面 ir 片段可**整段粘进 IR 的 children**(字段契约见 references/ir-format.md §2d):把 $title 换成标题变量、字号/颜色/宽高换成标注值即可 —— 这就是「照抄」的可复制形态,不要再临场翻译。实测该片段经 expand_ir.mjs 展开后 validate 0 error,且 p.align → properties[].align、s.position → style.position 的 itemId 均非空。",
|
|
8
57
|
"rules": [
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"标题
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"装饰条颜色读标注;设计稿常见为品牌色 20% 透明(如 rgba(47,134,246,0.2))"
|
|
58
|
+
"父容器用 AbsoluteLayout + align:\"BottomLeft\"(让后续兄弟从标题底部排起,而不是从条子底部)",
|
|
59
|
+
"装饰条与标题各自设 style.position.left/top —— 不是 margin,正负都不行(R38 禁负、r64 禁流式)",
|
|
60
|
+
"子节点顺序 [装饰条, 标题]:装饰条先绘制、标题后绘制压在条子上(反了下划线会盖住文字)",
|
|
61
|
+
"容器 rect.height = round(标题fontSize × 1.4)(渲染器行高,不是标注 rect.height)",
|
|
62
|
+
"几何取标注差值,别套 top = 行高 + gap 的公式(那只对条子挂在线框下方的稿子成立)",
|
|
63
|
+
"条子颜色/宽度读标注;宽度绑定表达式只在标题长度可变时才必要"
|
|
16
64
|
],
|
|
17
65
|
"apply": {
|
|
18
|
-
"_template": "标题+下划线重叠用 AbsoluteLayout
|
|
66
|
+
"_template": "标题+下划线重叠用 AbsoluteLayout,位置靠 position,不用 margin(正负都不行)"
|
|
19
67
|
},
|
|
20
68
|
"notes": "此范式泛用于所有「标题 + 底部高亮条」场景。关键洞察:装饰条通过 AbsoluteLayout 溢出到容器声明高度之外,但 align:BottomLeft 让后续兄弟只感知标题行高,不会因装饰条而下移。"
|
|
21
69
|
}
|
|
@@ -185,13 +185,9 @@ this.postMessage('syncList');
|
|
|
185
185
|
|
|
186
186
|
## 5. 金额格式化(千分号/正负号/货币符号)
|
|
187
187
|
|
|
188
|
-
用途:金额显示带千分号、2
|
|
188
|
+
用途:金额显示带千分号、2 位小数、可选正负号与货币符号。完整分类与口径(纯数字 / 带前缀文案 / 列表项 fallback / 健壮版)见 `amount_formatting.md §3`。
|
|
189
189
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
```javascript
|
|
193
|
-
'剩余应还¥' + ($currentPeriodAmount * 1).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
|
194
|
-
```
|
|
190
|
+
内联表达式(带前缀文案,来源 creditCardPayment,适合单处使用):结构同 `amount_formatting.md §3.3`,本节聚焦默认口径的健壮版——
|
|
195
191
|
|
|
196
192
|
健壮版表达式(**默认口径**:显示金额一律在 view 表达式里直出,不在 onReady 预生成 `*_fmt`):
|
|
197
193
|
|
|
@@ -650,9 +646,9 @@ ynet.callJSAPI('alertDialog', {
|
|
|
650
646
|
- **不产出 schema.json**:上游 `prompt.md` 对该画板已是「交互提示文档」,明确声明不建卡。下游消费时,把本调用补到**父卡**(上游 `clusters.json.overlays[].parentKey` 或 `prompt.md §4` 指示的触发卡)的对应按钮 / 条件事件中。
|
|
651
647
|
- 参考存量:`budgetAnalysis`(温馨提示→alertDialog)、`creditCardPayment`(还款前二次确认→alertDialog)。片段见 `recipe_snippets/alertDialog-call.json`。
|
|
652
648
|
|
|
653
|
-
## 13. 样式生成铁律(生成器通用:
|
|
649
|
+
## 13. 样式生成铁律(生成器通用:v3 适用)
|
|
654
650
|
|
|
655
|
-
用途:
|
|
651
|
+
用途:v3 生成 schema.json 时,**必须遵守的样式格式约束**(geo 生成器已于 2026-08-31 整体删除)。违反任一条都会导致渲染器丢弃样式(表现为「UI 样式几乎全部丢失」——文本无色无大小、背景透明、TabBar 无底色)。
|
|
656
652
|
|
|
657
653
|
> **根因记录(2026-08-13)**:首轮生成 4 张校准卡全部「样式全丢」,排查发现渲染器对以下三条零容忍。
|
|
658
654
|
|
|
@@ -805,6 +801,25 @@ node scripts/apply_opacity_pattern.mjs --id <cardId> --node comp_8 --container C
|
|
|
805
801
|
- 三列 Column + 列间**负 marginLeft(marginType=1)**还原水平重叠;列宽=成员外接宽,溢出靠 overflow visible。
|
|
806
802
|
- 列内行 paddingTop=spec y 差、**列首行相对带顶**;胶囊=Row 壳+文本+行尾箭头(左锚不居中)。
|
|
807
803
|
- 金额/标签走变量绑定(千分表达式/字符串变量);检出即消费 pills/金额/标签/媒体/箭头防重影。
|
|
804
|
+
## 18a. 顶点标签绝对定位(vertex-absolute,train-proven:seq5 card 12 雷达五边形)
|
|
805
|
+
|
|
806
|
+
用途:胶囊**空间散布**(非横排),围绕雷达/环形图的多边形顶点分布。生成器自动检测条件:
|
|
807
|
+
≥3 个带填充 pill、y 跨度 >40px、x 跨度 >80px、分布在 ≥3 个 y 带 → 图表顶点标签而非按钮排。
|
|
808
|
+
|
|
809
|
+
关键纪律(踩坑总结):
|
|
810
|
+
- **胶囊和雷达图放在同一个 AbsoluteLayout**,共享 spec 绝对坐标系。中心媒体从 isolatedBlocks
|
|
811
|
+
提出到 vertexClusters.chartMedia,与胶囊统一 margin 偏移。分属不同路径 → 坐标系不一致 → 位置偏移。
|
|
812
|
+
- **逐元素绝对定位**:每个文本/形状用自身 margin 偏移到 spec 坐标,不嵌套容器(嵌套偏移内层元素 → IoU 极低)。
|
|
813
|
+
- **AbsoluteLayout 须设显式 height**(container() 不处理 opts.height,须手动 setStyleChild),
|
|
814
|
+
值 = 最底顶点 (y+height+35) − 最顶顶点 y。auto → 塌缩 → 下游间距计算错误。
|
|
815
|
+
- **overflow:visible** 保留防止子节点溢出被裁。
|
|
816
|
+
- 消费元素须从所有返回值统一过滤 _vertexConsumedEls:texts/shapes/slices/content/buttonShapes/
|
|
817
|
+
isolatedBlocks/orphanSlices(group 类型会被 push 进 slices,仅清 isolatedBlocks 不够 → 重影)。
|
|
818
|
+
- AbsoluteLayout 从 r66 FULLWIDTH_CONTAINERS 排除(叠层定位容器有意设固定宽)。
|
|
819
|
+
|
|
820
|
+
生成器已自动化:v3_extract.mjs 检测 vertexClusters + chartMedia,v3_assemble.mjs 统一 AbsoluteLayout 渲染。
|
|
821
|
+
|
|
822
|
+
|
|
808
823
|
|
|
809
824
|
## 19. 柱状图 Canvas 自绘回放(bar-chart replay,export-proven 同族:assetAllocRadar gauge/radar)
|
|
810
825
|
|
|
@@ -837,3 +852,24 @@ node scripts/apply_opacity_pattern.mjs --id <cardId> --node comp_8 --container C
|
|
|
837
852
|
|
|
838
853
|
数据驱动升级路径:柱高/标签改绑 `$chartBars[i].value` 表达式 + onMeasured 读 `self.state`
|
|
839
854
|
(与 radar 范式同),静态回放仅作为标注缺省值首帧。
|
|
855
|
+
|
|
856
|
+
## 20. 半透明大椭圆装饰(radial-gradient 叠层,train-proven:seq4-l05)
|
|
857
|
+
|
|
858
|
+
**识别(结构信号,非业务词)**:卡顶部装饰椭圆——`opacity <= 0.5`、无圆角、宽/高 ≥ 卡宽 40%、顶沿贴卡顶(弥散光斑/氛围色块族)。
|
|
859
|
+
|
|
860
|
+
**正解**:**逐颗椭圆 radial-gradient 叠进卡片背景**,不采样成硬停色带——
|
|
861
|
+
1. 每颗椭圆按 spec 生成一层 `radial-gradient(circle at <中心x> <中心y>, <主色带 alpha> 0%, <主色 alpha:0> 70%)`;
|
|
862
|
+
2. **CSS 渐变首层在最上 → 按 spec 元素序反排**(底层椭圆排最后);
|
|
863
|
+
3. 写入 Root/首层容器的 `background.backgroundGradient`(与卡底色共存,背景声明组必须存在——铁律 §0-8)。
|
|
864
|
+
4. 卡身另有不透明磨砂玻璃壳罩在其上时,标注 alpha 需 **×0.5 置淡校准**,否则视觉过浓(实测 +5.6pp 偏差)。
|
|
865
|
+
|
|
866
|
+
**禁止**:用单条色带/硬停渐变近似(带底沿留矩形硬边,实测单带还原 32% 像素差);用纯色块伪装弥散。
|
|
867
|
+
|
|
868
|
+
**Workflow A 裁决**:此类装饰判 `decoration`,宽度/位置读标注 rect,透明度读标注 opacity(半透明合法,见铁律 §0-8);无导出切片时禁画板裁切(会带前景文字)。
|
|
869
|
+
|
|
870
|
+
---
|
|
871
|
+
### diffuse texture is bitmap not css gradient(r055 训练回灌)
|
|
872
|
+
> lesson: `LESSON-20260827-057` status: `promoted`
|
|
873
|
+
|
|
874
|
+
弥散、光晕、噪点和不规则纹理按位图处理:优先 slice,其次画板局部裁切,最后占位图;禁止退化为纯色或规则 CSS 渐变。
|
|
875
|
+
|
|
@@ -94,7 +94,8 @@
|
|
|
94
94
|
## 九、组件级硬约束
|
|
95
95
|
|
|
96
96
|
详见 `references/component_rules.md`(单表速查)。最易踩的几条:
|
|
97
|
-
- **根节点(Root/view)不设样式宽度**:根节点宽度由顶层 `view.width
|
|
97
|
+
- **根节点(Root/view)不设样式宽度**:根节点宽度由顶层 `view.width`(= **卡面宽**,非画板宽,见 `ir-format.md` §2f)承载;`view.style.properties` 里**不要**放 `rect` 组(含 `rect.width`)——放了是冗余/重复宽度(export-proven:49+ 张导出卡根 style 均无 rect,仅 `view.width` 定宽)。若手写时误加,`check_topology.py` 会以 `Root_style_rect_forbidden`(hard 级)提示删除。
|
|
98
|
+
- **宽度自适应(R66):占满语义禁写死 px**——纵向流父级(Root/Column/AbsoluteLayout)下,占满父内容盒(98%~103%)的容器子节点必须用 `width:"100%"` 或不写宽(容器 meta 默认就是 100%),px 只给真固定元素(图标/头像/徽章/图表画布/hug 宽标题壳)。写死 px 的内容层在非设计宽度的宿主容器里锁死不自适应。lint `r66-fullwidth-px` ERROR 拦截;契约全文见 `ir-format.md` §2f。
|
|
98
99
|
- **关键文本节点(金额/数字/标签/标题/占比/单位)必须显式 `flexShrink:"0"`**:`.ce_text` 默认 `width:fit-content; word-break:break-all`,父子链上任一层被压缩都会沿断行规则塌陷(字符竖排堆叠);关键文本节点 + 包裹它们的直接 Row/Column 容器都要 `flexShrink:"0"`(flex 组 itemId:"004")。详见 `generation_pitfalls.md §9-R17`。
|
|
99
100
|
- **嵌套需 space-between 的 Row 必须显式 `rect.width:"100%"`**:根节点级 Row 默认撑满,嵌套层不再默认撑满(收缩到内容宽 → space-between 失效);视觉上需要左右分布的行容器都要显式宽度 100%(同时按 R22 给该节点 `flexShrink:"1"`)。**例外**:父容器是纵向流且交叉轴 stretch(`Column`/`Root`/`SingleColumn`,CSS 强制 column + `align-items` 缺省 stretch)时子 Row 本就被拉满,或自身已给定宽(如 100px 胶囊)时 `space-between` 已生效——这两种情况 lint `r19` 不报,存量卡不要为消 WARN 去补 `width:"100%"`(会连带触发 R22/R35 两条 ERROR)。详见 `generation_pitfalls.md §9-R19`。
|
|
100
101
|
- **`rect.width:"100%"` 的节点必须保持可压缩(`flexShrink:"1"`),禁止同节点 `flexShrink:"0"`**:`width:100%` 在 flex 里语义是「占满主轴」,与兄弟节点并排时靠 `flexShrink` 协商让位;100% 节点若同时 `flexShrink:"0"`,协商被锁死,固定宽兄弟(图标/金额/按钮)会被挤出父容器或触发溢出。与 R17 按 `width` 分流:关键 Text(auto/fit-content/未设宽)走 R17 `flexShrink:"0"` 保护;`width:100%` 容器走本条 `flexShrink:"1"` 让位。详见 `generation_pitfalls.md §9-R22`。
|
|
@@ -165,7 +166,7 @@
|
|
|
165
166
|
|
|
166
167
|
### 13.1 标准选择交互(列 `1..n + 全部`;默认「列清单 + 提问后停下等序号」)
|
|
167
168
|
|
|
168
|
-
1. **先列全量清单(两种选择方式都要做)**:机械取画板(见 13.2),按 **`画板序号.卡片序号`** 编号逐行列出(序号 **1 起**,每行 `画板序号.卡片序号. 画板名 — 类型 / 尺寸`),清单末尾追加一项 **`全部`**。清单是用户的完整选项池,**不得只列节选**。**多卡画板展开为子项**(见 13.4):一个画板含 N 张卡片时列出 N 行(如画板有 2 张卡则 `1.1` 和 `1.2`),同一画板共享一张画板预览图但每张卡有各自的 `card-N.png` 聚焦图。**逐行配图(强制)**:每个卡片行下面紧跟**它自己那张**聚焦图绝对路径(`图: <绝对路径>`,`card-N.png`,无单独聚焦图时降级用画板 `artboard.png`),缺图该行显式写 `图:
|
|
169
|
+
1. **先列全量清单(两种选择方式都要做)**:机械取画板(见 13.2),按 **`画板序号.卡片序号`** 编号逐行列出(序号 **1 起**,每行 `画板序号.卡片序号. 画板名 — 类型 / 尺寸`),清单末尾追加一项 **`全部`**。清单是用户的完整选项池,**不得只列节选**。**多卡画板展开为子项**(见 13.4):一个画板含 N 张卡片时列出 N 行(如画板有 2 张卡则 `1.1` 和 `1.2`),同一画板共享一张画板预览图但每张卡有各自的 `card-N.png` 聚焦图。**逐行配图(强制)**:每个卡片行下面紧跟**它自己那张**聚焦图绝对路径(`图: <绝对路径>`,`card-N.png`,无单独聚焦图时降级用画板 `artboard.png`),缺图该行显式写 `图: (预览图缺失)`;**禁止把所有预览图统一堆在清单末尾**——部分卡片缺图时堆末尾会让序号与图错位,用户选不准。**路径照抄完整绝对路径**(`/Users/…` 或 `C:\…` 打头):**禁止用 `…` / `...` 省略前缀、禁止把路径包进反引号或代码块**——实测 `…/积分助手-4-xx.png` 这种缩写渲染端取不到图,那几行只剩一串路径=配图全丢(服务端已加文件名反查兜底,但那是补救,首轮就该写全)。
|
|
169
170
|
2. **再按环境能力选交互方式(优先级从高到低)**:
|
|
170
171
|
- **A|仅当工具清单里确实列出了原生弹窗工具时才用**(如宿主自带的交互式提问工具)→ 先输出上面的完整 `1..n + 全部` 清单,**紧接着调用该工具弹选择**。该工具每题最多 3 个选项、客户端会自动追加自由输入 Other,故:选项放**最具代表性的 2 个画板 + `全部`**(代表性优先 `artboardType=card` 且 `status=generated`),问题文案里写明「其余画板请在 Other 里填清单序号或画板名」;返回空答案时**不反复追问**,降级到 B。**注意:编辑器助手 / 本仓库助手的工具集里没有这个工具**(不存在 `request_user_input`)——不要去调用、也不要去寻找,直接走 B。
|
|
171
172
|
- **B|默认路径**→ 输出清单 + **一句纯文本提问**(如「请回复要处理的画板序号,或回复 `全部`」),然后**停止输出、结束本轮,等用户回序号**——不得在同一轮顺手产卡,不得自行挑画板。
|
|
@@ -173,9 +174,10 @@
|
|
|
173
174
|
3. **拿到序号才继续**:
|
|
174
175
|
- 回复单个序号 / 多个序号 → 只处理对应卡片;序号格式 `画板序号.卡片序号`(如 `1.2` = 第 1 个画板的第 2 张卡),单独回画板序号(如 `1`)时默认取该画板第 1 张卡(`1.1`);产卡时一轮只写一个 `src/cards/<id>/`。
|
|
175
176
|
- 回复 `全部` → **生成设计稿 UI 产物(sketch-spec-to-card 管线)允许**,整份导出全量跑(不加 `--only/--indices`);**产卡**时视为用户**显式批量授权**,按批量流程逐卡产出,交付里说明产了几张卡、分别对应哪些画板。
|
|
176
|
-
4.
|
|
177
|
+
4. **序号换算(优先照抄,不要换算)**:`parse_meaxure.py` 的清单**每行行尾自带可复制的 `--pick`**(板号 1 起、卡号 1 起:`--pick 8` / `--pick 8.1` / 同板连卡 `--pick 8.1~8.2` / 连板 `--pick 7~9`,多个逗号分隔),**用户回「8.1」就把这个串原样放进 args**——实测手工把 1 起序号减成 0 起 `--indices` 减错一位,建了错的画板(白跑 34s 解析 + 一步 LLM 才发现)。
|
|
178
|
+
`--indices` 仍是合法老写法(0 起),只在没有 `--pick` 可用时才手工换算:`--list` 与门禁清单**展示统一 1 起**(与 `manifest.canvases[].order` 同口径),用户回的 1 起序号 k 传 `--indices` 时 **-1(0 起)**。
|
|
177
179
|
5. **「全部」只能是用户的回答,不是 Agent 的默认值**:「UI 产物允许全量生成」指用户回复 `全部` 后可整份导出全跑;用户消息里没指定画板时,**包括「生成产物 / 前处理 / 跑管线」在内的任何请求都必须先过第 1–3 步**,Agent 不得替用户选「全部」。
|
|
178
|
-
6. **机械卡点(脚本层兜底)**:`sketch-spec-to-card/scripts/parse_meaxure.py` 不带 `--only/--indices/--sample/--scope-all` 的生成运行会打印 1..n + 全部 清单并 **exit 4
|
|
180
|
+
6. **机械卡点(脚本层兜底)**:`sketch-spec-to-card/scripts/parse_meaxure.py` 不带 `--pick/--only/--indices/--sample/--scope-all` 的生成运行会打印 1..n + 全部 清单并 **exit 4**(不生成任何东西);拿到用户回答后**照抄该行行尾的 `--pick`**(等价老写法序号 k → `--indices <k-1>`)、「全部」→ `--scope-all`。无人值守训练(`card-skill-trainer`)属显式批量例外,直接带 `--scope-all`(或 `SKETCH_SPEC_SCOPE_ALL=1`)。
|
|
179
181
|
|
|
180
182
|
### 13.2 清单来源(机械产出,不靠记忆)
|
|
181
183
|
|
|
@@ -187,11 +189,12 @@
|
|
|
187
189
|
|
|
188
190
|
- 清单**全量列名称**(几十个也列全,可按业务流分组),并标注哪些**不产卡**:`overlay_dialog` → 不生成独立卡,补到父卡事件(见 `dsl-card-generator/SKILL.md §8 Workflow A.5`)。
|
|
189
191
|
- 同一卡有多状态画板时,选择单位是 **cardKey(卡)**,并说明 `clusters.json.recommendedBase` 建议哪张作基线;用户选定卡但未指定状态时用 recommendedBase 并明确告知。
|
|
190
|
-
- 序号基准要对齐:`--list` 与 `manifest.canvases[].order` **展示都是 1 起**(同口径,可直接对齐);只有 `parse_meaxure.py --indices` **传参是 0 起**,用户回的序号 k → `--indices <k-1
|
|
192
|
+
- 序号基准要对齐:`--list` 与 `manifest.canvases[].order` **展示都是 1 起**(同口径,可直接对齐);只有 `parse_meaxure.py --indices` **传参是 0 起**,用户回的序号 k → `--indices <k-1>`,避免选错画板。清单行尾的 `--pick` 就是为免掉这次换算而加的——**能照抄就照抄**。
|
|
191
193
|
|
|
192
194
|
### 13.3 落盘纪律
|
|
193
195
|
|
|
194
196
|
- 一轮生成只写**一个卡片目录** `src/cards/<id>/`;批量仅在用户显式要求时进行。
|
|
197
|
+
- **批量建卡(用户一次点了多张 / 回了「全部」)走「一个回合多张卡」**:一次 `parse_meaxure.py --pick a.b,c.d`(或 `--indices`)出**全部**要素包,然后**在同一个回合里**按「S2→S5 走完一张,再走下一张」连续推进,各卡各写 IR、各跑 expand + iterate。**禁止每张卡各起一个回合**——每个新回合首步都要把整段对话重发一遍(实测 1.3–3 万 token/步),还要把同一份要素包重读一次,纯浪费。
|
|
195
198
|
- **显式批量例外**:用户点名跑无人值守训练(`card-skill-trainer`)或明确说「全部生成 / 批量建卡」时按批量执行——训练产物落隔离 run 目录、不写 `src/cards/`,交付时说明产了几张卡。
|
|
196
199
|
- 选定画板后走 `dsl-card-generator` Workflow A 六步闭环;`route_input.mjs` / MCP `card_route_input` 的形态判定仍是第一步。
|
|
197
200
|
|
|
@@ -258,5 +261,22 @@ Canvas 是**即时模式**绘制——后画的压先画的。所以「绘制层
|
|
|
258
261
|
|
|
259
262
|
- **禁止**把参考线画在数据标记之上(「先填充后画网格、网格才压得住」是反范式);spec 元素顺序**不可照抄**——标注导出常把网格线排在数据图元之后(assetAllocRadar 雷达:元素 34-39 是 `#DCDCDC` 网格/辐射线、40 才是渐变数据多边形)。
|
|
260
263
|
- 数据标记半透明(spec `opacity<1`)时把透明度**乘进颜色/渐变 stop**(代理 ctx 白名单**无 `globalAlpha`**),底层参考线才能透出来;设计稿「网格透过多边形可见」正是这个效果(assetAllocRadar 雷达 opacity 0.8)。
|
|
261
|
-
- 机械保证:`
|
|
264
|
+
- 机械保证:`lib/v3_shared.mjs` 的 `chartBarCanvasCode` 用 `chartPaintLayers` 强制分层(任一维 ≤2px 的细线 = 参考线;覆盖簇 bbox 一半以上的填充 = 绘图区底板;其余 = 数据标记),出码顺序 = 底板 → 参考线 → 数据标记 → 文字标签;回归护栏 `tools/tests/chart-layer-order.test.mjs`。手写图表(`recipes.md §15/§18`、`recipe_snippets/canvas-radar.json`)同口径。
|
|
262
265
|
- 数据取值导致的图形差异(多边形形状、柱高、扇区角度)不在本铁律管辖内——层序只约束「参考线 / 数据标记 / 文字标签」三类图层的先后。
|
|
266
|
+
|
|
267
|
+
## 十六、水平 margin 不叠加 width:100%(铁律 R63)
|
|
268
|
+
|
|
269
|
+
`rect.width:"100%"` 展开成 CSS `width:100%`(`adapter.doSize` 见 `%` 原样透传),此时元素宽度**已经等于父容器内容盒宽度**;再写 `marginLeft/Right`(或全边 `margin` + `marginType:"0"`)叠加的就是**外框**宽度 → 超出父容器、向右溢出,被 `overflow:hidden`(Root 与组件默认值)裁掉。生成侧最常见的低级错误:想表达「父容器水平内缩」,却把间距写在子节点上。
|
|
270
|
+
|
|
271
|
+
**lint `r63-width100-with-hmargin`(ERROR)的判定边界**:
|
|
272
|
+
|
|
273
|
+
- **报(必溢出)**:父容器按**列**排布时,水平方向是**交叉轴**,定宽不可缩——`Root` / `Column` / `SingleColumn` / 显式 `flexDirection:"column"` / `AbsoluteLayout`(子节点 `position:absolute` 脱标,`width:100%` 对定位包含块解析,margin 完全不参与收缩)。含 `marginType:"0"` 的全边 `margin`(四边同值,左右也吃)。
|
|
274
|
+
- **不报(主轴)**:父容器 `flex-direction:row`(`Row` / `MultiColumn` / ListLayout·GridLayout 的 item 壳)里 `flexShrink` 默认 1,会把节点缩回「容器宽 − margin」,margin 被吸收。存量 4 处这种写法(accountCreditedlist / memberLevelNone / pointsDetail / assetOverview)实测都不溢出——**故意不报主轴**,避免误报。
|
|
275
|
+
|
|
276
|
+
**正路(按设计意图三选一)**:
|
|
277
|
+
|
|
278
|
+
1. **间距放父容器**:`paddingLeft/paddingRight`(或父容器 `gap`),子节点保持 `width:100%`;
|
|
279
|
+
2. **改 `width:"auto"` 靠 stretch**:流式父容器(`Column`/`Root` 且 `alignItems` 为空/`stretch`)把 `rect.width` 从 `"100%"` 改成 `"auto"`,元素自动补满「容器宽 − margin」,视觉等价且不溢出。**必须显式写 `auto`**——Text/MultiText 有组件样式 `width:fit-content`,**只删 width 不会撑满**(实测:同一 375 宽父容器里 `auto` = 369px、删 width = 86.67px)。**AbsoluteLayout 子节点不适用**:abspos 的 `width:auto` 是 fit-content 不是 stretch,改走 ① 或显式像素宽;
|
|
280
|
+
3. **并排分栏**:左 `width:100% + flexShrink:1`、右 `width:auto + flexShrink:0`(quickref「space-between 三要素公式」)。
|
|
281
|
+
|
|
282
|
+
回归护栏:`tools/tests/card-gen-gates.test.mjs`(3 种命中形态 + 3 种不报形态)。
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# 校验脚本全集
|
|
2
|
+
|
|
3
|
+
> 从 SKILL.md §10 外迁(2026-09-21 优化方案 P0-3)。SKILL.md 只留高频命令;本文件是全集。详细参数见各脚本头注释。
|
|
4
|
+
|
|
5
|
+
## 全量命令表
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
node scripts/gate.mjs --id <卡id> [--spec <spec.json>] [--json] # ⚡ 门禁一条命令:autofix→validate→derive→lint→preflight 汇总
|
|
9
|
+
node scripts/route_input.mjs <输入路径> [--json] # Workflow 0 输入路由(0=已路由/3=unknown/1=用法错)
|
|
10
|
+
node scripts/spec_tree.mjs --spec <spec.json> [--card 1] [--compact] # spec 层级树(AI 读图)
|
|
11
|
+
node scripts/scaffold_card.mjs --intent <…> --id <卡id> --name <名称> [--from <模板卡id>] # 骨架(blank/克隆;常规建卡走 IR)
|
|
12
|
+
node scripts/add_component.mjs --id <卡id> --parent <cid> --className <X> [--text "…"] [--click] [--set-prop k=v] # 插组件(改卡专用,只增不改)
|
|
13
|
+
node scripts/expand_ir.mjs --ir <IR.json> --out src/cards/<卡id>/ # 紧凑 IR→合法 schema/data(切图自动汇聚);契约见 ir-format.md
|
|
14
|
+
node scripts/expand_ir.mjs --to-ir <schema.json> # 逆向:schema→紧凑 IR(`--self-test` 的往返无损校验用;也可用于「改卡后回出 IR」)
|
|
15
|
+
node scripts/generate_cards.mjs --ui <UI目录> --out <目录> [--only <子串>] [--popups] [--force] # 批量产线:spec→多卡粗骨架(走 v3 引擎,只出粗骨架,逐卡再走 iterate 精修)
|
|
16
|
+
node scripts/spec_to_schema_v3.mjs --spec <spec.json> --id <卡id> [--name <名称>] [--force] # 单卡直组装 CLI(v3 引擎底;批量产线与训练/回归都调它)
|
|
17
|
+
node scripts/ir_merge.mjs --ir <IR.json> --part <片段.json> # IR 分片合并(幂等+原子;每片 +40-140s,小卡禁拆;支持 {parts:[…]} 捆绑包一次合并)
|
|
18
|
+
node scripts/iterate.mjs --ir <IR.json> --out <dir> --design <png> [--set 'path=value'] # 一条龙闭环:expand→lint→render→pixel-diff(JSON 摘要,比逐脚本快 5×)
|
|
19
|
+
node scripts/ir_restore.mjs --list|--best|--round … # 迭代快照回退(见 iteration_protocol.md)
|
|
20
|
+
node scripts/lint_card.mjs --id <卡id> | --all [--json] # 行为规则体检(条数以实跑输出为准,文档不复制数字)
|
|
21
|
+
python3 scripts/validate_schema.py <schema.json> [--topology] # 结构合法性(CI 硬卡点)
|
|
22
|
+
python3 scripts/autofix_schema.py <schema.json> [--check] # 机械修复 itemValueFrom/重复 id/data 漂移
|
|
23
|
+
node scripts/preflight.mjs --spec <spec.json> --id <卡id> # 14 项机械 Pre-flight
|
|
24
|
+
node scripts/diff_card.mjs --id <卡id> [--baseline <path>] # 变更对比(铁律机械兜底,整体替换后必跑)
|
|
25
|
+
node scripts/check_fidelity.mjs --spec <spec.json> --schema <schema.json> # 保真度门禁(元素级对齐断言,npm run check:fidelity)
|
|
26
|
+
node scripts/render_card.mjs --id <卡id> [--width 375] [--out render.png] [--keep-alive] # 离线截图(vendor 产物+Chrome CDP,不占端口)
|
|
27
|
+
node scripts/render_diff.mjs --card <卡id> --design <设计图> # 并排视觉对比(--width 自动读 schema)
|
|
28
|
+
node scripts/render_baseline.mjs --snapshot|--diff [--id <卡id>] # 渲染回归基线(第四层校验)
|
|
29
|
+
node scripts/apply_opacity_pattern.mjs --id <卡id> [--check] # R57 透明度表达式机械改写
|
|
30
|
+
node scripts/diff_bands.mjs --id <卡id> --design <png> [--tol 2] # 量化偏差比对(dy/dh/主色直方图)
|
|
31
|
+
node scripts/check_magic_values.mjs # 魔法值门禁(改生成器后必跑,exit 70 供 CI)
|
|
32
|
+
node scripts/v3_regression.mjs [--update-baseline] [--cases <path>] # v3 改动回归门禁(金标清单 training/pass1/cases.json,skill 不持业务路径)
|
|
33
|
+
python3 scripts/derive_data_json.py <schema.json> --check|--overwrite # data.json 派生/检查
|
|
34
|
+
python3 scripts/inventory_schemas.py src/cards --examples # 盘点 DSL 面(同步新导出后先跑)
|
|
35
|
+
python3 scripts/extract_handcrafted.py # 刷新人力卡统计规则库 patterns.json
|
|
36
|
+
node scripts/build_card_index.mjs / build_card_templates.mjs # 重建卡片索引 / 模板库
|
|
37
|
+
node scripts/extract_subtree.mjs --id <卡id> --component-id <cid> # 抽子树为 recipe snippet
|
|
38
|
+
node scripts/describe_card.mjs --id <卡id> [--json] # 卡片反向描述机器初稿
|
|
39
|
+
node scripts/slices_normalize.mjs [--write] # 切图副本归一化(默认 dry-run)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## MCP 等价工具(P5,见 mcp/,无需 dev server)
|
|
43
|
+
|
|
44
|
+
`card_scaffold` / `card_add_component`(复用 lib/scaffold.mjs,插组件自带离线硬校验)/ `card_route_input`(复用 routeInput())/ `card_write({id, schema, verify:true})`(写卡+validate+render_status)/ `card_read` / `card_node_models` / `card_screenshot` / `ir_patch`(IR 点路径原子修补)。
|
|
45
|
+
|
|
46
|
+
## r057 保真规则(标注优先,8 条)
|
|
47
|
+
|
|
48
|
+
**属生成器与校验器内部规则(P2)**,改 `spec_to_schema_v3.mjs` 或 `check_fidelity.mjs` 时才读,常规生成不需要:孤立媒体间距 / 文字底壳全覆盖 / 透明形状 / 全卡渐变形 / 语义前缀外置 / 行内值几何三规则 r147 / 文字壳三态 r147 / 占位行保留行操作 r146。
|
|
49
|
+
|
|
50
|
+
## 四层校验自洽
|
|
51
|
+
|
|
52
|
+
validate(结构合法,CI 硬卡点)→ lint(体验达标)→ diff(铁律保留)→ render_baseline(像素回归)。四者互补不重叠,底层共用 `lib/paths.{mjs,py}`,目录重构无需同步多处。
|
|
53
|
+
|
|
54
|
+
校验器不证明设计器里的运行时行为,风险交互仍需在平台里核对。
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# 语义裁决协议(Workflow A 步骤 1 细则)
|
|
2
|
+
|
|
3
|
+
> 从 SKILL.md Workflow A 外迁(2026-09-21 优化方案 P0-3)。做语义裁决(步骤 1/2 之间)时读本文件;脚本可以给出候选信号,但**组件语义最终由 Agent 结合截图和层级树裁决**,禁止把业务词、画板名或某个项目的文案写进生成器。
|
|
4
|
+
|
|
5
|
+
每个可见簇先归入:`static-text / display-value / input / action / navigation / list / media / decoration`。
|
|
6
|
+
|
|
7
|
+
裁决必须同时记录三类证据:**视觉形态、标注几何、预期交互**。至少两个信号一致才可定稿;冲突时以截图中的可交互控件和内容承载方式优先,层级名称只作参考。
|
|
8
|
+
|
|
9
|
+
## 特别约束
|
|
10
|
+
|
|
11
|
+
- **展示值 vs 输入值**:大号货币符号 + 数字 + 右侧编辑/箭头控件,应判为 `input`;用 `Input` 承载数值,货币符号独立为 Text 前缀。输入框高度读标注或按字号行高推导,不得写死整卡高度。
|
|
12
|
+
- **单一消费**:一个源文本只能被一个语义块消费。列表标题、按钮文案、主金额、辅助文案不得重复渲染;发现重影时先检查语义块重叠消费,再调样式。
|
|
13
|
+
- **分段壳只消费一次**:顶部小高度壳若几何包含两个及以上 Tab 文本,按 segmented control 处理,不再同时作为顶部装饰背景。控件高度用「文字上边距 × 2 + 文字高度」推导,行与子项显式垂直居中;选中/未选中底色优先读壳和标注,缺失时按标注缺陷降级。已导出且像素身份可信的 Tab 背景切片作视觉层,其内置文字不得再显示一遍;保留透明 Text 只作为无障碍与点击状态层。
|
|
14
|
+
- **文字底壳跟随标注**:文字外缘附近的小型纯色 shape 是该文本的背景壳;宽度、高度、圆角和颜色必须一起迁移到包裹容器,不能只还原文字。大面板、列表壳和顶部整卡壳不得因局部包围关系被误判为文字底壳。
|
|
15
|
+
- **几何顺序**:顶层语义块按标注 y 插入,不按脚本段落顺序输出。同行判定必须消费原始标注元素;主金额合成片段不得复用为组标签;列表/重复媒体带内的分隔线留在项模板里,不提升为顶层 divider。
|
|
16
|
+
- **重复控件归模板**:同一右缘、同尺寸、等距重复的小控件是列表/重复行模板的一部分;必须由 ListLayout/重复项模板承载,禁止同时作为孤立媒体块再次渲染。副金额、按钮、表单值等已消费文本也必须从列表标题候选中互斥排除。
|
|
17
|
+
- **间距只计一次**:相邻语义块的标注间距不能同时塞进上一块底部和下一块顶部。列表标题、列表项和查看更多必须注册语义边界;列表行内边距由「标注行高 - 内容高」推导,ListLayout 行间距显式归零。
|
|
18
|
+
- **缺失容器高度用子元素推导**:面板标注缺 height 时,取已消费子元素最大底沿加面板 padding 作为语义底沿;后续按钮、标题和列表间距按最近底沿计算,不用段落级「上一文本」跳过中间媒体。
|
|
19
|
+
- **高度由内容撑开**:Root/普通容器用 `auto`;Input、Image、Icon、Divider 和单行 Text 等叶子控件允许从标注读显式高度。Text 高度来自标注 rect,不改 `lineHeight`;纵向节奏仍由容器 padding 控制。
|
|
20
|
+
- **媒体缺陷降级有序**:紧凑媒体缺导出切图时先消费上游 `cropSlice`(从原画板局部裁切的证据),失败才用 `http://iph.href.lu/{w}x{h}?text=...` 占位图;不要用纯色块伪装成图片。弥散、光晕、噪点和不规则纹理属于位图证据,不能用纯色或规则 CSS 渐变近似。宽幅/大面积背景是特例:没有可靠导出切片时禁止画板裁切(会带上前景文字),用标注近似纯色兜底并标记 `background-asset-unavailable` 留给人工补图;训练只局部遮罩该背景矩形,文本、组件和其余像素继续考核。独立细线必须映射 `Divider` 并保持逻辑 `1px`;ListLayout 必须显式声明 `row-spacing=0` 与 `edge-insets={0,0,0,0}`,重复项模板继承标注字号/字重/颜色。
|
|
21
|
+
- **面板重叠有界修正**:只有轻微重叠可把面板流式高度截到下一重叠顶边,且不超过 `max(16px, 25% 面板高度)`;列表等完整业务容器不裁剪。所有 `Text` 保持不可压缩,对齐决定辅助 padding 只落在必要一侧。
|
|
22
|
+
- **垂直节奏用 padding 不用 lineHeight**:渲染器文字行高 = `fontSize × 1.5`(实测;标注给了 lineHeight 就直接设,见 ui_fidelity_rules.md §15),设计稿 `rect.height` 比率在 1.27-1.50 间波动;控制元素垂直位置时优先通过容器 `paddingTop/paddingBottom` 调整,**不设 `lineHeight`**——设 lineHeight 会触发 flex 重新分配空间,简单卡片内容会被拉伸到整个卡片高度。区块间距从 `semanticTopGap(y)` 推导(= 当前块 y - 上一个语义块 bottom),确保渲染内容带 y 位置与设计稿对齐。列表项行内 padding 由「标注行高 - 内容高」÷2 推导,不用固定 token 兜底。
|
|
23
|
+
- **裁决留痕**:语义结论写进训练报告或 lessons,不写入 schema 的非 DSL 字段。
|
|
@@ -46,11 +46,12 @@ schema 引用:/slices/8.icon_通用_金额隐藏@3x.png
|
|
|
46
46
|
| `展开.png` / `icon (2).png` | `iconCollapse@3x.png` |
|
|
47
47
|
| `成都农商银行logo-26` | `logoBank@3x.png` |
|
|
48
48
|
|
|
49
|
-
- **禁用名**:中文/非 ASCII
|
|
49
|
+
- **禁用名**:中文/非 ASCII、空格、**URL 保留字符(`# ? % & +`)**、`编组 N` / `矩形 N` / `形状` / `位图`、导出目录前缀(`8.icon_…`、`99.切图_…`、`4.信息展示_…`)、副本后缀(`xxx (2)`、`xxx_alt2`、`xxx_副本`)、裸通用名(`icon.png`、`image1.png`、`bg.png`)、哈希/纯数字名。lint `r59-slice-semantic-name` 告警;**含空格按 R31、含保留字符按 R31b 报 ERROR**。
|
|
50
|
+
- **禁用名落盘前必须消毒**:`lib/slice_dedupe.mjs → sanitizeSliceFileName()` 是唯一入口(`crop_slices.mjs` 已内置),引用写**消毒后**的名字。**不要靠 URL 转义(`%23`)绕过**:字面 `%` 会二次编码成 `%2523`,且 `sliceNameFromRef()` 先 decode、`%23` 解码回 `#` 仍被 `r31b` 拦(详见 `generation_pitfalls.md §9-R31`)。
|
|
50
51
|
- **同一张图全项目只有一个名字**:多卡共用时引用同一语义名,**不允许一卡一名**(`assetAllocRadar_arrow_right.png` 与 `chevron_right.png` 同图两名 = 违规,见 §3.1 存量证据)。
|
|
51
52
|
- **改名要成套做**:改 `src/slices/` 文件名 + 用 `lib/slice_dedupe.mjs → rewriteSliceRefs` 改写**所有**卡片 schema/data 里的引用 + 同步 `src/slices/manifest.json`(已上传过的图改名后按内容 sha256 仍复用原线上地址,见 §3.2)。**禁止**新旧两份并存。
|
|
52
53
|
- **上游保留原名作证据**:`UI/<稿>/<画板>/slices/` 与 `spec.slices` 保持导出原名不动(来源可追溯),语义化只发生在 ② 汇聚落点这一层;元素名 ↔ 导出名匹配仍走 `resolveSliceExport`(§5)。
|
|
53
|
-
-
|
|
54
|
+
- **机械产线现状(2026-09-21 更新)**:`sliceRefPath()` 产出前已统一过 `sanitizeSliceFileName()`,即**消毒后的导出路径扁平名**(`8.icon/通用/警示-#999@3x.png` → `8.icon_通用_警示-999@3x.png`)——与落盘名严格同源,`r31b`(保留字符)/ `r31c`(磁盘无此文件)双 ERROR 由此可机械验证;**旧实现的 `%23` / `%3F` 编码已废弃**(生成器不得产出被自己门禁拦下的引用)。但它**仍是导出路径扁平名**(过渡形态):`r59` 会告警。**Agent 生成时优先在 IR 里直接写语义名**(`pi.value: "/slices/iconArrowRight@3x.png"`,并把该名字的切图放进要素包 `slices/`),或落地后按上一条成套改名;交付前 `r59` 告警应清零。
|
|
54
55
|
- **存量清理**:历史落地的非语义名 / 同图多名用 `scripts/slices_normalize.mjs` 成套归一化(默认 dry-run 出报告,`--write` 落盘;改名 + 合并副本 + 改写全部卡片引用 + 同步 manifest 一次做完)。脚本会同时报告**悬空引用**(卡片引用了 `src/slices/` 里不存在的文件)——这类问题改名救不了,需回设计稿重新导出或换图。
|
|
55
56
|
|
|
56
57
|
## 2. schema 里的引用格式:`/slices/***.**`
|
|
@@ -139,12 +140,11 @@ sketch-spec-to-card 产出的 spec 中,**slice 元素的 `name`(图层路径
|
|
|
139
140
|
| 职责 | 文件 / 函数 |
|
|
140
141
|
|---|---|
|
|
141
142
|
| 归一化 + 元素名↔导出匹配 + `/slices/` 路径生成 | `scripts/lib/slicePath.mjs`(`stripScale` / `resolveSliceExport` / `sliceFlatName` / `sliceRefPath`) |
|
|
142
|
-
|
|
|
143
|
-
| v3 变体(CLI 直组装)生成 Image 时引用 `/slices/` | `scripts/spec_to_schema_v3.mjs`(`imageFromSlice(sliceEl, sem.slicesExports)`,由 `extractSemantics` 透传 `spec.slices`) |
|
|
143
|
+
| v3 变体(CLI 直组装)生成 Image 时引用 `/slices/` | `scripts/spec_to_schema_v3.mjs`(`imageFromSlice(sliceEl, sem.slicesExports)`,由 `extractSemantics` 透传 `spec.slices`;geo 变体已随 geo 生成器删除) |
|
|
144
144
|
| 拷贝被引用切图到 `src/slices` | `scripts/generate_cards.mjs`(`collectSlicesToSrcSlices`,仅 referenced) |
|
|
145
145
|
| 本地 `/slices/*` 静态服务(自包含) | `scripts/render_card.mjs`(临时 HTTP 中间件,直接读 `slicesDir`) |
|
|
146
146
|
| ③ 阶段:上传切图 + 写映射 + 改写引用 | `skills/finmall-card-sync/scripts/slice_upload.mjs`(`uploadSlices`);HTTP 端点 `server/core/sliceUpload.js`(`POST /__slice-upload`);编辑器入口 `card-editor/App.vue`(`uploadCardSlices`);MCP `card_slice_upload` |
|
|
147
|
-
| 语义名判定 + 内容 sha256 去重 + 引用改写(共享实现) | `scripts/lib/slice_dedupe.mjs`(`checkSliceName` / `buildContentIndex` / `sha256File` / `duplicatePeers` / `rewriteSliceRefs` / `collectSliceRefStrings
|
|
147
|
+
| 语义名判定 + 消毒 + 内容 sha256 去重 + 引用改写(共享实现) | `scripts/lib/slice_dedupe.mjs`(`checkSliceName` / **`sanitizeSliceFileName`** / **`sliceNameMatches`** / **`findUrlReservedChars`** / **`sliceNameFromRef`** / **`isRemoteSliceRef`** / **`buildSliceFileIndex`** / **`resolveSliceRefOnDisk`** / `buildContentIndex` / `sha256File` / `duplicatePeers` / `rewriteSliceRefs` / `collectSliceRefStrings`)—— lint / preflight / crop_slices / slicePath / expand_ir / generate_cards 全部共用,**改一处即全链路生效** |
|
|
148
148
|
| 像素级内容判重(裁切图 ≡ 导出资产 / 已重命名共用切图) | `scripts/lib/slice_pixels.mjs`(`pixelsMatch` / `findPixelTwin` / `cropPng` / `pngPixelSha` / `readPng`) |
|
|
149
149
|
| 缺口审计:指纹分组 + 像素复用 + manifest 别名(`renamedFrom`/`mergedFrom`) | `scripts/spec_audit.mjs`(`groupByFingerprint` / `regionTwin` / `buildAliasMap`;verdict `content-twin`、crops[].reuseOf/alsoRects) |
|
|
150
150
|
| 裁切落盘前判重(批内 + reuse-dir + 工程 src/slices) | `scripts/crop_slices.mjs`(输出 `reused[]`,命中不写盘) |
|
|
@@ -154,7 +154,7 @@ sketch-spec-to-card 产出的 spec 中,**slice 元素的 `name`(图层路径
|
|
|
154
154
|
| 存量切图归一化(语义化改名 + 同内容合并 + 引用/manifest 成套改写;**默认 dry-run**) | `scripts/slices_normalize.mjs`(`--write` 落盘;`--dedupe` / `--prune-unreferenced` / `--rename old=new` / `--map file.json` / `--no-legacy` / `--json`;内置 `LEGACY_RENAMES` = 2026-09-17 逐张核图确认的存量映射) |
|
|
155
155
|
| 上传阶段同内容复用线上地址(`reusedFrom`) | `skills/finmall-card-sync/scripts/slice_upload.mjs`(`shaToUploaded` 索引 + 批次内延后复用) |
|
|
156
156
|
|
|
157
|
-
>
|
|
157
|
+
> v3 生成器引用切片必须走 `sliceRefPath()`(产出 `/slices/<flat>`),与其 `collectSlicesToSrcSlices` 拷贝目标(扁平 `<flat>`)严格对应,禁止各自拼字符串。
|
|
158
158
|
|
|
159
159
|
## 7. 常见错误
|
|
160
160
|
|
|
@@ -376,9 +376,6 @@ node skills/dsl-card-generator/scripts/render_card.mjs --id <id> --width 375 --o
|
|
|
376
376
|
|
|
377
377
|
**纪律**:spec 的 textAlign 是权威信息,任何语义块提取/渲染都要**原样传递 align**;主金额必须是金额(不是任何大字号数字)。
|
|
378
378
|
|
|
379
|
-
## 8. 闭环验证(同 §6)
|
|
380
|
-
|
|
381
|
-
|
|
382
379
|
## 8. 环形倒计时(Canvas 自绘,v3 自动识别)
|
|
383
380
|
|
|
384
381
|
**识别信号**(`extractSemantics` 的 `countdown`,纯几何/格式,泛化):
|
|
@@ -473,7 +470,7 @@ node skills/dsl-card-generator/scripts/render_card.mjs --id <id> --width 375 --o
|
|
|
473
470
|
**配合关系**:拓扑生成不应只靠下游自算——上游(sketch-spec-to-card)在解析阶段就按**几何 y 聚类**产出每卡的视觉分段,下游直接消费,两端同一套哲学(设计稿节点结构不作拓扑依据)。
|
|
474
471
|
|
|
475
472
|
**上游产出(parse_meaxure.py::detect_segments + build_prompt.py)**:
|
|
476
|
-
- `spec.json` 每卡新增 `segments[]`:`[{y, height,
|
|
473
|
+
- `spec.json` 每卡新增 `segments[]`:`[{y, height, hint, items}]`(卡内相对坐标)。hint ∈ decor-bg(装饰背景层,强制独立段)/ button / content。段间距不落盘(旧字段 `gap_top` 已删——首段恒等于 y、后续段由相邻 y/height 推导,见 `skills/shared/spec-contract.md` removed 档)。
|
|
477
474
|
- 排除:卡片容器本身(symbol 尺寸≈整卡)、卡外装饰(全屏背景/蒙版,卡 y 轴交集<50%)、元素 gap 阈值=卡高 2%。
|
|
478
475
|
- `prompt.md` §3 新增「视觉分段(几何推断,供拓扑生成)」段,并写明「设计稿 group/编组结构不作卡片拓扑依据」。
|
|
479
476
|
|
|
@@ -607,3 +604,35 @@ Text 的 `value`(`itemValueFrom:"1"`)绑:
|
|
|
607
604
|
- **转义**:写进 schema `itemValue` 时若含正则须双反斜杠(`\d`→`\\d`);本表达式无正则,直接可用。
|
|
608
605
|
- **同步 data.json**:新增数组字段后跑 `derive_data_json.py --overwrite` + validate。
|
|
609
606
|
- **分隔符保留**:join 参数必须用设计稿原文分隔符(含前后空格 `" · "`),否则视觉与设计稿不符。
|
|
607
|
+
|
|
608
|
+
## 20. 语义决策层(Phase 2:启发式提名,AI 终审)
|
|
609
|
+
|
|
610
|
+
**定位**:v3 的 extractSemantics 用几何启发式从 spec 提取语义决策(倒计时识别、表单行占位、动态参数绑定、画板类型等)。当生成结果语义层出错(误识别/漏识别/绑定错)时,**优先走决策回路改裁决,不改脚本**——脚本只在出现全新布局模式时才动。
|
|
611
|
+
|
|
612
|
+
**机制**(`spec_to_schema_v3.mjs`,2026-09-21 起):
|
|
613
|
+
|
|
614
|
+
```bash
|
|
615
|
+
# 1) 提名:导出语义决策视图(--dry 只审不改,不生成卡片)
|
|
616
|
+
node scripts/spec_to_schema_v3.mjs --spec <spec.json> --id <卡id> --sem-dump .cache/sem/<卡id>.sem.json --dry
|
|
617
|
+
|
|
618
|
+
# 2) 终审:AI 审阅 decisions 字段,纠错后写 decisions 文件(只含要改的字段)
|
|
619
|
+
node scripts/spec_to_schema_v3.mjs --spec <spec.json> --id <卡id> --decisions .cache/sem/<卡id>.decisions.json
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
**decisions 白名单**(其余字段一律忽略,几何证据保持脚本所有):
|
|
623
|
+
|
|
624
|
+
| 字段 | 类型 | 用途 |
|
|
625
|
+
|---|---|---|
|
|
626
|
+
| `artboardType` | string | 画板类型改判(影响骨架选择) |
|
|
627
|
+
| `bindings` | object | 动态参数绑定改判(entities/literalBind/multiToken 整体替换;姓名/卡号等该绑定没绑的在此补) |
|
|
628
|
+
| `countdown` | object\|null | 倒计时改判:null 禁用;改 numberText/unitText/strokeWidth/配色。**consumed 由脚本按 numberText+几何重链**,AI 只给纯数据 |
|
|
629
|
+
| `cardFill` / `cardBackgroundGradient` | string\|object | 卡底色/渐变改判(注意:--design-image 采样会在其之上二次校正) |
|
|
630
|
+
| `disable` | string[] | 禁用提名字段(listTitle/viewMore/collapseBtn/countdown/topDecoration/midCluster → 置 null),如「误判出列表标题」→ `{"disable":["listTitle"]}` |
|
|
631
|
+
|
|
632
|
+
**铁律**:sem 内元素引用(sourceEl)参与同一性排除(`shell.texts.includes(aux.sourceEl)` 等),**decisions 文件里禁止出现任何 sourceEl / 元素对象**——只有上表白名单字段会被消费,其余静默忽略。`semDecisionsView()` 导出的视图已全部剥元素引用,可直接 JSON 回写。
|
|
633
|
+
|
|
634
|
+
**CLI 附带优化**:`--sem-dump`/`--decisions` 路径下语义提取全程只跑一次(CLI 提取 → 裁决 → semOverride 注入 generate),修复原先 CLI+generate 双跑 extractSemantics 的浪费(该函数 4000+ 行,双跑 ≈ 每卡多付一次全量几何分析)。
|
|
635
|
+
|
|
636
|
+
**文件结构(2026-09-21 拆分,行为不变)**:v3 已拆为三层——`scripts/lib/v3_shared.mjs`(共享 helper/常量,1116 行)、`scripts/lib/v3_extract.mjs`(语义识别 + 决策回路,4108 行)、`scripts/lib/v3_assemble.mjs`(schema 机械组装,5443 行);`scripts/spec_to_schema_v3.mjs` 只剩 CLI 编排壳(96 行)并 re-export `generate/extractSemantics/semDecisionsView/applySemDecisions`(外部消费方 import 路径不变)。组装器与识别器分别演进:**新布局模式只改 extract 层;组件映射/样式组装问题只改 assemble 层**。`check_magic_values.mjs` 的 TARGETS 已覆盖全部四文件。
|
|
637
|
+
|
|
638
|
+
**冻结启发式增长(2026-09-21 起,硬性)**:v3_extract.mjs 的语义启发式规则数冻结——新语义模式**不再**往识别层加规则。处置路径:① sem 决策回路(--sem-dump 审阅 → --decisions 终审,本节上文);② decisions 白名单覆盖不了的(新增语义块、新布局拓扑)→ AI 视觉主判走 Workflow A 整树 IR;③ 仅当同一规则缺陷影响 ≥3 张存量卡还原度时才可改 extract 层,改后必跑 `scripts/v3_regression.mjs`(Pass@1 + 基线漂移门禁;金标清单登记在仓库 `training/pass1/cases.json`,skill 脚本不持有业务 UI 路径;漂移属预期改进才 --update-baseline 并随改动提交)。历史教训档案 `training/lessons/lessons.jsonl`(116 条)已标注 `solidified` 字段:lint=18(固化进校验规则)/ script=24(固化进脚本行为)/ doc=70(固化进 references)/ pending=3 / superseded=1——该文件属 P2 维护档案,**生成上下文不加载**。
|
|
@@ -80,7 +80,7 @@ IoU(交并比)是位置评分核心(权重 40%),同时受位置和尺
|
|
|
80
80
|
|
|
81
81
|
## 9. 背景与色彩兜底
|
|
82
82
|
|
|
83
|
-
-
|
|
83
|
+
- 卡片背景**不可完全透明**(必须有可见底色;**半透明允许**,cardFill 含 alpha 时原样还原):从 spec `cardFill` 取;缺失时从截图采样主色调(实色);都不可得时用 `#FFFFFF`。
|
|
84
84
|
- 缺少切图素材的背景区域:用**纯色近似**替代(取截图平均色),不计入还原度考核。
|
|
85
85
|
- **禁止从画板裁切背景**(会带入前层叠加文本),除非该区域确认无叠加内容。
|
|
86
86
|
|
|
@@ -129,3 +129,15 @@ IoU(交并比)是位置评分核心(权重 40%),同时受位置和尺
|
|
|
129
129
|
文本自身几何属性,两者不冲突。容器间距仍用 margin/padding,不用 lineHeight。
|
|
130
130
|
- 混排基线陷阱:不同字号 Text 同行用 `alignItems:baseline` 时,小字号行盒会被基线对齐
|
|
131
131
|
推出大字号行盒之外(Row 高度 +2px);标注同顶同高时改用 `alignItems:center`。
|
|
132
|
+
|
|
133
|
+
## 16. 混排文本口径(MultiText,train-proven:seq3-l05)
|
|
134
|
+
|
|
135
|
+
**问题**:同一视觉文本被标注拆成多个元素(加粗前缀 + 中段 + 后缀、金额 + 单位),渲染端按混排/金额铁律合并或拆分后,**期望侧组件计数与渲染端口径不一致** → 误报 component-count-mismatch。
|
|
136
|
+
|
|
137
|
+
**口径纪律(两端必须同源)**:
|
|
138
|
+
|
|
139
|
+
1. **一个视觉文本 = 一个语义块**(Workflow A 裁决):同段多 style 用 **MultiText itemList** 承载(每段 value/color/fontSize/fontWeight),不拆成多个顶层 Text 节点;金额 + 单位按数字语义提取铁律(§0-14)走「数字绑定 + 单位 Text/后缀」,同属一个语义块。
|
|
140
|
+
2. **期望侧(trainer 要素断言 / 组件计数)与渲染端同口径**:要么两端都按「合并后一条」计,要么都按渲染节点计——切不可渲染端合并、期望侧逐元素计。
|
|
141
|
+
3. position / duplication 维度沿用既有去重口径,不因混排重复计数。
|
|
142
|
+
|
|
143
|
+
**渲染范式(MultiText)**:itemList 每项独立 properties(value/color/fontSize/fontWeight/fontStyle/textDecoration/backgroundColor/tag);同一行多段不同字体;点击 event.__do__ 冒泡去重(component_specs.md 有完整 itemId 表)。
|
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
/** 跨平台 Python 探测:DSL_CARD_PYTHON > WORKBUDDY_PYTHON > python3 > python */
|
|
5
|
-
function findPython() {
|
|
6
|
-
if (process.env.DSL_CARD_PYTHON) return process.env.DSL_CARD_PYTHON;
|
|
7
|
-
if (process.env.WORKBUDDY_PYTHON) return process.env.WORKBUDDY_PYTHON;
|
|
8
|
-
for (const bin of ['python3', 'python']) {
|
|
9
|
-
try {
|
|
10
|
-
if (spawnSync(bin, ['--version'], { stdio: 'ignore' }).status === 0) return bin;
|
|
11
|
-
} catch {}
|
|
12
|
-
}
|
|
13
|
-
return 'python3';
|
|
14
|
-
}
|
|
2
|
+
// Python 解释器解析集中在 lib/python.mjs(单一来源;此处取候选列表以获得真实回退链)。
|
|
3
|
+
import { pythonCandidates } from './lib/python.mjs'
|
|
15
4
|
/**
|
|
16
5
|
* add_component.mjs — 向已有卡片的某个容器节点插入一个「合法最小组件」,避免手写样板。
|
|
17
6
|
*
|
|
@@ -95,7 +84,9 @@ const setMsg = r.setProps.length ? ` set[${r.setProps.join(', ')}]` : ''
|
|
|
95
84
|
console.log(`[ok] 已在 ${r.parentId}(${r.parentClassName}) 插入 ${r.className} -> ${r.id}${setMsg}`)
|
|
96
85
|
|
|
97
86
|
// 复校验
|
|
98
|
-
|
|
87
|
+
// 用候选列表而非「最优单个」:首选解释器若跑不通(假别名/包装器),还能回退到下一个,
|
|
88
|
+
// 否则这里的 validate 会静默跳过(历史上正是这个原因让复校验形同不存在)。
|
|
89
|
+
const PY_CANDIDATES = pythonCandidates()
|
|
99
90
|
let validated = false
|
|
100
91
|
for (const py of PY_CANDIDATES) {
|
|
101
92
|
try {
|