cardbuddy 0.14.2 → 0.15.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.
Files changed (68) hide show
  1. package/dist/cli.js +0 -0
  2. package/dist/preview-editor/assets/{index-BN79pTkQ.js → index-CIIfMlwI.js} +43 -40
  3. package/dist/preview-editor/assets/index-DQn0zQBP.css +1 -0
  4. package/dist/preview-editor/index.html +2 -2
  5. package/dist/standalone.js +3 -0
  6. package/dist/vendored/mcp/client.mjs +8 -1
  7. package/dist/vendored/server/core/ai-chat.js +1521 -124
  8. package/dist/vendored/server/core/browser.js +12 -3
  9. package/dist/vendored/server/core/cardFiles.js +3 -0
  10. package/dist/vendored/server/core/cardHistory.js +1 -0
  11. package/dist/vendored/server/core/cardImportMeta.js +7 -6
  12. package/dist/vendored/server/core/cardOpenFolder.js +1 -0
  13. package/dist/vendored/server/core/cardSave.js +1 -0
  14. package/dist/vendored/server/core/cardSync.js +3 -0
  15. package/dist/vendored/server/core/codeBridge.js +5 -1
  16. package/dist/vendored/server/core/logStream.js +1 -0
  17. package/dist/vendored/server/core/sliceStatic.js +24 -4
  18. package/dist/vendored/server/core/sliceUpload.js +1 -0
  19. package/dist/vendored/server/core/turnJournal.js +6 -0
  20. package/dist/vendored/skills/card-skill-trainer/SKILL.md +196 -195
  21. package/dist/vendored/skills/dsl-card-ai/SKILL.md +1 -1
  22. package/dist/vendored/skills/dsl-card-generator/SKILL.md +142 -380
  23. package/dist/vendored/skills/dsl-card-generator/references/generation_pitfalls.md +65 -18
  24. package/dist/vendored/skills/dsl-card-generator/references/ir-format.md +116 -2
  25. package/dist/vendored/skills/dsl-card-generator/references/iteration_protocol.md +58 -0
  26. package/dist/vendored/skills/dsl-card-generator/references/previewer_contract.md +1 -1
  27. package/dist/vendored/skills/dsl-card-generator/references/quickref.md +15 -7
  28. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/title-underline-overlay.json +59 -11
  29. package/dist/vendored/skills/dsl-card-generator/references/recipes.md +14 -0
  30. package/dist/vendored/skills/dsl-card-generator/references/rules.md +23 -4
  31. package/dist/vendored/skills/dsl-card-generator/references/scripts_reference.md +51 -0
  32. package/dist/vendored/skills/dsl-card-generator/references/semantic_adjudication.md +23 -0
  33. package/dist/vendored/skills/dsl-card-generator/references/slices_convention.md +4 -3
  34. package/dist/vendored/skills/dsl-card-generator/references/spec_to_schema_patterns.md +32 -0
  35. package/dist/vendored/skills/dsl-card-generator/references/ui_fidelity_rules.md +13 -1
  36. package/dist/vendored/skills/dsl-card-generator/scripts/autofix_schema.py +45 -2
  37. package/dist/vendored/skills/dsl-card-generator/scripts/check_fidelity.mjs +116 -10
  38. package/dist/vendored/skills/dsl-card-generator/scripts/check_magic_values.mjs +6 -1
  39. package/dist/vendored/skills/dsl-card-generator/scripts/crop_slices.mjs +92 -6
  40. package/dist/vendored/skills/dsl-card-generator/scripts/expand_ir.mjs +227 -23
  41. package/dist/vendored/skills/dsl-card-generator/scripts/gate.mjs +144 -0
  42. package/dist/vendored/skills/dsl-card-generator/scripts/generate_cards.mjs +12 -8
  43. package/dist/vendored/skills/dsl-card-generator/scripts/ir_merge.mjs +288 -0
  44. package/dist/vendored/skills/dsl-card-generator/scripts/ir_restore.mjs +175 -0
  45. package/dist/vendored/skills/dsl-card-generator/scripts/iterate.mjs +644 -42
  46. package/dist/vendored/skills/dsl-card-generator/scripts/lib/geometry_audit.mjs +131 -0
  47. package/dist/vendored/skills/dsl-card-generator/scripts/lib/layout_rules.mjs +160 -0
  48. package/dist/vendored/skills/dsl-card-generator/scripts/lib/slicePath.mjs +17 -5
  49. package/dist/vendored/skills/dsl-card-generator/scripts/lib/slice_dedupe.mjs +102 -0
  50. package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_assemble.mjs +5443 -0
  51. package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_extract.mjs +4108 -0
  52. package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_shared.mjs +1116 -0
  53. package/dist/vendored/skills/dsl-card-generator/scripts/lint_card.mjs +214 -4
  54. package/dist/vendored/skills/dsl-card-generator/scripts/preflight.mjs +48 -2
  55. package/dist/vendored/skills/dsl-card-generator/scripts/render_card.mjs +91 -43
  56. package/dist/vendored/skills/dsl-card-generator/scripts/route_input.mjs +4 -3
  57. package/dist/vendored/skills/dsl-card-generator/scripts/spec_audit.mjs +150 -38
  58. package/dist/vendored/skills/dsl-card-generator/scripts/spec_to_schema_v3.mjs +43 -10499
  59. package/dist/vendored/skills/dsl-card-generator/scripts/v3_regression.mjs +186 -0
  60. package/dist/vendored/skills/sketch-spec-to-card/SKILL.md +18 -12
  61. package/dist/vendored/skills/sketch-spec-to-card/references/spec-schema.md +1 -1
  62. package/dist/vendored/skills/sketch-spec-to-card/scripts/build_prompt.py +47 -1
  63. package/dist/vendored/skills/sketch-spec-to-card/scripts/parse_meaxure.py +460 -49
  64. package/dist/vendored/skills/sketch-spec-to-card/scripts/png_utils.py +51 -0
  65. package/dist/vendored/skills/sketch-spec-to-card/scripts/test_text_only_artboard.py +157 -0
  66. package/dist/vendored/skills/sketch-spec-to-card/scripts/vision.py +24 -0
  67. package/package.json +1 -1
  68. package/dist/preview-editor/assets/index-cbTCHp8J.css +0 -1
@@ -1,191 +1,117 @@
1
1
  ---
2
2
  name: dsl-card-generator
3
- description: 生成、编辑并以 AI 辅助 FinMall/YNet 低代码卡片 schema.json。掌握每个可渲染组件的属性、事件、插槽、样式默认值、$ 表达式绑定引擎、作用域插槽访问(表达式用 $field/$$/_index;事件脚本用 this.state.<field> 直读 item 字段,禁 $$)、v-show 指令、globalSet/postMessage 数据透传、alertCard 弹窗、Canvas 自绘标题头、金额格式化、列表展开/收起,以及图片到 schema 的管线。在创建卡片 schema、修复 DSL 结构、将平台变量映射到 UI、逆向工程编辑器 DSL、编辑已有卡片、从图片/设计稿生成卡片,或从 schema.json 解释建卡逻辑时使用。也由以下触发词触发:图片生成卡片、读图建卡、编辑卡片、改卡片、AI 快速开发卡片。内置提速工具链:route_input 输入路由(MeaXure 标注导出自动先转 sketch-spec-to-card)、scaffold_card 脚手架(秒出合法骨架)、add_component 插组件、autofix_schema 机械修复 1 轮清零、build_card_index 意图路由选近邻、recipe_snippets 范式片段库、quickref 速查卡——常规任务只看 quickref,避免长思考与手写样板。另有 MCP 等价工具(P5,无需 dev server):card_route_input 判建卡输入形态、card_scaffold 建骨架、card_add_component 插组件并自带离线硬校验。输入为 Sketch MeaXure 标注导出目录时,先经 sketch-spec-to-card 产出建卡要素再回本 skill 生成(见 §8 Workflow 0 输入路由)。
3
+ description: 生成、编辑 FinMall/YNet 低代码卡片 schema.json + data.json(15 个可渲染组件、$ 表达式绑定、v-show、globalSet、alertCard、Canvas 自绘)。适用:从图片/设计稿/MeaXure 导出生成卡片、编辑已有卡片、逆向解释 schema。触发词:图片生成卡片、读图建卡、编辑卡片、改卡片。核心流程:route_input 判输入形态 →(MeaXure 导出先经 sketch-spec-to-card 前处理)→ 整树 IR 一次下发建卡 → validate+lint 双门禁 → iterate 视觉闭环(改卡用 add_component/ir_patch)。常规任务只读 references/quickref.md,避免加载全部 references。
4
4
  ---
5
5
 
6
6
  # DSL 卡片 Schema 生成器(DSL Card Schema Generator)
7
7
 
8
8
  为 FinMall/YNet 卡片设计器生成合规的 schema.json + data.json。本 skill 把平台当作一套**带版本号的 JSON DSL**:卡片 = 数据字典 + 组件树 + 属性元数据 + 样式分组 + 指令 + 插槽 + 事件动作。一切结构都有据可查,不臆造。
9
9
 
10
- > **图片/AI 生成卡片 = 你(Agent)直接读图产出 schema,不接外部接口。** 你本身就是「图片→卡片」的执行者:读取截图/设计稿(多模态),按本 skill 的组件/样式/表达式/事件规则直接写出合规 schema.json + data.json,再用内置校验器自查。工程里没有图片生成 API,也不需要。详见下方 Workflow A 与 references/image_to_card.md。
10
+ > **图片/AI 生成卡片 = 你(Agent)直接读图产出 schema,不接外部接口**:读截图/设计稿(多模态),按组件/样式/表达式/事件规则直接写出合规 schema.json + data.json,再用内置校验器自查。详见 §8 Workflow A 与 references/image_to_card.md。
11
11
 
12
- > **自包含 / 可移植**:本 skill 不复制、不维护第二套预览组件;离线渲染使用 `vendor/preview/` 里的**构建产物**(由宿主工程根目录 `npm run build:skill-preview` 把 `preview/` 编译输出到该目录,同时拷贝 `meta/*.json`)。`scripts/render_card.mjs` 运行时只依赖 Node 内置模块 + 系统 Chrome(CDP),不再依赖 vite/vue/playwright,也不再向上引用宿主工程的 `preview/`、`vite.config.mjs`、`server/core/cardFiles.js`。离线渲染要求 Node >= 22(内置 `WebSocket`)与已安装的系统 Chrome。宿主卡片与切图目录默认取执行时的 cwd(`<cwd>/src/cards`、`<cwd>/src/slices`),可用 `DSL_CARD_PROJECT_ROOT` / `DSL_CARD_CARDS_DIR` / `DSL_CARD_SLICES_DIR` / `DSL_CARD_META_DIR` / `DSL_CARD_PREVIEW_DIR` / `DSL_CARD_BASELINES_DIR` 覆盖。路径单一来源见 `scripts/lib/paths.mjs`。
12
+ > **自包含 / 可移植**:离线渲染用 `vendor/preview/` 构建产物(宿主 `npm run build:skill-preview` 产出)+ 系统 Chrome(CDP),只依赖 Node 内置模块(>=22)。卡片/切图目录默认取 cwd,可用 `DSL_CARD_PROJECT_ROOT` / `DSL_CARD_CARDS_DIR` / `DSL_CARD_SLICES_DIR` / `DSL_CARD_META_DIR` / `DSL_CARD_PREVIEW_DIR` / `DSL_CARD_BASELINES_DIR` 覆盖(单一来源 `scripts/lib/paths.mjs`)。
13
13
 
14
14
  ## 0. 铁律(项目最高纪律)
15
15
 
