cardbuddy 0.15.0 → 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.
Files changed (77) hide show
  1. package/dist/preview-editor/assets/index-BkbBxDTT.css +1 -0
  2. package/dist/preview-editor/assets/{index-CIIfMlwI.js → index-Bro07DTa.js} +91 -88
  3. package/dist/preview-editor/index.html +2 -2
  4. package/dist/vendored/card-editor/runtime/adapter.js +2 -2
  5. package/dist/vendored/mcp/client.mjs +1 -1
  6. package/dist/vendored/server/core/ai-chat.js +251 -80
  7. package/dist/vendored/server/core/cardValidate.js +16 -5
  8. package/dist/vendored/skills/card-skill-trainer/SKILL.md +61 -6
  9. package/dist/vendored/skills/card-skill-trainer/scripts/diagnose_case.mjs +14 -82
  10. package/dist/vendored/skills/card-skill-trainer/scripts/lib/diagnosis.mjs +207 -0
  11. package/dist/vendored/skills/card-skill-trainer/scripts/lib/layout-correction-sim.mjs +62 -0
  12. package/dist/vendored/skills/card-skill-trainer/scripts/lib/paradigm-router.mjs +76 -0
  13. package/dist/vendored/skills/card-skill-trainer/scripts/lib/pixel-worker-runner.mjs +31 -0
  14. package/dist/vendored/skills/card-skill-trainer/scripts/lib/pixel-worker.mjs +138 -0
  15. package/dist/vendored/skills/card-skill-trainer/scripts/lib/scoring.mjs +24 -20
  16. package/dist/vendored/skills/card-skill-trainer/scripts/lib/telemetry.mjs +46 -0
  17. package/dist/vendored/skills/card-skill-trainer/scripts/plan_next_round.mjs +4 -1
  18. package/dist/vendored/skills/card-skill-trainer/scripts/run_round.mjs +279 -90
  19. package/dist/vendored/skills/card-skill-trainer/scripts/score_round.mjs +74 -2
  20. package/dist/vendored/skills/card-skill-trainer/scripts/sink_rules.mjs +127 -0
  21. package/dist/vendored/skills/dsl-card-generator/SKILL.md +93 -27
  22. package/dist/vendored/skills/dsl-card-generator/references/dsl_structure.md +1 -15
  23. package/dist/vendored/skills/dsl-card-generator/references/expression_engine.md +11 -20
  24. package/dist/vendored/skills/dsl-card-generator/references/generation_pitfalls.md +169 -44
  25. package/dist/vendored/skills/dsl-card-generator/references/ir-format.md +87 -3
  26. package/dist/vendored/skills/dsl-card-generator/references/iteration_protocol.md +35 -6
  27. package/dist/vendored/skills/dsl-card-generator/references/quickref.md +28 -27
  28. package/dist/vendored/skills/dsl-card-generator/references/recipes.md +30 -8
  29. package/dist/vendored/skills/dsl-card-generator/references/rules.md +3 -2
  30. package/dist/vendored/skills/dsl-card-generator/references/scripts_reference.md +5 -2
  31. package/dist/vendored/skills/dsl-card-generator/references/slices_convention.md +2 -3
  32. package/dist/vendored/skills/dsl-card-generator/references/spec_to_schema_patterns.md +1 -4
  33. package/dist/vendored/skills/dsl-card-generator/scripts/add_component.mjs +5 -14
  34. package/dist/vendored/skills/dsl-card-generator/scripts/autofix_schema.py +88 -0
  35. package/dist/vendored/skills/dsl-card-generator/scripts/check_fidelity.mjs +143 -42
  36. package/dist/vendored/skills/dsl-card-generator/scripts/check_topology.py +0 -35
  37. package/dist/vendored/skills/dsl-card-generator/scripts/expand_ir.mjs +99 -53
  38. package/dist/vendored/skills/dsl-card-generator/scripts/gate.mjs +5 -2
  39. package/dist/vendored/skills/dsl-card-generator/scripts/iterate.mjs +388 -29
  40. package/dist/vendored/skills/dsl-card-generator/scripts/lib/component_contract.mjs +315 -0
  41. package/dist/vendored/skills/dsl-card-generator/scripts/lib/layout-tokens.mjs +1 -1
  42. package/dist/vendored/skills/dsl-card-generator/scripts/lib/paths.mjs +23 -0
  43. package/dist/vendored/skills/dsl-card-generator/scripts/lib/paths.py +0 -37
  44. package/dist/vendored/skills/dsl-card-generator/scripts/lib/python.mjs +99 -0
  45. package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/adaptive-width.mjs +105 -0
  46. package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/bindings.mjs +260 -0
  47. package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/expr.mjs +52 -0
  48. package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/image.mjs +96 -0
  49. package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/registry.mjs +166 -0
  50. package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/structure.mjs +127 -0
  51. package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/walk.mjs +61 -0
  52. package/dist/vendored/skills/dsl-card-generator/scripts/lib/scope_alias.mjs +125 -0
  53. package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_assemble.mjs +325 -41
  54. package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_extract.mjs +112 -6
  55. package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_shared.mjs +0 -6
  56. package/dist/vendored/skills/dsl-card-generator/scripts/lint_card.mjs +119 -78
  57. package/dist/vendored/skills/dsl-card-generator/scripts/preflight.mjs +48 -50
  58. package/dist/vendored/skills/dsl-card-generator/scripts/readiness.mjs +634 -0
  59. package/dist/vendored/skills/dsl-card-generator/scripts/register_card_template.mjs +5 -8
  60. package/dist/vendored/skills/dsl-card-generator/scripts/route_input.mjs +5 -10
  61. package/dist/vendored/skills/dsl-card-generator/scripts/spec_audit.mjs +122 -8
  62. package/dist/vendored/skills/dsl-card-generator/scripts/v3_regression.mjs +3 -1
  63. package/dist/vendored/skills/dsl-card-generator/scripts/validate_schema.py +23 -15
  64. package/dist/vendored/skills/shared/exit-codes.md +79 -0
  65. package/dist/vendored/skills/shared/spec-contract.md +118 -0
  66. package/dist/vendored/skills/sketch-spec-to-card/SKILL.md +26 -9
  67. package/dist/vendored/skills/sketch-spec-to-card/references/spec-schema.md +3 -3
  68. package/dist/vendored/skills/sketch-spec-to-card/scripts/build_prompt.py +0 -8
  69. package/dist/vendored/skills/sketch-spec-to-card/scripts/cluster_states.py +14 -25
  70. package/dist/vendored/skills/sketch-spec-to-card/scripts/parse_meaxure.py +91 -47
  71. package/dist/vendored/skills/sketch-spec-to-card/scripts/png_utils.py +0 -5
  72. package/dist/vendored/skills/sketch-spec-to-card/scripts/test_text_only_artboard.py +75 -15
  73. package/dist/vendored/skills/sketch-spec-to-card/scripts/vision.py +40 -34
  74. package/package.json +1 -1
  75. package/dist/preview-editor/assets/index-DQn0zQBP.css +0 -1
  76. package/dist/vendored/skills/card-skill-trainer/scripts/test_missing_slice.py +0 -122
  77. package/dist/vendored/skills/dsl-card-generator/scripts/analyze_component_usage.mjs +0 -153
