cardbuddy 0.11.0 → 0.12.1

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 (313) hide show
  1. package/README.md +46 -5
  2. package/dist/cli.js +51 -1
  3. package/dist/config.js +51 -16
  4. package/dist/core.js +2 -2
  5. package/dist/init.js +425 -0
  6. package/dist/preview-editor/assets/index-CHuGGIR3.js +276 -0
  7. package/dist/preview-editor/assets/{index-B4VZgNSK.css → index-CQScMhyg.css} +1 -1
  8. package/dist/preview-editor/index.html +2 -2
  9. package/dist/standalone.js +8 -0
  10. package/dist/vendored/mcp/client.mjs +537 -0
  11. package/dist/vendored/mcp/tools.mjs +268 -0
  12. package/dist/vendored/preview/assets/default-image.png +0 -0
  13. package/dist/{server-core → vendored/server/core}/ai-chat.js +82 -38
  14. package/dist/{server-core → vendored/server/core}/browser.js +1 -1
  15. package/dist/{server-core → vendored/server/core}/cardSave.js +8 -3
  16. package/dist/{server-core → vendored/server/core}/cardSync.js +6 -14
  17. package/dist/{server-core → vendored/server/core}/cardValidate.js +11 -8
  18. package/dist/{server-core → vendored/server/core}/index.js +5 -3
  19. package/dist/vendored/server/core/skillPaths.js +124 -0
  20. package/dist/{server-core → vendored/server/core}/sliceUpload.js +5 -8
  21. package/dist/vendored/skills/README.md +93 -0
  22. package/dist/vendored/skills/card-doctor/SKILL.md +52 -0
  23. package/dist/vendored/skills/card-skill-trainer/SKILL.md +312 -0
  24. package/dist/vendored/skills/card-skill-trainer/baselines/v3-r038n.json +43 -0
  25. package/dist/vendored/skills/card-skill-trainer/baselines/v3-r039n.json +47 -0
  26. package/dist/vendored/skills/card-skill-trainer/baselines/v3-r050n.json +48 -0
  27. package/dist/vendored/skills/card-skill-trainer/scripts/build_dataset.py +472 -0
  28. package/dist/vendored/skills/card-skill-trainer/scripts/build_sample.mjs +162 -0
  29. package/dist/vendored/skills/card-skill-trainer/scripts/diagnose_case.mjs +241 -0
  30. package/dist/vendored/skills/card-skill-trainer/scripts/lib/scoring.mjs +525 -0
  31. package/dist/vendored/skills/card-skill-trainer/scripts/round_diff.mjs +133 -0
  32. package/dist/vendored/skills/card-skill-trainer/scripts/run_round.mjs +1043 -0
  33. package/dist/vendored/skills/card-skill-trainer/scripts/score_round.mjs +195 -0
  34. package/dist/vendored/skills/card-skill-trainer/scripts/test_missing_slice.py +122 -0
  35. package/dist/vendored/skills/dsl-card-ai/SKILL.md +191 -0
  36. package/dist/vendored/skills/dsl-card-generator/SKILL.md +480 -0
  37. package/dist/vendored/skills/dsl-card-generator/config/render-contract.json +11 -0
  38. package/dist/vendored/skills/dsl-card-generator/references/amount_formatting.md +138 -0
  39. package/dist/vendored/skills/dsl-card-generator/references/card_index.json +1597 -0
  40. package/dist/vendored/skills/dsl-card-generator/references/card_templates.json +384 -0
  41. package/dist/vendored/skills/dsl-card-generator/references/card_to_summary.md +56 -0
  42. package/dist/vendored/skills/dsl-card-generator/references/component_rules.md +76 -0
  43. package/dist/vendored/skills/dsl-card-generator/references/component_specs.md +393 -0
  44. package/dist/vendored/skills/dsl-card-generator/references/dsl_structure.md +208 -0
  45. package/dist/vendored/skills/dsl-card-generator/references/event_patterns.md +287 -0
  46. package/dist/vendored/skills/dsl-card-generator/references/expression_engine.md +121 -0
  47. package/dist/vendored/skills/dsl-card-generator/references/field_name_dict.json +34 -0
  48. package/dist/vendored/skills/dsl-card-generator/references/field_naming.md +101 -0
  49. package/dist/vendored/skills/dsl-card-generator/references/generation_pitfalls.md +928 -0
  50. package/dist/vendored/skills/dsl-card-generator/references/handcrafted/README.md +69 -0
  51. package/dist/vendored/skills/dsl-card-generator/references/handcrafted/examples_index.json +2967 -0
  52. package/dist/vendored/skills/dsl-card-generator/references/handcrafted/patterns.json +2835 -0
  53. package/dist/vendored/skills/dsl-card-generator/references/handcrafted/recipes_v2.json +648 -0
  54. package/dist/vendored/skills/dsl-card-generator/references/handcrafted/recipes_v2_schema.md +295 -0
  55. package/dist/vendored/skills/dsl-card-generator/references/image_diff.md +48 -0
  56. package/dist/vendored/skills/dsl-card-generator/references/image_to_card.md +129 -0
  57. package/dist/vendored/skills/dsl-card-generator/references/ir-format.md +212 -0
  58. package/dist/vendored/skills/dsl-card-generator/references/message-center-pattern.md +149 -0
  59. package/dist/vendored/skills/dsl-card-generator/references/platform_jsapi.md +196 -0
  60. package/dist/vendored/skills/dsl-card-generator/references/previewer_contract.md +54 -0
  61. package/dist/vendored/skills/dsl-card-generator/references/quickref.md +197 -0
  62. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/absolute-badge.json +84 -0
  63. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/accordion-group-toggle.json +18 -0
  64. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/alertCard-pair.json +12 -0
  65. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/alertDialog-call.json +12 -0
  66. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-countdown.json +15 -0
  67. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-donut.json +15 -0
  68. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-gauge-score.json +11 -0
  69. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-radar.json +10 -0
  70. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-title-head.json +13 -0
  71. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/globalSet.json +12 -0
  72. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/label-col-modes.json +21 -0
  73. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/list-expand-collapse.json +17 -0
  74. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/money-format.json +11 -0
  75. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/opacity-switch.json +130 -0
  76. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/progress-bar.json +25 -0
  77. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/tab-billMode.json +15 -0
  78. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/title-underline-overlay.json +21 -0
  79. package/dist/vendored/skills/dsl-card-generator/references/recipes.md +797 -0
  80. package/dist/vendored/skills/dsl-card-generator/references/rules.md +235 -0
  81. package/dist/vendored/skills/dsl-card-generator/references/slices_convention.md +177 -0
  82. package/dist/vendored/skills/dsl-card-generator/references/spec_to_schema_patterns.md +609 -0
  83. package/dist/vendored/skills/dsl-card-generator/references/ui_fidelity_rules.md +131 -0
  84. package/dist/vendored/skills/dsl-card-generator/scripts/add_component.mjs +118 -0
  85. package/dist/vendored/skills/dsl-card-generator/scripts/analyze_component_usage.mjs +153 -0
  86. package/dist/vendored/skills/dsl-card-generator/scripts/apply_opacity_pattern.mjs +343 -0
  87. package/dist/vendored/skills/dsl-card-generator/scripts/build_card_index.mjs +108 -0
  88. package/dist/vendored/skills/dsl-card-generator/scripts/build_card_templates.mjs +190 -0
  89. package/dist/vendored/skills/dsl-card-generator/scripts/check_fidelity.mjs +257 -0
  90. package/dist/vendored/skills/dsl-card-generator/scripts/check_magic_values.mjs +79 -0
  91. package/dist/vendored/skills/dsl-card-generator/scripts/crop_slices.mjs +98 -0
  92. package/dist/vendored/skills/dsl-card-generator/scripts/describe_card.mjs +265 -0
  93. package/dist/vendored/skills/dsl-card-generator/scripts/diff_bands.mjs +257 -0
  94. package/dist/vendored/skills/dsl-card-generator/scripts/diff_card.mjs +316 -0
  95. package/dist/vendored/skills/dsl-card-generator/scripts/expand_ir.mjs +873 -0
  96. package/dist/vendored/skills/dsl-card-generator/scripts/extract_handcrafted.py +1199 -0
  97. package/dist/vendored/skills/dsl-card-generator/scripts/extract_subtree.mjs +191 -0
  98. package/dist/vendored/skills/dsl-card-generator/scripts/generate_cards.mjs +389 -0
  99. package/dist/vendored/skills/dsl-card-generator/scripts/inventory_schemas.py +160 -0
  100. package/dist/vendored/skills/dsl-card-generator/scripts/iterate.mjs +700 -0
  101. package/dist/vendored/skills/dsl-card-generator/scripts/lib/design_image.mjs +56 -0
  102. package/dist/vendored/skills/dsl-card-generator/scripts/lib/design_sample.mjs +328 -0
  103. package/dist/vendored/skills/dsl-card-generator/scripts/lib/layout-tokens.mjs +79 -0
  104. package/dist/vendored/skills/dsl-card-generator/scripts/lib/paths.mjs +103 -0
  105. package/dist/vendored/skills/dsl-card-generator/scripts/lib/scaffold.mjs +575 -0
  106. package/dist/vendored/skills/dsl-card-generator/scripts/lib/slicePath.mjs +81 -0
  107. package/dist/vendored/skills/dsl-card-generator/scripts/lib/slice_dedupe.mjs +147 -0
  108. package/dist/vendored/skills/dsl-card-generator/scripts/lint_autofix.mjs +363 -0
  109. package/dist/vendored/skills/dsl-card-generator/scripts/lint_card.mjs +1399 -0
  110. package/dist/vendored/skills/dsl-card-generator/scripts/preflight.mjs +249 -0
  111. package/dist/vendored/skills/dsl-card-generator/scripts/register_card_template.mjs +208 -0
  112. package/dist/vendored/skills/dsl-card-generator/scripts/render_baseline.mjs +231 -0
  113. package/dist/vendored/skills/dsl-card-generator/scripts/render_card.mjs +801 -0
  114. package/dist/vendored/skills/dsl-card-generator/scripts/render_diff.mjs +239 -0
  115. package/dist/vendored/skills/dsl-card-generator/scripts/resolve_card_template.mjs +73 -0
  116. package/dist/vendored/skills/dsl-card-generator/scripts/route_input.mjs +447 -0
  117. package/dist/vendored/skills/dsl-card-generator/scripts/scaffold_card.mjs +81 -0
  118. package/dist/vendored/skills/dsl-card-generator/scripts/slices_normalize.mjs +316 -0
  119. package/dist/vendored/skills/dsl-card-generator/scripts/spec_audit.mjs +462 -0
  120. package/dist/vendored/skills/dsl-card-generator/scripts/spec_to_schema_v3.mjs +7735 -0
  121. package/dist/vendored/skills/dsl-card-generator/scripts/spec_tree.mjs +355 -0
  122. package/dist/vendored/skills/dsl-card-generator/scripts/validate_all_schemas.py +71 -0
  123. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/default-image-wzZYQ86K.png +0 -0
  124. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-C9k-07y7.css +1 -0
  125. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-oFRM4f4V.js +55 -0
  126. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/svg-symbols-B2exBrAr.svg +118 -0
  127. package/dist/vendored/skills/dsl-card-generator/vendor/preview/meta/component-meta.json +2714 -0
  128. package/dist/vendored/skills/dsl-card-generator/vendor/preview/meta/directive-meta.json +40 -0
  129. package/dist/vendored/skills/dsl-card-generator/vendor/preview/meta/style-meta.json +1086 -0
  130. package/dist/vendored/{standalone.html → skills/dsl-card-generator/vendor/preview/standalone.html} +2 -1
  131. package/dist/vendored/skills/finmall-card-sync/SKILL.md +262 -0
  132. package/dist/vendored/skills/finmall-session-navigator/SKILL.md +169 -0
  133. package/dist/vendored/skills/finmall-session-navigator/scripts/finmall_login.mjs +882 -0
  134. package/dist/vendored/skills/finmall-session-navigator/scripts/package-lock.json +59 -0
  135. package/dist/vendored/skills/finmall-session-navigator/scripts/package.json +10 -0
  136. package/dist/vendored/skills/finmall-session-navigator/scripts/session-manager.mjs +96 -0
  137. package/dist/vendored/skills/shared/README.md +55 -0
  138. package/dist/vendored/skills/shared/auth-model.md +79 -0
  139. package/dist/vendored/skills/sketch-spec-to-card/SKILL.md +329 -0
  140. package/dist/vendored/skills/sketch-spec-to-card/references/boundary.json +14 -0
  141. package/dist/vendored/skills/sketch-spec-to-card/references/card_refs.json +13 -0
  142. package/dist/vendored/skills/sketch-spec-to-card/references/spec-schema.md +103 -0
  143. package/dist/vendored/skills/sketch-spec-to-card/scripts/build_prompt.py +797 -0
  144. package/dist/vendored/skills/sketch-spec-to-card/scripts/card_refs.py +94 -0
  145. package/dist/vendored/skills/sketch-spec-to-card/scripts/cluster_states.py +706 -0
  146. package/dist/vendored/skills/sketch-spec-to-card/scripts/parse_meaxure.py +1781 -0
  147. package/dist/vendored/skills/sketch-spec-to-card/scripts/png_utils.py +318 -0
  148. package/dist/vendored/skills/sketch-spec-to-card/scripts/vision.py +1118 -0
  149. package/dist/vendored/templates/helloCard/data.json +10 -0
  150. package/dist/vendored/templates/helloCard/schema.json +238 -0
  151. package/dist/vendored/tools/link-skills.mjs +485 -0
  152. package/package.json +2 -1
  153. package/dist/preview-editor/assets/index-jl4YJzKv.js +0 -180
  154. package/dist/vendored/App.vue +0 -2858
  155. package/dist/vendored/ansi.js +0 -185
  156. package/dist/vendored/assets/apple-touch-icon.png +0 -0
  157. package/dist/vendored/assets/default-image.png +0 -0
  158. package/dist/vendored/assets/favicon-16.png +0 -0
  159. package/dist/vendored/assets/favicon-32.png +0 -0
  160. package/dist/vendored/assets/site-icon.png +0 -0
  161. package/dist/vendored/assets/svgIcons/icons.svg +0 -87
  162. package/dist/vendored/assets/svgIcons/index.js +0 -39
  163. package/dist/vendored/components/AiSettings.vue +0 -392
  164. package/dist/vendored/components/AlertCardInstance.vue +0 -58
  165. package/dist/vendored/components/AlertCardLayer.vue +0 -59
  166. package/dist/vendored/components/ApiCallPanel.vue +0 -127
  167. package/dist/vendored/components/ApiPayloadTree.vue +0 -201
  168. package/dist/vendored/components/AppToast.vue +0 -37
  169. package/dist/vendored/components/CardGallery.vue +0 -291
  170. package/dist/vendored/components/CardPreview.vue +0 -72
  171. package/dist/vendored/components/CardSelect.vue +0 -206
  172. package/dist/vendored/components/ChatComposer.vue +0 -413
  173. package/dist/vendored/components/ChatMessage.vue +0 -383
  174. package/dist/vendored/components/ChatPanel.vue +0 -937
  175. package/dist/vendored/components/ComponentPalette.vue +0 -301
  176. package/dist/vendored/components/ConfirmDialog.vue +0 -54
  177. package/dist/vendored/components/DatePickerOverlay.vue +0 -345
  178. package/dist/vendored/components/DebugPanel.vue +0 -1734
  179. package/dist/vendored/components/DeviceOverlay.vue +0 -70
  180. package/dist/vendored/components/Dropdown.vue +0 -193
  181. package/dist/vendored/components/EventEditor.vue +0 -273
  182. package/dist/vendored/components/HoverGuides.vue +0 -122
  183. package/dist/vendored/components/JsonTree.vue +0 -547
  184. package/dist/vendored/components/LogValue.vue +0 -52
  185. package/dist/vendored/components/MarkdownBody.vue +0 -64
  186. package/dist/vendored/components/NewCardDialog.vue +0 -132
  187. package/dist/vendored/components/NodeInspector.vue +0 -295
  188. package/dist/vendored/components/NumberStepper.vue +0 -125
  189. package/dist/vendored/components/PickerOverlay.vue +0 -131
  190. package/dist/vendored/components/PropControl.vue +0 -245
  191. package/dist/vendored/components/PropTree.vue +0 -128
  192. package/dist/vendored/components/RemotePreview.vue +0 -173
  193. package/dist/vendored/components/SelectionOverlay.vue +0 -105
  194. package/dist/vendored/components/StructureTree.vue +0 -1002
  195. package/dist/vendored/components/Switch.vue +0 -38
  196. package/dist/vendored/components/setters/SetBgPosition.vue +0 -64
  197. package/dist/vendored/components/setters/SetBgSize.vue +0 -90
  198. package/dist/vendored/components/setters/SetCode.vue +0 -198
  199. package/dist/vendored/components/setters/SetColor.vue +0 -244
  200. package/dist/vendored/components/setters/SetCustomSelect.vue +0 -58
  201. package/dist/vendored/components/setters/SetExpr.vue +0 -107
  202. package/dist/vendored/components/setters/SetGradient.vue +0 -125
  203. package/dist/vendored/components/setters/SetGrid9.vue +0 -68
  204. package/dist/vendored/components/setters/SetIconSelect.vue +0 -67
  205. package/dist/vendored/components/setters/SetIfForm.vue +0 -61
  206. package/dist/vendored/components/setters/SetImage.vue +0 -43
  207. package/dist/vendored/components/setters/SetList.vue +0 -139
  208. package/dist/vendored/components/setters/SetNumber.vue +0 -118
  209. package/dist/vendored/components/setters/SetNumberStyle.vue +0 -113
  210. package/dist/vendored/components/setters/SetPadding.vue +0 -77
  211. package/dist/vendored/components/setters/SetSegment.vue +0 -160
  212. package/dist/vendored/components/setters/SetSelect.vue +0 -51
  213. package/dist/vendored/components/setters/SetSwitch.vue +0 -49
  214. package/dist/vendored/components/setters/SetText.vue +0 -52
  215. package/dist/vendored/components/setters/props.js +0 -23
  216. package/dist/vendored/fidelity/baseline-messageOverview.json +0 -87
  217. package/dist/vendored/fidelity/calibration.json +0 -29
  218. package/dist/vendored/fidelity/fidelity-messageOverview-after.html +0 -114
  219. package/dist/vendored/fidelity/fidelity-messageOverview-after.json +0 -272
  220. package/dist/vendored/fidelity/fidelity-messageOverview.html +0 -185
  221. package/dist/vendored/fidelity/fidelity-messageOverview.json +0 -336
  222. package/dist/vendored/fidelity/fidelity-messageOverview_v2.html +0 -262
  223. package/dist/vendored/fidelity/fidelity-messageOverview_v2.json +0 -502
  224. package/dist/vendored/fidelity/fidelity-messageOverview_v3.html +0 -201
  225. package/dist/vendored/fidelity/fidelity-messageOverview_v3.json +0 -417
  226. package/dist/vendored/fidelity/fidelity-messageOverview_visual.html +0 -125
  227. package/dist/vendored/fidelity/fidelity-messageOverview_visual.json +0 -338
  228. package/dist/vendored/index.html +0 -16
  229. package/dist/vendored/main.js +0 -37
  230. package/dist/vendored/runtime/ai-api.js +0 -165
  231. package/dist/vendored/runtime/alert-card.js +0 -125
  232. package/dist/vendored/runtime/app-toast.js +0 -33
  233. package/dist/vendored/runtime/clipboard-node.js +0 -78
  234. package/dist/vendored/runtime/clipboard.js +0 -29
  235. package/dist/vendored/runtime/event-model.js +0 -300
  236. package/dist/vendored/runtime/hoverGuide.js +0 -31
  237. package/dist/vendored/runtime/jsapi-mock.js +0 -256
  238. package/dist/vendored/runtime/markdown.js +0 -140
  239. package/dist/vendored/runtime/node-model.js +0 -217
  240. package/dist/vendored/skills/.manifest.json +0 -1101
  241. package/dist/vendored/standalone-main.js +0 -36
  242. package/dist/vendored/styles/ai-chat.css +0 -1850
  243. package/dist/vendored/styles/app.css +0 -3648
  244. package/dist/vendored/styles/card-gallery.css +0 -285
  245. package/dist/vendored/styles/card-select.css +0 -288
  246. package/dist/vendored/styles/components.css +0 -383
  247. package/dist/vendored/styles/dropdown.css +0 -324
  248. package/dist/vendored/styles/json-tree.css +0 -308
  249. package/dist/vendored/styles/node-inspector.css +0 -800
  250. package/dist/vendored/styles/setters.css +0 -1050
  251. package/dist/vendored/styles/standalone.css +0 -27
  252. package/dist/vendored/styles/structure-tree.css +0 -460
  253. /package/dist/vendored/{api.js → preview/api.js} +0 -0
  254. /package/dist/vendored/{assets → preview/assets}/svg-symbols.svg +0 -0
  255. /package/dist/vendored/{components → preview/components}/AbsoluteLayout.vue +0 -0
  256. /package/dist/vendored/{components → preview/components}/Button.vue +0 -0
  257. /package/dist/vendored/{components → preview/components}/Canvas.vue +0 -0
  258. /package/dist/vendored/{components → preview/components}/CheckGroup.vue +0 -0
  259. /package/dist/vendored/{components → preview/components}/Column.vue +0 -0
  260. /package/dist/vendored/{components → preview/components}/CustomPlaceholder.vue +0 -0
  261. /package/dist/vendored/{components → preview/components}/Divider.vue +0 -0
  262. /package/dist/vendored/{components → preview/components}/GridLayout.vue +0 -0
  263. /package/dist/vendored/{components → preview/components}/Icon.vue +0 -0
  264. /package/dist/vendored/{components → preview/components}/Image.vue +0 -0
  265. /package/dist/vendored/{components → preview/components}/Input.vue +0 -0
  266. /package/dist/vendored/{components → preview/components}/ListLayout.vue +0 -0
  267. /package/dist/vendored/{components → preview/components}/Map.vue +0 -0
  268. /package/dist/vendored/{components → preview/components}/MultiColumn.vue +0 -0
  269. /package/dist/vendored/{components → preview/components}/MultiText.vue +0 -0
  270. /package/dist/vendored/{components → preview/components}/ProgressSlider.vue +0 -0
  271. /package/dist/vendored/{components → preview/components}/RadioButton.vue +0 -0
  272. /package/dist/vendored/{components → preview/components}/RenderTree.vue +0 -0
  273. /package/dist/vendored/{components → preview/components}/Root.vue +0 -0
  274. /package/dist/vendored/{components → preview/components}/Row.vue +0 -0
  275. /package/dist/vendored/{components → preview/components}/SvgCode.vue +0 -0
  276. /package/dist/vendored/{components → preview/components}/SvgSymbols.vue +0 -0
  277. /package/dist/vendored/{components → preview/components}/Text.vue +0 -0
  278. /package/dist/vendored/{components → preview/components}/Video.vue +0 -0
  279. /package/dist/vendored/{components → preview/components}/ViewContainer.vue +0 -0
  280. /package/dist/vendored/{components → preview/components}/base.js +0 -0
  281. /package/dist/vendored/{components → preview/components}/icons.js +0 -0
  282. /package/dist/vendored/{components → preview/components}/index.js +0 -0
  283. /package/dist/vendored/{meta → preview/meta}/component-meta.json +0 -0
  284. /package/dist/vendored/{meta → preview/meta}/directive-meta.json +0 -0
  285. /package/dist/vendored/{meta → preview/meta}/style-meta.json +0 -0
  286. /package/dist/vendored/{runtime → preview/runtime}/StyleParser.js +0 -0
  287. /package/dist/vendored/{runtime → preview/runtime}/adapter.js +0 -0
  288. /package/dist/vendored/{runtime → preview/runtime}/assets.js +0 -0
  289. /package/dist/vendored/{runtime → preview/runtime}/canvas-context.js +0 -0
  290. /package/dist/vendored/{runtime → preview/runtime}/card-runtime.js +0 -0
  291. /package/dist/vendored/{runtime → preview/runtime}/evaluate.js +0 -0
  292. /package/dist/vendored/{runtime → preview/runtime}/property.js +0 -0
  293. /package/dist/vendored/{runtime → preview/runtime}/selectionOverlay.js +0 -0
  294. /package/dist/vendored/{runtime → preview/runtime}/toPreview.js +0 -0
  295. /package/dist/{server-core → vendored/server/core}/assetProxy.js +0 -0
  296. /package/dist/{server-core → vendored/server/core}/bbox.js +0 -0
  297. /package/dist/{server-core → vendored/server/core}/buildDataFromSchema.js +0 -0
  298. /package/dist/{server-core → vendored/server/core}/cardFiles.js +0 -0
  299. /package/dist/{server-core → vendored/server/core}/cardHistory.js +0 -0
  300. /package/dist/{server-core → vendored/server/core}/cardImage.js +0 -0
  301. /package/dist/{server-core → vendored/server/core}/cardImportMeta.js +0 -0
  302. /package/dist/{server-core → vendored/server/core}/cardListCache.js +0 -0
  303. /package/dist/{server-core → vendored/server/core}/cardOpenFolder.js +0 -0
  304. /package/dist/{server-core → vendored/server/core}/cardSidecar.js +0 -0
  305. /package/dist/{server-core → vendored/server/core}/codeBridge.js +0 -0
  306. /package/dist/{server-core → vendored/server/core}/logStream.js +0 -0
  307. /package/dist/{server-core → vendored/server/core}/logger.js +0 -0
  308. /package/dist/{server-core → vendored/server/core}/nodeModels.js +0 -0
  309. /package/dist/{server-core → vendored/server/core}/renderStatus.js +0 -0
  310. /package/dist/{server-core → vendored/server/core}/screenshot.js +0 -0
  311. /package/dist/{server-core → vendored/server/core}/sliceStatic.js +0 -0
  312. /package/dist/vendored/{meta → skills/dsl-card-generator/vendor/preview/meta}/action-meta.json +0 -0
  313. /package/dist/{server-core/_vendor → vendored/tools}/finmall-env.mjs +0 -0