16
- **铁律集中陈述见 `references/rules.md`**(十三节:证据分级 / 不污染 DSL / DSL 契约 / 运行时作用域与 Input 回写 / 速查优先 / 验证三层自洽 / 视觉真相优先 / 远端同步前置 / 组件级硬约束 / 从脚手架起步 / 标注可信度分层 / 卡片目录命名 / **画板聚焦与卡片生成单位**)。
17
-
18
- 本节只强调与生成直接相关的最易违反的条目:
19
-
20
- 0. **视觉优先原则(最高优先级)**:当模型具备图像识别/多模态能力时,**必须优先调用模型的视觉分析能力**来理解设计稿的空间布局、视觉层次和间距关系,而非仅依赖 spec.json 的文本标注做机械坐标映射。具体做法:
21
- - **同时读取 `card-N.png` 截图和 `spec.json` 标注**——截图回答「它长什么样」,标注回答「精确数值是多少」,两者交叉验证。
22
- - **模型视觉判断 > 脚本启发式**:当脚本输出的布局结果与模型从截图中观察到的视觉结构不一致时,以模型的视觉判断为准,修正脚本输出。
23
- - **补全缺失推断**:标注中没有的间距/高度/宽度/圆角,模型应从截图中目视推断(如垂直居中推算高度、水平等分推算宽度、视觉间距推算 padding/gap)。
24
- - **识别脚本盲区**:脚本无法理解「这块区域视觉上是一个整体」「这两个元素左对齐」「这个间距明显比上面大」——模型可以直接看到这些关系并据此调整布局。
25
- - **分层使用**:spec.json 提供精确数值(坐标、字号、颜色、圆角),截图提供视觉真相(相对位置、空间关系、整体结构),模型综合两者产出最优 schema。详见 `references/ui_fidelity_rules.md §8`。
26
-
27
- 1. **绝不臆造**:每个组件/属性/事件/action 必须是 export-proven(导出实证)或 meta-proven(编辑器元数据),详见 `rules.md §一`。
28
- 2. **不污染 DSL**:绝不写 `$schema` / Generator 产物 / `x-*` 字段进 schema;整体替换时未涉及节点逐字节保留,改完用 `diff_card.mjs` 核对,详见 `rules.md §二`。
29
- 3. **根节点不设样式宽度**:Root(view)的宽度由顶层 `view.width` 承载(如 `"375"`),`style.properties` 里**不要**放 `rect` 组(含 width)——冗余且与 view.width 冲突,详见 `rules.md §九`。
30
- 4. **标注可信度分层(属性值读标注,几何看实图)**:圆角 radius / 阴影 shadow / 字体 textStyle / 边框 border / 背景 fill 等**属性值类**直接读 spec 标注,**禁止抄存量卡**(存量卡只用于确认 meta 取值合法性);rect 尺寸 / 元素间距等**几何类**标注是绝对定位、不遵循节点树尺寸逻辑,必须按实图 / 容器边界(shape/group/symbol rect)分析。派生规则:元素间距用 `margin` 不用 `padding`(padding 污染 box 高)、卡片根背景来自 `spec.cards[].cardFill`(卡片自身 fill),**绝不使用画布灰底,也绝不从透明化截图四角/边缘采样背景色**;若 spec 标 `cardFillConflict:true`(标注 fill 与底部 4% 横条采样通道差>6),以 `cardFillSampled` 合成色为准(标注叠透明度导致的偏差,如 #FFFFFF→#F7F7F7)(背景可能非均匀/非固定,聚焦图几何外已透明,见 §九)。详见 `generation_pitfalls.md §9-R1/R8/R9/R10`。
31
- 5. **渲染器默认样式必须显式覆盖 + 运行期数据必须绑定**:渲染器对未显式设置的 Row/Column/ListLayout 默认注入 `padding:2px + gap:4px + justify-content:center`(Column 垂直居中)、ListLayout 默认 `edge-insets {2,2,2,2}`——必须显式归零/指定(详见 §9-R11)。金额/日期等**运行期数据**必须绑定为 inputParams(金额 `type:"number"` + 表达式还原,详见 `amount_formatting.md`),设计稿分割线必须实现(§9-R12/R13/R14)。
32
- 4. **类型+值成对检查**:`borderRadius` 必须配 `borderRadiusType`、`border` 必须配 `isBorder`、`shadow` 必须配 `isShadow`——只写值不写类型开关 = 渲染器静默丢弃(pitfalls §R44,lint `r44-*` 拦截,expand_ir 自动补)。
33
- 6. **卡片背景不透明**5. **卡片背景不透明**:卡片背景**不存在透明**——Root/首层容器必须有 `background.backgroundColor` + `backgroundType:"01"`;取色优先读标注 `cardFill`,标注缺失从聚焦截图中心采样兜底(pitfalls §R43,lint `r43-card-transparent-background` 拦截)。
34
- 8. **`rect.width:"100%"` 必须可压缩**:100% 宽节点保持 `flexShrink:"1"`(默认值),**禁止同节点显式 `flexShrink:"0"`**,否则固定宽同级节点会被挤出父容器/溢出。与关键 Text 的 `flexShrink:"0"` 按 `width` 分流(auto→R17,100%→R22)。**受外宽约束的多子容器(width 非 auto 且 2+ 并列子节点)至少一个子节点 flexShrink:1**(R35)——全部 shrink:0 会导致外容器宽度变化时定宽累加溢出;`width:auto` 内容自适应容器内的固定子元素可保持 shrink:0。详见 `rules.md §九` / `generation_pitfalls.md §9-R22/R35`。
35
-
36
- 9. **Text 高度与 maxLine 必须协调**:`maxLine=0`(允许换行)时**不设 rect.height**(固定高度 + 换行 = scrollHeight > clientHeight → 文本溢出裁切);`maxLine=1`(单行)时保留 spec rect.height 防止意外撑高。判定:spec 文本高度 > fontSize × 1.5 → 多行(maxLine=0 无高度),否则 → 单行(maxLine=1 有高度)。详见 `references/ui_fidelity_rules.md §1`。
37
- 10. **标注 + 图像交叉验证(模型多模态能力)**:生成卡片时必须**同时读取 spec.json 标注和 card-N.png 聚焦截图**——标注提供精确数值(坐标/字号/颜色),图像提供视觉真相(相对位置/留白/层级关系)。两者冲突时标注优先但标记缺陷;标注缺失时从图像推断(如垂直居中推算 Tab 高度 = 文字高度 + 上下间距 × 2)。详见 `references/ui_fidelity_rules.md §8`。
38
- 11. **透明度禁止表达式(铁律 R57)**:移动端渲染不支持 `style.other.opacity` 走表达式(`itemValueFrom:"1"`),预览构建产物更是完全不消费组件级 opacity(截图查不出来)。透明度需随状态变化时走**固定范式**:按用到的透明度取值生成等量**静态变体节点**,统一套一层 Row/Column 容器——**容器宽高 = 被展示节点宽高 + `other.overflow:"hidden"`**,**点击等事件挂在容器上**,变体用**互斥 v-show** 显隐切换实现视觉透明度变化。lint `r57-opacity-expression` ERROR;机械改写 `scripts/apply_opacity_pattern.mjs --id <卡id>`。详见 `recipes.md §17` / `recipe_snippets/opacity-switch.json`(serviceEvaluation 为首个落地卡)。
39
- 12. **非 UI 变更不做视觉验证(铁律)**:视觉验证(`render_card.mjs` / `card_screenshot` / Playwright 截图)只在变更会改变画面时做。改 `inputParams` / 绑定表达式取值逻辑 / `eventactions` 事件代码 / `data.json` / 文档脚本 → 只跑 validate + lint + `derive_data_json --check`,再用**单元断言**收口(`evalExpr` 比对表达式返回值;`new Function(code).call(假 this)` 跑 customAction 断言 `state`,`code` 与 `compiled` 两份都要跑)。改 `style.*` / 节点增删移动 / 静态文案 / 图片资源 / v-show 条件 → 才做视觉收口。边界:改动副作用会改变布局几何(文案变长换行、`width:"100%"` 内容变长、v-show 改变可见节点集合)时仍按 UI 变更处理。详见 `references/rules.md §七.1`。
40
- 13. **画板聚焦(多画板必须先选卡,铁律 §十三)**:设计稿含多个画板而用户未指定目标时——**UI 产物允许全量生成**(sketch-spec-to-card 管线一次跑完全部画板),但**卡片生成必须以单个画板 / 单个 UI 产物为单位**:先用 `spec_tree.mjs --list-cards <UI目录>`(按「画板序号.卡片序号」展开每个画板下的卡片)或 `parse_meaxure.py --src <导出目录> --list` / `UI/<稿名>/manifest.json`(`canvases[]`)/ `clusters.json`(`cards[]`)机械取全量画板,按 **`画板序号.卡片序号` 编号列出(序号 1 起;一个画板含 N 张卡则展开为 K.1 ~ K.N,详见 §13.4)+ 末尾追加一项 `全部`**;**让用户选的方式 = 正文里逐条写出清单 + 一句明确提问,然后停止输出、等用户回序号再继续**——编辑器助手面板**没有**弹选项工具(不存在 `request_user_input`,**不要去调用或寻找它**),更不要替用户挑;宿主若自带同名工具才可优先弹选项,但那不是前提,不要依赖。**UI 产物允许回 `全部` 全量生成**(sketch-spec-to-card 管线整份导出全跑);**卡片生成默认以单个画板 / 单个 cardKey 为单位**,用户回 `全部` 才视为显式批量授权(交付说明产了几张卡)。**禁止**自行挑画板、**禁止**未经同意批量产卡、**禁止**「先建一张看看」。系统级弹窗不可实现,「正文列清单 + 兜底 `1..n + 全部` → 停下等序号」即等价物;清单**逐行配图**(每画板行下紧跟它自己的预览图绝对路径,缺图显式标缺失,禁止堆末尾),转给用户时逐条内联 Markdown 图片看图选。详见 `references/rules.md §十三`。
41
- 14. **数字语义变量提取(中文+数字混排串铁律 §十四)**:每个落盘文本先过 **AI 语义解析**——串里含**运行期数字信息**(金额含千分号 / 笔数 / 利率 / 期限 / 日期等,判定看「数字是否随数据变化」而非「是否含数字」;版本号 / 固定营销话术不提取)时,关键数字抽成 `inputParams` 变量(金额 `type:"number"` + **原始数字** defaultValue),原混排串用**表达式拼接**展示(`'静态文案' + <数字表达式> + '静态文案'`,`itemValueFrom:"1"` + `itemDefaultValue` 兜底);**格式化后带千分号的金额照惯例 = 原始数字变量 + `amount_formatting.md` §3 格式化表达式**(中文前后缀拼在表达式外)。**禁止**整串写死、整串绑一个 string 变量、把格式化串写进字典、正则见数字就拆。lint `r61-text-numeric-literal` 提醒级复核。详见 `references/rules.md §十四`。
42
- 15. **图表绘制层序(铁律 R62)**:Canvas 即时模式后画压先画 → **出码顺序必须 = 底 → 顶**:**坐标系/参考系线条(柱状图网格线与轴线、折线图网格与刻度线、雷达图环与辐射线、环形底环/轨道)最底层 → 数据标记(柱/折线/多边形/扇区/高亮弧)→ 文字标签(轴标签/值标签/中心分数)最上层**。**禁止**「先填充后画网格」反范式;spec 元素顺序不可照抄(标注导出常把网格线排在数据图元之后)。数据标记半透明(spec `opacity<1`)把透明度**乘进颜色/渐变 stop**(代理 ctx 无 `globalAlpha`),底层参考线才透得出来。生成器 `chartBarCanvasCode` 已用 `chartPaintLayers` 强制分层,护栏 `tools/tests/chart-layer-order.test.mjs`。详见 `references/rules.md §十五` / `recipes.md §15/§18`。
16
+ **集中陈述见 `references/rules.md`**。本节只留一句话索引;每条完整定义、根因与验收见对应文档。
17
+
18
+ 1. **视觉优先(最高优先级)**:同时读 `card-N.png`(长什么样)与 `spec.json`(精确数值)交叉验证;**模型视觉判断 > 脚本启发式**,冲突以视觉为准修正脚本输出;标注缺失的间距/高度从截图目测推断(ui_fidelity_rules.md §8)。
19
+ 2. **绝不臆造**:组件/属性/事件/action 必须 export-proven 或 meta-proven(rules.md §一)。
20
+ 3. **不污染 DSL**:禁写 `$schema` / `x-*` / Generator 产物;整体替换后跑 `diff_card.mjs` 核对未涉及节点逐字节保留(rules.md §二)。
21
+ 4. **根节点不设样式宽度**:宽度由顶层 `view.width` 承载,style 里不放 rect 组(rules.md §九)。
22
+ 5. **标注可信度分层**:属性值类(圆角/阴影/字体/边框/fill)读标注、禁抄存量卡;几何类(rect/间距)按实图与容器边界分析;间距用 margin 不用 padding;卡底从 `cardFill` 取,conflict 时以 `cardFillSampled` 为准(pitfalls §9-R1/R8-R10)。
23
+ 6. **渲染器默认样式显式覆盖 + 运行期数据必须绑定**:Row/Column/ListLayout 默认注入 padding:2px/gap:4px/居中——必须显式归零(§9-R11);金额/日期等运行期数据必须绑 inputParams(金额 type:"number" + 表达式还原,`amount_formatting.md`)。
24
+ 7. **类型+值成对**:borderRadius↔borderRadiusType、border↔isBorder、shadow↔isShadow——只写值不写开关 = 静默丢弃(R44,lint `r44-*`,expand_ir 自动补)。
25
+ 8. **卡片背景不可完全透明(半透明允许)**:Root/首层容器必须有背景声明(`background.backgroundColor` + `backgroundType:"01"`);**带 alpha 的半透明底色/渐变合法**,按标注 cardFill 原样还原(含 alpha);lint `r43` 只判「声明存在性」,不判色值 alpha(pitfalls §R43)。
26
+ 9. **width:"100%" 必须可压缩**:100% 宽保持 `flexShrink:"1"`,禁同节点 shrink:0(R22);受外宽容器 2+ 并列子节点至少一个 shrink:1(R35)(rules.md §九)。
27
+ 10. **Text 高度与 maxLine 协调**:maxLine=0(换行)不设 rect.height;spec 文本高度 > fontSize×1.5 判多行(ui_fidelity_rules.md §1)。
28
+ 11. **透明度禁表达式(R57)**:opacity 变化走静态变体 + 等宽高 `overflow:hidden` 容器 + 事件上移 + 互斥 v-show(recipes.md §17,`apply_opacity_pattern.mjs` 机械改写)。
29
+ 12. **非 UI 变更不做视觉验证**:改 inputParams/表达式/事件代码/data.json → 只跑 validate+lint+derive+单元断言;改 style/节点/静态文案/图片/v-show → 才视觉收口;副作用改布局几何时按 UI 变更处理(rules.md §七.1)。
30
+ 13. **画板聚焦(多画板必须先选卡)**:UI 产物可全量生成,但产卡必须锁定单个画板/cardKey——用户未指定时机械列清单(`spec_tree.mjs --list-cards` / `parse_meaxure.py --list` / manifest `canvases[]`,`画板序号.卡片序号` 编号 + 末尾 `全部`),正文逐条列清单 + 一句提问后**停止输出等序号**(无弹选项工具,勿找 `request_user_input`;清单逐行内联预览图,完整绝对路径禁 `…` 省略);回 `全部` 才算显式批量授权;禁自挑画板 / 未授权批量产卡 / 「先建一张看看」(rules.md §十三,§13.4)。
31
+ 14. **数字语义变量提取(中文+数字混排)**:含运行期数字(金额/笔数/利率/期限/日期;判据=数字是否随数据变化)→ 数字抽 inputParams(金额 type:"number" + 原始数字)+ 表达式拼接展示;格式化金额 = 原始数字 + `amount_formatting.md` §3;禁整串写死 / 整串绑 string / 正则见数字就拆(lint r61)(rules.md §十四)。
32
+ 15. **图表绘制层序(R62)**:Canvas 出码 = 底→顶:参考线/网格/轨道最底 → 数据标记 → 文字标签最顶;禁先填充后画网格;半透明标记把 alpha 乘进颜色(代理 ctx 无 globalAlpha)(rules.md §十五;护栏 `tools/tests/chart-layer-order.test.mjs`)。
43
33
 
44
34
  ## 0.5 速查优先
45
35
 
46
- 常规任务只看 `references/quickref.md`(含最常用组件骨架、表达式、事件范式、命令、范式片段索引);只有遇到 quickref 未覆盖的内容才按需读其他 references。详见 `rules.md §五`。
36
+ 常规任务只看 `references/quickref.md`(最常用组件骨架、表达式、事件范式、命令、片段索引);quickref 未覆盖才按需读其他 references。详见 `rules.md §五`。
47
37
 
48
38
  ## 1. 平台模型速览
49
39
 
50
- - **设计器**:JSON 驱动低代码编辑器。左:卡片列表/变量/模板/Schema/历史/i18n/枚举/组件面板;中:375px 移动端预览(iframe);右:选中节点的属性/事件/插槽/结构树;顶:AI 生成/发布/预览/保存。
51
- - **渲染器**(vendor/preview/):schema.json → buildPreview() → 预览树 → RenderTree.vue 递归渲染。实现 **15** 个组件类(见下表);编辑器 palette 注册 **20** 个,多出的 6 个(CheckGroup/RadioButton/ProgressSlider/Video/Map/MultiColumn)**预览不渲染**,仅在有平台导出证明其 JSON 形状时才生成。
40
+ - **渲染器**(vendor/preview/):schema.json → buildPreview() → RenderTree.vue 递归渲染。实现 **15** 个组件类;编辑器 palette 注册 20 个,多出 6 类(CheckGroup/RadioButton/ProgressSlider/Video/Map/MultiColumn)**预览不渲染**,仅在有平台导出证明 JSON 形状时才生成。
52
41
  - **数据流**:dictionary.inputParams[] 是数据模型;$key 绑定引用字典字段;ListLayout/GridLayout 作用域插槽内 $field 引用当前项;表达式经 evalExpr 在 with(vars) 下求值。
53
- - **事件**:eventactions[] 里 customAction 跑 JS,this 持有 state/data/setData/setRootData/addEventListener/postMessage;JSAPI 由平台注入为全局 `ynet`,事件里**直接 `ynet.callJSAPI(...)`**(不要 window. 前缀、不要存在性守卫,铁律 R56)。
42
+ - **事件**:eventactions[] 里 customAction 跑 JS,this 持有 state/data/setData/setRootData/addEventListener/postMessage;JSAPI 平台注入全局 `ynet`,**直接 `ynet.callJSAPI(...)`**(禁 window. 前缀与存在性守卫,铁律 R56)。
54
43
 
55
44
  ## 2. 顶层结构 & data.json
56
45
 
57
- 顶层 schema 形状:
58
-
59
- - version("1.0.0")、pageId、name、desc、platform("002")、dictionary.inputParams[]、view。
60
- - dictionary.inputParams[] 每项必须包含完整 7 字段:`{ key, name, type, defaultValue, desc, rule, children }`。`name` 为字段中文名(缺省用 key 兜底),`rule` 空值为 `{ type: '', options: {} }`,`children` 空值为 `[]`。type ∈ boolean|string|number|object|array。`expand_ir` 的 `normalizeInputParams()` 自动补全缺失字段;`validate_schema.py` 检查完整性,缺失 → ERROR(pitfalls §R49)。
61
- - data.json 必须**逐字节**等于 { inputParams[i].key: inputParams[i].defaultValue }。增删改字段时同步改 data.json,或跑 scripts/derive_data_json.py --overwrite。
62
- - view 是根组件树(className:"Root"),用 components(非 slots)。其余组件用 slots[].components。
46
+ - 顶层:version("1.0.0")、pageId、name、desc、platform("002")、dictionary.inputParams[]、view。
47
+ - inputParams[] 每项完整 7 字段 `{ key, name, type, defaultValue, desc, rule, children }`(rule 空=`{type:'',options:{}}`,children 空=`[]`;type ∈ boolean|string|number|object|array)。expand_ir 自动补全;validate 缺失 → ERROR(pitfalls §R49)。
48
+ - data.json **逐字节**等于 `{ inputParams[i].key: defaultValue }`;改字段同步改 data.json 或跑 `derive_data_json.py --overwrite`。
49
+ - view 是根组件树(className:"Root",用 components 非 slots);其余组件用 slots[].components。
63
50
 
64
51
  ## 3. 组件速查(15 个可渲染)
65
52
 
66
- 来自 vendor/preview/meta/component-meta.json + 渲染器源码。styleState = 编辑器默认样式基线(生成新组件以此为起点)。详见 references/component_specs.md。
67
-
68
- - **Root**:容器(components),事件 onReady。根节点,宽度由顶层 `view.width="375"` 承载;**style 里不放 rect 组**(见 rules.md §九)。
69
- - **Row**:容器,click。默认 rect.width=100%, flex.gap=4/alignItems=center, padding.paddingType=0/padding=2。横向布局/可点击区。
70
- - **Column**:容器,click。默认 rect.width=100%, flex.gap=4/flexDirection=column/justifyContent=center, padding 同 Row。纵向堆叠。
71
- - **ListLayout**:容器(slotScope:true),click。属性 value(数组,绑定)、direction(vertical|horizontal)、row-spacing、edge-insets"{t,l,b,r}"。默认 rect.width=100%/height=auto。按数组逐项重复插槽模板。
72
- - **GridLayout**:容器(slotScope:true),click。属性 value、column、row-spacing、item-spacing、edge-insets。默认同 ListLayout。等分列网格。
73
- - **AbsoluteLayout**:容器,无事件。属性 align(TopLeft|TopCenter|TopRight|CenterLeft|Center|CenterRight|BottomLeft|BottomCenter|BottomRight)。默认 rect.width=100%/height=100。子元素绝对定位。
74
- - **Canvas**:叶,onMeasured。默认 rect.width=100%。配合 onMeasured 自绘(event.canvas 设计稿坐标系)。
75
- - **Text**:叶,click。属性 value。无默认样式(用样式预设)。渲染文本(value 经 doI18n)。
76
- - **Button**:叶,click。属性 value、disabled(可 $绑定)。默认 font.color=#fff/fontSize=16, bg=#0079f2, radius=100, rect=120x40。disabled 真值判断(字符串 "false" 也为真)。
77
- - **Input**:叶,无事件。属性 value(绑定)、placeholder、inputType(text|number|email|password|phone)、maxLength。默认 rect=100%x35, bg=#fff。value 绑定 $key 时渲染层兜底回写:onInput 写 `rt.state[key]`(**只写运行期克隆 rt.state、绝不写持久化 rt.rootScope**,故打字不触发"保存到本地"脏标记);实时输入值在事件代码里**仅用 `this.data.xxx` 读**(this.data 的唯一合法用途:渲染层兜底把 Input 输入回写 rt.state),故 `this.data.payAmount` 即实时输入值,无需任何"交互前 flush";其余任何运行态变量一律用 `this.state` 读(见 §6 读写约定,lint `r51` 拦截误用)。
78
- - **Icon**:叶,click。属性 icon(glyph 名或图片 URL)、color、content(自定义 svg)。默认 rect=32x32, icon=angle_right, color=#a5afbc。必须声明 emits:click 否则 handler 双触发。
79
- - **Image**:叶,click。属性 value(@filestore 或 $绑定)。默认 rect=72x72, image.cropMode=00。cropMode 00=铺满/cover, 01=填充/contain。
80
-
81
- ### 切图资产唯一落点规则(issue-slices 系列,强制)
82
- - **唯一落点 `src/slices/`(扁平)**:dsl-card-generator 生成卡片时,卡片引用到的切图(slice PNG)必须统一输出到仓库根 `src/slices/`,且**只放图片本体、扁平命名**(导出相对路径的 `/` 替换为 `_`,如 `8.icon_通用_金额隐藏@3x.png`),禁止多级子目录、禁止落到 `src/cards/<id>/` 或停留在 `UI/<artboard>/slices/`。
83
- - **schema 引用格式 `/slices/***.**`**:图片 `value` 一律用绝对前缀 `/slices/<扁平名>`(如 `/slices/8.icon_通用_金额隐藏@3x.png`)。**同步远程时**由 `syncRemote` 改写为 `@filestore/<ns>/<ts>_<name>@3x.png`,运行时经 adapter 解析 `/minio/...` 加载远端。
84
- - **URL 保留字符必须编码**:扁平文件名中的 `#`、`?` 是 URL 分隔符;写入 schema 前分别转成 `%23`、`%3F`。否则浏览器只请求片段前路径,切图会表现为 404。
85
- - **仅引用才拷贝**:`generate_cards.mjs` 写卡后自动把**该卡 schema 实际引用**的切图(遍历 `view` 收集 `/slices/` 引用)扁平汇聚到 `src/slices/`,未引用的切图不落地。全量回归开始时还会先清空 `src/slices` 再重建,杜绝历史残留。
86
- - **语义化命名(落地即重命名,强制)**:切图名由**实际功能与作用**决定,不由素材原名决定——`<角色><业务对象>[@Nx].png`(`iconArrowRight@3x.png` / `bgCardHeaderDiffuse@3x.png` / `logoBank@3x.png`)。**禁止**沿用设计稿图层名与导出目录前缀(`编组 4.png`、`矩形 4.png`、`8.icon_通用_箭头右@3x.png`、`99.切图_…`)、中文名、空格、副本后缀(`icon (2).png`、`xxx_alt2`)、裸通用名(`icon.png`、`image1.png`)。同一张图**全项目只有一个名字**(不允许一卡一名);改名要成套做(文件名 + 所有卡片 schema/data 引用 + `manifest.json`,用 `lib/slice_dedupe.mjs → rewriteSliceRefs`),禁止新旧并存。上游 `UI/<画板>/slices/` 保留导出原名作来源证据。lint `r59-slice-semantic-name` 告警。详见 `references/slices_convention.md §1.1`。
87
- - **落地前判重:已存在则直接引用,不重复导入(强制)**:拷贝进 `src/slices/` 前必须判重——**内容 sha256 是字节级快路径**(一致即同图 → 复用既有文件 + 把引用改写到既有名,不新增副本);sha 未命中必须再做**像素容差判重**(裁切派生图与导出资产/已重命名共用切图像素同内容但字节不同,`lib/slice_pixels.mjs`);按名查找走 manifest 别名(`renamedFrom`/`mergedFrom`)。尺寸 w×h / 字节数只作快速预筛,来源只作辅助证据与命名依据。派生裁切先按指纹分组、再逐组像素复用(`spec_audit.mjs` / `crop_slices.mjs`,pitfalls §15)。`expand_ir.mjs`(`syncReferencedSlices`)与 `generate_cards.mjs`(`collectSlicesToSrcSlices`)已机械执行并回写 schema/data;lint `r60-slice-duplicate-content` 兜底,`lint_card.mjs --all | grep r60` 列全仓重复组。存量教训:`src/slices/` 60 个文件仅 50 份唯一内容(7 组同图多名),已用 `scripts/slices_normalize.mjs --write` 归一化收敛(改名 + 合并副本 + 成套改写引用/manifest,默认 dry-run 可复查)。详见 `references/slices_convention.md §3.1`。
88
- - **上传去重:共用切图只上传一次(强制)**:`slice_upload.mjs` 按内容 sha256 判重且**不认文件名**——多卡共用同一张图只上传一次,其余卡引用改写为**同一个** `@filestore/...` 地址;同内容不同名的副本、同批次内的同内容文件也复用(`manifest.json` 记 `reusedFrom`),**绝不为同一张图产生第二个线上地址**(`--force` 才重传)。上传复用是兜底,真正的去重在落地阶段完成。详见 `references/slices_convention.md §3.2` + `skills/finmall-card-sync/SKILL.md`「幂等与共享切图」。
89
- - **本地预览可访问**:自包含 `vendor/vite.config.mjs` 与 `scripts/render_card.mjs` 把 `/slices/*` 映射到宿主 `src/slices/*`,离线截图可直接加载卡片切图,无需先同步远端;宿主若另有 dev server(如 cardbuddy:8787)也只需挂载同款 `/slices/*` 静态中间件。
90
- - **导出目录切图(`99.切图/*`)不渲染**:MeaXure 导出会在图层里塞入 `99.切图/...` 组(导出清单/占位,非卡片内容),几乎每张卡都有。渲染阶段(`buildLeaf`、`imageFromSlice`)跳过图层名以 `99.切图` 开头的 slice,避免凭空注入切换语音/箭头等图标并误引用(曾导致 `切换语音@3x.png` 被 200+ 次错误引用)。过滤只看图层名前缀,不看导出路径。
91
- - 元素名 ↔ 导出文件名靠 `lib/slicePath.mjs` 的 `resolveSliceExport` 归一化(去 `@Nx` / 尾随 `_icon` / 扩展名后按 basename 匹配)对齐;匹配不到的 slice 退化占位图。
92
- - 完整机制、目录对照、匹配规则、常见错误与校验清单见 `references/slices_convention.md`。
93
- - **MultiText**:叶,click。属性 textList → itemList[] → 每段 properties(value/color/fontSize/fontWeight/fontStyle/textDecoration/backgroundColor/tag)。一行多段不同字体文本;点击 event.__do__ 冒泡去重。
94
- - **Divider**:叶,无事件。默认 rect=100%x1, bg=#f1f1f1。纯样式分割/间距。
95
- - **ViewContainer**:叶,action。属性 customClass、customProps(可 $绑定)、customRemarkImg。默认 rect=100%/height=auto。宿主自定义组件占位,预览画占位块。
96
-
97
- > 编辑器专属(预览不渲染):MultiColumn/CheckGroup/RadioButton/ProgressSlider/Video/Map。仅在有平台导出时生成,否则预览画"未知组件"占位但仍渲染子树。
53
+ 完整 JSON 骨架 + 属性 itemId 表见 `references/component_specs.md`。**编辑器专属 6 类预览不渲染**:MultiColumn/CheckGroup/RadioButton/ProgressSlider/Video/Map(仅平台导出时生成,否则占位但渲染子树)。
54
+
55
+ | 组件 | 事件 | 关键属性/默认 | 用途 |
56
+ |---|---|---|---|
57
+ | Root | onReady | 宽由 `view.width="375"` 承载;style 不放 rect 组 | 根容器(components) |
58
+ | Row | click | 默认 width:100%、gap:4、居中、padding:2 | 横向布局/可点击区 |
59
+ | Column | click | 同 Row;flexDirection:column、justifyContent:center | 纵向堆叠 |
60
+ | ListLayout | click | slotScope:true;value(数组绑定)/direction/row-spacing/edge-insets | 数组逐项重复模板 |
61
+ | GridLayout | click | slotScope:true;value/column/row-spacing/item-spacing | 等分列网格 |
62
+ | AbsoluteLayout | — | align(9 方位);子元素绝对定位 | 叠加/角标 |
63
+ | Canvas | onMeasured | event.canvas 设计稿坐标系;onMeasured 自绘 | 自定义绘制 |
64
+ | Text | click | value(经 doI18n);无默认样式 | 文本 |
65
+ | MultiText | click | itemList[] 多段 value/color/fontSize/fontWeight | 一行多段富文本 |
66
+ | Button | click | value/disabled(**"false" 也为真**);默认 #0079f2 120x40 r100 | 按钮 |
67
+ | Input | events:[] | value($key 回写 rt.state)/placeholder/inputType/maxLength;实时值读 `this.data.<key>` | 输入 |
68
+ | Icon | click | icon(glyph)/color/content;默认 32x32;**必须 emits:click** 否则双触发 | 图标 |
69
+ | Image | click | value(@filestore/$绑定);cropMode 00 铺满/01 填充 | 图片 |
70
+ | Divider | — | 默认 100%x1 #f1f1f1 | 分割/间距 |
71
+ | ViewContainer | action | customClass/customProps($绑定) | 宿主自定义组件占位 |
72
+
73
+ ### 切图资产唯一落点(issue-slices 系列,强制)
74
+
75
+ **全集见 `references/slices_convention.md`**。要点:① 唯一落点 `src/slices/` 扁平(`/`→`_`),schema 引用 `/slices/<扁平名>`(远端由 syncRemote 改写 @filestore);② 文件名禁 URL 保留字符 `# ? % & +`——静默 404(网络 200 零报错),重命名不转义(禁 `%23`),lint r31b/r31c;③ 语义化命名(`iconArrowRight@3x.png` 式)全项目同名唯一,改名成套做(`rewriteSliceRefs`);④ 落地即判重(sha256+像素容差,lint r60)、仅引用才拷贝(expand_ir/generate_cards 自动汇聚)、上传按内容判重只传一次;⑤ 导出目录 `99.切图/*` 不渲染;元素名↔导出文件名靠 `lib/slicePath.mjs → resolveSliceExport` 归一化。
98
76
 
99
77
  ## 4. 表达式与数据绑定(核心引擎)
100
78
 
101
79
  完整规则见 references/expression_engine.md。要点:
102
80
 
103
- - **$ 前缀 JS 表达式**:itemValueFrom:"1" 的属性,itemValue 是一个 JS 表达式,作用域里的 foo 被改名为 $foo 注入,用 with(vars){ return (expr) } 求值。例:$amount > 0 ? '#f00' : '#0a0'、$theme[$level*1-1].bg、$title.length*16。
104
- - **itemValueFrom**:"0"=常量(itemValue 直接用);"1"=变量/表达式(**必须**带 itemDefaultValue 作 undefined 兜底);"2"=图片/文件值。未定义的 $ 变量经 Proxy 解析为 undefined(不抛 ReferenceError),故 $foo || 默认值 兜底可生效;真正的语法错误仍抛错并被上层 try/catch。
105
- - **作用域插槽**:ListLayout/GridLayout 的 slotScope:true 下,子作用域 = 父作用域 ∪ 当前 item 展开 ∪ $$(整个 item) ∪ _index(索引)。**表达式**里用 **`$fieldName`**(直接访问 item 字段,推荐),**禁止 `$$.fieldName`**($$ 是整个 item 对象,`$$.field` 是冗余写法,lint R50 拦截)。例:`$title`、`$amount`、`$_index<3`。`$$` 仅在表达式需要引用整个 item 时使用(如 `$$.length`)。**事件脚本(customAction)里没有 `$$`**:当前 item 字段已展开到 `this.state`,直接 `this.state.<field>` 读取(平台保证,不是需要兜底的不确定行为),任何 `$$` / `this.state.$$` / `$$.field` 写法与「$$ 兜底判断」一律禁止(铁律,lint R55 ERROR)。
106
- - **逐项 try/catch**:applyVars 对每个绑定表达式单独 try/catch,某项求值失败时该项保持 meta 默认值(而非 undefined)——这正是引用未定义变量的卡仍能渲染的原因。
107
- - **itemCondition 可见性**:属性级条件显隐,语法 a.b(同级 itemCondition==="a" 的属性其值须等于 "b" 才可见)、a.!b(取反)、a.b|c(多选)。段数为奇数的项自身是条件源。例:showType.01|02、paddingType.0、isBorder.1、backgroundType.01。Properties 空数组 = 全部沿用 meta 默认(restoreProperty 在 items 为空时直接返回)。
108
- - **v-show 指令**:directives[] 里 itemKey:"v-show",itemValue:{} + itemChildren.properties:showType(00 始终显/01 满足条件显/02 满足条件隐) + condition:{expression:"$flag==='1'"}。渲染期用 evalExpr 实时算。**显隐设置的显示名 `condition.name` 为必填字段(不能为空)**:表达式长度 <15 时 name 与表达式保持一致;>=15 时设置合适的短命名(不得抄表达式)。expand_ir 对 <15 自动补、>=15 缺 name 直接报错;name 缺失/空 validate 报 ERROR,命名不合规 lint `r52` 报 WARN。**铁律(lint `r53` ERROR)**:多列布局(MultiColumn)/列表(ListLayout)/网格(GridLayout)/绝对布局(AbsoluteLayout)/Canvas/Map/Video/自定义节点(ViewContainer) 需设显示条件时,必须外套一层 Row/Column 并把 v-show 设在包裹层,禁止直接设在这些节点上;其他节点可直接设置。详见 references/dsl_structure.md §指令。
109
- - **Input 双向回写**:Input 的 value 绑定形如 $key 时,渲染层兜底:onInput 写 `rt.state[key]`(**只写运行期克隆 rt.state、绝不写 rt.rootScope**,故打字不触发"保存到本地"脏标记)。实时输入值在事件代码里仅用 `this.data.payAmount` 读(this.data 的唯一合法用途),无需"交互前 flush";普通运行态变量一律用 `this.state` 读(lint `r51` 拦截误用)。Input 无可绑定事件(events:[]),卡片无法自己接线 change,故由渲染层兜底。
81
+ - **$ 表达式**:itemValueFrom:"1" 的 itemValue 是 JS 表达式,作用域变量改名 $foo 注入 `with(vars)` 求值;未定义 $ 变量解析为 undefined(不抛错),`$foo || 默认值` 兜底可生效;逐项 try/catch,单项失败保持 meta 默认值。
82
+ - **itemValueFrom**:"0"常量 / "1"表达式(**必须**带 itemDefaultValue)/ "2"图片值。
83
+ - **作用域插槽**:slotScope 下**表达式**用 `$fieldName` 直读 item 字段(禁 `$$.field`,lint R50);`$$` 仅指整个 item。**事件脚本无 `$$`**:item 字段已展开到 `this.state` 直接读(铁律,lint R55 ERROR)。
84
+ - **itemCondition**:属性级条件显隐 `a.b` / `a.!b` / `a.b|c`;Properties 空数组 = 沿用 meta 默认。
85
+ - **v-show**:directives[] itemKey:"v-show",showType 00 始终显/01 条件显/02 条件隐 + condition.expression;**condition.name 必填**(<15 字符与表达式一致,>=15 起短名);**容器类组件(MultiColumn/List/Grid/Absolute/Canvas/Map/Video/ViewContainer)必须外套 Row/Column 设在包裹层**(lint r53 ERROR)。
86
+ - **Input 回写**:value 绑 `$key` 时 onInput 写 rt.state[key](只写克隆不脏 rootScope);实时输入值事件代码**仅 `this.data.<key>`**(唯一合法用途),其余一律 this.state(lint r51)。
110
87
 
111
88
  ## 5. 样式系统
112
89
 
113
- 来自 vendor/preview/meta/style-meta.json,由 StyleParser.js 解析。分组与 itemId:
114
-
115
- - 100 flex:flexDirection(row|column)、justifyContent(flex-start|flex-end|center|space-between|space-around|space-evenly)、alignItems(flex-start|flex-end|center|stretch|baseline)、gap、flexShrink。
116
- - 200 rect:width、height(auto)。
117
- - 300 background:backgroundType(00 透明|01 颜色|02 图片|03 渐变)、backgroundColor、backgroundImage、backgroundPosition、backgroundSize、backgroundRepeat、backgroundGradient。
118
- - 400 padding:paddingType(""无|0 全边|1 自定义)、padding、paddingLeft/Top/Right/Bottom。
119
- - 500 margin:marginType(""无|0 全边|1 自定义)、margin、marginLeft/Top/Right/Bottom。
120
-
121
- > **渲染器默认样式(必读)**:Row/Column/ListLayout 未显式设置时默认注入 `padding:2px` + `gap:4px` + `justify-content:center`(Column 内容垂直居中),ListLayout 默认 `edge-insets {2,2,2,2}`。**`expand_ir.mjs` 的 `ensureCoreDefaults()` 会自动为每个节点补入核心样式默认值**(margin=0、background=透明、padding=0[仅容器]、borderRadius=0、gap=0[仅容器]),消除隐性默认值的不确定性(pitfalls §R48)。但仍需显式设置 Column justifyContent flex-start、ListLayout edge-insets '{0,0,0,0}' 等 expand_ir 不覆盖的属性(见 §9-R11)。
122
-
123
- > **间距语义(必读)**:元素与相邻元素的间距用 `margin`(marginTop/Bottom),**不用 padding**——padding 表达"盒子内部留白",会污染 box 高(fidelity Δh 误报、下一元素位置计算偏差);padding 只用于结构性内边距(容器/卡片内边距、文本缩进 paddingLeft)。**卡片圆角放最外层 Root**(圆角与背景均读 spec 标注),内层白底主体不放圆角(被外层白底填平)。详见 `generation_pitfalls.md §9-R8/R9`。
124
- - 600 borderRadius:borderRadiusType(""无|0 全角|1 自定义)、borderRadius、各角。
125
- - 700 border:isBorder(0|1)、borderWidth、borderStyle(solid|dashed|dotted)、borderColor。
126
- - 800 shadow:isShadow、shadowColor、shadowX/Y/Blur/Spread。
127
- - 900 transform:rotate(deg)。
128
- - 1000 other:overflow(hidden|visible)、opacity。**opacity 只允许静态常量(0-100)**:移动端不支持透明度表达式,预览构建产物也不消费组件级 opacity;会变化的透明度必须走透明度切换固定范式(铁律 R57,见 §0 第 11 条 / `recipes.md §17`)。
129
- - 1100 font:color、fontSize、lineHeight、textAlign(left|center|right)、fontWeight(400|500|600)、fontStyle(normal)、textDecoration(none)、isMaxLine(0|1)、maxLine。
130
- - 1200 image:cropMode(00 铺满|01 填充)。
131
- - 1300 position:left、top、right、bottom。
90
+ **itemId 权威来源 = `vendor/preview/meta/style-meta.json`**(完整表见 dsl_structure.md / component_specs.md)。分组:100 flex(flexDirection=**006**、justifyContent、alignItems、gap、flexShrink)|200 rect|300 background(backgroundType 00 透明|01 颜色|02 图片|03 渐变 + Color/Gradient)|400 padding / 500 margin(xType ""|0|1 + 值 + 四边分项)|600 borderRadius(borderRadiusType 成对)|700 border(isBorder)|800 shadow(isShadow)|900 transform|1000 other(overflow、**opacity 只允许静态常量 0-100**,禁表达式——铁律 R57)|1100 font(color/fontSize/lineHeight/textAlign/**fontWeight 枚举 400|500|600**/isMaxLine/maxLine)|1200 image(cropMode)|1300 position。
132
91
 
133
- 解析顺序固定:rect→flex→background→padding→margin→borderRadius→border→shadow→transform→other,后覆盖前同名;boxSizing:border-box。font/image 由 Text/MultiText/Image 等单独调。颜色 rgba(r,g,b,a) 或 #rrggbb 经 adapter.doColor 归一;尺寸数字字符串经 adapter.doSize 转 px。background.backgroundType 02 图片时 backgroundImage 经 doReplaceUrl + doI18n,默认 position 50% 50%、size cover、repeat no-repeat。
134
-
135
- > **style itemId 权威来源 = `vendor/preview/meta/style-meta.json`**,不要凭记忆。**易错**:`flexDirection` 的 itemId 是 **`006`**(不是 `002`,`002` 是 `alignItems`)。`validate_schema.py` 现已校验「样式子项 itemId 是否与该组预期一致」,写错直接 ERROR。完整避坑(含 itemValueFrom 必填、图片解析、布局陷阱、Playwright 视觉验证)见 `references/generation_pitfalls.md`。
92
+ > **渲染器默认样式**:Row/Column/ListLayout 默认注入 padding:2px+gap:4px+justify-content:center,ListLayout 默认 edge-insets{2,2,2,2}——必须显式归零;expand_ir ensureCoreDefaults 自动补核心默认(§R48),Column justifyContent:flex-start、ListLayout edge-insets 仍需显式(§9-R11)。
93
+ > **间距语义**:相邻间距用 margin 不用 padding(padding 污染 box 高);卡片圆角放最外层 Root(pitfalls §9-R8/R9)。
94
+ > **易错**:flexDirection 的 itemId 是 **006**(002 是 alignItems),写错 validate ERROR;解析顺序 rect→…→other 后覆盖前,boxSizing:border-box。
136
95
 
137
96
  ## 6. 事件与运行时
138
97
 
139
- 完整见 references/event_patterns.md + references/platform_jsapi.md。要点:
98
+ 完整见 references/event_patterns.md + platform_jsapi.md。要点:
140
99
 
141
- - **eventactions[]**:{ event:{eventName,eventKey}, action:{actionType, properties:[{itemKey:"function", itemValue:{code, compiled}}]} }。
142
- - **eventKey**:高频(click:绝大多数交互组件)、中频(onReady:Root 就绪生命周期;onMeasured:Canvas 画布就绪)、低频(其他)。完整频次表见 `references/event_patterns.md`。**关键约束**:onReady/onLoad/onCreate/空串 属"就绪类生命周期",首帧前由 runLoadActions 执行一次,不注册成被动 handler(避免与组件自身 emit 重复触发)。
143
- - **actionType**:高频 customAction(跑 JS,是主形态)、低频 startRouter/closeAlertCard/sendAiChatMessage。alertCard/alertDialog/showToast/showPicker 等**不是** actionType,是 customAction 里的 JSAPI 调用。
144
- - **customAction 的 this**:this.state(可变运行态=rt.state)、this.data(**仅**读 Input 绑定键的实时输入值,禁止读普通运行态变量)、this.setData(patch)(合并到 state 并 markDirty 重渲染)、this.setRootData(patch)(改持久化 rt.rootScope,触发"保存到本地"脏标记——只在确实要写盘时用)、this.addEventListener(name,fn)、this.postMessage(name,payload)(向注册同名监听器的节点发消息)。
145
- - **读写约定**:运行态变量(选中态/锁定态/用户输入/绑定派生值等)一律用 **this.state** 读、写走 this.setData(...);**this.data 仅允许读 Input 绑定键的实时输入值**(唯一例外,渲染层 onInput 兜底回写 rt.state);持久卡片态存 this.state;任何应重渲染的改动后调 this.setData(...)。customAction 里读 this.data.<非Input绑定键> 会被 lint `r51-event-this-data-misuse` 拦截。**调试交互绝不调 setRootData**——否则会污染"保存到本地"基线。
146
- - **slotScope item 字段在事件里直读(铁律 r55)**:slotScope:true 的 List/Grid item 事件脚本里,当前 item 的字段**直接 `this.state.<field>` 读取**(如 `var q = this.state.query;`)——这是平台保证的作用域展开,**不是**需要 `if (q === undefined && this.state.$$)` 之类兜底判断的不确定行为。事件脚本禁止出现 `$$`(含 `this.state.$$`、`$$.field`);lint `r55-event-no-dollar-dollar` ERROR 拦截。
147
- - **globalSet 数据透传(铁律 r54 固定范式)**:ListLayout/GridLayout 的 item 事件里修改绑定数据禁止直接改——必须 `this.postMessage('globalSet', patch)`(patch 对象与 `setData(...)` 入参逐字一致),并**先在根节点 onReady** `this.addEventListener('globalSet', fn)` 注册监听、监听里 `self.setData(patch)` 代写;其他节点事件可直接 setData。详见 references/recipes.md §globalSet,lint `r54` 拦截。
148
- - **JSAPI**(ynet.callJSAPI(name, params, cb)):**直接裸调 `ynet.callJSAPI(...)`**——ynet 是平台注入的全局,**禁止 `window.ynet` 前缀与 `typeof window !== 'undefined' && window.ynet && ...` 之类存在性守卫**(铁律,lint R56 ERROR;49 张导出卡全量裸调为证)。高频 closeAlertCard/alertCard/alertDialog/sendAiChatMessage/startRouter/showToast,低频 showLoadingEx/showPicker/showKeyboard/hideKeyboard/showDatePicker/copyToClipboard 等。回调结构 {success, error, errorMessage, data}。**完整签名 + 实测频次见 `references/platform_jsapi.md`**(权威表,不在本节重复数字)。
100
+ - **eventactions[]**:`{ event:{eventName,eventKey}, action:{actionType, properties:[{itemKey:"function", itemValue:{code, compiled}}]} }`。click 高频;onReady/onMeasured 就绪类生命周期首帧前跑一次,不注册被动 handler。
101
+ - **actionType**:customAction 跑 JS(主形态);alertCard/showToast 等是 customAction 里的 **JSAPI 调用**(不是 actionType)。
102
+ - **customAction 的 this**:state(运行态,写走 setData)/ data(**仅** Input 实时输入值,lint r51)/ setData(patch) / setRootData(patch,触发存盘脏标记,调试禁用) / addEventListener / postMessage。slotScope item 字段在事件里直接 `this.state.<field>` 读(铁律 r55,禁 `$$` 兜底判断)。
103
+ - **globalSet(铁律 r54)**:List/Grid item 事件改绑定数据必须 `this.postMessage('globalSet', patch)`(与 setData 入参一致)+ 根节点 onReady `addEventListener('globalSet')` 代写;其他节点直接 setData(recipes.md §globalSet)。
104
+ - **JSAPI**:裸调 `ynet.callJSAPI(name, params, cb)`(禁 window 前缀/存在性守卫,铁律 R56);回调 `{success, error, errorMessage, data}`;完整签名+频次见 platform_jsapi.md。
149
105
 
150
106
  ## 7. 通用范式(Recipes)
151
107
 
152
- 可复用、export-proven 的组合配方。**完整代码与 JSON 见 `references/recipes.md`,可直接复制的片段块见 `references/recipe_snippets/*.json`**(后者含 `apply` 字段指明插在哪、插什么,可用 `extract_subtree.mjs` 从任意导出卡抽新片段扩充)。
153
-
154
- | 范式 | 适用场景 | 主要来源 |
155
- |---|---|---|
156
- | Canvas 画布标题头 | 自绘文字标题(坐标用**设计稿 px**) | creditCardBill / dreamPlan / assetOverview |
157
- | 弹一张卡片(alertCard) | 子母卡弹窗 + 回传 data | creditCardBill / monthlyBill / activityLevel |
158
- | 列表展开/收起 | 长列表「查看更多」交互 | account / activityList / depositList |
159
- | globalSet 子→父透传 | 子节点改根作用域 + 列表重算(**铁律 r54**:List/Grid item 改绑定数据必须走此透传,payload 即 patch、与 setData 入参一致) | serviceEvaluation(规范 patch 实现)/ activityRight / DepositHolding / creditCardBill(历史 {key,value} 实现) |
160
- | 金额格式化 | 千分号 / 正负号 / 货币符 | creditCardBill($formatMoney helper) |
161
- | Tab ↔ 标题头(billMode) | 多模式切换(**陷阱**:activeTab 默认值不随模式变,依赖 $activeTab 的可见性须 AND $billMode==='0') | creditCardBill |
162
- | 透明度切换固定范式(R57) | 透明度随状态变化(选中/点亮/禁用):静态变体 ×N + 等宽高 overflow:hidden 容器 + 事件上移 + 互斥 v-show | serviceEvaluation(10 个表情档) |
163
-
164
- 遇到 quickref §1-§5 未覆盖的组合需求时查上表选近邻参考;复杂边界细节读 `recipes.md` 对应小节。
108
+ 可复用、export-proven 配方全集见 **`references/recipes.md`**(Canvas 标题头 / alertCard 弹窗 / 列表展开收起 / globalSet / 金额格式化 / Tab-billMode / 手风琴 / Canvas 甜甜圈·仪表盘·雷达 / 角标 / 标题下划线 / 透明度切换等),可复制片段见 **`references/recipe_snippets/*.json`**(含 `apply` 字段;`extract_subtree.mjs` 可从导出卡抽新片段)。quickref §1-§5 未覆盖的组合需求查 recipes 选近邻。
165
109
 
166
110
  ## 7.5 意图路由(选模板零扫文件)
167
111
 
168
- 用户一句话意图 → 选脚手架意图 + 查 `references/card_index.json` 找近邻,**不扫源卡**:
112
+ 用户意图 → 脚手架意图 + 查 `references/card_index.json` 找近邻,**不扫源卡**:列表→`list`|明细→`detail`|表单→`form`|弹窗→`dialog`|多 Tab→`tab`|画布标题头→`canvas-head`|纯静态→`blank`;MeaXure 导出→Workflow 0 入口①;图片/设计稿→Workflow A。
169
113
 
170
- | 用户意图 | 脚手架 `--intent` | 最小编辑配方 | 近邻参考(card_index.json) |
171
- |---|---|---|---|
172
- | 列表 / 任务流 | `list` | 改 `$title`;ListLayout 子项模板填字段 | `tags` 含 `list` |
173
- | 明细 / 详情页 | `detail` | 改 `$title`/`$detailList`/`$btnText` | `tags` 含 `detail`/`result`/`amount` |
174
- | 表单 / 输入 | `form` | 加 Input + 按钮逻辑 | `tags` 含 `form` |
175
- | 弹窗 / Dialog | `dialog` | 改 `$title`/`$desc`/`$confirmText`;接 alertCard | `recipes` 含 `alertCard` |
176
- | 多 Tab | `tab` | 改 `$tab1`/`$tab2`/`$content` | `recipes` 含 `tab-billMode` |
177
- | 画布标题头 | `canvas-head` | 改 Canvas onMeasured 绘制 | `recipes` 含 `canvas-head` |
178
- | 纯静态 / 自定义 | `blank` | 用 add_component 从空容器搭起 | — |
179
- | **Sketch MeaXure 标注导出建卡** | **Workflow 0 入口①** | `route_input.mjs <路径>` 判形态 → 先 `sketch-spec-to-card` 前处理出 `UI/<稿名>/` → 逐卡 Workflow A | 产物 `UI/<稿名>/clusters.json` + `card_index.json` |
180
- | 图片 / 设计稿建卡 | Workflow A | 读图 + `spec_tree.mjs` → scaffold → 视觉闭环 | inventory / card_index |
181
-
182
- > `card_index.json` 由 `build_card_index.mjs` 生成,每张卡带 `components`/`recipes`/`tags`。按 `recipes`(用过哪些范式)与 `tags`(意图)过滤即可。新增导出后先重跑该脚本。
183
- >
184
- > **整卡模板库**:`card_templates.json`(`build_card_templates.mjs` 生成)把存量卡按结构标签聚成 8 簇并选出代表卡。有近似存量卡时用 `scaffold_card.mjs --from <模板卡id>`(或 MCP `card_scaffold` 的 `from` 参数)整卡克隆改写——只重写 pageId/name,版式纪律与绑定范式原样继承,比空骨架堆组件快且稳;封面不复制(定稿后重新渲染)。从零建卡才走 `--intent` 空骨架。
185
- >
186
- > **模板沉淀闭环**:① 检索——建卡前 `resolve_card_template.mjs <一句话意图>`,命中直接 `--from` 克隆;② 沉淀——卡片交付后跑 `register_card_template.mjs --id <cardId> --check` 做新颖性检查(结构签名 Jaccard 相似度,<60% 为新模式),若是首次出现的布局/交互模式且用户确认,用 `--cluster <簇key> --keywords <通用意图词>` 登记(健康门禁:validate 0 error + lint 0 error;keywords 只用通用 UI 意图词,业务词不入库);③ 重建 `build_card_templates.mjs` 保留 pinned 代表与 keywords,人工登记永不被打分覆盖。
187
- >
188
- > 脚手架工具命令(scaffold_card / add_component / autofix_schema)见 §10;范式片段库(recipe_snippets/)见 §7。
114
+ > **整卡模板库**:有近似存量卡时 `resolve_card_template.mjs <一句话意图>` 命中即 `scaffold_card.mjs --from <模板卡id>` 整卡克隆改写(只重写 pageId/name,版式纪律原样继承)。**沉淀**:交付后 `register_card_template.mjs --id <cardId> --check` 新颖性检查(Jaccard<60% 为新模式),用户确认后登记(keywords 只用通用 UI 意图词,业务词不入库)。
189
115
 
190
116
  ## 8. 工作流
191
117
 
@@ -194,289 +120,125 @@ description: 生成、编辑并以 AI 辅助 FinMall/YNet 低代码卡片 schema
194
120
  收到建卡请求,**第一步永远是机械判形态,不靠感觉**:
195
121
 
196
122
  ```bash
197
- node scripts/route_input.mjs <用户给的输入路径> [--json] # 传目录或其中的入口文件(index.html / manifest.json / spec.json / prompt.md / schema.json / card-N.png)都可
123
+ node scripts/route_input.mjs <输入路径> [--json] # 只挂 MCP 无 shell 时用等价工具 card_route_input({path})
198
124
  ```
199
125
 
200
- > 只挂了 MCP、没有 shell 的环境用等价工具 **`card_route_input({ path })`**(同一份判定实现,离线、无需 dev server),返回 `kind` / `evidence` / `next` / `preprocess` 与 CLI `--json` 一致;`path` 支持工程根相对路径与 `~` 展开。
201
-
202
- 它给出 `kind` + 判定证据 + 下一步可直接执行的命令(含跨 skill 的上游脚本路径),四类入口:
126
+ 四类入口(工具返回 kind + 判定证据 + 下一步命令):
203
127
 
204
- 1. **`meaxure-export` — Sketch MeaXure 标注导出目录**(结构特征:`index.html` 内嵌 `let data = {"artboards":[...]}` + `preview/*.png` 画板整图 + `assets/` 切图素材)→ **禁止直接拿 `preview/` 整图建卡**,必须先经 `sketch-spec-to-card` 前处理产出建卡要素:
128
+ 1. **`meaxure-export`**(index.html 内嵌 artboards + preview/ + assets/)→ **禁止拿 preview 整图建卡**,先经 sketch-spec-to-card 前处理:
205
129
  ```bash
206
- # ① 先列画板并请用户选范围(多画板门禁,rules.md §十三):不带范围直接跑下面 parse 会 exit 4 并打印 1..n+全部 清单
130
+ # ① 列画板请用户选范围(铁律 §0-13):不带范围直接跑 parse 会 exit 4 打印 1..n+全部 清单
207
131
  python3 skills/sketch-spec-to-card/scripts/parse_meaxure.py --src <导出目录> --list
208
- # ② 用户回复序号 k → --indices <k-1>;回复「全部」→ --scope-all(UI 产物全量;产卡视为显式批量授权)
132
+ # ② 用户回序号 k → --indices <k-1>;回「全部」→ --scope-all(UI 产物全量;产卡视为显式批量授权)
209
133
  python3 skills/sketch-spec-to-card/scripts/parse_meaxure.py --src <导出目录> --draft <稿名> --build --indices <k-1>
210
134
  python3 skills/sketch-spec-to-card/scripts/cluster_states.py --out UI/<稿名>
211
135
  ```
212
- 产出 `UI/<稿名>/` 要素包(`card-N.png` 聚焦图 + `spec.json` + `prompt.md` + `slices/` + `manifest.json` + `clusters.json`)后回到本 skill,按入口 ② 选卡生成(UI 产物可全量,产卡一次一张)。**幂等**:`route_input.mjs` 会用 `UI/*/manifest.json` 的 `export` 字段比对该导出是否已前处理过(稿名同名目录兜底),已处理则直接给存量产物路径、跳过管线,不重复跑。
213
- 2. **`ui-bundle` — `UI/` 要素包 / 画板产物**(`card-N.png` + `spec.json` + `prompt.md`,或含 `manifest.json` 的稿目录)→ 先按 `manifest.json` / `clusters.json` 列清单让用户选定单个画板 / cardKey(未指定不产卡,见 §0 第 13 条),再走 Workflow A;`subcard` / `overlay_dialog` 按 Workflow A.5 分流(弹框不产卡,补到父卡事件)。
214
- 3. **`image` — 单张截图/设计稿 PNG**(无标注导出)→ 直接走 Workflow A,无 spec 时全靠 AI 视觉推断。**若这张图其实来自某份 MeaXure 导出,改传导出目录走入口 ①**——聚焦裁切边界与标注数值都在导出里,只给整图等于丢掉它们。
215
- 4. **`card-dir` — 已存在卡片目录**(含 `schema.json`)→ 这是改卡不是建卡,转 Workflow B。
136
+ 产出 `UI/<稿名>/` 要素包(card-N.png + spec.json + prompt.md + slices/ + manifest/clusters)后按入口 ② 走 Workflow A。幂等:manifest export 已匹配则跳过管线直接给存量产物。
137
+ 2. **`ui-bundle`**(card-N.png + spec.json + prompt.md 或含 manifest 稿目录)→ 列清单选定单画板/cardKey(§0-13)→ Workflow A;subcard/overlay_dialog 按 A.5 分流。
138
+ 3. **`image`**(单图)→ Workflow A 纯视觉;若图来自某 MeaXure 导出,改传导出目录走入口 ①。
139
+ 4. **`card-dir`**(含 schema.json)→ 改卡,Workflow B。
216
140
 
217
- > **路由原则**:MeaXure 导出是「多画板 + 标注」的批量输入——卡片聚焦裁切、多卡分离、弹窗/气泡识别、切图收集都是 `sketch-spec-to-card` 的专职能力。跳过它直接读整图,等于放弃聚焦边界与标注数值,还原度必然受损。故 `kind=meaxure-export` 且 `preprocess.needed=true` 时,**先跑上游管线再生成,不允许"先建一张看看"**。
218
- >
219
- > `route_input.mjs` 退出码:0 = 已路由;3 = `unknown`(判不出形态);1 = 用法/路径错误。拿到 `unknown` 时不要猜形态,先向用户确认输入。
220
- >
221
- > **多画板选择门禁(铁律 §十三)**:判出 `meaxure-export` / `ui-bundle`(多画板)后,**UI 产物可全量生成**(前处理一次跑完整份导出),但**产卡前必须锁定单个画板 / 单个 cardKey**——用户未指定时先机械列清单(`parse_meaxure.py --src <导出目录> --list`,或 `UI/<稿名>/manifest.json` 的 `canvases[]`、`clusters.json` 的 `cards[]`)按 **`1..n` 编号列出(序号 1 起)+ 末尾追加 `全部`**;**没有弹选项工具**(不存在 `request_user_input`,**不要去调用或寻找**),一律在正文列出清单 + 一句纯文本提问后**停止输出、等用户回序号**。**UI 产物可回 `全部` 全量生成**;**产卡默认单画板 / 单 cardKey**,用户回 `全部` 才算显式批量授权。**禁止**自行挑画板、禁止未经同意批量产卡。选定后再进 Workflow A / A.5。详见 `references/rules.md §十三`。
141
+ > 退出码 0 已路由 / 3 unknown(先问用户别猜)/ 1 用法错。meaxure-export 且 preprocess.needed=true 时**不允许"先建一张看看"**——跳过前处理等于放弃聚焦边界与标注数值。
222
142
 
223
- > **快路径总则**:建卡前先 `route_input.mjs <输入路径>` 定入口(`meaxure-export` 必须先经 `sketch-spec-to-card` 前处理,见 Workflow 0),再 `resolve_card_template.mjs <意图>` 查模板库(命中即 `--from` 克隆跳过生成);未命中走 Workflow A 六步闭环——AI 读图 + `spec_tree.mjs` 理解布局 → `scaffold_card.mjs` 出骨架 → `add_component.mjs` 逐区填充 → **validate + lint 双门禁**(结构合法性 + 行为规则零 ERROR)→ `render_diff.mjs` 视觉对比修正(核心闭环)→ 交付。交付时判断:本卡是否首次出现的布局/交互模式?是则跑 `register_card_template.mjs --id <cardId> --check`,确认新颖后经用户同意登记入模板库。改卡用 `add_component.mjs` / `card_node_models` 定位;范式读 `recipe_snippets/`;校验-修复用 `autofix_schema.py` 压成 1 轮。
224
- >
225
- > **⚡ 输出格式(P0 优化,token 关键)**:`schema.json` 是编辑器持久化格式(单卡可达百 KB,充斥可推导 `itemId` / 空 `itemDefaultValue` / 空数组)。**AI 不要直接吐完整 schema**——改为输出 **紧凑 IR(Intermediate Representation)**,再由 `expand_ir.mjs` 展开成完整 schema(内置 `itemId` 推导、UI 同目录切图汇聚、`validate`/`lint` 双门禁)。节点树实测压缩 **4.4–4.8x**,建新卡省略 `dictionary` 整体省 **78–81%** token,视觉闭环每轮也只传 IR。字段契约 + 示例见 `references/ir-format.md`;上游 `sketch-spec-to-card/scripts/build_prompt.py` 已向 AI 下发同一 IR 契约。
143
+ ### Workflow A — AI 读图 + 整树 IR + 视觉闭环(唯一建卡主路径)
226
144
 
227
- > **设计稿归档(自动)**:生成卡片时,脚本会自动把对应的 `card-*.png` 复制到卡片目录并重命名为 `<卡id>.png`(复制而非移动,UI 原件保留)。`iterate.mjs` 用 `--design` 的 `card-N.png`;`expand_ir.mjs` 支持可选 `--design`;`generate_cards.mjs` 按多卡序号自动匹配画板内 `card-N.png`。最终卡片目录形如 `src/cards/<id>/{schema.json, data.json, <id>.png}`。
145
+ 分工:**AI 主判「看图理解布局 + 语义裁决」**,工具做「机械构建 + 校验 + 渲染」。层级由 `spec_tree.mjs` 从 elements 矩形包含关系几何重建(MeaXure layers 图层树≠卡片拓扑,已废弃)。
228
146
 
229
- ### Workflow A — AI 读图 + 工具辅助 + 视觉闭环(推荐路径)
230
-
231
- 当上游 `sketch-spec-to-card` 已输出 `UI/<画板>/spec.json`(含 `elements` 几何信息)时,由 **AI(多模态)读图理解布局 + 工具辅助构建 + 视觉对比闭环**,而非用确定性脚本猜布局。核心分工:AI 做「看图理解布局」(它擅长的),工具做「机械构建+校验+渲染」(确定性的)。布局层级由 `spec_tree.mjs` 从 `elements` 的矩形包含关系几何重建(2026-08-31 决策 B:MeaXure layers 是纯视觉图层树,不等于卡片拓扑,已废弃)。
232
-
233
- > **工具分工一览**:
234
- > | 角色 | 工具 | 用途 |
235
- > |---|---|---|
236
- > | **AI 主判** | 读 `card-1.png` + `spec_tree.mjs` 输出 | 理解布局层级、识别语义区、判断组件映射 |
237
- > | **构建** | `scaffold_card.mjs` → `add_component.mjs` | 机械组装骨架、逐个插入组件(自带离线硬校验) |
238
- > | **校验(双门禁)** | `validate_schema.py` + `lint_card.mjs` → `autofix_schema.py` | 结构合法性(validate 0 ERROR)+ 行为规则(lint 0 ERROR:flexShrink/格式/Icon click/R9 等)→ 自动修 itemValueFrom/itemId |
239
- > | **视觉闭环** | `render_diff.mjs` | 渲染 vs 设计稿并排对比 → AI 看差异 → 修正 → 再对比 |
240
-
241
- **步骤(6 步闭环)**:
242
-
243
- #### 语义裁决协议(AI 主判,脚本只提供证据)
244
-
245
- 脚本可以给出候选信号,但**组件语义最终由 Agent 结合截图和层级树裁决**,禁止把业务词、画板名或某个项目的文案写进生成器。每个可见簇先归入:
246
-
247
- ```text
248
- static-text / display-value / input / action / navigation / list / media / decoration
249
- ```
250
-
251
- 裁决必须同时记录三类证据:视觉形态、标注几何、预期交互。至少两个信号一致才可定稿;冲突时以截图中的可交互控件和内容承载方式优先,层级名称只作参考。特别约束:
252
-
253
- - **展示值 vs 输入值**:大号货币符号 + 数字 + 右侧编辑/箭头控件,应判为 `input`;用 `Input` 承载数值,货币符号独立为 Text 前缀。输入框高度读标注或按字号行高推导,不得写死整卡高度。
254
- - **单一消费**:一个源文本只能被一个语义块消费。列表标题、按钮文案、主金额、辅助文案不得重复渲染;发现重影时先检查语义块重叠消费,再调样式。
255
- - **分段壳只消费一次**:顶部小高度壳若几何包含两个及以上 Tab 文本,按 segmented control 处理,不再同时作为顶部装饰背景。控件高度用「文字上边距 × 2 + 文字高度」推导,行与子项显式垂直居中;选中/未选中底色优先读壳和标注,缺失时按标注缺陷降级。已导出且像素身份可信的 Tab 背景切片作视觉层,其内置文字不得再显示一遍;保留透明 Text 只作为无障碍与点击状态层。
256
- - **文字底壳跟随标注**:文字外缘附近的小型纯色 shape 是该文本的背景壳;宽度、高度、圆角和颜色必须一起迁移到包裹容器,不能只还原文字。大面板、列表壳和顶部整卡壳不得因局部包围关系被误判为文字底壳。
257
- - **几何顺序**:顶层语义块按标注 y 插入,不按脚本段落顺序输出。同行判定必须消费原始标注元素;主金额合成片段不得复用为组标签;列表/重复媒体带内的分隔线留在项模板里,不提升为顶层 divider。
258
- - **重复控件归模板**:同一右缘、同尺寸、等距重复的小控件是列表/重复行模板的一部分;必须由 ListLayout/重复项模板承载,禁止同时作为孤立媒体块再次渲染。副金额、按钮、表单值等已消费文本也必须从列表标题候选中互斥排除。
259
- - **间距只计一次**:相邻语义块的标注间距不能同时塞进上一块底部和下一块顶部。列表标题、列表项和查看更多必须注册语义边界;列表行内边距由“标注行高 - 内容高”推导,ListLayout 行间距显式归零。
260
- - **缺失容器高度用子元素推导**:面板标注缺 height 时,取已消费子元素最大底沿加面板 padding 作为语义底沿;后续按钮、标题和列表间距按最近底沿计算,不用段落级“上一文本”跳过中间媒体。
261
- - **高度由内容撑开**:Root/普通容器用 `auto`;Input、Image、Icon、Divider 和单行 Text 等叶子控件允许从标注读显式高度。Text 高度来自标注 rect,不改 `lineHeight`;纵向节奏仍由容器 padding 控制。
262
- - **媒体缺陷降级有序**:紧凑媒体缺导出切图时先消费上游 `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}`,重复项模板继承标注字号/字重/颜色。
263
- - **面板重叠有界修正**:只有轻微重叠可把面板流式高度截到下一重叠顶边,且不超过 `max(16px, 25% 面板高度)`;列表等完整业务容器不裁剪。所有 `Text` 保持不可压缩,对齐决定辅助 padding 只落在必要一侧。
264
- - **垂直节奏用 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 兜底。
265
- - **裁决留痕**:语义结论写进训练报告或 lessons,不写入 schema 的非 DSL 字段。
266
-
267
- **步骤 1 — 理解(AI 多模态)**:
268
- ```bash
269
- # 读设计稿截图 + 层级树(AI 同时消费两种输入)
270
- node scripts/spec_tree.mjs --spec UI/<画板>/spec.json --card 1
271
- # 同时查看 card-1.png(设计稿聚焦图)
272
- ```
273
- AI 读 `card-1.png`(视觉)+ `spec_tree.mjs` 输出(层级树),输出 3-5 行中文布局理解:
274
- > "这张卡是:顶部标题区(标题+关闭按钮),中部列表区(3 个同构列表项,每项左 Logo+中文本),底部'查看更多'文本按钮。"
147
+ **语义裁决协议(AI 主判,脚本只提名)**:每个可见簇归入 `static-text / display-value / input / action / navigation / list / media / decoration`;记录「视觉形态 + 标注几何 + 预期交互」三类证据,≥2 信号一致才定稿,冲突以截图中可交互控件与内容承载方式优先;裁决留痕进训练报告/lessons,禁写 schema 非 DSL 字段。**细则(展示值 vs 输入值/单一消费/分段壳/文字底壳/重复控件归模板/间距只计一次/高度内容撑开/媒体降级/垂直节奏等)见 `references/semantic_adjudication.md`(步骤 1 前必读)**。
275
148
 
276
- **步骤 2 — 骨架(scaffold)**:
149
+ **步骤 1 — 理解**:
277
150
  ```bash
278
- node scripts/scaffold_card.mjs --intent <按§7.5选> --id <新卡id> --name <名称>
151
+ node scripts/spec_tree.mjs --spec UI/<画板>/spec.json --card 1 # 层级树;同时读 card-1.png 聚焦图
279
152
  ```
280
- 秒出合法最小骨架 + 自洽 data.json(零手写样板)。
153
+ AI 输出 3-5 行中文布局理解(顶部…/中部…/底部…)。
281
154
 
282
- **步骤 3 — 逐区填充(AI 主判 + add_component)**:
283
- AI 从 `spec_tree.mjs` 输出的层级树逐区读取子元素 → 每个区域用 `add_component` 逐个插入:
155
+ **步骤 2 — 整树 IR 一次下发(唯一主路径)**:一次性写出整卡紧凑 IR(契约 `references/ir-format.md`;节点树压缩 4.4-4.8x,省 token 78-81%)再机械展开:
284
156
  ```bash
285
- node scripts/add_component.mjs --id <卡id> --parent <componentId> --className <X> [--text "文案"] [--click]
157
+ node scripts/expand_ir.mjs --ir .cache/ai-scratch/<卡id>.ir.json --out src/cards/<卡id>/
286
158
  ```
287
- 样式值(颜色/字号/间距/圆角)**直接从 spec element 的 style 读,不猜**。文案绑定 `$变量`;运行期数据(金额/日期/姓名/卡号等)必须提取为 inputParams(rules.md §三);中文+数字混排串按 rules.md §十四 拆「静态文案 + 数字变量 + 表达式拼接」(金额存原始数字、显示走格式化表达式,AI 语义判定提取与否)。
159
+ - 1 次下发替代 O(N) 次 add_component 往返(单卡 20+ LLM 回合的主因);add_component 退役为改卡专用,scaffold 仅存 blank 调试;截断/大卡 `ir_merge.mjs` 分片(幂等+原子,**小卡禁拆**——每片 +40-140s;支持 `{parts:[…]}` 捆绑包:一次 fs_write 写多片 + 一次 merge 全收,幂等粒度=整包文件名)。
160
+ - 样式值从 spec element style 读不猜;运行期数据必须绑 inputParams(rules.md §三);中文+数字混排按 rules.md §十四拆「静态文案+数字变量+表达式」。
161
+ - 视觉迭代修改用 `ir_patch` / `iterate.mjs --set` 点路径修,**不整份重写 IR**(`references/iteration_protocol.md`)。
288
162
 
289
- **步骤 4 — 校验双门禁(validate + lint,渲染前必须全过)**:
163
+ **步骤 4 — 校验门禁(渲染前必须全过)**:
290
164
  ```bash
291
- # 门禁 1:结构合法性(字段完整/itemId 正确/itemValueFrom 不缺)
292
- python3 scripts/validate_schema.py src/cards/<id>/schema.json # 必须 0 ERROR 0 WARN
293
- node scripts/derive_data_json.py src/cards/<id>/schema.json --check # data.json 一致
294
- python3 scripts/autofix_schema.py src/cards/<id>/schema.json # 一键补 itemValueFrom(如有遗漏)
295
-
296
- # 门禁 2:行为规则(渲染前拦截格式错误,避免到截图才发现)
297
- node scripts/lint_card.mjs --id <卡id> # 必须 0 ERROR
298
- node scripts/preflight.mjs --spec UI/<画板>/spec.json --id <卡id> # 必须 0 ERROR(一键 10 项机械检查)
165
+ node scripts/gate.mjs --id <卡id> [--spec UI/<画板>/spec.json] [--json] # ⚡ autofix→validate→derive→lint→preflight 一条命令,任一 ERROR exit 1
299
166
  ```
300
-
301
- > **lint 双门禁不可跳过**。lint 现已把以下规则从 WARN 升级为 **ERROR**(渲染前拦截,不等到截图阶段才发现):
302
- > - **R17**:叶子节点(Text/Icon/Image/MultiText)缺 `flexShrink:0` → 渲染器默认 `flexShrink:1` 会压缩元素(文本换行/图标变形)
303
- > - **R22**:`width:100%` + `flexShrink:0` 冲突 → 固定宽兄弟被挤出
304
- > - **R35**:受外宽约束(width 非 auto)的 Row/Column 有 2+ 并列子节点且全部 `flexShrink:0` → 定宽累加溢出
305
- > - **R-FMT1**:borderRadius 用元组格式 `{12,12,12,12}` → 渲染器无法解析,应为标量 `"12"`
306
- > - **R-FMT2**:padding/margin 用元组格式 `{16,0,12,0}` → 预览器解析失败,应拆为单独属性
307
- > - **R-FMT3**:Icon 缺 click eventaction → onClick 双触发
308
- > - **R-FMT4**:MultiText itemList 项 `itemValueFrom:"1"` 缺 itemDefaultValue → 渲染空
309
- > - **R9/R10**:Root(view)设非零 padding/margin → 裁切后间距归零铁律
310
- > - **R38**:任何节点 marginTop 为负值 → 标题/文本与装饰条重叠必须用 AbsoluteLayout + position.top(pitfalls §R42)
311
- > - **R43**:卡片背景透明 → 必须设非透明背景色,优先从标注 cardFill 取色,缺失则从截图采样(pitfalls §R43)
312
- > - **R44**:borderRadius/border/shadow 有值但缺类型开关(borderRadiusType/isBorder/isShadow)→ 值被静默丢弃,渲染无效果(pitfalls §R44,expand_ir 自动补)
313
- > - **R45**:元素宽度靠直觉猜或依赖默认(Row 默认 100% 撑满)→ 必须从标注或截图像素扫描实测(pitfalls §R45)
314
- > - **R46**:装饰元素写死固定宽度而不绑定表达式 → 与文本联动的装饰条应表达式化(pitfalls §R46)
315
- > - **R47**:视觉迭代盲目改间距而不先像素分析 → 必须先像素扫描定位差异根因再改(pitfalls §R47)
316
- > - **R48**:根节点核心样式属性(gap/background/padding/borderRadius)缺失显式默认值 → expand_ir ensureRootDefaults 自动补入(仅根节点)(pitfalls §R48)
317
- > - **R50**:slotScope 内绑定用 `$$.field` 而非 `$field` → 冗余写法,应为 `$fieldName`(lint 拦截)
318
- > - **R51**:customAction 读 `this.data.<key>`(key 未被任何 Input value 绑定)→ this.data 仅用于 Input 实时输入值,运行态变量一律 `this.state`(lint ERROR 拦截)
319
- > - **R52**:v-show 显隐设置显示名 `condition.name` 缺失/为空(validate ERROR,必填不能为空);命名不合规——表达式 <15 字符应与表达式一致、>=15 字符应设合适命名不得抄表达式(lint WARN 提醒)
320
- > - **R53**:MultiColumn/ListLayout/GridLayout/AbsoluteLayout/Canvas/Map/Video/ViewContainer 直接设 v-show 显隐条件 → 铁律要求外套一层 Row/Column 并把 v-show 设在包裹层(lint ERROR 拦截);其他节点可直接设置
321
- > - **R54**:ListLayout/GridLayout item 事件直接改绑定数据(this.setData/setRootData/state 赋值)→ 铁律要求 `postMessage('globalSet', patch)`(patch 与 setData 入参一致)+ 根节点 onReady `addEventListener('globalSet')` 代写;用 globalSet 但根无监听同样 ERROR(lint 拦截)
322
- > - **R55**:customAction 事件脚本出现 `$$`(含 `this.state.$$` / `$$.field` / 「$$ 兜底判断」)→ 铁律要求 slotScope item 字段在事件里直接 `this.state.<field>` 读取(平台保证展开到 state),禁止任何 `$$` 写法(lint ERROR 拦截)
323
- > - **R57**:`style.other.opacity` 用表达式/绑定(`itemValueFrom:"1"`)→ 移动端不支持透明度表达式,必须走透明度切换固定范式(静态变体 ×N + 等宽高 overflow:hidden 容器 + 事件上移 + 互斥 v-show;`apply_opacity_pattern.mjs` 可机械改写)(lint ERROR 拦截)
324
- > - **R62**:图表 Canvas 把坐标系/参考线(网格线/轴线/雷达环与辐射线/环形轨道)画在数据标记之上 → 铁律要求出码顺序 = 底 → 顶(参考线最底、数据标记居中、文字标签最顶);半透明数据标记把 opacity 乘进颜色/渐变(代理 ctx 无 globalAlpha)(生成器 `chartPaintLayers` 强制分层 + `tools/tests/chart-layer-order.test.mjs` 护栏)
325
- - **R56**:customAction 事件脚本用 `window.ynet` 前缀或 `typeof window` 存在性守卫调 JSAPI → 铁律要求直接裸调 `ynet.callJSAPI(name, params, cb)`(ynet 为平台注入全局,无需前缀与守卫)(lint ERROR 拦截)
326
- > - **R49**:inputParams 缺少必填字段(key/name/type/defaultValue/desc/rule/children)→ validate ERROR;expand_ir 自动补全(pitfalls §R49)
327
- >
328
- > **为什么 lint 必须在 render 之前**:这些错误在 `validate_schema.py` 里不会被检出(validate 只查结构字段),但会在渲染时造成可见的视觉问题(文本压缩、间距异常、图标不响应)。lint 把这些问题前移到渲染之前,避免到截图比对阶段才发现。
167
+ > **lint(46 条 ERROR 渲染前拦截)**:报错自带规则号与修法,按报告逐条修,无需预背。分类:压缩/溢出(R17/R22/R35/R63/R38)、格式(R-FMT1/2、R44、R49)、交互事件(R-FMT3、R51/R54/R55/R56)、显隐透明(R52/R53/R57)、切图资产(R31b/c)、画布层序(R62)、背景(**R43 禁完全透明,半透明合法**)。全集按号查 `generation_pitfalls.md` / `component_rules.md`。lint 在 render 前:validate 只查结构,这些错渲染时才炸。
329
168
 
330
169
  **步骤 5 — 视觉闭环(核心)**:
331
170
  ```bash
332
- # ⚡ 一条龙闭环(推荐):单进程 expand → lint → render → pixel-diff,输出 JSON 摘要
333
- node scripts/iterate.mjs --ir <IR.json> --out src/cards/<卡id>/ --design UI/<画板>/card-1.png --width 343
334
-
335
- # IR 微调直接随迭代下发(原子写回 IR,可重复多次 --set;点路径数字段=数组下标):
336
- node scripts/iterate.mjs --ir <IR.json> --out src/cards/<卡id>/ --design <card-1.png> \
337
- --set 'children.0.children.2.children.1.s.margin.marginTop="7"'
338
-
339
- # 量化偏差比对(防肉眼误判/幻影调查):文本带 dy/dh + 总高 + 分区主色直方图 → 结构化 JSON
340
- node scripts/diff_bands.mjs --id <卡id> --design UI/<画板>/card-1.png --tol 2
341
-
342
- # 或逐步骤(慢):渲染当前卡 + 设计稿 → 并排对比图(左=设计稿灰标签,右=渲染蓝标签)
343
- node scripts/render_diff.mjs --card <卡id> --design UI/<画板>/card-1.png --out .card-diffs/<卡id>_diff.png
171
+ # ⚡ 一条龙:expand + lint + render + pixel-diff(~2s,快 5×);--set 原子点修 IR
172
+ node scripts/iterate.mjs --ir <IR.json> --out src/cards/<卡id>/ --design UI/<画板>/card-1.png --width 343 [--set 'children.0…marginTop="7"']
173
+ node scripts/diff_bands.mjs --id <卡id> --design <card-1.png> --tol 2 # 量化偏差(dy/dh/主色直方图),防肉眼误判
344
174
  ```
345
- `iterate.mjs` 单次调用把 expand + lint + render + 像素对比合成一步完成,并输出结构化 JSON(`pixelDiff.match` 百分比 + `lint.errors` 计数 + `render.ms` 耗时)。比逐脚本调用快 **5×**(~2s vs ~8s),消除了 3 次 Node 进程冷启 + 1 次 Chrome 冷启。
346
- 若 `--design` 指向 `card-N.png`,结果 JSON 的 `designImage` 字段同时给出归档后的 `<卡id>.png` 路径。
347
-
348
- > **差异定位提速(2026-09-06)**:结果 JSON 自动带 `pixelDiff.hotspots`——4×8 网格中像素差占比 >3% 的前 ≤8 个热区格子(坐标已换算为 1× 设计 px),AI 直接读坐标定位差异区域,多数轮次不必打开整张并排对比图。加 `--elements` 还会输出渲染卡的元素盒数组 `elements`(keep-alive 模式请求体加 `"elements": true`),可把热区坐标落到具体组件上,零额外 Chrome 冷启。
349
-
350
- AI 读 JSON 摘要和对比图,逐项列出差异(位置偏移/缺元素/颜色不对/间距不对/字号不对)→ 逐项修正 IR → 重跑 `iterate.mjs`。**重复 2-3 轮直到 `pixelDiff.match > 85%` 且 `lint.errors === 0`**。
351
-
352
- > **批量迭代 keep-alive 模式**:连续生成多张卡时,用 `iterate.mjs --keep-alive` 复用 Chrome 进程,每轮降至 ~0.6s:
353
- > ```bash
354
- > node scripts/iterate.mjs --keep-alive
355
- > # stdin 逐行输入 JSON:{"ir":"...","out":"...","design":"...","width":343}
356
- > ```
357
-
358
- > **多轮迭代的节奏**:第一轮修系统性问题(整卡偏移、缺失区域、主色错误);第二轮修细节(间距、字号、对齐);第三轮验收(逐元素比对)。通常 2-3 轮即可收敛。
359
-
360
- **步骤 6 — 交付**:确认 `validate` 0 ERROR + `lint` 0 ERROR + `render_diff` 视觉一致后,卡片即完成。可用 MCP `card_write({id, schema, verify:true})` 走完整收口(validate + render_status 一并返回)。
361
-
362
- > **validate 现已把两类隐形 bug 升级为 ERROR**:① 缺失/空串 `itemValueFrom`(运行时静默丢弃该属性);② 样式子项 `itemId` 错配(如 `flexDirection` 写 `002` 而非 `006`)。`autofix_schema.py` 可一键补。生成避坑全集见 `references/generation_pitfalls.md`。
175
+ 结果 JSON:`pixelDiff.match` + `hotspots`(>3% 差异热区,--elements 落到具体组件)+ `fidelity` + `stalled` + `contributors`。逐项修 IR 重跑,**2-3 轮至 match>85% 且 lint 0**。
363
176
 
364
- > **纯截图/手稿建卡(无 spec.json)**:跳过 `spec_tree.mjs`,AI 直接读截图(步骤 1 只用图片),其余步骤 2-6 相同。这种情况 AI 完全靠视觉判断布局与样式,精确度取决于图片清晰度。
177
+ > **迭代纪律(硬性;全集 `references/iteration_protocol.md`)**:每卡 ≤15 轮;stalled(连续 3 轮未刷新最好成绩,含震荡下滑)即 `ir_restore.mjs --best` 回退;要素线先骨架后样式(gate.stage=structure 先补要素别调像素,丢要素的一轮不算进步);一轮只改 `contributors[0].elements[0]` 一个属性;多卡连跑 `--keep-alive` 每轮 ~0.6s。
365
178
 
366
- **批量产线(多画板 → 多卡)**:当需要一次性处理多个画板时,可先用 `generate_cards.mjs` 批量生成粗骨架,再对每张卡走步骤 5 视觉闭环精修:
367
- ```bash
368
- node skills/dsl-card-generator/scripts/generate_cards.mjs --ui <UI目录> --out <输出目录> [--only <名称子串>] [--force]
369
- ```
370
- > **字段命名复核(生成后必做)**:通读 `dictionary.inputParams` 与设计稿标签,确认每个 key 语义化(camelCase 英文,禁止 `obj_N`/`amount_0` 序号名)。词典 `references/field_name_dict.json` 覆盖通用标签;新业务标签按实际含义补语义化 key。命名规则见 `references/field_naming.md`。
371
- ### Workflow A.5 — 消费 `sketch-spec-to-card` 的画板分类(overlay_dialog / subcard / card)
179
+ **步骤 6 — 交付**:validate 0 + lint 0 + 视觉一致即完成;MCP `card_write({id, schema, verify:true})` 完整收口。首次出现的布局/交互模式 → `register_card_template.mjs --check` 新颖性检查,经用户同意登记模板库(§7.5)。
372
180
 
373
- 上游 `sketch-spec-to-card` 已对每张画板判 `artboardType`(`card` / `overlay_dialog` / `subcard`),下游据此分流(用户 2026-08-13 补充):
181
+ > **纯截图建卡(无 spec.json)**:跳过 spec_tree,AI 直接读截图,其余相同。
182
+ > **批量产线**:`generate_cards.mjs --ui <UI目录> --out <目录> [--only <子串>]` 批量粗骨架后逐卡走步骤 5 精修;生成后按 `field_naming.md` 复核字段命名(禁 obj_N/amount_0)。
374
183
 
375
- 1. **`card`(普通卡片主体)**:严格还原设计稿,按 Workflow A 1-6 正常生成本卡 schema。多形态(Tab/条件/表单/时序)**推荐走 Workflow A 多模态分步生成**(按 `clusters.json` 维度装配 TabBar + `v-show` / Input 绑定变量);如需批量产线脚手架,可用 `generate_cards.mjs`(见 Workflow A 批量产线段)。
376
- 2. **`subcard`(带标题+关闭按钮的模态子卡 → alertCard 父子卡)**:
377
- - 生成为一张独立 **Dialog 卡**(pageId 建议 `<slug>Dialog`),切图/样式按子卡自身内容正常还原(忠实设计稿)。
378
- - 标题栏关闭按钮(×,glyph `times`)→ `closeAlertCard`;父卡(上游 `clusters.json` 的 `parentKey` 或 `prompt.md §6`)用 `alertCard` 打开。范式见 `recipes.md §2` + 片段 `recipe_snippets/alertCard-pair.json`。
379
- 3. **`overlay_dialog`(弹框/确认框/消息提示/toast → alertDialog JSAPI)**:**不生成独立卡片**。将该画板对应的 `alertDialog` 调用补到**父卡**(上游 `clusters.json.overlays[].parentKey` / `prompt.md §4` 指示的触发卡)的对应按钮 / 条件事件中。文案取自上游 `prompt.md §3` 提取的 title/message/按钮;视觉不必还原,以平台原生弹框为准。范式见 `recipes.md §12` + 片段 `recipe_snippets/alertDialog-call.json`。参考存量 `budgetAnalysis`(温馨提示)、`creditCardPayment`(还款前二次确认)。
184
+ ### Workflow A.5 — 画板分类分流(artboardType)
380
185
 
381
- > 边界:overlay_dialog 的 `prompt.md` 是「交互提示文档」而非建卡 prompt,下游**不要**对其跑 scaffold/validate 产卡;subcard 的 `prompt.md` 是常规建卡 prompt + 末尾 `## 6. 弹窗形态`,正常产卡再补 alertCard 调用即可。
186
+ 1. **`card`**:正常 Workflow A;多形态(Tab/条件/表单/时序)按 clusters.json 维度装配。
187
+ 2. **`subcard`**(模态子卡):独立 Dialog 卡(pageId `<slug>Dialog`)忠实还原;关闭(glyph `times`)→ closeAlertCard;父卡 alertCard 打开(recipes §2 + `recipe_snippets/alertCard-pair.json`)。
188
+ 3. **`overlay_dialog`**(弹框/toast):**不产卡**——alertDialog 调用补到父卡(clusters.json.overlays[].parentKey)对应事件;文案取上游 prompt.md §3。
189
+ 4. **`text_only`**(纯文本板):**不产卡**(上游 status=skipped),勿人工绕过。
190
+ > prompt.md 边界:overlay_dialog=交互提示文档(勿产卡);subcard=常规建卡 prompt;text_only=只写判定依据。
382
191
 
383
192
  ### Workflow B — 编辑已有卡片
384
193
 
385
194
  1. 读卡:`card_read(id)` 或直接读 `src/cards/<id>/schema.json`(及 data.json)。
386
- 2. 改结构:用 `node scripts/add_component.mjs --id <id> --parent <componentId> --className <X> [--text "文案"] [--click]` 插新组件,或直接调 MCP `card_add_component({id,parent,className,text,click})`(插入后自带离线硬校验);或按 `card_node_models(id)` 定位字段改值。保留未涉及节点与孤儿字段。
195
+ 2. 改结构:`add_component.mjs --id <id> --parent <cid> --className <X> [--text "…"] [--click]` 或 MCP `card_add_component`(自带离线硬校验);`card_node_models(id)` 定位字段。保留未涉及节点与孤儿字段。
387
196
  3. 同步数据:增删改 inputParams[] 时 data.json 同步(autofix 或 derive)。
388
- 4. 闭环:`card_write({id, schema, verify:true})`;`card_screenshot(id)` 复核视觉。
197
+ 4. 闭环:`card_write({id, schema, verify:true})`;`card_screenshot(id)` 复核。
389
198
 
390
199
  ### Workflow C — AI 快速开发
391
200
 
392
- - 从脚手架/证据起步,绝不从空文件手写(先 `scaffold_card.mjs`)。
393
- - 描述**组件组成**而非像素("顶部 Row 放标题+关闭 Icon,下面 ListLayout 列 3 任务,每项左 Icon 右 Text"),产出远好于"做一个好看的卡"。
394
- - 生成—校验—修复闭环:`scaffold_card` / 改 → `card_write({verify:true})` → 有误 `autofix_schema.py` 1 轮清零 [ERROR] → `card_screenshot` 确认。
201
+ - 从脚手架/证据起步,绝不从空文件手写;描述**组件组成**而非像素("顶部 Row 放标题+关闭 Icon,下面 ListLayout 列 3 任务")。
202
+ - 闭环:scaffold/改 → `card_write({verify:true})` → 有误 `autofix_schema.py` 1 轮清零 → `card_screenshot` 确认。
395
203
 
396
204
  ## 9. 本地预览工作流
397
205
 
398
- - **自包含截图(默认)**:`node scripts/render_card.mjs --id <id> --width 375 --out render.png` 用 `vendor/preview/` 构建产物 + 系统 Chrome(CDP)截图,随机空闲端口用完即关,不依赖宿主 `npm run dev`、不占 5180/8787。构建产物由根目录 `npm run build:skill-preview` 手动生成。
399
- - **宿主 dev server(可选集成)**:若宿主项目另有 `npm run dev`(vite,--mode cb)预览服务器 + `/__cards` `/__card/<id>` `/__save` `/__sync` 接口,可实时从磁盘读卡片(GET `/__card/<id>`),不缓存;卡片列表走 GET `/__cards`。alertCard 也实时读目标卡 `/__card/<cardCode>`。改 schema.json / data.json 后刷新即可。复制本 skill 到其他项目时不要求这些接口。
206
+ - **自包含截图(默认)**:`node scripts/render_card.mjs --id <id> --out render.png` 用 vendor 构建产物 + 系统 Chrome(CDP),随机端口用完即关,不占 5180/8787;产物由根目录 `npm run build:skill-preview` 生成。
207
+ - **宿主 dev server(可选)**:宿主有 `/__cards` `/__card/<id>` 接口时可实时读卡;skill 复制到其他项目不要求这些接口。
400
208
 
401
209
  ## 10. 校验脚本
402
210
 
403
- ### r057 保真规则(标注优先)
404
-
405
- 1. **孤立媒体间距不对称**:上一元素底沿、媒体上下沿、下一元素顶沿都必须分别消费;不得把上下空隙平均分配后撑高或上移图标。
406
- 2. **文字底壳全覆盖**:普通辅助文案、金额后文案和同行头部文案共用同一底壳/高亮壳消费路径。小于文字高度的底色条映射为文字容器内的底部渐变高亮,不丢弃、不另画独立色块。
407
- 3. **透明形状样式不丢**:按钮或形状的 `opacity` 必须进入 CSS 颜色/渐变;预览器不保证消费组件级 opacity 时,应把透明度乘入渐变 stop。
408
- 4. **全卡渐变形**:覆盖卡片 90% 以上的渐变形映射为卡片容器 `backgroundGradient`,禁止塞进固定高度顶部装饰容器,避免内容被裁切。
409
- 5. **语义前缀外置**:展示层动作前缀只允许来自字段词典配置;生成器逻辑不得内置行业名词、风格名或卡片名。
410
- 6. **行内值几何三规则**(r147):① 行内左对齐值行(label 与值同侧 flex-start 流)的右 padding 恒为 0——值右缘只是内容的自然结束位置,按值缘推 padding 会把可用宽度压窄造成文本裁切;② 多行值(值高 > 1.6×字号)的标签与值首行顶对齐(align: flex-start),居中会把标签推到值块中部;③ 值文本右侧紧邻(≤40px)的小图标是行内尾随图标(值流一部分),值列须 hug 内容渲染,图标紧随值右沿而非行右缘——区别于贴卡片右缘的行尾 affordance。
411
- 7. **文字壳三态**(r147):文字底壳按 spec 分三态——填充壳(backgroundShell,壳 fill 的 **opacity 必须乘入背景色**,半透明红壳=粉徽章底,丢 alpha 会变实色块吞掉同色文字)、描边壳(outlineShell,isBorder+圆角 pill,如标题行右侧的描边操作钮)、高亮壳(highlightShell,文字底部渐变高亮)。同行分组判定:y 容差 或 垂直中心差 ≤ 较矮元素高度×0.75(大小字号混排的同一视觉行)。
412
- 8. **占位行不剥夺行操作与行内反馈**(r146):占位(placeholder)只说明值未填。占位表单行仍必须渲染:① 行尾操作件(affordance 图标,候选池对 slice 与 symbol 同等对待——同一视觉图标在标注里可能是任一形态);② 行尾 pill 操作按钮(如 获取验证码);③ 值下方的行内反馈子值(如红色错误提示),右簇改纵向排布 [输入行, 子值行],子值与值列右对齐,标签与输入行顶对齐。
413
-
414
- - scripts/validate_schema.py path/to/schema.json:校验单卡(JSON/结构/组件嵌套/已知类/属性样式键/itemValueFrom/v-show/data.json 一致性/componentId 唯一/指令/事件/action 形状)。加 --no-data 跳过 data.json。**加 `--topology` 可选触发拓扑合规性检查**(基于人力卡统计规则库 `references/handcrafted/patterns.json`,对比生成卡与人力卡范式偏离度,输出 INFO 级 hints 不阻断 validate)。`--json --topology` 输出机器可读 `topologyHints` 数组供 dev-server/CI 消费。
415
- - scripts/check_topology.py path/to/schema.json:独立拓扑合规性检查器(纯建议层,永远不抛 error)。检查维度:骨架稀有度(父节点子序列在人力卡中支持度)/ slot 约定偏离 / 缺失必备 style itemKey / 变量命名非 ASCII / 文本绑定 $expr 比例偏离。`--quiet` 只输出摘要。
416
- - scripts/validate_all_schemas.py src/cards:全量校验。
417
- - scripts/derive_data_json.py path/to/schema.json --check / --overwrite:派生/检查 data.json。
418
- - scripts/inventory_schemas.py src/cards --examples:盘点当前 DSL 面(组件类/属性/样式/指令/事件/action 频次 + 示例),同步新导出后先跑这个。
419
- - scripts/scaffold_card.mjs --intent <blank|list|detail|form|dialog|tab|canvas-head> --id <卡id> --name <名称>:秒出合法最小骨架 + 自洽 data.json(提速核心,形状来自 lib/scaffold.mjs)。
420
- - scripts/extract_handcrafted.py:从 src/cards 抽取人力卡拓扑/参数/变量规则,刷新 `references/handcrafted/patterns.json`(统计规则库)与 `examples_index.json`(示例库索引)。src/cards 增删卡后重跑即可。
421
- - scripts/add_component.mjs --id <卡id> --parent <componentId> --className <Text|Row|...> [--text "文案"] [--click] [--set-prop key=value ...]:向容器插入合法最小组件,只增不改;`--set-prop`(可重复,值以 `$` 开头判为绑定)用于视觉闭环单点改属性,免去每轮全文重写 schema。
422
- - scripts/autofix_schema.py <schema.json> [--check]:机械修复 itemValueFrom=1 缺 itemDefaultValue、重复 componentId、data.json 漂移,复校验并报告。
423
- - scripts/build_card_index.mjs:重算 references/card_index.json(意图路由选近邻用)。
424
- - **scripts/route_input.mjs <输入路径> [--json]**:建卡输入形态路由(Workflow 0 的机械判定器)。判 `meaxure-export`(index.html 内嵌 artboards + preview/ + assets/)/ `ui-bundle`(manifest.json,或 spec.json + card-N.png)/ `card-dir`(schema.json → Workflow B)/ `image`(单图 → Workflow A)/ `unknown`,输出判定证据 + 下一步命令。MeaXure 导出会比对 `UI/*/manifest.json` 的 `export` 字段判断是否已前处理(已处理则跳过管线、直接给存量产物路径),未处理则给出 `sketch-spec-to-card` 的 `parse_meaxure.py --draft` + `cluster_states.py --out` 两条命令。退出码 0=已路由 / 3=unknown / 1=用法或路径错误。
425
- - **scripts/apply_opacity_pattern.mjs --id <卡id> [--check|--dry] [--json] [--node <cid>] [--container Row|Column]**:把透明度表达式节点机械改写成透明度切换固定范式(R57)——解析 `COND ? A : B` / 右嵌套三元为静态变体表,生成等宽高 + overflow:hidden 的容器、事件上移、互斥 v-show;`--check` 只报告(有则 exit 1,可进 CI)。不可解析的表达式 exit 2 提示手工改写。范式见 `recipes.md §17`。
426
- - **scripts/expand_ir.mjs --ir <IR.json> --out src/cards/<卡id>/**:紧凑 IR → 合法 schema/data;自动从 IR 同目录 `slices/` 汇聚 schema 引用的静态切图到 `src/slices/`。详见 `references/ir-format.md`。
427
- - **scripts/diff_card.mjs --id <卡id> [--baseline <path.json> | --against <otherId>] [--json] [--strict]**:卡片变更对比(铁律机械兜底)。默认基线 = git HEAD;输出删除/新增/修改的 componentId 与字段级 diff。整体替换 schema 后必跑一次,确认未涉及节点真保留。
428
- - **scripts/lint_card.mjs --id <卡id> | --all [--json] [--strict]**:卡片健康度体检(≠ validate)。检查项:脚手架占位残留、未使用的 inputParams、Button 缺 click、Root 缺 background、Text 缺 font.color、Canvas 缺 onMeasured、eventKey 与组件类型不匹配等。
429
- - **scripts/extract_subtree.mjs --id <卡id> --component-id <cid> [--name <片段名>] [--out <path>]**:从已导出卡抽取任意子树,生成可复用片段(recipe snippet)。输出含完整 subtree + 自动探测的 requiresInputParams(依赖键),让范式库可持续增长(不再只有 6 个硬编码配方)。详见 references/recipe_snippets/。
430
- - **scripts/describe_card.mjs --id <卡id> [--json]**:卡片反向描述初稿生成器。自动产出元信息 / 组件树 ASCII / 数据来源 / 事件流四节,作为自然语言摘要的机器初稿;一句话定位与可改范围需人/AI 润色。模板见 references/card_to_summary.md。
431
- - **scripts/preflight.mjs --spec <spec.json> --id <卡id>**:一键机械 Pre-flight(10 项自动拦截:元素计数/组件白名单/Image itemValueFrom/rect.height/切图文件名空格/Icon eventaction/百分比表达式/Divider vs Row/根样式完整性/空 itemId)。替代 AI 手动过 12 项 checklist。
432
- - **scripts/check_magic_values.mjs**:魔法值门禁(L3)。扫描 v3 生成器源码,禁止 layout 魔法值(gap/padding/borderRadius/fontSize 裸数字),必须引用 lib/layout-tokens.mjs(T.gap/T.pad/T.radius/T.font/T.color)。有违规 exit 70 供 CI。改生成器后必跑。
433
- - **scripts/check_fidelity.mjs --spec <spec.json> --schema <schema.json> [--card 1] [--json] [--font-tol 2] [--color-tol 24]**:保真度门禁(L4/fidelity)。元素级对齐断言——spec 每个文本元素应在 schema 中找到对应 Text(内容匹配),字号偏差 ≤ `--font-tol`、颜色 rgba 各通道差 ≤ `--color-tol`;缺失/超差即 exit 非 0。覆盖间距/颜色/字号这类前四层查不出的高频错误。`npm run check:fidelity` 接入 CI。
434
- - **scripts/lib/layout-tokens.mjs**:布局 Token 表(L1)。GAP/PADDING/RADIUS/FONT/COLOR 语义化常量,v3 生成器统一引用;改值单点生效。**禁止在生成器里写裸魔法值**。
435
- - **scripts/render_baseline.mjs [--snapshot|--diff] [--id <卡id>] [--threshold 0.01] [--json]**:渲染回归基线(离线独立渲染器,不依赖 dev server)。`--snapshot` 建立/刷新 `<宿主>/.card-baselines/<id>.png`(可用 `DSL_CARD_BASELINES_DIR` 覆盖);`--diff` 对比当前渲染与基线的像素差异(装 sharp 启用真实像素 diff,否则降级为字节/大小比较),差异超阈值时 exit 70 供 CI 识别。配合 §四层校验自洽说明使用。
436
- - **scripts/render_card.mjs --id <卡id> [--width 375] [--out render.png] [--format json|png]**:单卡离线截图。复用 `vendor/preview/` 构建产物(`npm run build:skill-preview` 产出),纯 Node HTTP 托管 + 系统 Chrome CDP 截取 `.phone__screen`,截图后立即关闭,不占 5180/8787、不与本地开发服务冲突。视觉迭代多轮可用 `--keep-alive`,逐行输入 `{"id":"<卡id>","outFile":"<png>"}`,stdin EOF 后自动清理 Chrome。
437
- - **scripts/render_diff.mjs --card <卡id> --design <设计图>**:并排视觉对比;省略 `--width` 时自动读取 `schema.view.width`,避免 343 卡被按 375 渲染后误判裁切。
438
- - **MCP 脚手架工具(P5,见 mcp/,直接复用 lib/scaffold.mjs)**:`card_scaffold({intent,id,name,outBase,platform,force})` 建骨架、`card_add_component({id,parent,className,text,click})` 插组件并离线校验。与上面 CLI 同源,无需 dev server。
439
- - **MCP 输入路由(见 mcp/,直接复用 scripts/route_input.mjs 的 `routeInput()`)**:`card_route_input({path})` 判建卡输入形态(`meaxure-export` / `ui-bundle` / `card-dir` / `image` / `unknown`),返回 `evidence` + `next` + `preprocess`,等价 CLI `--json`。`meaxure-export` 且 `preprocess.needed=true` 时,`next` 直接给出 sketch-spec-to-card 的两条前处理命令;已前处理过则给存量产物路径。离线,无需 dev server;`path` 支持工程根相对路径与 `~` 展开,路径不存在返回 `error:"input-not-found"`。
440
-
441
- 校验器不证明设计器里的运行时行为,风险交互仍需在平台里核对。
442
-
443
- > **三层校验自洽**:validate(结构合法)→ lint(体验达标)→ diff(铁律保留)。三者互补不重叠——validate 是 CI 硬卡点;lint 是软提醒(脚手架生成卡默认会触发若干 info);diff 是"刚改完这张卡,确认没误删什么"的回看工具。三者底层都从同一份 lib/paths.{mjs,py} 解析路径,目录重构时无需同步多处。
444
- >
445
- > **第四层:渲染回归基线**(`render_baseline.mjs`)——每张导出卡在固定视口下存 PNG 基线,改完渲染器后 `--diff` 检测像素级视觉回归。基线存 `<宿主>/.card-baselines/`(gitignored),CI 单独存 artifact。已改为离线独立渲染器,无需 dev server、不占端口。
446
-
447
- ## 11. 参考路由(按需只读需要的)
448
-
449
- - **references/rules.md**:**铁律集中陈述**(十三节:证据分级 / 不污染 DSL / DSL 契约 / 运行时作用域与 Input 回写 / 速查优先 / 验证三层自洽 / 视觉真相优先 / 远端同步前置 / 组件级硬约束 / 从脚手架起步 / 标注可信度分层 / 卡片目录命名 / **画板聚焦与卡片生成单位(多画板必须先选卡)**)。其他文档只引用,不再重复措辞。
450
- - references/dsl_structure.md:顶层结构、数据模型、data.json、view 根、组件形状、插槽/作用域、样式系统完整 itemId 表、指令(v-show)、已知事实。
451
- - references/component_specs.md:15 个可渲染组件的完整 JSON 骨架 + 属性 itemId 表 + styleState 默认 + 事件/插槽。
452
- - references/expression_engine.md:$ 表达式引擎、evalExpr、itemValueFrom 0/1/2、itemDefaultValue、itemCondition 语法、作用域插槽 $$/_index、v-show、Input 回写、表达式片段库(金额/尾号/slice/三元)。
453
- - references/amount_formatting.md:**金额处理规范**(生成器默认口径)。设计稿出现「金额 / 余额 / 还款额 / ¥」等字样 → 对应 inputParams 默认 `type:"number"` + 显示用表达式做千分号;含 canonical 表达式库(带/不带 `¥` 前缀、列表项 fallback、反向解析回数字)、与上游 `_fmt` 预格式化的取舍、data.json 一致性;§7 给「句中带金额」混排串拼接范式。中文+数字混排串的语义提取铁律见 `references/rules.md §十四`。权威范例 = multiMonthTransitionList(export-proven)。
454
- - references/event_patterns.md:eventactions 形状、eventKey/actionType、this.state/data/setData、addEventListener/postMessage、globalSet、JSAPI 调用范式、生成清单。
455
- - **references/field_naming.md**:**字段命名规范(双层机制)**。脚本层:标签词典 `field_name_dict.json`(领域通用词表)+ 值格式角色兜底,永不出现 obj_N/amount_0 序号名;智能体层:生成后按 UI 标签实际含义复核/优化 key,同步 data.json 与绑定表达式。含命名风格、禁止项、词典词表、增补方法、复核清单。**数组统计字段(枚举统计行)命名见 §3.1**。v3 直组装范式的命名细则见 `spec_to_schema_patterns.md §2.3`。
456
- - references/recipes.md:Canvas 标题头、alertCard 弹窗 + 同名 Dialog 表、列表展开收起、globalSet 子→父、金额格式化、Tab↔标题头 billMode、**纯 Tab 切换(activeTab + setData + v-show,§7)**、**背景图承载复杂背景 / 状态相关视觉(backgroundImage,§8)**、**手风琴分组折叠(icon 按钮 + 每组 hide 布尔 + globalSet 深路径翻转 + 内容 v-show,§9)**、**Canvas 甜甜圈/环形比例图(onMeasured 双弧回描 + onReady 算 ratio,§10)**、**角标/角标签(AbsoluteLayout 叠加小标 + align 选角 + 胶囊样式,§11)**、**标题+下划线重叠(AbsoluteLayout 绝对定位,禁止负 marginTop,见 recipe_snippets/title-underline-overlay.json)**、**Canvas 评分仪表盘(弧形进度环 + 真实字体中心分数,§14)**、**Canvas 五维雷达图(渐变填充 + 网格,§15)**、**维度标签列同行式/堆叠式两范式(§16)**、**透明度切换固定范式(静态变体 ×N + 等宽高 overflow:hidden 容器 + 事件上移 + 互斥 v-show,§17,铁律 R57)**,各含 export-proven 代码。
457
- - references/previewer_contract.md:**预览器行为契约**(排雷必读)。容器默认 width:100% / Column 默认 justifyContent:center / 组件默认 overflow:hidden / AbsoluteLayout 不支持 position 组(用 align+margin)/ Canvas ctx 白名单(禁 save/scale/transform/drawImage)/ fontWeight 枚举 400-500-600 / font 组无 fontFamily(特殊字体宽度匹配法或走 Canvas)/ 预览默认 line-height:1.5 / 间距微调防级联 / 量化证据优先于肉眼。
458
- - **references/spec_to_schema_patterns.md**:v3 直组装范式全集。**§19 枚举统计行(Enum-Stat Strip)**:`类型标签+数字` 枚举统计(`标签A 5 · 标签B 2 · 标签C 1 · 标签D 4`)→ 结构化数组字段 + map/join 渲染表达式,纯结构信号识别,泛用于未读分类/状态计数/分类金额/占比分布等任何枚举统计场景。**§10 动态参数提取**:姓名/卡号等运行期数据必须绑定。
459
- - references/image_to_card.md:图片→schema 管线(dsl_json 契约)、编辑器 add/edit 语义、prompt 配方、组成清单方法论。
460
- - references/slices_convention.md:**切图资产约定(issue-slices 系列)**。扁平唯一落点 `src/slices/`、schema 引用格式 `/slices/<扁平名>`、仅拷贝被引用切图(全量回归先清空再重建)、Vite 与 cardbuddy 均挂载 `/slices/*` 本地预览、排除 `99.切图/*` 导出目录切图、元素名↔导出文件名归一化匹配、常见错误与校验清单。
461
- - **references/message-center-pattern.md**:消息中心/通知列表类卡生成范式(2026-08-17 沉淀)。canonical 拓扑 + 字段命名(unreadTotal/unreadBreakdown/messageList)+ 列表项模板(红点 Divider 模拟 + 双行文本 + 箭头)+ 关键纪律(Icon glyph 有效名/padding 多值拆 4 边/slots[0].components 形式)+ 端到端构造脚本范式。**分类统计副标题(`标签A 5 · 标签B 2 · 标签C 1 · 标签D 4`)已升级为结构化数组绑定(§3),是枚举统计行范式的权威范例**。
462
- - references/platform_jsapi.md:JSAPI 完整目录(alertCard/closeAlertCard/alertDialog/showToast/showPicker/showDatePicker/showLoadingEx/startRouter 等)签名 + 49 卡实测频次。
463
- - references/quickref.md:**一页纸速查**(常用组件最小骨架 + 表达式 + 事件范式 + data.json 铁律 + 快路径命令 + 范式片段索引)。常规任务只看它,不要加载下面全部 references。
464
- - references/recipe_snippets/:预校验可复制的范式块(list-expand-collapse / alertCard-pair / globalSet / money-format / canvas-title-head / tab-billMode / accordion-group-toggle / canvas-donut / absolute-badge / progress-bar / **title-underline-overlay(标题+下划线重叠用 AbsoluteLayout,禁止负 margin)** / **canvas-gauge-score(评分仪表盘)** / **canvas-radar(雷达图)** / **label-col-modes(维度标签列两范式 + pill/tag 参数)** / **opacity-switch(透明度切换固定范式,R57)**),各含 `apply` 字段。新范式可用 `extract_subtree.mjs` 从已导出卡抽取,无需手抄。
465
- - references/card_index.json:`build_card_index.mjs` 产出的卡片索引(每张卡 `components`/`recipes`/`tags`),意图路由选近邻用,不扫源文件。
466
- - **references/component_rules.md**:组件级硬约束速查表(Icon 必须 emits:click 否则双触发、Input events:[]、ListLayout/GridLayout slotScope:true、Canvas 必须 onMeasured、Button.disabled 字符串 "false" 也为真、v-show 放置位置等)。一次性看完,避免遗漏。
467
- - **references/image_diff.md**:视觉验证清单(七维度:布局/层级/文案/留白/配色/字号/交互元素)。Workflow A 第 6 步 `card_screenshot` 后按此逐项核对,把"基本一致"升级为有结构的逐项核查。
468
- - **references/generation_pitfalls.md**:**生成避坑清单**(沉淀自 gen-card-test 真实生成)。集中讲透:itemValueFrom 必填(运行时静默丢弃根因)、style itemId 权威来源与 flexDirection=006 陷阱、图片/背景资源解析、**缺素材时用占位图**(Image 空 value→自动 default-image 占位图、Icon 缺 glyph 改占位 Image、复杂背景效果先占位)、布局陷阱(Column 100% 宽换行 / 空 Row 不渲染→Divider / alignItems)、Playwright 视觉真相验证配方、生成—校验—修复闭环。常规生成任务先扫一遍。
469
- - **references/ui_fidelity_rules.md**:**UI 还原度规则**(沉淀自 r060-r083 批量训练 20+ 轮)。聚焦「渲染了但不像设计稿」的布局/尺寸/对齐问题:①Text 高度与 maxLine 协调(多行不设高度,单行 maxLine=1);②Text 宽度由内容决定(控制宽度需包裹容器);③表单行布局范式(标签固定宽度+右簇 flex 撑满);④Flexbox 坐标累积偏移原理与校正;⑤高度由内容撑起不写死;⑥width:100% 使用纪律;⑦位置还原优先级(存在→尺寸→中心→IoU);⑧标注与图像交叉验证(模型多模态能力);⑨背景不透明铁律与纯色兜底。**生成卡片前必读——直接影响 UI 还原度评分。**
470
- - **references/card_to_summary.md**:卡片反向描述模板(元信息/组件树 ASCII/数据来源/事件流/可改范围)。给非设计人员解释卡片、写文档、AI 上下文压缩时套用;机器初稿用 `describe_card.mjs` 生成。
211
+ **高频命令**(全集 + MCP 工具 + r057 内部规则 + 四层校验自洽见 `references/scripts_reference.md`):
212
+
213
+ ```bash
214
+ node scripts/gate.mjs --id <卡id> [--spec <spec.json>] [--json] # ⚡ 门禁一条命令
215
+ node scripts/route_input.mjs <输入路径> [--json] # Workflow 0 输入路由
216
+ node scripts/expand_ir.mjs --ir <IR.json> --out src/cards/<卡id>/ # IR→schema/data(建卡主路径)
217
+ node scripts/iterate.mjs --ir <IR> --out <dir> --design <png> [--set 'p=v'] # 一条龙视觉闭环
218
+ node scripts/spec_tree.mjs --spec <spec.json> [--card 1] [--compact] # 层级树
219
+ node scripts/lint_card.mjs --id <卡id> | --all [--json] # 行为规则 46 条
220
+ node scripts/diff_card.mjs --id <卡id> [--baseline <path>] # 变更对比(整体替换后必跑)
221
+ node scripts/check_fidelity.mjs --spec <spec.json> --schema <schema.json> # 保真门禁(npm run check:fidelity)
222
+ node scripts/render_card.mjs --id <卡id> [--keep-alive] # 离线截图
223
+ node scripts/v3_regression.mjs [--update-baseline] [--cases <path>] # v3 回归门禁(金标清单 training/pass1/cases.json)
224
+ ```
225
+
226
+ ## 11. 参考路由(分级按需加载,勿全量读)
227
+
228
+ > **v3 演进纪律(冻结启发式增长)**:v3 识别不了/识别错的新布局模式 → ① sem 决策回路(`--sem-dump` + `--decisions`,patterns §20)② AI 视觉主判走 Workflow A ③ 仅 ≥3 卡共性缺陷才改 extract 层且必跑 `v3_regression.mjs`(金标在仓库 `training/pass1/cases.json`,skill 不持业务路径)。lessons.jsonl(solidified 已标注)属 P2 档案,生成上下文不加载。
229
+
230
+ **P0 生成必读**:`quickref.md`(一页纸速查)+ `ui_fidelity_rules.md`(还原度规则)+ `component_rules.md`(组件硬约束)。
231
+
232
+ **P1 按场景**:`rules.md`(铁律全集)|`dsl_structure.md`(结构/itemId 全表)|`component_specs.md`(组件骨架)|`expression_engine.md`($ 引擎)|`amount_formatting.md`(金额)|`event_patterns.md` + `platform_jsapi.md`(事件/JSAPI)|`field_naming.md`(命名)|`recipes.md` + `recipe_snippets/`(范式)|`previewer_contract.md`(预览器契约)|`spec_to_schema_patterns.md`(v3 范式 + §20 决策层)|`iteration_protocol.md`(迭代协议)|`semantic_adjudication.md`(语义裁决细则)|`scripts_reference.md`(脚本全集)|`slices_convention.md`(切图)|`image_to_card.md`(图片管线)|`message-center-pattern.md`(消息中心)|`image_diff.md`(视觉清单)|`card_to_summary.md`(反向描述)|`card_index.json`/`card_templates.json`(路由索引)。
233
+
234
+ **P2 改脚本/排查**:`generation_pitfalls.md`(132KB 事故档案;lint 按规则号查询;已固化进 lint/expand_ir/autofix 的条目机械拦截,生成时无需预读)。
471
235
 
472
236
  ## 12. 重要边界
473
237
 
474
- - 生成避坑清单(itemValueFrom 必填、style itemId 权威来源、图片解析、布局陷阱、Playwright 视觉验证、**进度条必须数据驱动**)集中见 `references/generation_pitfalls.md`(§8 专讲进度条:绑定 ≤1 比率变量映射宽度,禁止静态写死百分比),常规生成任务先扫一遍。
238
+ - 生成避坑清单(itemValueFrom 必填、style itemId 权威来源、图片解析、布局陷阱、Playwright 视觉验证、**进度条必须数据驱动**)见 `generation_pitfalls.md`(§8 进度条:绑定 ≤1 比率变量映射宽度,禁静态写死百分比)。
475
239
  - 不要仅凭 palette/源码注册就声称组件或 action 可用;生成前要求有导出 JSON 形状。6 个编辑器专属类预览不渲染。
476
- - actionType 已验证值:customAction/startRouter/closeAlertCard/sendAiChatMessage。alertCard/showPicker/showToast 等是 customAction 里的 JSAPI 调用,不是 actionType。
477
- - 优先复用 src/cards/*/schema.json 结构;按模式选近邻参考(见 Workflow A step 3)。
478
- - 改已有 schema 时除非真新增组件,否则保持 componentId 稳定。
240
+ - actionType 已验证值:customAction/startRouter/closeAlertCard/sendAiChatMessage;alertCard/showPicker/showToast 等是 JSAPI 调用,不是 actionType。
241
+ - 优先复用 src/cards/*/schema.json 结构,按模式选近邻参考;改已有 schema 除非真新增组件,否则保持 componentId 稳定。
479
242
  - 不要把导出里的未知字段"归一化"掉;保留并告警,直到平台语义明确。
480
- - data.json 必须从 dictionary.inputParams[].defaultValue 逐字节可派生;改一处必改另一处。
481
- - itemValueFrom:"1" 的属性**必须**带 itemDefaultValue(预览器 undefined 兜底,否则渲染空)。
482
- - Canvas onMeasured 的 event.canvas 是**设计稿坐标系**包装对象(宽度/2dctx 坐标已按 scale 换算),绘图用逻辑设计 px,不要乘 devicePixelRatio。
243
+ - data.json 必须从 inputParams[].defaultValue 逐字节可派生;itemValueFrom:"1" 必须**带 itemDefaultValue**(否则渲染空)。
244
+ - Canvas onMeasured 的 event.canvas 是**设计稿坐标系**(已按 scale 换算),绘图用逻辑设计 px,不要乘 devicePixelRatio。