@@ -83,8 +83,51 @@ summary.correctorRejected = correctorRejected
83
83
  summary.correctorCoverage = reports.length ? correctorAttempts / reports.length : null
84
84
  summary.correctorAcceptRate = correctorAttempts ? correctorAccepted / correctorAttempts : null
85
85
 
86
- // --- 失败签名 / 未达标维度直方图 / 失败类分布 ---
87
- // 签名口径 v2(lib/scoring.mjs):取归一化缺口最大的维度,而非固定优先级顺序。
86
+ // --- Telemetry 聚合:速度 / 错误 / token(2026-09-25)---
87
+ // 旧报告缺 durationMs / telemetry.json 时对应段自动跳过,跨轮兼容;口径不进任何门禁。
88
+ const durations = reports.map((item) => item.durationMs).filter((v) => typeof v === 'number')
89
+ if (durations.length) {
90
+ const sorted = [...durations].sort((a, b) => a - b)
91
+ // nearest-rank 百分位:ceil(p*n)-1(P95 of [1.5s,3.5s] 应取 3.5s,floor 秩会错误取到 1.5s)
92
+ const pct = (p) => sorted[Math.min(sorted.length - 1, Math.max(0, Math.ceil(p * sorted.length) - 1))]
93
+ summary.avgCaseMs = Math.round(average(durations))
94
+ summary.p50CaseMs = pct(0.5)
95
+ summary.p95CaseMs = pct(0.95)
96
+ }
97
+ const telemetryPath = path.join(reportDir, 'telemetry.json')
98
+ if (fs.existsSync(telemetryPath)) {
99
+ try {
100
+ const t = JSON.parse(fs.readFileSync(telemetryPath, 'utf8'))
101
+ // 旧 telemetry.json 可能缺派生字段:从原始累计字段现算兜底
102
+ const derivedCpm = (t.wallMs > 0 && t.scoredCases)
103
+ ? Number((t.scoredCases / (t.wallMs / 60000)).toFixed(2)) : null
104
+ const derivedStageAverages = !t.stageAverages && t.stageMs && t.scoredCases
105
+ ? Object.fromEntries(Object.entries(t.stageMs)
106
+ .map(([stage, ms]) => [stage, Math.round(ms / t.scoredCases)]))
107
+ : null
108
+ summary.telemetry = {
109
+ wallMs: t.wallMs ?? null,
110
+ concurrency: t.concurrency ?? null,
111
+ casesPerMinute: t.casesPerMinute ?? derivedCpm,
112
+ stageAverages: t.stageAverages ?? derivedStageAverages,
113
+ renderCalls: t.renderCalls ?? null,
114
+ renderRetries: t.renderRetries ?? null,
115
+ sessionRebuilds: t.sessionRebuilds ?? null,
116
+ stageErrors: t.stageErrors ?? null,
117
+ caseErrors: t.caseErrors ?? null,
118
+ usage: t.usage ?? null,
119
+ reviewTokensEst: t.reviewTokensEst?.total != null
120
+ ? {
121
+ ...t.reviewTokensEst,
122
+ avgPerFailedCase: t.reviewTokensEst.cases
123
+ ? Math.round(t.reviewTokensEst.total / t.reviewTokensEst.cases) : 0,
124
+ }
125
+ : null,
126
+ }
127
+ } catch { /* telemetry.json 损坏不阻塞汇总 */ }
128
+ }
129
+
130
+ // --- 失败签名 / 未达标维度直方图 / 失败类分布 ---// 签名口径 v2(lib/scoring.mjs):取归一化缺口最大的维度,而非固定优先级顺序。
88
131
  // 旧口径会把 comp=0.42 / pos=0.42 的缺组件卡报成 duplicate-content-detected(重影只超 1 处),
89
132
  // 把下一轮修复方向带偏;跨轮签名趋势对比请以口径 v2 之后的轮次为界。
90
133
  const signatures = {}
@@ -175,6 +218,35 @@ if (summary.highMaskCases.length) {
175
218
  }
176
219
  } else lines.push('- (无)')
177
220
 
