cardbuddy 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -11
- package/dist/cli.js +2 -2
- package/dist/config.js +3 -3
- package/dist/init.js +13 -13
- package/dist/mcp.js +1 -1
- package/dist/preview-editor/assets/{index-CU8zVA2P.css → index-7RzNd7tf.css} +1 -1
- package/dist/preview-editor/assets/{index-CPpLZZyi.js → index-C7N5n_qW.js} +80 -80
- package/dist/preview-editor/index.html +2 -2
- package/dist/skills-upgrade.js +1 -1
- package/dist/standalone.js +2 -2
- package/dist/vendored/card-editor/runtime/adapter.js +10 -11
- package/dist/vendored/mcp/client.mjs +50 -13
- package/dist/vendored/mcp/tools.mjs +13 -7
- package/dist/vendored/server/core/ai-chat.js +540 -183
- package/dist/vendored/server/core/assetProxy.js +1 -1
- package/dist/vendored/server/core/browser.js +1 -1
- package/dist/vendored/server/core/cardDigest.js +324 -0
- package/dist/vendored/server/core/cardExport.js +5 -5
- package/dist/vendored/server/core/cardFiles.js +1 -1
- package/dist/vendored/server/core/cardHistoryRemote.js +1 -1
- package/dist/vendored/server/core/cardImportMeta.js +1 -1
- package/dist/vendored/server/core/cardListCache.js +92 -10
- package/dist/vendored/server/core/cardPublish.js +11 -6
- package/dist/vendored/server/core/cardPublishVersions.js +8 -8
- package/dist/vendored/server/core/cardSave.js +14 -10
- package/dist/vendored/server/core/cardSidecar.js +3 -3
- package/dist/vendored/server/core/cardSync.js +35 -23
- package/dist/vendored/server/core/cardVersion.js +1 -1
- package/dist/vendored/server/core/index.js +6 -6
- package/dist/vendored/server/core/logger.js +1 -1
- package/dist/vendored/server/core/remoteCard.js +5 -5
- package/dist/vendored/server/core/skillPaths.js +1 -1
- package/dist/vendored/server/core/sliceUpload.js +4 -4
- package/dist/vendored/skills/README.md +13 -13
- package/dist/vendored/skills/card-doctor/SKILL.md +2 -2
- package/dist/vendored/skills/dsl-card-ai/SKILL.md +4 -4
- package/dist/vendored/skills/dsl-card-generator/SKILL.md +2 -2
- package/dist/vendored/skills/dsl-card-generator/references/amount_formatting.md +1 -1
- package/dist/vendored/skills/dsl-card-generator/references/build_protocol.md +54 -0
- package/dist/vendored/skills/dsl-card-generator/references/component_rules.md +2 -2
- package/dist/vendored/skills/dsl-card-generator/references/converge_protocol.md +36 -0
- package/dist/vendored/skills/dsl-card-generator/references/event_patterns.md +1 -1
- package/dist/vendored/skills/dsl-card-generator/references/generation_pitfalls.md +1 -1
- package/dist/vendored/skills/dsl-card-generator/references/image_to_card.md +1 -1
- package/dist/vendored/skills/dsl-card-generator/references/quickref.md +1 -1
- package/dist/vendored/skills/dsl-card-generator/references/recipes.md +4 -4
- package/dist/vendored/skills/dsl-card-generator/references/rules.md +1 -1
- package/dist/vendored/skills/dsl-card-generator/references/slices_convention.md +6 -6
- package/dist/vendored/skills/dsl-card-generator/scripts/inventory_schemas.py +1 -1
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/scaffold.mjs +1 -1
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/slice_dedupe.mjs +2 -2
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-CeVX_y-M.js +137 -0
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-DiEiY59H.css +1 -0
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/standalone.html +2 -2
- package/dist/vendored/skills/{finmall-card-sync → platform-card-sync}/SKILL.md +26 -26
- package/dist/vendored/skills/{finmall-card-sync → platform-card-sync}/scripts/api_batch_sync.mjs +184 -162
- package/dist/vendored/skills/{finmall-card-sync → platform-card-sync}/scripts/slice_upload.mjs +48 -29
- package/dist/vendored/skills/{finmall-session-navigator → platform-session-navigator}/SKILL.md +13 -13
- package/dist/vendored/skills/{finmall-session-navigator → platform-session-navigator}/scripts/package-lock.json +2 -2
- package/dist/vendored/skills/{finmall-session-navigator → platform-session-navigator}/scripts/package.json +2 -2
- package/dist/vendored/skills/{finmall-session-navigator/scripts/finmall_login.mjs → platform-session-navigator/scripts/platform_login.mjs} +41 -31
- package/dist/vendored/skills/{finmall-session-navigator → platform-session-navigator}/scripts/session-manager.mjs +17 -17
- package/dist/vendored/skills/shared/README.md +21 -21
- package/dist/vendored/skills/shared/auth-model.md +8 -10
- package/dist/vendored/skills/shared/{finmall-config.mjs → platform-config.mjs} +10 -10
- package/dist/vendored/tools/{finmall-env.mjs → platform-env.mjs} +13 -13
- package/package.json +1 -1
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-BCLshIzC.css +0 -1
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-pP5kTKiI.js +0 -88
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
| G1 | 每个组件必有 `componentId`,全卡唯一且编辑期间稳定 | 校验失败、编辑面板无法定位 | dsl_structure |
|
|
12
12
|
| G2 | `itemValueFrom:"1"` 的属性**必须**带 `itemDefaultValue` | 渲染空白(undefined 兜底失败) | expression_engine |
|
|
13
13
|
| G3 | 不要把导出里的未知字段"归一化"掉;保留并告警 | DSL 契约字段丢失,保存污染 | SKILL.md §0 |
|
|
14
|
-
| G4 | `customAction` 的 `function` 字段含 `code` 与 `compiled`
|
|
14
|
+
| G4 | `customAction` 的 `function` 字段含 `code` 与 `compiled` 双份。**不要手写 compiled**:`expand_ir` 展开时自动 `compiled = code`,`card_patch` 写 `.code` 时也自动同步。只有用 `card_write` 整份写 schema 时才需自己保证两份一致(运行时取 `code || compiled`,编辑器读 compiled) | 编辑器与运行时分叉 | event_patterns |
|
|
15
15
|
| G5 | 改 schema 时**绝不**改未涉及节点的 `componentId` | 编辑面板状态断裂、引用丢失 | SKILL.md §12 |
|
|
16
16
|
| G6 | 容器必有 `slots`;叶组件通常省略 `slots` | 校验失败、子组件无法放置 | dsl_structure |
|
|
17
17
|
| G7 | `style.other.opacity` **禁止表达式/绑定**(铁律 R57):会变化的透明度走「静态变体 ×N + 等宽高 overflow:hidden 容器 + 事件上移 + 互斥 v-show」固定范式 | 移动端透明度不生效(预览也不消费 opacity,截图查不出来);lint `r57-opacity-expression` ERROR | recipes.md §17 / opacity-switch.json |
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
| eventKey 与组件类型不匹配 | **lint** |
|
|
74
74
|
| 脚手架占位文本残留 | **lint** |
|
|
75
75
|
|
|
76
|
-
未覆盖的(人工兜底):Icon 双触发、Canvas 坐标系、v-show tab↔header
|
|
76
|
+
未覆盖的(人工兜底):Icon 双触发、Canvas 坐标系、v-show tab↔header 面板泄漏、孤儿字段保留。compiled 与 code 同步已由 expand_ir / card_patch 自动完成,不再是人工项。
|
|
77
77
|
|
|
78
78
|
---
|
|
79
79
|
### single-button-width-magic-ratio(r273 训练回灌)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# 建卡协议(S4.9~S6):内容就绪 → 像素收敛 → 收尾门禁
|
|
2
|
+
|
|
3
|
+
> 本文件是 system 提示词里「建卡流程骨架」的**收敛段全文**。IR 已落盘、准备跑 iterate 前读本文件。
|
|
4
|
+
> 前半段(S0~S4.5)在 build_protocol.md。
|
|
5
|
+
|
|
6
|
+
## S4.9 内容就绪门禁
|
|
7
|
+
(**像素自循环的前置条件**;不达标时 iterate 会把本轮整体挡掉——不 render、不比对像素、不消耗迭代轮次):
|
|
8
|
+
IR 写完、变量抽完后,先跑 skill_exec script=skills/dsl-card-generator/scripts/readiness.mjs,args=["--id","<落点卡>","--spec","<bundle>/spec.json","--design","<bundle>/card-N.png","--json"]。退出码 0=就绪 / 3=未就绪(**3 不是脚本报错,是内容没准备好**)。
|
|
9
|
+
五道**声明性**判定(不需要渲染,所以能挡在最贵的一步之前):A 组件契约(属性键 / 样式组 / 样式子键必须在 component-meta.json + style-meta.json 白名单内)· B 要素齐全(设计稿每条文案在 schema 里找得到)· C 变量绑定完备($var 的根变量必须在 dictionary.inputParams / mocks / data.json / 事件 setData 里存在)· D 图片地址已配置(每个 Image 必须有非空 value)· E 切图路径已配置(每个 /slices/... 引用在 src/slices 里有对应文件)。
|
|
10
|
+
blockers 非空 ⇒ **先补内容,别进 S5**。这一步存在的唯一理由:内容没齐时像素分毫无参考价值。实测事故(2026-09-24 gen-1):跳过这步,在图片地址都没配的情况下连刷 10 轮像素(match 卡在 87-90.46),两回合烧掉 623 万 prompt token 全部作废。
|
|
11
|
+
三条高频坑(readiness 会在 blockers 里逐条点名,不要靠猜):① Image 的图片地址属性键**只能是 value**,写成 image 会被运行时按 itemKey 绑定失败后**整条静默丢弃** ⇒ value 为空 ⇒ 破图,而 validate / lint / preflight 全绿;② from:"2" 的值必须是静态路径、from:"1" 的值必须是 $表达式,写反 ⇒ 字符串直接进 url(...) 或表达式求值失败 ⇒ 画布空白;③ 切图**引用名与磁盘文件名必须同源**(crop_slices 按引用名落盘;引用一个 src/slices 里不存在的名字 ⇒ 渲染 404、图空白、控制台零报错)。
|
|
12
|
+
**iterate 每轮会自动再跑这道门禁**:报告里 gate.stage=content(或 verdict 以 BLOCKED(内容未就绪)开头)表示本轮被挡——没跑 render、没跑 pixelDiff、**没消耗迭代轮次**。按 gate.verdict 里的 blockers 补齐内容后**直接重跑 iterate**即可;禁止在被挡时反复重跑空转,更禁止去调像素(那时候没有像素分可调)。
|
|
13
|
+
逃生门 --no-readiness(或环境变量 DSL_CARD_SKIP_READINESS=1)只在排查「门禁本身误判」时用,正常流程禁止。
|
|
14
|
+
|
|
15
|
+
## S5 收敛循环
|
|
16
|
+
(唯一门禁 = iterate.mjs 报告里的 gate 字段:内部已跑 autofix + 真 validate + lint + preflight + **内容就绪(S4.9)** + render + 并集像素对比,别再单独跑 validate_schema.py):
|
|
17
|
+
skill_exec script=skills/dsl-card-generator/scripts/iterate.mjs,args=["--ir",".cache/ai-scratch/<id>.ir.json","--out","src/cards/<落点卡>/","--design","<bundle>/card-N.png","--width","<卡宽>","--spec","<bundle>/spec.json"]。
|
|
18
|
+
修 IR 用 **ir_patch**(点路径 JSON 补丁,一次可多条,形如 ops=[{"path":"children.0.s.flex.gap","value":"0"}])——**它是改 IR 的首选**:结构化参数不吃引号转义、原子写、回旧值→新值摘要;也可用 iterate.mjs 的 --set(字符串字面量语法,注意引号转义,例:--set children.0.s.flex={"gap":"0"})。数字段=数组下标;中段不存在会响亮报错,按报错改路径。**两条路径都不是「整份重写」**——同轮内对同一草稿文件第 2 次全量 fs_write 会收到提醒、第 3 次起被拒(要重排结构才传 rewrite:"true")。
|
|
19
|
+
**收敛 = 报告 gate.passed === true**(**内容就绪(S4.9)** ∧ validate/lint/preflight 零 error ∧ **elements.missing=0** ∧ match ≥ 95 ∧ heightDelta=0 ∧ widthDelta=0 ∧ |contentOffset1x| ≤ 2)。gate 是脚本判的,正文只能引用它的 verdict / failed,不得自行宣布达标。像素对比按并集画布算,删内容 / 高度塌缩只会降分,不可能凑分。
|
|
20
|
+
**要素是固定真值:先内容、后骨架、最后样式(三段式门禁,逐段推进)**。S2 审计里的每条文本 / 每张切图都是这张卡的既定要素,S5 只许改**样式与布局**(尺寸 / 间距 / 颜色 / 字号 / flex / 位置),**不许丢要素、不许自造内容**。报告带 gate.stage 与 gate.checks[].name === 'readiness.content':
|
|
21
|
+
· stage=content(gate.blocked=true)⇒ 内容层没齐(要素 / 变量绑定 / 图片地址 / 切图落盘),**本轮根本没 render**。照 verdict 里的 blockers 补内容,别调像素、别重跑空转。
|
|
22
|
+
· stage=structure(elements.missing>0 或 validate/lint/preflight 未过)⇒ **先补要素,别调像素**:把设计稿的文案补回对应节点;绑定变量的把 defaultValue 改成设计稿真值。改样式不会把丢掉的文案变回来。
|
|
23
|
+
· stage=pixel ⇒ 内容与结构都齐了,才进入样式 / 布局收敛(一轮只改一个分项)。
|
|
24
|
+
实测事故(2026-09-21):模型把设计稿的产品名 / 利率 / 期限整批换成自己编的占位值(「理财优选1号 / 2.45 / 每日申购」),像素照样刷到 89%、validate/lint 全绿,而卡的**内容真值整批丢失**,直到人工比对设计稿才发现。报告 elements.invented 会把这类「schema 里有、设计稿里没有」的自造文案逐条点名,elements.missing 列出漏掉的要素。
|
|
25
|
+
报告还带:contentOffset1x + contentOffsetHint(内容整体平移时先修对齐,别逐行调间距)、**contributors + contributorsNote(分项归因:每个差异热区给出最可能出问题的具体节点)**、widthMismatch(--width 与设计稿卡面宽不符 ⇒ 先改 --width,否则 match/dy 都不可信)、lint.warningDetails、preflight.details、renderImage(渲染原图路径)、diffImageNote(并排图顶部色条是工具标题栏,不是卡片内容)、stalled(**连续 3 轮未刷新历史最好成绩**——震荡下滑同样触发——附 best 与回退命令)、best(历史最好成绩 {match,round})。
|
|
26
|
+
**contributors 是「差在哪个节点」的唯一答案,用它替代整份重写 IR**:数组每项 = 一个热区(hotspot/diffPct)+ elements[](按最内层优先排序的具体节点:cid / cls / text / rect / h / hSibling(同类中位高度)/ designH / dH(与设计稿高度差)/ flags / hotCover)。读法:flags 里 text-clipped ⇒ 该节点文本被裁剪,查 isMaxLine / maxLine / lineHeight;vs-design ±N ⇒ 该节点高度与设计稿差 N,查它自己的 paddingTop/Bottom 与 lineHeight;vs-siblings ±N ⇒ 同类元素里只有它高度不同,查它自己的 padding/margin(**不是整层 gap**);bottom-edge / right-edge ⇒ 差异贴边界外,查下边距 / 圆角 / 阴影。**一轮只改 contributors[0].elements[0] 这一个节点的一个属性**,改完立刻重跑 iterate 看是否刷新最好成绩——同时改多项无法归因。designH/dH 为 null 表示没给 --spec 或文本匹配不上,此时只能靠 flags 与同类中位判断,**不要自己编设计稿高度**。
|
|
27
|
+
**报告里出现 envBlocked / verdict 以 BLOCKED 开头 ⇒ 本机没有可用的 python,validate 根本没跑**:这是环境问题,改 schema、继续 --set 都修不好它——立刻停手如实告诉用户(装 Python 或设 DSL_CARD_PYTHON=<解释器路径>)后重跑,别把它当门禁缺口反复重试。
|
|
28
|
+
**stalled 出现 ⇒ 第一件事是回退,不是接着调**(它=连续 3 轮没刷新最好成绩,说明 --set 已到天花板):① skill_exec script=skills/dsl-card-generator/scripts/ir_restore.mjs args=["--best",".cache/ai-scratch/<落点卡>.ir.best.json","--ir",".cache/ai-scratch/<落点卡>.ir.json"] 回退到历史最好成绩那一轮(写回前自动备份当前 IR),**回退后立刻重跑一轮 iterate**——旧报告的 schemaHash 已失效,--gate 会直接判 FAIL;② 之后一轮只改 gate.failed 里的**一个**分项;③ 同一项连改两轮都不动才换证据链:跑 diff_bands.mjs(args=["--id","<落点卡>","--design","<bundle>/card-N.png","--render","<报告 renderImage>"])拿逐带 dy/dh 与分区主色,再对照 references/generation_pitfalls.md 的「类型开关+值 / 静默丢弃」清单。**禁止**在 stalled 之后继续盲调 --set、**禁止**再全量重写 IR。实测事故:match 卡 73% 连调 6 轮无效,根因是 6 处 margin 漏 marginType,diff_bands 一轮就能指认;另一条轨迹 r10 做到 92.42 后 13 轮越改越差、烧掉 707 万 token 才被人工中止——正因为旧判据非要「完全不变」才触发,震荡下滑落在盲区里。
|
|
29
|
+
match 不升反降、heightDelta 非 0、或 hotspots 集中在原本已有内容的区域 ⇒ 说明在删内容凑分:回退该轮 --set 换思路。diffImage 每轮重写(报告里的路径),只给用户预览。
|
|
30
|
+
**取证禁令**:禁止用 shell_exec 写 node -e pngjs 像素脚本(行扫描 / 取色 / 自算 diff)——diff_bands.mjs、报告 hotspots、contentOffset1x、审计 colors/bgGradientHint 已经覆盖全部取证需求,实测手写脚本 4 次全是浪费步数;禁止 fs_read 任何图片;禁止在 S5 之前调 card_screenshot(渲染还没意义);禁止把 src/cards/<卡>/<卡>.png 当渲染图(那是设计稿归档,渲染原图是报告里的 renderImage)。同理**禁止自造分析 / 补丁脚本**(往草稿区写 .cjs/.mjs 再跑、node -e 扫像素或改 JSON 都在此列,实测白烧 11 轮)与**用 sed -i / 重定向直接改 IR、schema**(会绕过 validate、原子写与 .history 快照):改 IR 用 ir_patch(首选:点路径 JSON 补丁、原子、不吃引号转义)或 iterate.mjs --set,改卡片内容用 card_patch / card_write。
|
|
31
|
+
每轮用一两句正文汇报:gate.verdict / match% / heightDelta / 这轮修了什么,不要闷头连调。
|
|
32
|
+
|
|
33
|
+
## S6 收尾门禁
|
|
34
|
+
① skill_exec script=skills/dsl-card-generator/scripts/readiness.mjs,args=["--id","<落点卡>","--spec","<bundle>/spec.json","--design","<bundle>/card-N.png"],**必须 exit 0(就绪)**——内容层的四类缺口(要素 / 变量绑定 / 图片地址 / 切图落盘)在收尾时同样不许放过;② skill_exec script=skills/dsl-card-generator/scripts/check_fidelity.mjs,args=["--spec","<bundle>/spec.json","--schema","src/cards/<落点卡>/schema.json","--card","N"],按它报的文本缺失 / 字号偏差 / 颜色偏差修到无 ERROR;③ skill_exec script=skills/dsl-card-generator/scripts/iterate.mjs,args=["--gate","--out","src/cards/<落点卡>/"] 拿机器判定(读最近一轮落盘报告,schema 在报告之后被改过会直接判 FAIL;结论可能是 pass / stalled / **blocked-content** / iterating 四种,blocked-content 表示内容仍未就绪,不是收敛)。
|
|
35
|
+
正文汇报必须**逐字引用 --gate 的 verdict 与 failed 列表** + 落点卡 + 补了哪些切图 + 回滚方式。gate 未过禁止写「门禁全绿 / 已完成 / 验收通过」,必须列出未达标项、原因与下一步;禁止把未达标归因于「测量噪声 / 工具合成产物」——pixelDiff 只用渲染原图与设计原图,合成图色条不参与计算(见 diffImageNote)。
|
|
36
|
+
绑定对齐:check_fidelity 会按 data.json / defaultValue 把 $ 绑定解析回文案再对齐——报 missing-text 而该文本已绑定 ⇒ 是 mock 与设计稿不一致,改 defaultValue / data.json,**不是拆回字面量**。收尾自查:bindHints 每一项要么已绑定、要么正文说明为何保持字面量。
|
|
@@ -964,7 +964,7 @@ gen-card-test 第一轮就把收支占比条写成了静态 `43%` / `57%` 的 `D
|
|
|
964
964
|
- **判定口径**:**内容 sha256 = 唯一权威判据**;尺寸 w×h + 字节数 = 快速预筛(不单独作判据);来源(导出稿 / 画板 / 图层名,若可知)= 辅助证据与**命名依据**,不参与去重判定。
|
|
965
965
|
- **处理**:IR 里直接写语义名(`pi.value: "/slices/iconArrowRight@3x.png"`);机械产线(`sliceRefPath` / v3)产出的导出扁平名属过渡形态,交付前**成套改名**(文件名 + `lib/slice_dedupe.mjs → rewriteSliceRefs` 改写全部卡片 schema/data + 同步 `manifest.json`),**禁止新旧两份并存**。`expand_ir.mjs`(`syncReferencedSlices`)与 `generate_cards.mjs`(`collectSlicesToSrcSlices`)落地时已自动做同内容复用 + 引用回写并打印 `[slices] 内容已存在 → 复用 …`。`crop_slices.mjs --from-audit` 派生的 `_alt` 名内容确实不同(不算重复,见 §13 R-e),但仍是过渡名,建议改成语义名。
|
|
966
966
|
- **验收**:`node scripts/lint_card.mjs --id <卡id>` 无 `r59-slice-semantic-name` / `r60-slice-duplicate-content` 新增告警;`--all | grep r60` 为空;切图上传后 `src/slices/manifest.json` 里同一 sha256 只有一个 `url`(同内容副本记 `reusedFrom`)。
|
|
967
|
-
- **规则详情**:`slices_convention.md §1.1(命名)/ §3.1(落地判重)/ §3.2(上传去重)`;共享实现 `scripts/lib/slice_dedupe.mjs`;上传侧 `skills/
|
|
967
|
+
- **规则详情**:`slices_convention.md §1.1(命名)/ §3.1(落地判重)/ §3.2(上传去重)`;共享实现 `scripts/lib/slice_dedupe.mjs`;上传侧 `skills/platform-card-sync/scripts/slice_upload.mjs`。
|
|
968
968
|
|
|
969
969
|
## 15. 重复裁切根治:共用切图已落地仍被裁出 `_altN` 副本(2026-09-18 ynet-card 事故)
|
|
970
970
|
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
> **输入前置路由(先做这一步)**:本节 prompt 面向「单张设计稿图」或「已前处理的画板要素」。若用户给的是 **Sketch MeaXure 标注导出目录**(`index.html` 内嵌 `artboards` + `preview/*.png` + `assets/`),先跑 `node skills/dsl-card-generator/scripts/route_input.mjs <导出目录>`:它会判出 `meaxure-export` 并打印上游命令,由 `sketch-spec-to-card` 产出 `UI/<稿名>/` 要素包(`card-N.png` 聚焦图 + `spec.json` + `prompt.md` + `slices/`),再对每个画板套用本节 prompt——此时输入换成聚焦图 + 标注,而非整稿截图。**禁止跳过前处理直接读 `preview/` 整图建卡**(会丢掉卡片聚焦边界与标注数值)。详见 `SKILL.md §8 Workflow 0`。
|
|
42
42
|
|
|
43
43
|
```
|
|
44
|
-
你正在用 dsl-card-generator skill
|
|
44
|
+
你正在用 dsl-card-generator skill 生成一张远端卡片。
|
|
45
45
|
|
|
46
46
|
输入:附上的图片是卡片设计稿。
|
|
47
47
|
卡片意图:<一句话说明这张卡是干什么的,例如"成长等级任务卡,深色背景,列出可完成的任务与奖励">
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
| 选近邻模板 | 查 `references/card_index.json`(按 `recipes` / `tags` 过滤,不读源文件) |
|
|
45
45
|
| 写回+校验+渲染 | MCP `card_write({id, schema, verify:true})` → 再看 `card_render_status` |
|
|
46
46
|
| 视觉确认(**仅 UI 变更**) | MCP `card_screenshot({id})`(多模态读图;`render_status.ok ≠ 画对`)。按 `references/image_diff.md` 七维度(布局/层级/文案/留白/配色/字号/交互元素)逐项核对 |
|
|
47
|
-
| **非 UI 变更收口(不截图)** | 改 `inputParams` / 绑定表达式取值 / 事件代码 / `data.json` → 只跑 validate + lint + `derive_data_json --check`,再用单元断言:`evalExpr(expr, scope)` 比对返回值、`new Function(code).call(假 this)` 跑 customAction 断言 `state
|
|
47
|
+
| **非 UI 变更收口(不截图)** | 改 `inputParams` / 绑定表达式取值 / 事件代码 / `data.json` → 只跑 validate + lint + `derive_data_json --check`,再用单元断言:`evalExpr(expr, scope)` 比对返回值、`new Function(code).call(假 this)` 跑 customAction 断言 `state`(compiled 已由工具链同步,跑 code 即可)。铁律见 `references/rules.md §七.1` |
|
|
48
48
|
| **变更对比(铁律机械兜底)** | `node scripts/diff_card.mjs --id <卡id>`(vs git HEAD) / `--baseline <path.json>` / `--against <otherId>`。整体替换 schema 后必跑一次,列删除/新增/修改的 componentId |
|
|
49
49
|
| **行为规则门禁** | `node scripts/preflight.mjs --spec UI/<画板>/spec.json --id <卡id>` + `node scripts/lint_card.mjs --id <卡id>` 或 `--all`(≠ validate:lint 管渲染行为,validate 管结构字段)。**0 ERROR 是 Workflow A 步骤 4 的硬门禁**——拦截 flexShrink 缺失(R17)、元组格式(R-FMT1/2)、Icon 缺 click(R-FMT3)、Root 非零 padding(R9/R10)等「validate 漏报但渲染炸」的问题 |
|
|
50
50
|
| **抽范式片段** | `node scripts/extract_subtree.mjs --id <卡id> --component-id <cid> [--name <名>] [--out <path>]`:从已导出卡抽子树,输出含 subtree + 自动探测 requiresInputParams,写入 recipe_snippets/ 让范式库可持续增长 |
|
|
@@ -41,7 +41,7 @@ ctx.fillText(text, 0, canvas.height / 2);
|
|
|
41
41
|
|
|
42
42
|
要点:
|
|
43
43
|
- 坐标是**设计稿 px**,event.canvas 是设计稿坐标系包装对象(宽度/2dctx 已按 scale 换算)。不要乘 devicePixelRatio,不要用 canvas.width/2 做左对齐(那是设备 px 假设)。
|
|
44
|
-
-
|
|
44
|
+
- compiled 不必手写:expand_ir / card_patch 会自动同步;只有 card_write 整份写时才需自己补。
|
|
45
45
|
- 来源:creditCardBill(左对齐)、dreamPlan、assetOverview、financialProductDetail、debtDetails、myDeposit、querySelector 等 25 张卡都用 onMeasured。
|
|
46
46
|
|
|
47
47
|
## 2. 弹一张卡片(alertCard)+ 同名 Dialog 表
|
|
@@ -234,7 +234,7 @@ this.setData(this.state);
|
|
|
234
234
|
- 正号:`num > 0 && bothSign ? '+' : ''`(bothSign=true 时正数也显 +,适合收支列表)。
|
|
235
235
|
- 负号:`num < 0 ? '-' : ''`;货币符:`isPrefix ? '¥' : ''`(新生成卡用半角 `¥`,见 amount_formatting.md §2)。
|
|
236
236
|
- 金额之间的**相等比较**不能直接 `$a == $b`(两个字符串走字面比较,`"5638.00" == "5638"` 为 false,会切错条件分支)。写成 `((($a + '').replace(/,/g, '') * 1) == (($b + '').replace(/,/g, '') * 1))`;大小比较(`>` / `<`)JS 自动 coerce,无需改写。
|
|
237
|
-
-
|
|
237
|
+
- 改事件代码只写 `.code` 一份:card_patch 会自动同步 compiled(若你显式给了 compiled,则以你给的为准,不会被覆盖)。
|
|
238
238
|
|
|
239
239
|
## 6. Tab ↔ 标题头模式切换(billMode)
|
|
240
240
|
|
|
@@ -483,7 +483,7 @@ drawDonut(ratios, event.canvas, event.width, event.height);
|
|
|
483
483
|
- **坐标系**:`event.canvas` 是设计稿坐标系包装对象(缩放已换算),绘图用**逻辑设计 px**,**不要乘 devicePixelRatio**。圆心 `canvas.width/2`、半径 `width/2` 都在设计 px 内,自洽。
|
|
484
484
|
- **中心文字**:甜甜圈中间的总额/标题是**另叠一个绝对定位 Text**(不在 canvas 里画),便于用 DSL 文本样式与千分号(§5)。canvas 只负责环。
|
|
485
485
|
- **调色板**:`colors[i % colors.length]` 让扇区数超过调色板长度时循环取色;配色建议按 design 规范 Token 化(见 amount_formatting.md 配色约束),勿硬编码裸色(除非如本卡上游既定)。
|
|
486
|
-
- **
|
|
486
|
+
- **compiled 自动派生**:expand_ir / card_patch 都会让 compiled 跟随 code;只有 card_write 整份写时才需手工对齐。
|
|
487
487
|
- 铁律对齐:首行 `var self = this`、嵌套回调一律 `self`、每条语句 `;` 收尾(见 rules.md §四)。
|
|
488
488
|
- 来源:assetOverview(`onReady` 算 ratio + `canvas_4d` 140×140 甜甜圈,export-proven)。同思路可画进度环、半圆环(改 start 角与扫过的弧度范围即可)。
|
|
489
489
|
|
|
@@ -548,7 +548,7 @@ self._cdTimer = setInterval(function () {
|
|
|
548
548
|
- **进度语义**:`ratio = elapsed/total`(已消耗比例),0% 空环 → 100% 满环;中心显示 `total - elapsed`(10→0)。
|
|
549
549
|
- **重绘机制**:onMeasured 是生命周期只触发一次,定时器内必须**手动调 drawRing 重绘**,不依赖渲染器自动重触发。
|
|
550
550
|
- **配色**:背景环浅色、进度弧深色、中心数字高对比;v3 从环形组 border 色自动提取(非 #F0F0F0 描边 → 进度色)。
|
|
551
|
-
- 坐标设计 px 勿乘 DPR;
|
|
551
|
+
- 坐标设计 px 勿乘 DPR;compiled 由工具链自动同步(card_write 整份写时例外);铁律:`var self = this` + 每条 `;` 收尾。
|
|
552
552
|
|
|
553
553
|
## 11. 角标 / 角标签(AbsoluteLayout 叠加小标)
|
|
554
554
|
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
## 八、远端同步前置
|
|
89
89
|
|
|
90
90
|
- 远端保存(`card_save`)前确保本地镜像是外部最新(先 `card_sync`);保存后建议回读校验。
|
|
91
|
-
- Chrome 启用 remote-debugging(端口 9222
|
|
91
|
+
- Chrome 启用 remote-debugging(端口 9222),远端页面保持已登录态。
|
|
92
92
|
- IDC10010 保存只传内层 body + 必带 `sassAppId` / `sassWorkspaceId`;"交易成功" ≠ 真落库,须 IDC10012 读回校验。
|
|
93
93
|
|
|
94
94
|
## 九、组件级硬约束
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|---|---|---|
|
|
13
13
|
| ① 上游导出(sketch-spec-to-card 中间产物) | `UI/<artboard>/slices/<category>/<name>@3x.png` | 多级目录,保留导出原始结构,仅作为来源 |
|
|
14
14
|
| ② 生成汇聚 + 引用(dsl-card-generator) | `src/slices/<flat>@3x.png` + schema `value="/slices/<flat>@3x.png"` | **扁平落点**,无子目录;`<flat>` = 导出相对路径的 `/` 替换为 `_` |
|
|
15
|
-
| ③ 切图上传(`slice_upload.mjs` / 顶栏「远端 → 切图上传」) | `@filestore/<ns>/<ts>_<name>@3x.png` | 上传 `src/slices/`
|
|
15
|
+
| ③ 切图上传(`slice_upload.mjs` / 顶栏「远端 → 切图上传」) | `@filestore/<ns>/<ts>_<name>@3x.png` | 上传 `src/slices/` 真源到远端图片库(`CC0000.do`),schema 引用改写为 `@filestore/...`,并在 `src/slices/manifest.json` 记「本地文件 ↔ 线上地址」映射;运行时 `adapter.doReplaceUrl`:`@filestore/x` → `/minio/x` → assetProxy 拉远端 |
|
|
16
16
|
|
|
17
17
|
**扁平化示例**(用户给定基准):
|
|
18
18
|
```
|
|
@@ -59,7 +59,7 @@ schema 引用:/slices/8.icon_通用_金额隐藏@3x.png
|
|
|
59
59
|
- 一律用 **绝对路径前缀 `/slices/`**,后接扁平文件名(含 `@3x` 与扩展名)。
|
|
60
60
|
- 正确:`/slices/8.icon_通用_金额隐藏@3x.png`
|
|
61
61
|
- 错误:`src/slices/...`、`slices/...`、`UI/.../...@3x.png`、`@filestore/...`(同步前不允许)
|
|
62
|
-
- 生成阶段(上传前)只能出现 `/slices/...`;`@filestore/` 仅由「切图上传」(`skills/
|
|
62
|
+
- 生成阶段(上传前)只能出现 `/slices/...`;`@filestore/` 仅由「切图上传」(`skills/platform-card-sync/scripts/slice_upload.mjs`)在上传成功后改写产生——**没有自动上传**,只有用户点顶栏「远端 → 切图上传」/ 调 MCP `card_slice_upload` / 跑 CLI 时才会发生。
|
|
63
63
|
- 运行时解析:浏览器以页面 origin 为基解析 `/slices/...` → 命中 Vite 的 `/slices/*` 静态中间件(见 §4)。
|
|
64
64
|
|
|
65
65
|
## 2.1 排除「导出目录」切图层(`99.切图/*`)—— 不作为卡片内容
|
|
@@ -100,7 +100,7 @@ schema 引用:/slices/8.icon_通用_金额隐藏@3x.png
|
|
|
100
100
|
|
|
101
101
|
## 3.2 上传去重:共用切图只上传一次(强制)
|
|
102
102
|
|
|
103
|
-
③ 阶段 `skills/
|
|
103
|
+
③ 阶段 `skills/platform-card-sync/scripts/slice_upload.mjs` 按**内容 sha256** 判重,且**不认文件名**:
|
|
104
104
|
|
|
105
105
|
- 同一张图被多张卡引用 → **只上传一次**,其余卡的 `/slices/<name>` 引用改写为**同一个** `@filestore/...` 地址。
|
|
106
106
|
- `src/slices/` 里内容相同但命名不同的副本、以及**同一批次内**的同内容文件 → 复用已上传/待上传那一份的地址,`manifest.json` 记 `reusedFrom: <源文件名>`;**绝不为同一张图产生第二个线上地址**。
|
|
@@ -143,7 +143,7 @@ sketch-spec-to-card 产出的 spec 中,**slice 元素的 `name`(图层路径
|
|
|
143
143
|
| v3 变体(CLI 直组装)生成 Image 时引用 `/slices/` | `scripts/spec_to_schema_v3.mjs`(`imageFromSlice(sliceEl, sem.slicesExports)`,由 `extractSemantics` 透传 `spec.slices`;geo 变体已随 geo 生成器删除) |
|
|
144
144
|
| 拷贝被引用切图到 `src/slices` | `scripts/generate_cards.mjs`(`collectSlicesToSrcSlices`,仅 referenced) |
|
|
145
145
|
| 本地 `/slices/*` 静态服务(自包含) | `scripts/render_card.mjs`(临时 HTTP 中间件,直接读 `slicesDir`) |
|
|
146
|
-
| ③ 阶段:上传切图 + 写映射 + 改写引用 | `skills/
|
|
146
|
+
| ③ 阶段:上传切图 + 写映射 + 改写引用 | `skills/platform-card-sync/scripts/slice_upload.mjs`(`uploadSlices`);HTTP 端点 `server/core/sliceUpload.js`(`POST /__slice-upload`);编辑器入口 `card-editor/App.vue`(`uploadCardSlices`);MCP `card_slice_upload` |
|
|
147
147
|
| 语义名判定 + 消毒 + 内容 sha256 去重 + 引用改写(共享实现) | `scripts/lib/slice_dedupe.mjs`(`checkSliceName` / **`sanitizeSliceFileName`** / **`sliceNameMatches`** / **`findUrlReservedChars`** / **`sliceNameFromRef`** / **`isRemoteSliceRef`** / **`buildSliceFileIndex`** / **`resolveSliceRefOnDisk`** / `buildContentIndex` / `sha256File` / `duplicatePeers` / `rewriteSliceRefs` / `collectSliceRefStrings`)—— lint / preflight / crop_slices / slicePath / expand_ir / generate_cards 全部共用,**改一处即全链路生效** |
|
|
148
148
|
| 像素级内容判重(裁切图 ≡ 导出资产 / 已重命名共用切图) | `scripts/lib/slice_pixels.mjs`(`pixelsMatch` / `findPixelTwin` / `cropPng` / `pngPixelSha` / `readPng`) |
|
|
149
149
|
| 缺口审计:指纹分组 + 像素复用 + manifest 别名(`renamedFrom`/`mergedFrom`) | `scripts/spec_audit.mjs`(`groupByFingerprint` / `regionTwin` / `buildAliasMap`;verdict `content-twin`、crops[].reuseOf/alsoRects) |
|
|
@@ -152,7 +152,7 @@ sketch-spec-to-card 产出的 spec 中,**slice 元素的 `name`(图层路径
|
|
|
152
152
|
| 落地时同内容去重(不重复导入 + 改写引用回写) | `scripts/expand_ir.mjs`(`syncReferencedSlices`,已 export)、`scripts/iterate.mjs`(一条龙闭环内调用同一函数,支持 `--slices-dir`)、`scripts/generate_cards.mjs`(`collectSlicesToSrcSlices`) |
|
|
153
153
|
| 命名 / 重复告警(lint,WARN) | `scripts/lint_card.mjs`(`r59-slice-semantic-name` / `r60-slice-duplicate-content`) |
|
|
154
154
|
| 存量切图归一化(语义化改名 + 同内容合并 + 引用/manifest 成套改写;**默认 dry-run**) | `scripts/slices_normalize.mjs`(`--write` 落盘;`--dedupe` / `--prune-unreferenced` / `--rename old=new` / `--map file.json` / `--no-legacy` / `--json`;内置 `LEGACY_RENAMES` = 2026-09-17 逐张核图确认的存量映射) |
|
|
155
|
-
| 上传阶段同内容复用线上地址(`reusedFrom`) | `skills/
|
|
155
|
+
| 上传阶段同内容复用线上地址(`reusedFrom`) | `skills/platform-card-sync/scripts/slice_upload.mjs`(`shaToUploaded` 索引 + 批次内延后复用) |
|
|
156
156
|
|
|
157
157
|
> v3 生成器引用切片必须走 `sliceRefPath()`(产出 `/slices/<flat>`),与其 `collectSlicesToSrcSlices` 拷贝目标(扁平 `<flat>`)严格对应,禁止各自拼字符串。
|
|
158
158
|
|
|
@@ -164,7 +164,7 @@ sketch-spec-to-card 产出的 spec 中,**slice 元素的 `name`(图层路径
|
|
|
164
164
|
| schema 引用写 `src/slices/...` 或 `slices/...` | Vite 不识别,预览拉不到 | 一律 `/slices/<flat>@3x.png` |
|
|
165
165
|
| 无差别拷贝 `UI/<artboard>/slices/` 下全部切图 | 带进 chrome / 冗余素材 | `collectSlicesToSrcSlices` 仅拷 referenced |
|
|
166
166
|
| 元素名直接当文件名(未归一化) | `顶部返回_icon` ≠ `顶部返回` 匹配失败 → 退化占位图 | 走 `resolveSliceExport` 归一化匹配 |
|
|
167
|
-
| 推送远端时仍残留 `/slices/` 引用 | 线上取不到图(远端没有 `/slices/` 静态服务) | 推送前先跑「切图上传」:`node skills/
|
|
167
|
+
| 推送远端时仍残留 `/slices/` 引用 | 线上取不到图(远端没有 `/slices/` 静态服务) | 推送前先跑「切图上传」:`node skills/platform-card-sync/scripts/slice_upload.mjs --cardId <id>`,改写为 `@filestore/<ns>/<ts>_<name>@3x.png` |
|
|
168
168
|
| 沿用素材原名落地(`编组 4.png` / `8.icon_通用_箭头右@3x.png` / `展开.png`) | 名字看不出用途,无法判断能否复用,后期改名要翻全部卡片 | 按功能/作用语义化命名(§1.1);lint `r59` 告警 |
|
|
169
169
|
| 同图多名各存一份(`chevron_right.png` + `assetAllocRadar_arrow_right.png`) | `src/slices` 堆重复副本,上传后同一张图多个线上地址 | 落地前按 sha256 判重、命中即复用既有名(§3.1);lint `r60` 告警 |
|
|
170
170
|
| 改名只改文件名、不改引用 | schema 指向不存在的文件 → 预览 404 | `rewriteSliceRefs` 成套改写全部卡片 schema/data + 同步 `manifest.json` |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
Inventory exported
|
|
3
|
+
Inventory exported 远端/YNet card schemas.
|
|
4
4
|
|
|
5
5
|
Use this after syncing cards into src/cards to discover component classes,
|
|
6
6
|
properties, style groups, directives, events, action types, and representative
|
|
@@ -393,7 +393,7 @@ const INTENTS = ['blank', 'list', 'detail', 'form', 'dialog', 'tab', 'canvas-hea
|
|
|
393
393
|
* (padding:2px / gap:4px / justify-content:center)。形状与存量卡根节点一致
|
|
394
394
|
* (style-meta.json:marginType / paddingType,'0' = 全边统一值)。
|
|
395
395
|
* 同一形状还有两份内联副本(禁跨包 import):server/core/cardFiles.js 的 blankResetStyle、
|
|
396
|
-
* skills/
|
|
396
|
+
* skills/platform-card-sync api_batch_sync.mjs 的 buildBlankSchema —— 改这里必须同步改那两处。
|
|
397
397
|
*/
|
|
398
398
|
function blankResetStyle() {
|
|
399
399
|
return {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 直接扁平化落到 src/slices/ 后无法从名字看出用途;
|
|
7
7
|
* 2. 同一张图被不同画板/不同卡各导出一份,名字不同但内容完全一致 → src/slices/ 里堆重复副本
|
|
8
8
|
* (实测 60 个文件只有 50 份唯一内容);
|
|
9
|
-
* 3.
|
|
9
|
+
* 3. 重复副本各自上传到远端图片库 → 同一张图产生多个 @filestore 地址。
|
|
10
10
|
*
|
|
11
11
|
* 判定口径(权威 → 辅助):
|
|
12
12
|
* - **内容 sha256**:唯一权威判据,一致即同一张图;
|
|
@@ -209,7 +209,7 @@ export function sliceNameFromRef(ref) {
|
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
/**
|
|
212
|
-
*
|
|
212
|
+
* 远端切图引用判定:已上传远端(@filestore)、外链(http/https/protocol-relative)、内联 data URI。
|
|
213
213
|
* 这类引用天然没有本地文件,不能参与「本地 src/slices 里是否存在」的判定(否则满仓误报)。
|
|
214
214
|
*/
|
|
215
215
|
export function isRemoteSliceRef(ref) {
|