@@ -0,0 +1,706 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ cluster_states.py — sketch-spec-to-card 卡片维度聚类与产物聚合(2026-08-14 重写)。
5
+
6
+ 把 parse_meaxure.py(视觉驱动)产出的 UI/<画板>/spec.json 按「卡片维度」聚类为
7
+ 逻辑卡片,并聚合多状态产物:
8
+
9
+ 输入:UI/<画板名>/spec.json (每画板含 cards/elements/styleSummary/artboardType)
10
+ 输出:UI/cards/<cardKey>/ 按卡片维度聚合的产物
11
+ card.json 卡片元信息:状态集合 / 交互 / 样式 / 列表 / 维度
12
+ states/<stateKey>.png 各状态的代表性聚焦截图(从对应画板 card-*.png 软链/拷贝)
13
+ states/<stateKey>/spec.json 该状态的视觉要素标注(复制自画板 spec.json)
14
+ UI/clusters.json 全局聚类 + 每卡状态模型 + 交互维度
15
+ UI/interactions/index.yaml 跨卡片交互图(弹框/子卡父子关系)
16
+
17
+ 设计原则(HANDOFF Part 1 判定原则):
18
+ - 抽象通用化:状态键用 state-overview/state-empty/state-selected 等抽象名,不写业务词。
19
+ - 子卡独立成卡:subcard 独立一个 cardKey,仅通过 parentKey 与父卡建立交互关系。
20
+ - 多状态聚焦截图在同一卡片下:一张卡片可对应多个画板(多状态),各自一张聚焦图。
21
+
22
+ 用法:
23
+ python cluster_states.py --out <UI目录>
24
+ python cluster_states.py --out <UI目录> --auto-refs # 从 src/cards 动态探测参考卡
25
+
26
+ 设计原则(强化):维度判定 100% 基于视觉结构(指纹 diff / Jaccard 相似度),
27
+ 不依赖任何业务文案、枚举值列表、领域关键词。换业务域零调整即可生效。
28
+ 残留的语法功能词(否定前缀/登录态/结果尾缀)仅用于状态键命名归类,不参与维度判定。
29
+ """
30
+ import argparse
31
+ import json
32
+ import os
33
+ import re
34
+ import shutil
35
+ import sys
36
+ from collections import defaultdict
37
+
38
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
39
+ sys.path.insert(0, SCRIPT_DIR)
40
+ from card_refs import load_card_refs
41
+
42
+ ALERTDIALOG_REF_CARDS = []
43
+ ALERTCARD_REF_CARDS = []
44
+
45
+ # ----------------------------------------------------------------------------
46
+ # 1. 维度识别:纯结构(fingerprint diff),无业务词集
47
+ # ----------------------------------------------------------------------------
48
+ # 设计原则:维度判定依据 = 同卡片多状态间的视觉结构差异(文本/块/颜色集合的增减),
49
+ # 绝不依赖任何业务文案、枚举值列表、领域关键词。换业务域零调整即可生效。
50
+ # 残留的语法功能词(否定前缀/登录态等)仅用于状态键命名,不参与维度判定。
51
+
52
+ # 语法功能标记(跨业务通用,非领域词):用于把结构检测出的状态归入抽象状态键。
53
+ # 仅匹配画板名 qualifier token 的「语法角色」,不匹配任何业务实体名。
54
+ _NEG_PREFIX = re.compile(r"^(无|未|空|没)(?=[\u4e00-\u9fff])") # 否定前缀:无/未/空/没+汉字
55
+ _EMPTY_HINT = re.compile(r"^(空|无.{0,4}|未登录|未绑定|暂无|没有)") # 空态语法(保守)
56
+ _RESULT_HINT = re.compile(r"(成功|失败|完成|结果|处理中|已.{0,2})$") # 结果态语法尾缀
57
+ _SELECTED_HINT = re.compile(r"(选中|已选|激活|active|selected)", re.I)
58
+ _AUTH_HINT = re.compile(r"(登录|授权|登入)", re.I)
59
+ # 过渡态:画板名 qualifier 指向另一卡(跨卡跳转),由结构判定,不用业务名正则
60
+
61
+
62
+ def _resolve_src_cards(explicit):
63
+ if explicit:
64
+ return explicit
65
+ repo_root = os.path.dirname(os.path.dirname(SCRIPT_DIR))
66
+ cand = os.path.join(repo_root, "src", "cards")
67
+ return cand if os.path.isdir(cand) else None
68
+
69
+
70
+ # ----------------------------------------------------------------------------
71
+ # 2. 画板名解析(抽象、通用)
72
+ # ----------------------------------------------------------------------------
73
+ def normalize_name(canvas_name):
74
+ return re.sub(r"^\d+", "", canvas_name or "").strip()
75
+
76
+
77
+ def card_key(canvas_name):
78
+ return normalize_name(canvas_name).split("_", 1)[0]
79
+
80
+
81
+ def qualifiers(canvas_name):
82
+ nm = normalize_name(canvas_name)
83
+ if "_" not in nm:
84
+ return []
85
+ return [q for q in nm.split("_", 1)[1].split("_") if q]
86
+
87
+
88
+ def slugify_dim(name, idx):
89
+ """维度绑定变量名:纯抽象 dim{idx},不映射任何业务词。"""
90
+ return f"dim{idx}"
91
+
92
+
93
+ def abstract_state_key(qs, fp=None):
94
+ """从 qualifier token 的语法角色推导抽象状态键(纯语法,无业务词)。
95
+ 判定顺序:空态语法 → 结果态语法尾缀 → 选中态 → 登录态 → 默认 overview。
96
+ fp(指纹)可选,用于辅助空态判定(内容元素极少 → 强化空态判定)。
97
+ """
98
+ joined = "_".join(qs)
99
+ # 内容稀疏度(辅助):无 fp 时保守不判
100
+ sparse = False
101
+ if fp:
102
+ sparse = (len(fp.get("texts", [])) + len(fp.get("blocks", []))) <= 3
103
+ if _EMPTY_HINT.search(joined) or sparse:
104
+ return "state-empty"
105
+ if _RESULT_HINT.search(joined):
106
+ return "state-result"
107
+ if _SELECTED_HINT.search(joined):
108
+ return "state-selected"
109
+ if _AUTH_HINT.search(joined):
110
+ return "state-auth"
111
+ return "state-overview"
112
+
113
+
114
+ # ----------------------------------------------------------------------------
115
+ # 3. 指纹(基于新 spec 结构:elements/cards/styleSummary)
116
+ # ----------------------------------------------------------------------------
117
+ def fingerprint(spec):
118
+ """卡片主体指纹:文本集合 + 视觉块集合 + 颜色集合(用于状态间 diff)。"""
119
+ texts, blocks, colors = [], [], []
120
+ for el in spec.get("elements", []):
121
+ nm = (el.get("name") or "").split("/")[-1].strip()
122
+ if el.get("type") == "text" and el.get("text"):
123
+ texts.append(str(el["text"]).strip())
124
+ elif el.get("type") in ("group", "shape", "symbol") and nm:
125
+ blocks.append(nm)
126
+ # 颜色
127
+ st = el.get("style") or {}
128
+ bkg = st.get("background") or {}
129
+ if bkg.get("color"):
130
+ colors.append(bkg["color"])
131
+ ts = el.get("textStyle") or {}
132
+ if ts.get("color"):
133
+ colors.append(ts["color"])
134
+ ss = spec.get("styleSummary") or {}
135
+ colors = sorted(set(colors)) or sorted(set(ss.get("colors", [])))
136
+ return {
137
+ "texts": sorted(set(texts)),
138
+ "blocks": sorted(set(blocks)),
139
+ "colors": colors,
140
+ }
141
+
142
+
143
+ def jaccard(a, b):
144
+ if not a and not b:
145
+ return 1.0
146
+ return len(a & b) / len(a | b)
147
+
148
+
149
+ def diff_fingerprint(base, other):
150
+ bt, bb = set(base["texts"]), set(base["blocks"])
151
+ ot, ob = set(other["texts"]), set(other["blocks"])
152
+ return {
153
+ "addedTexts": sorted(ot - bt),
154
+ "removedTexts": sorted(bt - ot),
155
+ "addedBlocks": sorted(ob - bb),
156
+ "removedBlocks": sorted(bb - ob),
157
+ "textJaccard": round(jaccard(bt, ot), 3),
158
+ "blockJaccard": round(jaccard(bb, ob), 3),
159
+ }
160
+
161
+
162
+ # ----------------------------------------------------------------------------
163
+ # 4. 维度提取(tab / conditional / form / sequential)—— 纯结构,无业务词
164
+ # ----------------------------------------------------------------------------
165
+ def _fp_sim(a, b):
166
+ """两状态指纹平均相似度(文本 Jaccard + 块 Jaccard)/2。"""
167
+ jt = jaccard(set(a["fp"]["texts"]), set(b["fp"]["texts"]))
168
+ jb = jaccard(set(a["fp"]["blocks"]), set(b["fp"]["blocks"]))
169
+ return (jt + jb) / 2
170
+
171
+
172
+ def is_empty_or_error(qs, fp=None):
173
+ """空/错误态判定:qualifier 含否定语法前缀 或 指纹内容极稀疏。"""
174
+ joined = "_".join(qs)
175
+ if _EMPTY_HINT.search(joined):
176
+ return True
177
+ if fp and (len(fp.get("texts", [])) + len(fp.get("blocks", []))) <= 3:
178
+ return True
179
+ return False
180
+
181
+
182
+ def _is_result_token(q):
183
+ """结果态语法尾缀(成功/失败/完成/已…),跨业务通用语法,非领域词。"""
184
+ return bool(_RESULT_HINT.search(q))
185
+
186
+
187
+ def detect_positional_tabs(cluster_states):
188
+ """统一结构化维度检测(纯结构,无业务词)。
189
+
190
+ 核心信号:**互斥值对跨多个独立 qualifier 分组反复共现 = 真维度**。
191
+ 一个真正的 Tab/枚举维度(如 已出账/未出账)会在卡片的不同状态下反复出现
192
+ (无论其他 qualifier 是什么),因此同一值对会在多个分组里被观察到。
193
+ 而局部状态变体(如 已逾期/已还最低)只在单一狭窄分组出现 → 不升维。
194
+
195
+ 两阶段:
196
+ 阶段A「局部配对」:对每个 qualifier 位置,按其余位置分组,组内 ≥2 个不同值
197
+ → 记录所有 (值A, 值B) 配对及其出现组。
198
+ 阶段B「跨组聚合」:同一值对在 ≥2 个独立组出现 → 真维度(Tab/Conditional/Sequential)。
199
+ 单组出现的局部变化不升维(避免把状态变体误判为维度)。
200
+
201
+ 分类(基于值的语法角色,非领域词):
202
+ - 含否定前缀(无/未/空) → conditional
203
+ - 全部带结果尾缀(成功/失败/已…) → sequential
204
+ - 其余 → tab
205
+ """
206
+ # 阶段A:局部配对收集
207
+ local_groups = defaultdict(list)
208
+ for st in cluster_states:
209
+ qs = st["qualifiers"]
210
+ for i in range(len(qs)):
211
+ key = tuple(qs[:i] + qs[i + 1:])
212
+ local_groups[(i, key)].append(st)
213
+
214
+ # pair_groups: {(pos, valA, valB): set(其他qualifier元组)} — 记录每对值在多少独立组出现
215
+ pair_groups = defaultdict(set)
216
+ for (i, other_key), members in local_groups.items():
217
+ vals = sorted({m["qualifiers"][i] for m in members})
218
+ if len(vals) < 2:
219
+ continue
220
+ for a in range(len(vals)):
221
+ for b in range(a + 1, len(vals)):
222
+ pair_groups[(i, vals[a], vals[b])].add(other_key)
223
+
224
+ # 阶段B:跨组聚合 — 值对在 ≥2 独立组出现才升维
225
+ # 先按值对集合归并(同一维度可能有多对值,如三值枚举 A/B/C 有 3 对)
226
+ # 用并查集合并同位置的连通值对
227
+ pairs_by_pos = defaultdict(list) # pos -> [(valA, valB, group_count)]
228
+ for (i, va, vb), groups in pair_groups.items():
229
+ if len(groups) >= 2: # ≥2 独立组才是真维度
230
+ pairs_by_pos[i].append((va, vb, len(groups)))
231
+ # 并查集合并同位置连通值
232
+ out = []
233
+ _DIM_VAL_MAXLEN = 6 # 维度值最长 6 字符(tab 标签/短枚举),过长的是状态描述非维度
234
+ for i, pairs in sorted(pairs_by_pos.items()):
235
+ if not pairs:
236
+ continue
237
+ # 过滤过长的值(状态描述如"没有已出账单也没有未出账单"不是维度标签)
238
+ pairs = [(va, vb, gc) for va, vb, gc in pairs
239
+ if len(va) <= _DIM_VAL_MAXLEN and len(vb) <= _DIM_VAL_MAXLEN]
240
+ if not pairs:
241
+ continue
242
+ # 并查集
243
+ parent = {}
244
+ def find(x):
245
+ parent.setdefault(x, x)
246
+ while parent[x] != x:
247
+ parent[x] = parent[parent[x]]
248
+ x = parent[x]
249
+ return x
250
+ def union(a, b):
251
+ ra, rb = find(a), find(b)
252
+ if ra != rb:
253
+ parent[ra] = rb
254
+ for va, vb, _ in pairs:
255
+ union(va, vb)
256
+ # 按根分组
257
+ clusters = defaultdict(set)
258
+ for va, vb, _ in pairs:
259
+ clusters[find(va)].add(va)
260
+ clusters[find(vb)].add(vb)
261
+ for root, vals in clusters.items():
262
+ uniq = sorted(vals)
263
+ if len(uniq) < 2:
264
+ continue
265
+ # 分类:语法角色
266
+ # conditional 须有「纯净否定值」:整值=无/未/空/没 或 否定前缀+≤2字(无X/未X)
267
+ # 避免把 未出账/已出账(枚举值恰好含"未"字)误判为条件显隐
268
+ _PURE_NEG = re.compile(r"^(无|未|空|没|没有)$")
269
+ _NEG_SHORT = re.compile(r"^(无|未|空|没)(.{0,2})$")
270
+ has_pure_neg = any(_PURE_NEG.match(v) or _NEG_SHORT.match(v) for v in uniq)
271
+ # 若否定值与肯定值仅前缀不同(无X↔有X),则是条件;否则否定只是巧合 → tab
272
+ is_conditional = False
273
+ if has_pure_neg:
274
+ negs = [v for v in uniq if (_PURE_NEG.match(v) or _NEG_SHORT.match(v))]
275
+ poss = [v for v in uniq if not (_PURE_NEG.match(v) or _NEG_SHORT.match(v))]
276
+ # 条件显隐特征:存在 neg 与 pos 去掉前缀后词干相同(无交易↔有交易)
277
+ _STRIP_NEG = re.compile(r"^(无|未|空|没|没有)")
278
+ stems_match = any(
279
+ _STRIP_NEG.sub("", n) and _STRIP_NEG.sub("", n) in [re.sub(r"^(有|已)", "", p) for p in poss]
280
+ for n in negs
281
+ )
282
+ # 若所有否定值去掉前缀后能与某肯定值词干对上 → conditional
283
+ # 否则(如 未出账/已出账,"未出账"去"未"="出账"≠"已出账"去"已"="出账" → 其实对得上)
284
+ # 更精确:neg 去 neg 前缀 == pos 去 pos 前缀(有/已/正)
285
+ _STRIP_POS = re.compile(r"^(有|已)")
286
+ stems_match = any(
287
+ (_STRIP_NEG.sub("", n) or n) == (_STRIP_POS.sub("", p) or p)
288
+ for n in negs for p in poss
289
+ )
290
+ is_conditional = stems_match
291
+ all_result = all(_is_result_token(v) for v in uniq)
292
+ # 该簇的总独立组数
293
+ gc = sum(len(pair_groups[(i, va, vb)]) for va in uniq for vb in uniq if va < vb)
294
+ if all_result:
295
+ out.append({
296
+ "type": "sequential", "dim": f"phase{i}", "values": uniq,
297
+ "phases": uniq, "boundVar": slugify_dim(f"seq{i}", len(out)),
298
+ "tokens": uniq, "positional": True, "position": i, "groupCount": gc,
299
+ "note": "结果/状态态,由上游流程驱动;基础态 + 结果块 v-show 绑定 status 变量",
300
+ })
301
+ elif is_conditional:
302
+ negs = [v for v in uniq if (_PURE_NEG.match(v) or _NEG_SHORT.match(v))]
303
+ poss = [v for v in uniq if not (_PURE_NEG.match(v) or _NEG_SHORT.match(v))]
304
+ neg = negs[0] if negs else uniq[0]
305
+ pos = poss[0] if poss else uniq[-1]
306
+ out.append({
307
+ "type": "conditional", "dim": f"empty{i}", "kind": "presence",
308
+ "negative": neg, "positive": pos, "values": uniq,
309
+ "boundVar": slugify_dim(f"cond{i}", len(out)),
310
+ "showWhen": pos, "hideWhen": neg,
311
+ "tokens": uniq, "positional": True, "position": i, "groupCount": gc,
312
+ })
313
+ else:
314
+ out.append({
315
+ "type": "tab", "dim": f"tab{i}", "values": uniq,
316
+ "default": uniq[0], "boundVar": slugify_dim(f"tab{i}", len(out)),
317
+ "tokens": uniq, "positional": True, "position": i, "groupCount": gc,
318
+ })
319
+ return out
320
+
321
+
322
+ def detect_forms(cluster_states):
323
+ """表单态检测(纯结构):状态指纹相对基础态仅新增少量文本输入块(blockJaccard 高、
324
+ textJaccard 中等、新增 blocks 含输入/选择控件类型名)。
325
+ 控件类型名取通用 UI 语义(input/select/选择/输入),非业务词。
326
+ """
327
+ out, seen = [], set()
328
+ _FORM_CONTROL = re.compile(r"(input|select|输入|选择|填写|修改)", re.I)
329
+ for st in cluster_states:
330
+ df = st.get("diffVsBase") or {}
331
+ added_blocks = df.get("addedBlocks", [])
332
+ if not added_blocks:
333
+ continue
334
+ # 新增块里有输入/选择控件类型 → 表单态
335
+ if any(_FORM_CONTROL.search(b) for b in added_blocks):
336
+ token = "_".join(st["qualifiers"]) or st["name"]
337
+ if token in seen:
338
+ continue
339
+ field = added_blocks[0].split("/")[-1].strip()
340
+ out.append({
341
+ "type": "form", "token": token, "field": field,
342
+ "control": "input", "boundVar": slugify_dim(f"form{len(out)}", len(out)),
343
+ })
344
+ seen.add(token)
345
+ return out
346
+
347
+
348
+ # 兼容旧调用名(build_cluster 仍引用)
349
+ def detect_enums(cluster_states):
350
+ return [d for d in detect_positional_tabs(cluster_states) if d["type"] == "tab"]
351
+
352
+
353
+ def detect_pairs(cluster_states):
354
+ return [d for d in detect_positional_tabs(cluster_states) if d["type"] == "conditional"]
355
+
356
+
357
+ def detect_sequential(cluster_states):
358
+ seqs = [d for d in detect_positional_tabs(cluster_states) if d["type"] == "sequential"]
359
+ # 保持旧输出结构(phases + 单变量)
360
+ return [{"type": "sequential", "phases": s["values"], "boundVar": s["boundVar"],
361
+ "note": s.get("note", "")} for s in seqs]
362
+
363
+
364
+ # ----------------------------------------------------------------------------
365
+ # 5. 主流程:聚类 + 产物聚合
366
+ # ----------------------------------------------------------------------------
367
+ def load_specs(out_dir):
368
+ specs = []
369
+ for name in sorted(os.listdir(out_dir)):
370
+ sd = os.path.join(out_dir, name)
371
+ sj = os.path.join(sd, "spec.json")
372
+ if not os.path.isfile(sj):
373
+ continue
374
+ try:
375
+ spec = json.load(open(sj, encoding="utf-8"))
376
+ except Exception as e:
377
+ print(f"[WARN] 读取失败 {sj}: {e}", file=sys.stderr)
378
+ continue
379
+ specs.append((name, spec))
380
+ return specs
381
+
382
+
383
+ def build_cluster(card_key_name, items):
384
+ """items: [(canvas_name, spec)]。聚合为一张逻辑卡片的多状态信息。"""
385
+ states = []
386
+ # 结构性过渡态判定:若某状态的某 qualifier 恰好是其他卡片的 cardKey,则它是过渡态
387
+ all_card_keys = {card_key(cn) for cn, _ in items}
388
+ for cn, spec in items:
389
+ qs = qualifiers(cn)
390
+ fp = fingerprint(spec)
391
+ # 过渡态:qualifier 里出现了别的 cardKey(跨卡跳转)
392
+ trans_to = next((q for q in qs if q in all_card_keys and q != card_key_name), None)
393
+ is_trans = trans_to is not None
394
+ states.append({
395
+ "name": cn,
396
+ "stateKey": abstract_state_key(qs, fp),
397
+ "qualifiers": qs,
398
+ "isTransition": is_trans,
399
+ "transitionTo": trans_to,
400
+ "fp": fp,
401
+ "artboardType": spec.get("artboardType", "card"),
402
+ "cardCount": len(spec.get("cards", [])),
403
+ "interaction": spec.get("interaction", ""),
404
+ "jsapiInteractions": spec.get("jsapiInteractions", []),
405
+ "flexLayouts": [c.get("flexLayout") for c in spec.get("cards", []) if c.get("flexLayout")],
406
+ })
407
+ # 推荐基础态:排除过渡态与空/错误态后,指纹最丰富
408
+ def richness(s):
409
+ return len(s["fp"]["texts"]) + len(s["fp"]["blocks"])
410
+ candidates = [s for s in states if not s["isTransition"] and not is_empty_or_error(s["qualifiers"], s["fp"])]
411
+ if not candidates:
412
+ candidates = [s for s in states if not s["isTransition"]]
413
+ if not candidates:
414
+ candidates = states
415
+ base = max(candidates, key=richness)
416
+ for st in states:
417
+ st["diffVsBase"] = diff_fingerprint(base["fp"], st["fp"])
418
+ # 维度提取(纯结构,统一由 detect_positional_tabs 产出 tab/conditional/sequential)
419
+ dims_all = detect_positional_tabs(states)
420
+ tabs = [d for d in dims_all if d["type"] == "tab"]
421
+ conds = [d for d in dims_all if d["type"] == "conditional"]
422
+ seqs = [{"type": "sequential", "phases": d["values"], "boundVar": d["boundVar"],
423
+ "note": d.get("note", "")} for d in dims_all if d["type"] == "sequential"]
424
+ forms = detect_forms(states)
425
+ seen_var = set()
426
+ tabs_dedup = []
427
+ for t in tabs:
428
+ if t["boundVar"] in seen_var:
429
+ continue
430
+ seen_var.add(t["boundVar"])
431
+ tabs_dedup.append(t)
432
+ # 合并所有状态的样式汇总(颜色去重)
433
+ all_colors = sorted({c for s in states for c in s["fp"]["colors"]})
434
+ return {
435
+ "cardKey": card_key_name,
436
+ "canvasCount": len(states),
437
+ "stateNames": [s["name"] for s in states],
438
+ "stateKeys": sorted({s["stateKey"] for s in states}),
439
+ "recommendedBase": base["name"],
440
+ "states": [{k: v for k, v in s.items() if k != "fp"} | {"fingerprint": s["fp"]} for s in states],
441
+ "dimensions": {
442
+ "tabs": tabs_dedup,
443
+ "conditionals": conds,
444
+ "forms": forms,
445
+ "sequential": seqs,
446
+ },
447
+ "colors": all_colors,
448
+ }
449
+
450
+
451
+ def aggregate_card_products(out_dir, card):
452
+ """把一张逻辑卡片的多状态产物聚合到 cards/<cardKey>/。"""
453
+ cdir = os.path.join(out_dir, "cards", card["cardKey"])
454
+ states_dir = os.path.join(cdir, "states")
455
+ os.makedirs(states_dir, exist_ok=True)
456
+ aggregated = {"copiedStates": []}
457
+ # 每个状态:复制代表性聚焦截图 + spec.json 到 states/<stateKey>_<idx>/
458
+ seen_state_keys = defaultdict(int)
459
+ for st in card["states"]:
460
+ cn = st["name"]
461
+ src_canvas_dir = os.path.join(out_dir, cn)
462
+ if not os.path.isdir(src_canvas_dir):
463
+ continue
464
+ sk = st["stateKey"]
465
+ seen_state_keys[sk] += 1
466
+ state_name = f"{sk}_{seen_state_keys[sk]}" if seen_state_keys[sk] > 1 else sk
467
+ dst_state_dir = os.path.join(states_dir, state_name)
468
+ os.makedirs(dst_state_dir, exist_ok=True)
469
+ # 复制该画板的聚焦截图(card-*.png)到 states/<state>/
470
+ n_img = 0
471
+ for fn in sorted(os.listdir(src_canvas_dir)):
472
+ if re.match(r"card-\d+\.png$", fn):
473
+ shutil.copy2(os.path.join(src_canvas_dir, fn), os.path.join(dst_state_dir, fn))
474
+ n_img += 1
475
+ # 复制整图(参考)
476
+ if os.path.isfile(os.path.join(src_canvas_dir, "artboard.png")):
477
+ shutil.copy2(os.path.join(src_canvas_dir, "artboard.png"),
478
+ os.path.join(dst_state_dir, "artboard.png"))
479
+ # 复制 spec.json
480
+ src_spec = os.path.join(src_canvas_dir, "spec.json")
481
+ if os.path.isfile(src_spec):
482
+ shutil.copy2(src_spec, os.path.join(dst_state_dir, "spec.json"))
483
+ aggregated["copiedStates"].append({
484
+ "stateName": state_name, "canvas": cn,
485
+ "stateKey": sk, "images": n_img,
486
+ })
487
+ # 写 card.json
488
+ # 聚合所有状态的 JSAPI 交互控件与 Flex 布局(去重)
489
+ jsapi_all = []
490
+ seen_js = set()
491
+ flex_all = []
492
+ for st in card["states"]:
493
+ for j in st.get("jsapiInteractions", []):
494
+ k = j.get("control")
495
+ if k and k not in seen_js:
496
+ jsapi_all.append(j)
497
+ seen_js.add(k)
498
+ for fl in st.get("flexLayouts", []):
499
+ if fl and fl not in flex_all:
500
+ flex_all.append(fl)
501
+ card_json = {
502
+ "cardKey": card["cardKey"],
503
+ "pageId": card.get("pageId"),
504
+ "cardKeyDisplay": card.get("cardKeyDisplay"),
505
+ "isDialog": card.get("isDialog", False),
506
+ "parentKey": card.get("parentKey"),
507
+ "canvasCount": card["canvasCount"],
508
+ "recommendedBase": card["recommendedBase"],
509
+ "stateKeys": card["stateKeys"],
510
+ "stateNames": card["stateNames"],
511
+ "dimensions": card["dimensions"],
512
+ "colors": card["colors"],
513
+ "jsapiInteractions": jsapi_all,
514
+ "flexLayouts": flex_all,
515
+ "states": aggregated["copiedStates"],
516
+ }
517
+ with open(os.path.join(cdir, "card.json"), "w", encoding="utf-8") as f:
518
+ json.dump(card_json, f, ensure_ascii=False, indent=2)
519
+ return aggregated["copiedStates"]
520
+
521
+
522
+ def write_interactions_index(out_dir, cards, overlays):
523
+ """跨卡片交互图:弹框/子卡父子关系 + 维度切换。"""
524
+ lines = ["# 跨卡片交互图(由 cluster_states.py 生成)", ""]
525
+ if overlays:
526
+ lines.append("## 弹框 / 提示(overlay_dialog → alertDialog JSAPI,不建卡)")
527
+ for o in overlays:
528
+ lines.append(f"- `{o['name']}`:父卡候选 `{o['parentKey']}`;"
529
+ f"参考 `{', '.join(o.get('refs', []))}`")
530
+ lines.append("")
531
+ subcards = [c for c in cards if c.get("isDialog")]
532
+ if subcards:
533
+ lines.append("## 子卡(subcard → alertCard 父子卡片,独立 Dialog 卡)")
534
+ for c in subcards:
535
+ lines.append(f"- `{c['cardKey']}`:父卡 `{c.get('parentKey')}`;"
536
+ f"打开 `alertCard({{cardCode}})` / 关闭 `closeAlertCard`")
537
+ lines.append("")
538
+ lines.append("## 维度切换(多形态卡片)")
539
+ for c in cards:
540
+ d = c["dimensions"]
541
+ dims = []
542
+ for t in d["tabs"]:
543
+ dims.append(f"Tab:{t['dim']}={t['values']}")
544
+ for cond in d["conditionals"]:
545
+ dims.append(f"Cond:{cond['kind']}({cond['negative']}↔{cond['positive']})")
546
+ for fm in d["forms"]:
547
+ dims.append(f"Form:{fm['field']}({fm['control']})")
548
+ for sq in d["sequential"]:
549
+ dims.append(f"Seq:{sq['phases']}")
550
+ if dims:
551
+ lines.append(f"- `{c['cardKey']}`:{'; '.join(dims)}")
552
+ lines.append("")
553
+ # JSAPI 外部交互控件聚合
554
+ jsapi_cards = [c for c in cards if any(s.get("jsapiInteractions") for s in c["states"])]
555
+ if jsapi_cards:
556
+ lines.append("## JSAPI 外部交互控件(密码/验证码/数字键盘/toast/二次确认)")
557
+ for c in jsapi_cards:
558
+ seen_ctl = set()
559
+ for st in c["states"]:
560
+ for j in st.get("jsapiInteractions", []):
561
+ ctl = j.get("control")
562
+ if ctl and ctl not in seen_ctl:
563
+ seen_ctl.add(ctl)
564
+ lines.append(f"- `{c['cardKey']}` ← `{ctl}`:{j.get('note', '')}")
565
+ lines.append("")
566
+ lines.append("")
567
+ idx_dir = os.path.join(out_dir, "interactions")
568
+ os.makedirs(idx_dir, exist_ok=True)
569
+ with open(os.path.join(idx_dir, "index.yaml"), "w", encoding="utf-8") as f:
570
+ f.write("\n".join(lines))
571
+
572
+
573
+ def augment_prompt(out_dir, card):
574
+ """给每画板 prompt.md 追加 ## 5. 多形态归属。"""
575
+ dims = card["dimensions"]
576
+ lines = ["", "## 5. 多形态归属(由 cluster_states.py 生成)", ""]
577
+ lines.append(f"- **所属逻辑卡片**:`{card['cardKey']}`(共 {card['canvasCount']} 个形态)")
578
+ lines.append(f"- **抽象状态键**:{card['stateKeys']}")
579
+ lines.append(f"- **推荐基础态**:`{card['recommendedBase']}`")
580
+ lines.append("")
581
+ lines.append("### 检测到的切换维度")
582
+ if dims["tabs"]:
583
+ for t in dims["tabs"]:
584
+ lines.append(f"- **Tab 维度** `{t['dim']}`:取值 {t['values']},默认 `{t['default']}`,绑定变量 `{t['boundVar']}`")
585
+ if dims["conditionals"]:
586
+ for c in dims["conditionals"]:
587
+ lines.append(f"- **条件显隐** `{c['kind']}`:`{c['negative']}` ↔ `{c['positive']}`,变量 `{c['boundVar']}`")
588
+ if dims["forms"]:
589
+ for fm in dims["forms"]:
590
+ lines.append(f"- **动态表单** `{fm['token']}`:控件 `{fm['control']}`,字段 `{fm['field']}`,变量 `{fm['boundVar']}`")
591
+ if dims["sequential"]:
592
+ for s in dims["sequential"]:
593
+ lines.append(f"- **时序/结果态** 阶段 {s['phases']},变量 `{s['boundVar']}`")
594
+ if not (dims["tabs"] or dims["conditionals"] or dims["forms"] or dims["sequential"]):
595
+ lines.append("- (未检测到明确切换维度,按单态生成)")
596
+ lines.append("")
597
+ lines.append("> 多形态建卡:以推荐基础态建基础 schema,Tab 用 TabBar + `v-show` 切内容;"
598
+ "条件显隐对差异块加 `v-show`;表单插 Input/Select 绑变量。")
599
+ if card.get("isDialog"):
600
+ lines.append("")
601
+ lines.append("### 本卡为 alertCard 子卡(Dialog 卡)")
602
+ lines.append("- 关闭按钮(×) → `ynet.callJSAPI('closeAlertCard', { cardCode: '<pageId>' })`")
603
+ parent = card.get("parentKey") or "对应触发卡"
604
+ lines.append(f"- 父卡(`{parent}`)打开:`ynet.callJSAPI('alertCard', {{ cardCode: '<pageId>', data: {{...}}, gravity: 'bottom' }})`")
605
+ if ALERTCARD_REF_CARDS:
606
+ lines.append(f"- 参考存量:{', '.join(ALERTCARD_REF_CARDS)}")
607
+ block = "\n".join(lines)
608
+ for st in card["states"]:
609
+ cn = st["name"]
610
+ pm = os.path.join(out_dir, cn, "prompt.md")
611
+ if not os.path.isfile(pm):
612
+ continue
613
+ txt = open(pm, encoding="utf-8").read()
614
+ txt = re.sub(r"\n## 5\. 多形态归属.*?(?=\n## |\Z)", "", txt, flags=re.S)
615
+ txt = txt.rstrip() + "\n" + block + "\n"
616
+ open(pm, "w", encoding="utf-8").write(txt)
617
+
618
+
619
+ def main():
620
+ ap = argparse.ArgumentParser(description="卡片维度聚类 + 产物聚合(视觉驱动 spec 结构)")
621
+ ap.add_argument("--out", required=True, help="UI 目录(含各画板子目录与 spec.json)")
622
+ ap.add_argument("--card-refs", default=None)
623
+ ap.add_argument("--auto-refs", action="store_true")
624
+ ap.add_argument("--src-cards", default=None)
625
+ args = ap.parse_args()
626
+
627
+ global ALERTDIALOG_REF_CARDS, ALERTCARD_REF_CARDS
628
+ refs = load_card_refs(args.card_refs, args.auto_refs, _resolve_src_cards(args.src_cards))
629
+ ALERTDIALOG_REF_CARDS = refs["alertDialog"]
630
+ ALERTCARD_REF_CARDS = refs["alertCard"]
631
+ if args.auto_refs:
632
+ print(f"[INFO] 自动探测参考卡:alertDialog×{len(ALERTDIALOG_REF_CARDS)} / alertCard×{len(ALERTCARD_REF_CARDS)}")
633
+
634
+ out_dir = os.path.abspath(args.out)
635
+ if not os.path.isdir(out_dir):
636
+ print(f"[ERR] 目录不存在: {out_dir}", file=sys.stderr)
637
+ sys.exit(1)
638
+
639
+ specs = load_specs(out_dir)
640
+ if not specs:
641
+ print(f"[ERR] {out_dir} 下未找到任何 spec.json(请先运行 parse_meaxure.py --build)", file=sys.stderr)
642
+ sys.exit(1)
643
+
644
+ # 聚类分流:普通卡按 card_key;subcard 独立;overlay_dialog 进 overlays
645
+ clusters_map = defaultdict(list)
646
+ overlays = []
647
+ for cn, spec in specs:
648
+ at = spec.get("artboardType", "card")
649
+ if at == "overlay_dialog":
650
+ parts = normalize_name(cn).split("_", 1)
651
+ parent_key = parts[0] if len(parts) > 1 else None
652
+ overlays.append({
653
+ "name": cn, "artboardType": "overlay_dialog",
654
+ "interaction": spec.get("interaction", ""),
655
+ "parentKey": parent_key, "refs": list(ALERTDIALOG_REF_CARDS),
656
+ })
657
+ continue
658
+ if at == "subcard":
659
+ key = normalize_name(cn)
660
+ else:
661
+ key = card_key(cn)
662
+ clusters_map[key].append((cn, spec))
663
+
664
+ cards = []
665
+ for key in sorted(clusters_map):
666
+ items = clusters_map[key]
667
+ card = build_cluster(key, items)
668
+ # 英文 pageId(稳定序号,供下游当 id 用,解决 cardKey 中文无法当 pageId)+ 中文 cardKeyDisplay(展示用)
669
+ card["pageId"] = f"card-{len(cards) + 1}"
670
+ card["cardKeyDisplay"] = key
671
+ is_dialog = any(s.get("artboardType") == "subcard" for _, s in items)
672
+ card["isDialog"] = is_dialog
673
+ if is_dialog:
674
+ pk = key.split("_", 1)
675
+ card["parentKey"] = pk[0] if len(pk) > 1 else None
676
+ aggregate_card_products(out_dir, card)
677
+ augment_prompt(out_dir, card)
678
+ cards.append(card)
679
+
680
+ draft_name = os.path.basename(out_dir.rstrip("/\\"))
681
+ result = {
682
+ "draft": draft_name,
683
+ "totalCanvases": len(specs),
684
+ "cardCount": len(cards),
685
+ "cards": cards,
686
+ "overlays": overlays,
687
+ }
688
+ with open(os.path.join(out_dir, "clusters.json"), "w", encoding="utf-8") as f:
689
+ json.dump(result, f, ensure_ascii=False, indent=2)
690
+ write_interactions_index(out_dir, cards, overlays)
691
+
692
+ print(f"[OK] 草稿「{draft_name}」共 {len(specs)} 画板 → {len(cards)} 张逻辑卡片"
693
+ + (f" + {len(overlays)} 弹框(overlay_dialog)" if overlays else ""))
694
+ for c in cards:
695
+ d = c["dimensions"]
696
+ ntab = len(d["tabs"]); ncond = len(d["conditionals"]); nform = len(d["forms"]); nseq = len(d["sequential"])
697
+ tag = " [Dialog子卡]" if c.get("isDialog") else ""
698
+ print(f" - {c['cardKey']}{tag}: {c['canvasCount']}态 keys={c['stateKeys']} | "
699
+ f"tab×{ntab} cond×{ncond} form×{nform} seq×{nseq} | base={c['recommendedBase']}")
700
+ for o in overlays:
701
+ print(f" - [overlay_dialog] {o['name']} (父卡候选: {o['parentKey']})")
702
+ print(f"[OK] 已写出 {out_dir}/clusters.json + cards/<cardKey>/ + interactions/index.yaml")
703
+
704
+
705
+ if __name__ == "__main__":
706
+ main()