221
+ // —— 速度与消耗(telemetry,2026-09-25)——
222
+ lines.push('', '## 速度与消耗', '')
223
+ if (summary.avgCaseMs != null) {
224
+ lines.push(`- 单例耗时: 均值 ${(summary.avgCaseMs / 1000).toFixed(1)}s · P50 ${(summary.p50CaseMs / 1000).toFixed(1)}s · P95 ${(summary.p95CaseMs / 1000).toFixed(1)}s`)
225
+ }
226
+ if (summary.telemetry) {
227
+ const t = summary.telemetry
228
+ lines.push(
229
+ `- 墙钟: ${t.wallMs != null ? `${(t.wallMs / 1000).toFixed(1)}s` : 'N/A'}${t.concurrency != null ? `(${t.concurrency} 并发渲染)` : ''}`,
230
+ `- 吞吐: ${t.casesPerMinute != null ? `${t.casesPerMinute} 例/分钟` : 'N/A'}${t.renderCalls != null ? ` · 渲染 ${t.renderCalls} 次` : ''}${t.renderRetries ? ` · 重试 ${t.renderRetries}` : ''}${t.sessionRebuilds ? ` · 会话重建 ${t.sessionRebuilds}` : ''}`,
231
+ )
232
+ if (t.stageAverages && Object.keys(t.stageAverages).length) {
233
+ const stageStr = Object.entries(t.stageAverages)
234
+ .sort((a, b) => b[1] - a[1])
235
+ .map(([stage, ms]) => `${stage} ${(ms / 1000).toFixed(2)}s`)
236
+ .join(' · ')
237
+ lines.push(`- 阶段均值(按案例): ${stageStr}`)
238
+ }
239
+ if (t.stageErrors && Object.keys(t.stageErrors).length) {
240
+ lines.push(`- 阶段错误: ${Object.entries(t.stageErrors).map(([stage, n]) => `${stage}×${n}`).join(' · ')}`)
241
+ }
242
+ if (t.usage) lines.push(`- LLM usage(生成链路): ${t.usage.calls} 次 / in ${t.usage.inputTokens} / out ${t.usage.outputTokens} tok`)
243
+ if (t.reviewTokensEst) {
244
+ lines.push(`- 归因证据包估算: ${t.reviewTokensEst.total} tok(${t.reviewTokensEst.cases} 个失败例 · 均 ${t.reviewTokensEst.avgPerFailedCase} tok)— Agent 在环归因预算参考`)
245
+ }
246
+ } else if (summary.avgCaseMs == null) {
247
+ lines.push('- (无 telemetry 数据:旧轮次报告或未启用计时)')
248
+ }
249
+
178
250
  // —— 逐案截图对比图廊 ——
179
251
  lines.push('', '## 截图对比', '')
180
252
  const sortedReports = [...reports].sort((a, b) =>
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * sink_rules.mjs — 把 lessons.jsonl 的已推广规则回灌到目标 skill 文档。
4
+ *
5
+ * 训练闭环的最后一环:lesson 标了 status=promoted + targetFiles 指向
6
+ * dsl-card-generator / sketch-spec-to-card / card-skill-trainer 的规则文档
7
+ * (generation_pitfalls.md / SKILL.md / recipes.md 等),但规则文本从未真正
8
+ * 写进这些文件——本脚本补上这一步。
9
+ *
10
+ * 规则:
11
+ * 1. 读 lessons.jsonl,筛 status=promoted(或 candidate 且有 rule 文本)
12
+ * 2. targetFiles 含 .md 的 lesson,检查 rule 文本是否已在目标文件中
13
+ * 3. 不在 → 按目标文件的节号约定追加新节(generation_pitfalls 用 ## N.,
14
+ * SKILL.md / recipes.md 用 ### 训练规则 N)
15
+ * 4. 已在 → 跳过(幂等)
16
+ *
17
+ * 用法:
18
+ * node skills/card-skill-trainer/scripts/sink_rules.mjs [--dry-run]
19
+ *
20
+ * 退出码:0 = 正常(即使无新规则也 0);1 = 写入失败。
21
+ */
22
+ import fs from 'node:fs'
23
+ import path from 'node:path'
24
+ import { fileURLToPath } from 'node:url'
25
+
26
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..')
27
+ const LESSONS = path.join(ROOT, 'training', 'lessons', 'lessons.jsonl')
28
+
29
+ const dryRun = process.argv.includes('--dry-run')
30
+
31
+ function loadLessons() {
32
+ if (!fs.existsSync(LESSONS)) return []
33
+ return fs.readFileSync(LESSONS, 'utf8')
34
+ .split('\n').filter((l) => l.trim())
35
+ .map((l) => { try { return JSON.parse(l) } catch { return null } })
36
+ .filter(Boolean)
37
+ }
38
+
39
+ /** 规则文本是否已存在于目标文件(归一化后前 40 字符片段匹配) */
40
+ function ruleAlreadyInFile(filePath, rule) {
41
+ if (!fs.existsSync(filePath)) return false
42
+ const content = fs.readFileSync(filePath, 'utf8').replace(/\s+/g, '')
43
+ const snippet = (rule || '').replace(/\s+/g, '').slice(0, 40)
44
+ return snippet.length >= 10 && content.includes(snippet)
45
+ }
46
+
47
+ /** 按目标文件分组待回灌的 lessons */
48
+ function groupByTargetFile(lessons) {
49
+ const groups = new Map()
50
+ for (const d of lessons) {
51
+ if (!d.targetFiles || !d.rule) continue
52
+ // 只回灌 status=promoted 或有实质 rule 的 candidate
53
+ if (d.status && d.status !== 'promoted' && d.status !== 'candidate') continue
54
+ // candidate 但 rule 还是 TODO 占位 → 跳过
55
+ if (d.rule.includes('TODO')) continue
56
+ for (const tf of d.targetFiles) {
57
+ if (!tf.endsWith('.md')) continue
58
+ const fullPath = path.resolve(ROOT, tf)
59
+ if (ruleAlreadyInFile(fullPath, d.rule)) continue
60
+ if (!groups.has(tf)) groups.set(tf, [])
61
+ groups.get(tf).push(d)
62
+ }
63
+ }
64
+ return groups
65
+ }
66
+
67
+ /** 生成回灌的 markdown 片段 */
68
+ function buildSection(lessons, filePath) {
69
+ const isPitfalls = filePath.includes('generation_pitfalls')
70
+ const lines = []
71
+ let n = 0
72
+ for (const d of lessons) {
73
+ const date = d.round || d.lessonId || ''
74
+ const sig = d.signature || ''
75
+ const id = d.lessonId || d.id || ''
76
+ n++
77
+ if (isPitfalls) {
78
+ lines.push(`\n## auto. ${sig.slice(0, 60) || '训练规则'}${date ? `(${date} 训练回灌)` : ''}`)
79
+ } else {
80
+ lines.push(`\n---\n### ${sig.slice(0, 60) || '训练规则'}${date ? `(${date} 训练回灌)` : ''}`)
81
+ }
82
+ if (id) lines.push(`> lesson: \`${id}\` status: \`${d.status || 'promoted'}\``)
83
+ lines.push('')
84
+ lines.push(d.rule.trim())
85
+ lines.push('')
86
+ }
87
+ return { text: lines.join('\n'), count: n }
88
+ }
89
+
90
+ function main() {
91
+ const lessons = loadLessons()
92
+ if (!lessons.length) {
93
+ console.log('[sink_rules] 无 lessons.jsonl 或为空,跳过')
94
+ return
95
+ }
96
+
97
+ const groups = groupByTargetFile(lessons)
98
+ if (!groups.size) {
99
+ console.log('[sink_rules] 所有规则已回灌或无 .md 目标,无需操作')
100
+ return
101
+ }
102
+
103
+ let totalSunk = 0
104
+ for (const [tf, fileLessons] of groups) {
105
+ const fullPath = path.resolve(ROOT, tf)
106
+ if (!fs.existsSync(fullPath)) {
107
+ console.warn(`[sink_rules] 目标文件不存在: ${tf},跳过`)
108
+ continue
109
+ }
110
+ const content = fs.readFileSync(fullPath, 'utf8')
111
+ const isPitfalls = tf.includes('generation_pitfalls')
112
+ const { text, count } = buildSection(fileLessons, tf)
113
+
114
+ if (dryRun) {
115
+ console.log(`[sink_rules] DRY-RUN: 将向 ${tf} 追加 ${count} 条规则`)
116
+ } else {
117
+ const append = content.endsWith('\n') ? text : '\n' + text
118
+ fs.appendFileSync(fullPath, append + '\n')
119
+ console.log(`[sink_rules] ${tf}: 回灌 ${count} 条规则`)
120
+ }
121
+ totalSunk += count
122
+ }
123
+
124
+ console.log(`[sink_rules] 共回灌 ${totalSunk} 条规则到 ${groups.size} 个文件${dryRun ? '(dry-run,未实际写入)' : ''}`)
125
+ }
126
+
127
+ main()
@@ -11,25 +11,24 @@ description: 生成、编辑 FinMall/YNet 低代码卡片 schema.json + data.jso
11
11
 
12
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
- ## 0. 铁律(项目最高纪律)
15
-
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`)。
14
+ ## 0. 铁律索引(最高纪律;完整定义 / 根因 / 验收见 `references/rules.md`)
15
+
16
+ 1. **视觉优先** — 模型视觉判断 > 脚本启发式,冲突以视觉为准(rules.md §七 / ui_fidelity_rules.md §8)
17
+ 2. **绝不臆造** — 组件/属性/事件/action 须 export/meta/renderer-proven(rules.md §一)
18
+ 3. **不污染 DSL** — 禁写 `$schema`/`x-*`/Generator 产物;整体替换后跑 `diff_card.mjs`(rules.md §二)
19
+ 4. **根节点不设样式宽度** — 宽度由 `view.width` 承载,style 不放 rect 组(rules.md §九)
20
+ 5. **标注可信度分层** — 属性值读标注禁抄存量卡;几何看实图/容器边界;间距用 margin;卡底取 `cardFill`(rules.md §十一 / pitfalls §9-R1/R8-R10)
21
+ 6. **渲染器默认样式显式覆盖 + 运行期数据必绑定** — Row/Column/ListLayout 默认 padding:2px/gap:4px/居中须归零;金额/日期等绑 inputParams(rules.md §九 / amount_formatting.md)
22
+ 7. **类型+值成对** — borderRadius↔borderRadiusType、border↔isBorder、shadow↔isShadow,只写值不写开关 = 静默丢弃(R44,lint `r44-*`)
23
+ 8. **卡片背景不可完全透明** — Root/首层须有背景声明;半透明合法(R43,pitfalls §R43)
24
+ 9. **width:"100%" 必须可压缩** — 保持 `flexShrink:"1"`,禁同节点 shrink:0(R22/R35,rules.md §九)
25
+ 10. **Text 高度与 maxLine 协调** — maxLine=0 不设 rect.height;spec 文本高 > fontSize×1.5 判多行(ui_fidelity_rules.md §1)
26
+ 11. **透明度禁表达式(R57)** — 走静态变体 + 等宽高 `overflow:hidden` 容器 + 事件上移 + 互斥 v-show(recipes.md §17)
27
+ 12. **非 UI 变更不做视觉验证** — 改 inputParams/表达式/事件/data 只跑 validate+lint+derive+单元断言(rules.md §七.1)
28
+ 13. **画板聚焦(多画板必须先选卡)** — 未指定时机械列清单(`spec_tree.mjs --list-cards` / `parse_meaxure.py --list` / manifest `canvases[]`,`画板序号.卡片序号` + 末尾 `全部`)+ 停等序号,禁自挑/未授权批量/「先建一张看看」(rules.md §十三)
29
+ 14. **数字语义变量提取** — 含运行期数字(金额/笔数/利率/期限/日期)抽 inputParams + 表达式拼接;禁整串写死/绑 string/正则见数字就拆(R61,rules.md §十四)
30
+ 15. **图表绘制层序(R62)** — 底→顶:参考线/网格/轨道最底 → 数据标记 → 文字标签最顶;禁先填充后画网格(rules.md §十五)。机械兜底在 v3 生成器 `chartPaintLayers`(`lib/v3_shared.mjs`)+ 回归测试 `tools/tests/chart-layer-order.test.mjs`;**lint 不拦这一条**(无法可靠解析 Canvas 表达式的调用顺序),AI 手写时靠渲染后目视。
31
+ 16. **宽度自适应(占满禁写死 px,R66)** — 顶层 `view.width` = 卡面宽(非画板宽);纵向流父级里占满父内容盒的容器**禁写 px 宽**,用 `100%` 或不写(meta 默认即 100%),px 只给真固定元素(ir-format.md §2f,lint `r66-fullwidth-px`)。写死 px 的卡在非设计宽度的宿主容器里锁死不自适应。
33
32
 
34
33
  ## 0.5 速查优先
35
34
 
@@ -127,6 +126,8 @@ node scripts/route_input.mjs <输入路径> [--json] # 只挂 MCP 无 shell
127
126
 
128
127
  1. **`meaxure-export`**(index.html 内嵌 artboards + preview/ + assets/)→ **禁止拿 preview 整图建卡**,先经 sketch-spec-to-card 前处理:
129
128
  ```bash
129
+ # 解释器:下面示例写 python3,按本机可用者替换(python3 / python / py)——脚本侧统一解析
130
+ # 见 scripts/lib/python.mjs(Windows 的 python3 常是 Store 假别名,只有 py 启动器时用 py)
130
131
  # ① 列画板请用户选范围(铁律 §0-13):不带范围直接跑 parse 会 exit 4 打印 1..n+全部 清单
131
132
  python3 skills/sketch-spec-to-card/scripts/parse_meaxure.py --src <导出目录> --list
132
133
  # ② 用户回序号 k → --indices <k-1>;回「全部」→ --scope-all(UI 产物全量;产卡视为显式批量授权)
@@ -138,7 +139,7 @@ node scripts/route_input.mjs <输入路径> [--json] # 只挂 MCP 无 shell
138
139
  3. **`image`**(单图)→ Workflow A 纯视觉;若图来自某 MeaXure 导出,改传导出目录走入口 ①。
139
140
  4. **`card-dir`**(含 schema.json)→ 改卡,Workflow B。
140
141
 
141
- > 退出码 0 已路由 / 3 unknown(先问用户别猜)/ 1 用法错。meaxure-export 且 preprocess.needed=true 时**不允许"先建一张看看"**——跳过前处理等于放弃聚焦边界与标注数值。
142
+ > 退出码 0 已路由 / 3 unknown(**先问用户别猜**)/ 1 用法错 —— 完整退出码语义见 `skills/shared/exit-codes.md`(权威;3 属 `needs-user`,**不是脚本报错**)。meaxure-export 且 preprocess.needed=true 时**不允许"先建一张看看"**——跳过前处理等于放弃聚焦边界与标注数值。
142
143
 
143
144
  ### Workflow A — AI 读图 + 整树 IR + 视觉闭环(唯一建卡主路径)
144
145
 
@@ -152,6 +153,20 @@ node scripts/spec_tree.mjs --spec UI/<画板>/spec.json --card 1 # 层级树
152
153
  ```
153
154
  AI 输出 3-5 行中文布局理解(顶部…/中部…/底部…)。
154
155
 
156
+ **要素盘点 + 列表数组化计划(步骤 2 之前必须落定,2026-09-25)**:基本要素齐全是像素自迭代的前提。
157
+ 布局理解输出里必须包含:① **全类型要素计数**——文本 / 图标(symbol)/ 分割线(细长条 shape)/ 切图 / 图表 / 形状装饰,
158
+ 逐类清点(后续 `check_fidelity` 的 `elements.symbols/shapes` 与 readiness 会按同口径对账,漏建会被点名);
159
+ ② **列表数组化计划**——`spec_audit` 的 `listCandidates[]`(同构重复行 / tile,值逐行不同)逐条回应:
160
+ 数组变量名(照 values 列语义化命名)+ 字段清单 + 来源提名序号;设计稿里重复出现的数据行**禁止逐行静态展开**
161
+ (数据绑定齐全是硬前提:readiness 会拦「列表语义数组零消费」与「提名了列表却零数组化」,见步骤 3 门 G;
162
+ 范式 `ir-format.md` §2e + `recipe_snippets/list-expand-collapse.json`);
163
+ ③ **图表范式计划**——盘点出图表(雷达 / 环形 / 仪表盘 / 柱状 / 折线 / 散点)≥1 时,逐图写明
164
+ 「范式来源三件套」:recipes.md 图表章节锚点 + `recipe_snippets/canvas-*.json` 片段 + card_index.json 近邻图表卡。
165
+ IR 下发前任一件缺位 = 要素盘点未完成(**禁止**用 Text/Image 堆砌模拟图表——像素与 token 双输),
166
+ Canvas 自绘范式与数据契约见 `ir-format.md` §2f 末段;
167
+ ④ **宽度自适应计划**——顶层 width 写卡面宽(非画板宽);占满父内容盒的容器用 `100%`/不写宽
168
+ (lint `r66-fullwidth-px` error),px 只给图标 / 头像 / 徽章 / 图表画布等真固定元素(`ir-format.md` §2f)。
169
+
155
170
  **步骤 2 — 整树 IR 一次下发(唯一主路径)**:一次性写出整卡紧凑 IR(契约 `references/ir-format.md`;节点树压缩 4.4-4.8x,省 token 78-81%)再机械展开:
156
171
  ```bash
157
172
  node scripts/expand_ir.mjs --ir .cache/ai-scratch/<卡id>.ir.json --out src/cards/<卡id>/
@@ -160,21 +175,37 @@ node scripts/expand_ir.mjs --ir .cache/ai-scratch/<卡id>.ir.json --out src/card
160
175
  - 样式值从 spec element style 读不猜;运行期数据必须绑 inputParams(rules.md §三);中文+数字混排按 rules.md §十四拆「静态文案+数字变量+表达式」。
161
176
  - 视觉迭代修改用 `ir_patch` / `iterate.mjs --set` 点路径修,**不整份重写 IR**(`references/iteration_protocol.md`)。
162
177
 
178
+ **步骤 3 — 内容就绪门禁(S4.9,**像素循环的前置条件**)**:
179
+ ```bash
180
+ node scripts/readiness.mjs --id <卡id> --spec UI/<画板>/spec.json --design UI/<画板>/card-N.png --json
181
+ # 退出码 0=就绪 / 3=未就绪(**3 不是脚本报错,是内容没准备好**,kind=not-ready)/ 1=用法错误
182
+ # 完整退出码语义(含各脚本的 gate-fail / needs-user / diagnostic)见 skills/shared/exit-codes.md
183
+ ```
184
+ 七道判定(前五道是 **schema 侧**声明性判定,不需要渲染 ⇒ 挡在最贵的一步之前;第六道是 **spec 输入侧**契约检查,只在传了 `--spec` 时跑):**A** 组件契约(属性键/样式组/样式子键必须在 `component-meta.json` + `style-meta.json` 白名单内)· **B** 要素齐全(文本逐条对账;另有 `elements.symbols/shapes` 图标与分割线数量对账,缺口进迭代报告 `gate.elementHints` 每轮自查)· **C** 变量绑定完备(`$var` 的根变量必须真存在;**且列表语义数组不允许零消费**——`arraysUnconsumed.hard` 即 blocker,列表数据必须数组绑定)· **G** 列表提名闭环(传 `--audit` 时:S2 审计 `listCandidates` 提名了列表而 schema 数组化列表为 0 ⇒ blocker;提名>数组化只记 note)· **D** 图片地址已配置(每个 `Image` 必须有非空 `value`)· **E** 切图路径已配置(每个 `/slices/...` 引用在 `src/slices` 有对应文件)· **F** 上游产物契约(`spec.json` 的 `cards[<i>].mask` / `rect` 必须存在,仅指 `cards` 非空时;契约 `skills/shared/spec-contract.md` —— 缺 `mask` 多为旧版 spec 产物,下游会静默取默认圆角,须回上游重跑而非在下游加兼容分支。注意 **`cards=[]` 不是错误**:`text_only` / `overlay_dialog` 两类本就不单独产卡(由父卡承载),`artboardType=card` 的「0 卡 · 结构丰富」是上游已知态,须走整图 / AI 读图建卡;**`subcard` 自 2026-09-24 起由上游产 1 张卡**(底部抽屉面板几何直取,`focusSkipReason` 仍为 `bottom_sheet_geometry`)—— 若 `subcard` 仍 `cards=[]` 即旧版产物,回上游重跑。这三类空 `cards` 只记 `checks.spec.notes`,不阻断)。
185
+
186
+ `blockers` 非空 ⇒ **先补内容,别进步骤 5**。理由:内容没齐时像素分毫无参考价值。实测(2026-09-24 gen-1)跳过这步,在 `Image` 的图片地址都没配(属性键写成 `image`,合法键只有 `value` ⇒ 运行时整条静默丢弃 ⇒ 破图)的情况下连刷 10 轮像素,两个回合 623 万 prompt token 全部作废。
187
+
188
+ `iterate.mjs` **每轮自动再跑这道门禁**:未就绪时本轮**不 render、不 pixelDiff、不消耗迭代轮次**,报告 `gate.stage=content` + `gate.blocked=true` + `blockedAt.{stage,blockers,hint}`。按 blockers 补齐内容后直接重跑 iterate;被挡时禁止反复重跑空转,更禁止去调像素。逃生门 `--no-readiness`(或 `DSL_CARD_SKIP_READINESS=1`)仅 debug 用。
189
+
163
190
  **步骤 4 — 校验门禁(渲染前必须全过)**:
164
191
  ```bash
165
192
  node scripts/gate.mjs --id <卡id> [--spec UI/<画板>/spec.json] [--json] # ⚡ autofix→validate→derive→lint→preflight 一条命令,任一 ERROR exit 1
166
193
  ```
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 只查结构,这些错渲染时才炸。
194
+ > **lint(ERROR 级渲染前拦截)**:报错自带规则号与修法,按报告逐条修,无需预背。分类:压缩/溢出(R17/R22/R35/R63/R38)、格式(R-FMT1/2、R44、R49)、交互事件(R-FMT3、R51/R54/R55/R56)、显隐透明(R52/R53/R57)、切图资产(R31b/c)、背景(**R43 禁完全透明,半透明合法**)。**规则条数以脚本为准,本文档不复制数字**(`node scripts/lint_card.mjs --all` 实跑即得)。**code → 文档锚点的唯一权威 = `scripts/lib/rules/registry.mjs`**(每条 code 登记真正对应的 references/ 章节;门禁 `tools/tests/lint-rule-registry.test.mjs` 保证「实现 ↔ 注册表 ↔ 文档锚点」三方对账)——**不要按号直接猜文档**:lint 的 R50–R57 与 `generation_pitfalls.md` 的同号 R 是**两套无关语义**(pitfalls 是训练侧编号),按号查 pitfalls 会查到别的规则。
195
+ >
196
+ > 注意 **R62(图表绘制层序)不是 lint 规则** —— 它要求 Canvas 出码顺序 = 底→顶(网格/轴线 → 数据标记 → 文字标签)。机械保证在 v3 生成器的 `chartPaintLayers`(`lib/v3_shared.mjs`,测试 `tools/tests/chart-layer-order.test.mjs`);AI 手写 Canvas 表达式时 lint 无法可靠解析调用顺序,靠 render 后目视核对 + `diff_bands.mjs`。
168
197
 
169
198
  **步骤 5 — 视觉闭环(核心)**:
170
199
  ```bash
171
200
  # ⚡ 一条龙: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"']
201
+ # --spec 别省:没有它 widthMismatch 宽度基准校验与 contributors 的 designH 都拿不到设计稿口径
202
+ node scripts/iterate.mjs --ir <IR.json> --out src/cards/<卡id>/ --design UI/<画板>/card-1.png --spec UI/<画板>/spec.json --width 343 [--set 'children.0…marginTop="7"']
173
203
  node scripts/diff_bands.mjs --id <卡id> --design <card-1.png> --tol 2 # 量化偏差(dy/dh/主色直方图),防肉眼误判
174
204
  ```
175
- 结果 JSON:`pixelDiff.match` + `hotspots`(>3% 差异热区,--elements 落到具体组件)+ `fidelity` + `stalled` + `contributors`。逐项修 IR 重跑,**2-3 轮至 match>85% 且 lint 0**。
205
+ 结果 JSON:`pixelDiff.match` + `hotspots`(>3% 差异热区,--elements 落到具体组件)+ `fidelity` + `stalled` + `contributors` + `structuralGaps`(逐行几乎全异的连续带区——整块漏建/背景错涂的第一信号,存在时先修结构再看微调线索)。逐项修 IR 重跑,**2-3 轮至 match 达门禁线且 lint 0**。
206
+ > **门禁线唯一权威是代码**:`iterate.mjs` 的 `GATE.match`(默认 **95**,可用环境变量 `DSL_CARD_GATE_MATCH` 覆盖)。本行数字仅为说明,改阈值请改 `iterate.mjs:60` 附近那处常量 —— 文档与报告的 `--gate` 判定永远同源,不会出现「文档说 85、门禁判 95」的返工。
176
207
 
177
- > **迭代纪律(硬性;全集 `references/iteration_protocol.md`)**:每卡 ≤15 轮;stalled(连续 3 轮未刷新最好成绩,含震荡下滑)即 `ir_restore.mjs --best` 回退;要素线先骨架后样式(gate.stage=structure 先补要素别调像素,丢要素的一轮不算进步);一轮只改 `contributors[0].elements[0]` 一个属性;多卡连跑 `--keep-alive` 每轮 ~0.6s。
208
+ > **迭代纪律(硬性;全集 `references/iteration_protocol.md`)**:每卡 ≤15 轮;stalled(连续 3 轮未刷新最好成绩,含震荡下滑)即 `ir_restore.mjs --best` 回退;**三段式门禁逐段推进 —— `gate.stage=content` 先补内容(要素/变量绑定/图片地址/切图落盘,本轮没 render)、`stage=structure` 再补要素与结构、`stage=pixel` 才谈样式像素**;最好成绩判据是 `score = match − 6×缺要素数`(丢要素的一轮不算进步);一轮只改 `contributors[0].elements[0]` 一个属性;多卡连跑 `--keep-alive` 每轮 ~0.6s。
178
209
 
179
210
  **步骤 6 — 交付**:validate 0 + lint 0 + 视觉一致即完成;MCP `card_write({id, schema, verify:true})` 完整收口。首次出现的布局/交互模式 → `register_card_template.mjs --check` 新颖性检查,经用户同意登记模板库(§7.5)。
180
211
 
@@ -184,7 +215,7 @@ node scripts/diff_bands.mjs --id <卡id> --design <card-1.png> --tol 2 # 量
184
215
  ### Workflow A.5 — 画板分类分流(artboardType)
185
216
 
186
217
  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`)。
218
+ 2. **`subcard`**(模态子卡):独立 Dialog 卡(pageId `<slug>Dialog`)忠实还原;关闭(glyph `times`)→ closeAlertCard;父卡 alertCard 打开(recipes §2 + `recipe_snippets/alertCard-pair.json`)。上游已为底部抽屉产 **1 张卡**(面板 rect + 顶圆角遮罩 + `card-N.png` + 卡内 `elements`/`segments`)—— 直接按其 `rect` / `mask.radius` 定位还原,**不要**再读整图自定边界。
188
219
  3. **`overlay_dialog`**(弹框/toast):**不产卡**——alertDialog 调用补到父卡(clusters.json.overlays[].parentKey)对应事件;文案取上游 prompt.md §3。
189
220
  4. **`text_only`**(纯文本板):**不产卡**(上游 status=skipped),勿人工绕过。
190
221
  > prompt.md 边界:overlay_dialog=交互提示文档(勿产卡);subcard=常规建卡 prompt;text_only=只写判定依据。
@@ -216,7 +247,7 @@ node scripts/route_input.mjs <输入路径> [--json] # Wor
216
247
  node scripts/expand_ir.mjs --ir <IR.json> --out src/cards/<卡id>/ # IR→schema/data(建卡主路径)
217
248
  node scripts/iterate.mjs --ir <IR> --out <dir> --design <png> [--set 'p=v'] # 一条龙视觉闭环
218
249
  node scripts/spec_tree.mjs --spec <spec.json> [--card 1] [--compact] # 层级树
219
- node scripts/lint_card.mjs --id <卡id> | --all [--json] # 行为规则 46 条
250
+ node scripts/lint_card.mjs --id <卡id> | --all [--json] # 行为规则体检(条数以实跑为准)
220
251
  node scripts/diff_card.mjs --id <卡id> [--baseline <path>] # 变更对比(整体替换后必跑)
221
252
  node scripts/check_fidelity.mjs --spec <spec.json> --schema <schema.json> # 保真门禁(npm run check:fidelity)
222
253
  node scripts/render_card.mjs --id <卡id> [--keep-alive] # 离线截图
@@ -231,7 +262,7 @@ node scripts/v3_regression.mjs [--update-baseline] [--cases <path>] # v3 回
231
262
 
232
263
  **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
264
 
234
- **P2 改脚本/排查**:`generation_pitfalls.md`(132KB 事故档案;lint 按规则号查询;已固化进 lint/expand_ir/autofix 的条目机械拦截,生成时无需预读)。
265
+ **P2 改脚本/排查**:`generation_pitfalls.md`(132KB 事故档案,**训练侧编号体系**,与 lint 的 code 不是一套——查 lint 规则用 `scripts/lib/rules/registry.mjs`;已固化进 lint/expand_ir/autofix 的条目机械拦截,生成时无需预读)。
235
266
 
236
267
  ## 12. 重要边界
237
268
 
@@ -242,3 +273,38 @@ node scripts/v3_regression.mjs [--update-baseline] [--cases <path>] # v3 回
242
273
  - 不要把导出里的未知字段"归一化"掉;保留并告警,直到平台语义明确。
243
274
  - data.json 必须从 inputParams[].defaultValue 逐字节可派生;itemValueFrom:"1" 必须**带 itemDefaultValue**(否则渲染空)。
244
275
  - Canvas onMeasured 的 event.canvas 是**设计稿坐标系**(已按 scale 换算),绘图用逻辑设计 px,不要乘 devicePixelRatio。
276
+
277
+ ---
278
+ ### large currency value with right edit affordance rendered as (r018n 训练回灌)
279
+ > lesson: `LESSON-20260825-036` status: `promoted`
280
+
281
+ 货币符号 + 数值 + 右侧编辑/箭头控件是金额输入候选;Agent 复核后用 Input + 独立货币前缀,控件高度读标注或行高推导。禁止用业务词触发该规则。
282
+
283
+
284
+ ---
285
+ ### button text consumed again as list title caused duplicate gh(r018n 训练回灌)
286
+ > lesson: `LESSON-20260825-037` status: `promoted`
287
+
288
+ 源文本单一消费:按钮文案必须从列表标题候选中互斥排除;列表标题必须绑定完整业务容器,而不是按钮壳。
289
+
290
+
291
+ ---
292
+ ### fixed root height clipped variable-length content(r018n 训练回灌)
293
+ > lesson: `LESSON-20260825-038` status: `promoted`
294
+
295
+ Root/普通容器高度必须 auto,由子内容、gap 和标注 padding 撑开;只有叶子控件可读标注高度。
296
+
297
+
298
+ ---
299
+ ### panel value and repeated right affordance leaked into other (r039n 训练回灌)
300
+ > lesson: `LESSON-20260826-044` status: `promoted`
301
+
302
+ 源文本单一消费扩展到副金额:面板 label/value 必须从列表标题候选互斥排除;同一右缘、同尺寸、等距重复的小控件由列表或重复行模板唯一消费,不得再作为孤立媒体块渲染。
303
+
304
+
305
+ ---
306
+ ### semantic gaps were double-counted and list spacing overflowe(r039n 训练回灌)
307
+ > lesson: `LESSON-20260826-045` status: `promoted`
308
+
309
+ 列表标题、列表项和查看更多必须注册语义边界;相邻块间距只计一次。列表行内边距由标注行高减内容高推导,ListLayout 行间距显式归零,容器高度继续由内容撑开。
310
+
@@ -169,21 +169,7 @@ showType:00 始终显示;01 满足条件显示;02 满足条件隐藏。con
169
169
 
170
170
  ## 事件 eventactions
171
171
 
172
- ```
173
- {
174
- "eventactions": [
175
- {
176
- "event": { "eventName": "点击事件", "eventKey": "click" },
177
- "action": {
178
- "actionType": "customAction",
179
- "properties": [
180
- { "itemKey": "function", "itemValue": { "code": "var that = this;\n...", "compiled": "var that = this;\n..." } }
181
- ]
182
- }
183
- }
184
- ]
185
- }
186
- ```
172
+ **形状与 code/compiled 同步规则见 `event_patterns.md §eventaction 形状`**。
187
173
 
188
174
  eventKey:click 高频、onReady/onMeasured 中频。actionType:customAction 高频、其余低频。**完整频次表见 event_patterns.md §频次统计**(不在本节重复数字)。
189
175
 
@@ -27,7 +27,15 @@
27
27
 
28
28
  `slotScope:true` 的插槽,子作用域 = 父作用域 ∪ 当前 item 展开 ∪ $$ (整个 item) ∪ _index (索引)。
29
29
 
30
- - 列表项内既能写 `$fieldName`(item 的字段),也能写 `$$`(整个 item)和 `$_index`(索引)。
30
+ - 列表项内的**规范形**:`$fieldName`(item 的字段)/ `$$`(整个 item)/ `$_index`(索引)。
31
+ - **别名 `$item.<字段>` / `$item` / `$item._index` 会被生成器自动归一化**,不是需要你规避的坑:
32
+ schema 里只存规范形,改写由 `expand_ir.mjs`(IR→schema 入口,实现 `lib/scope_alias.mjs`)与
33
+ `autofix_schema.py` 规则 5(schema 级兜底)完成;写别名不会中止写盘、也不会被打回。
34
+ 为什么归一化而不是禁止:作用域里本来**没有 `item` 这个键**(它是「字段逐个展开 ∪ {$$, _index}」),
35
+ 所以 `$item.<字段>` 求值恒为 `undefined`,而求值器用 Proxy 兜底(见 §4 的逐项 try/catch)**不抛错**
36
+ —— validate / preflight / render 全绿,只有 `/__render-status` 单列的 `bindingErrors` 会记一笔。
37
+ 这类静默失败在入口消灭比让作者背规则可靠。唯一例外:卡片声明了名为 `item` 的根变量时不改写。
38
+ (CI 侧 `lint_card.mjs` 的 `r50b-unnormalized-scope-alias` 只作为「该 schema 绕过了归一化管线」的哨兵。)
31
39
  - **以上 $ 作用域只适用于绑定表达式 / v-show 条件**。**事件脚本(customAction)里没有 `$$`**:slotScope 当前 item 的字段已展开到 `this.state`,直接 `this.state.<field>` 读取(铁律 r55,禁 `$$` / `this.state.$$` / 「$$ 兜底判断」,lint ERROR)。
32
40
  - ListLayout.value = "$payeeList",item = { acctName:"张三", amount:"100.00" },子 Text.value = "$acctName" 渲染当前项 acctName。
33
41
  - 常见表达式:
@@ -65,25 +73,8 @@
65
73
 
66
74
  ## 6. v-show 指令
67
75
 
68
- 组件级显隐,在 `directives[]` 里:
69
-
70
- ```
71
- {
72
- "itemId": "001",
73
- "itemKey": "v-show",
74
- "itemValueFrom": "0",
75
- "itemValue": {},
76
- "itemChildren": {
77
- "properties": [
78
- { "itemId": "001", "itemKey": "showType", "itemValueFrom": "0", "itemValue": "00" },
79
- { "itemId": "002", "itemKey": "condition", "itemValueFrom": "0", "itemValue": { "name": "", "expression": "$flag === '1'" } }
80
- ]
81
- }
82
- }
83
- ```
84
-
85
- - showType:00 始终显示;01 满足条件显示;02 满足条件隐藏。
86
- - condition.expression 是 $ 表达式,渲染期 evalExpr 实时算。无表达式时兜底:01 显 / 02 隐。
76
+ 组件级显隐,在 `directives[]` 里。**形状与语义(showType 00/01/02、condition.name 必填、lint `r52`/`r53` 铁律)见 `dsl_structure.md §指令 directives`**,本节只讲表达式陷阱。
77
+
87
78
  - **关键陷阱(tab↔header)**:`activeTab` 默认值(如 "1")不随 billMode 变。依赖 `$activeTab` 的面板可见性必须 AND `$billMode === '0'` 再 OR 强制模式,否则首个面板泄漏到其他模式:
88
79
  - 错:`$activeTab === '1' || $billMode === '1'`(billMode='2' 时 activeTab 仍是 '1' → 首面板错显)。
89
80
  - 对:`($billMode === '0' && $activeTab === '1') || $billMode === '1'`。