@sarjallab09/figma-intelligence 1.0.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 (286) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +327 -0
  3. package/bin/cli.js +859 -0
  4. package/design-bridge/.env.example +5 -0
  5. package/design-bridge/bridge.js +196 -0
  6. package/design-bridge/lib/assets.js +367 -0
  7. package/design-bridge/lib/prompt.js +85 -0
  8. package/design-bridge/lib/server.js +66 -0
  9. package/design-bridge/lib/stitch.js +37 -0
  10. package/design-bridge/lib/tokens.js +82 -0
  11. package/design-bridge/package-lock.json +579 -0
  12. package/design-bridge/package.json +19 -0
  13. package/figma-bridge-plugin/README.md +97 -0
  14. package/figma-bridge-plugin/anthropic-chat-runner.js +192 -0
  15. package/figma-bridge-plugin/bridge-relay.js +2363 -0
  16. package/figma-bridge-plugin/chat-runner.js +459 -0
  17. package/figma-bridge-plugin/code.js +1528 -0
  18. package/figma-bridge-plugin/codex-runner.js +505 -0
  19. package/figma-bridge-plugin/component-schemas.js +110 -0
  20. package/figma-bridge-plugin/content-context.js +869 -0
  21. package/figma-bridge-plugin/create-button.js +216 -0
  22. package/figma-bridge-plugin/gemini-cli-runner.js +291 -0
  23. package/figma-bridge-plugin/gemini-runner.js +187 -0
  24. package/figma-bridge-plugin/html-to-figma.js +927 -0
  25. package/figma-bridge-plugin/knowledge-hub/.gitkeep +0 -0
  26. package/figma-bridge-plugin/knowledge-hub/uspec-references/anatomy-spec.md +159 -0
  27. package/figma-bridge-plugin/knowledge-hub/uspec-references/api-spec.md +162 -0
  28. package/figma-bridge-plugin/knowledge-hub/uspec-references/color-spec.md +148 -0
  29. package/figma-bridge-plugin/knowledge-hub/uspec-references/full-spec-template.md +314 -0
  30. package/figma-bridge-plugin/knowledge-hub/uspec-references/property-spec.md +175 -0
  31. package/figma-bridge-plugin/knowledge-hub/uspec-references/screen-reader-spec.md +180 -0
  32. package/figma-bridge-plugin/knowledge-hub/uspec-references/structure-spec.md +165 -0
  33. package/figma-bridge-plugin/manifest.json +21 -0
  34. package/figma-bridge-plugin/package-lock.json +1936 -0
  35. package/figma-bridge-plugin/package.json +20 -0
  36. package/figma-bridge-plugin/perplexity-runner.js +188 -0
  37. package/figma-bridge-plugin/references/SKILL.md +178 -0
  38. package/figma-bridge-plugin/references/anatomy-spec.md +159 -0
  39. package/figma-bridge-plugin/references/api-spec.md +162 -0
  40. package/figma-bridge-plugin/references/color-spec.md +148 -0
  41. package/figma-bridge-plugin/references/full-spec-template.md +314 -0
  42. package/figma-bridge-plugin/references/property-spec.md +175 -0
  43. package/figma-bridge-plugin/references/screen-reader-spec.md +180 -0
  44. package/figma-bridge-plugin/references/structure-spec.md +165 -0
  45. package/figma-bridge-plugin/shared-prompt-config.js +604 -0
  46. package/figma-bridge-plugin/spec-helpers/build-table.js +269 -0
  47. package/figma-bridge-plugin/spec-helpers/classify-elements.js +189 -0
  48. package/figma-bridge-plugin/spec-helpers/index.js +35 -0
  49. package/figma-bridge-plugin/spec-helpers/parse-figma-link.js +49 -0
  50. package/figma-bridge-plugin/spec-helpers/position-markers.js +158 -0
  51. package/figma-bridge-plugin/stitch-auth.js +322 -0
  52. package/figma-bridge-plugin/stitch-runner.js +1427 -0
  53. package/figma-bridge-plugin/token-resolver.js +107 -0
  54. package/figma-bridge-plugin/ui.html +4467 -0
  55. package/figma-intelligence-layer/.env.example +39 -0
  56. package/figma-intelligence-layer/docs/local-image-generation.md +60 -0
  57. package/figma-intelligence-layer/examples/comfyui-workflow-template.example.json +101 -0
  58. package/figma-intelligence-layer/jest.config.js +14 -0
  59. package/figma-intelligence-layer/mcp-config.json +19 -0
  60. package/figma-intelligence-layer/package-lock.json +5892 -0
  61. package/figma-intelligence-layer/package.json +48 -0
  62. package/figma-intelligence-layer/scripts/setup-comfyui-local.sh +67 -0
  63. package/figma-intelligence-layer/scripts/start-comfyui.sh +33 -0
  64. package/figma-intelligence-layer/src/index.ts +2233 -0
  65. package/figma-intelligence-layer/src/shared/auto-layout-validator.ts +404 -0
  66. package/figma-intelligence-layer/src/shared/cache.ts +187 -0
  67. package/figma-intelligence-layer/src/shared/color-operations.ts +533 -0
  68. package/figma-intelligence-layer/src/shared/color-utils.ts +138 -0
  69. package/figma-intelligence-layer/src/shared/component-script-builder.ts +413 -0
  70. package/figma-intelligence-layer/src/shared/component-templates.ts +2767 -0
  71. package/figma-intelligence-layer/src/shared/concept-taxonomy.ts +694 -0
  72. package/figma-intelligence-layer/src/shared/decision-log.ts +128 -0
  73. package/figma-intelligence-layer/src/shared/design-system-context.ts +568 -0
  74. package/figma-intelligence-layer/src/shared/design-system-intelligence.ts +131 -0
  75. package/figma-intelligence-layer/src/shared/design-system-matcher.ts +184 -0
  76. package/figma-intelligence-layer/src/shared/design-system-normalizers.ts +196 -0
  77. package/figma-intelligence-layer/src/shared/design-system-tokens.ts +295 -0
  78. package/figma-intelligence-layer/src/shared/dtcg-validator.ts +530 -0
  79. package/figma-intelligence-layer/src/shared/enrichment-pipeline.ts +671 -0
  80. package/figma-intelligence-layer/src/shared/figma-bridge.ts +1408 -0
  81. package/figma-intelligence-layer/src/shared/font-config.ts +126 -0
  82. package/figma-intelligence-layer/src/shared/icon-catalog.ts +360 -0
  83. package/figma-intelligence-layer/src/shared/icon-fetch.ts +80 -0
  84. package/figma-intelligence-layer/src/shared/prototype-script-builder.ts +162 -0
  85. package/figma-intelligence-layer/src/shared/response-compression.ts +440 -0
  86. package/figma-intelligence-layer/src/shared/semantic-token-catalog.ts +324 -0
  87. package/figma-intelligence-layer/src/shared/token-binder.ts +505 -0
  88. package/figma-intelligence-layer/src/shared/token-math.ts +427 -0
  89. package/figma-intelligence-layer/src/shared/token-naming.ts +468 -0
  90. package/figma-intelligence-layer/src/shared/token-utils.ts +420 -0
  91. package/figma-intelligence-layer/src/shared/types.ts +346 -0
  92. package/figma-intelligence-layer/src/shared/typography-presets.ts +94 -0
  93. package/figma-intelligence-layer/src/shared/unsplash.ts +165 -0
  94. package/figma-intelligence-layer/src/shared/vision-client.ts +607 -0
  95. package/figma-intelligence-layer/src/shared/vision-provider-anthropic.ts +334 -0
  96. package/figma-intelligence-layer/src/shared/vision-provider-openai.ts +446 -0
  97. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotate-handler.ts +782 -0
  98. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotate-renderer.ts +496 -0
  99. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotation-kit.ts +230 -0
  100. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/colorblind-sim.ts +66 -0
  101. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/index.ts +810 -0
  102. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-analyzer.ts +1191 -0
  103. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-figma-page.ts +1346 -0
  104. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-handler.ts +148 -0
  105. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/vpat-figma-page.ts +499 -0
  106. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/vpat-report.ts +910 -0
  107. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/wcag-checker.ts +989 -0
  108. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/wcag-criteria.ts +1160 -0
  109. package/figma-intelligence-layer/src/tools/phase1-vision/design-from-ref/index.ts +424 -0
  110. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/component-recognizer.ts +38 -0
  111. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/ds-matcher.ts +111 -0
  112. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/font-matcher.ts +114 -0
  113. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/icon-resolver.ts +103 -0
  114. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/index.ts +1060 -0
  115. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/layout-segmenter.ts +18 -0
  116. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/token-inferencer.ts +39 -0
  117. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/vision-pipeline.ts +58 -0
  118. package/figma-intelligence-layer/src/tools/phase1-vision/sketch-to-design/index.ts +298 -0
  119. package/figma-intelligence-layer/src/tools/phase1-vision/visual-audit/index.ts +197 -0
  120. package/figma-intelligence-layer/src/tools/phase2-accuracy/component-audit/index.ts +494 -0
  121. package/figma-intelligence-layer/src/tools/phase2-accuracy/intent-translator/index.ts +356 -0
  122. package/figma-intelligence-layer/src/tools/phase2-accuracy/layout-intelligence/container-patterns.ts +123 -0
  123. package/figma-intelligence-layer/src/tools/phase2-accuracy/layout-intelligence/index.ts +663 -0
  124. package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/built-in-rules.yaml +56 -0
  125. package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/index.ts +614 -0
  126. package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/rule-engine.ts +113 -0
  127. package/figma-intelligence-layer/src/tools/phase2-accuracy/theme-generator/color-theory.ts +178 -0
  128. package/figma-intelligence-layer/src/tools/phase2-accuracy/theme-generator/index.ts +470 -0
  129. package/figma-intelligence-layer/src/tools/phase2-accuracy/variant-expander/index.ts +429 -0
  130. package/figma-intelligence-layer/src/tools/phase2-accuracy/variant-expander/token-override-maps.ts +226 -0
  131. package/figma-intelligence-layer/src/tools/phase3-generation/ai-image-insert/index.ts +535 -0
  132. package/figma-intelligence-layer/src/tools/phase3-generation/component-archaeologist/index.ts +660 -0
  133. package/figma-intelligence-layer/src/tools/phase3-generation/component-archaeologist/pattern-fingerprints.ts +209 -0
  134. package/figma-intelligence-layer/src/tools/phase3-generation/composition-builder/index.ts +540 -0
  135. package/figma-intelligence-layer/src/tools/phase3-generation/figma-animated-build.ts +391 -0
  136. package/figma-intelligence-layer/src/tools/phase3-generation/page-architect/index.ts +2019 -0
  137. package/figma-intelligence-layer/src/tools/phase3-generation/page-architect/screen-templates.ts +131 -0
  138. package/figma-intelligence-layer/src/tools/phase3-generation/prototype-map/index.ts +381 -0
  139. package/figma-intelligence-layer/src/tools/phase3-generation/prototype-wire/index.ts +565 -0
  140. package/figma-intelligence-layer/src/tools/phase3-generation/swarm-build/index.ts +764 -0
  141. package/figma-intelligence-layer/src/tools/phase3-generation/system-drift/index.ts +535 -0
  142. package/figma-intelligence-layer/src/tools/phase3-generation/unsplash-search/index.ts +84 -0
  143. package/figma-intelligence-layer/src/tools/phase3-generation/url-to-frame/index.ts +401 -0
  144. package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/css-animations.ts +68 -0
  145. package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/framer-motion.ts +78 -0
  146. package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/swift-animations.ts +93 -0
  147. package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/index.ts +596 -0
  148. package/figma-intelligence-layer/src/tools/phase4-sync/ci-check/index.ts +462 -0
  149. package/figma-intelligence-layer/src/tools/phase4-sync/export-tokens/index.ts +1470 -0
  150. package/figma-intelligence-layer/src/tools/phase4-sync/generate-component-code/index.ts +829 -0
  151. package/figma-intelligence-layer/src/tools/phase4-sync/handoff-spec/index.ts +702 -0
  152. package/figma-intelligence-layer/src/tools/phase4-sync/icon-library-sync/index.ts +483 -0
  153. package/figma-intelligence-layer/src/tools/phase4-sync/sync-from-code/index.ts +501 -0
  154. package/figma-intelligence-layer/src/tools/phase4-sync/sync-from-code/storybook-parser.ts +106 -0
  155. package/figma-intelligence-layer/src/tools/phase4-sync/watch-docs/index.ts +676 -0
  156. package/figma-intelligence-layer/src/tools/phase4-sync/webhook-listener/index.ts +560 -0
  157. package/figma-intelligence-layer/src/tools/phase5-governance/apg-doc/index.ts +1043 -0
  158. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/component-detection.ts +620 -0
  159. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/anatomy.ts +331 -0
  160. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/color-tokens.ts +77 -0
  161. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/properties.ts +54 -0
  162. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/snapshot.ts +287 -0
  163. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/spacing.ts +71 -0
  164. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/states.ts +43 -0
  165. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/typography.ts +71 -0
  166. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/index.ts +221 -0
  167. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/_default.ts +166 -0
  168. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/accordion.ts +232 -0
  169. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/alert.ts +234 -0
  170. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/avatar-group.ts +270 -0
  171. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/avatar.ts +249 -0
  172. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/badge.ts +231 -0
  173. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/banner.ts +293 -0
  174. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/breadcrumb.ts +240 -0
  175. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/button.ts +243 -0
  176. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/calendar.ts +307 -0
  177. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/card.ts +143 -0
  178. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/checkbox.ts +227 -0
  179. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/chip.ts +233 -0
  180. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/combobox.ts +282 -0
  181. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/datepicker.ts +276 -0
  182. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/divider.ts +223 -0
  183. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/drawer.ts +255 -0
  184. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/dropdown-menu.ts +289 -0
  185. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/empty-state.ts +261 -0
  186. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/file-uploader.ts +290 -0
  187. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/form.ts +265 -0
  188. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/grid.ts +238 -0
  189. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/icon.ts +255 -0
  190. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/index.ts +128 -0
  191. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/inline-edit.ts +286 -0
  192. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/inline-message.ts +255 -0
  193. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/input.ts +330 -0
  194. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/link.ts +247 -0
  195. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/list.ts +250 -0
  196. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/menu.ts +247 -0
  197. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/modal.ts +144 -0
  198. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/navbar.ts +264 -0
  199. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/navigation.ts +251 -0
  200. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/number-input.ts +261 -0
  201. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/pagination.ts +248 -0
  202. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/popover.ts +270 -0
  203. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/progress.ts +251 -0
  204. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/radio.ts +142 -0
  205. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/range-slider.ts +282 -0
  206. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/rating.ts +250 -0
  207. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/search.ts +258 -0
  208. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/segmented-control.ts +265 -0
  209. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/select.ts +319 -0
  210. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/skeleton.ts +256 -0
  211. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/slider.ts +232 -0
  212. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/spinner.ts +239 -0
  213. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/status-dot.ts +252 -0
  214. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/stepper.ts +270 -0
  215. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/table.ts +244 -0
  216. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tabs.ts +143 -0
  217. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tag.ts +243 -0
  218. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/textarea.ts +259 -0
  219. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/time-picker.ts +293 -0
  220. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toast.ts +144 -0
  221. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toggle.ts +289 -0
  222. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toolbar.ts +267 -0
  223. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tooltip.ts +232 -0
  224. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/treeview.ts +257 -0
  225. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/typography.ts +319 -0
  226. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/legacy-compat.ts +121 -0
  227. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/anatomy-diagram.ts +430 -0
  228. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/figma-page.ts +312 -0
  229. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/json.ts +129 -0
  230. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/markdown.ts +78 -0
  231. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/visual-doc.ts +2333 -0
  232. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/accessibility.ts +100 -0
  233. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/anatomy.ts +32 -0
  234. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/color-tokens.ts +59 -0
  235. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/content-guidance.ts +18 -0
  236. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/design-tokens.ts +53 -0
  237. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/interaction-rules.ts +19 -0
  238. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/overview.ts +91 -0
  239. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/properties-api.ts +71 -0
  240. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/qa-criteria.ts +19 -0
  241. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/related-components.ts +110 -0
  242. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/responsive.ts +19 -0
  243. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/size-specs.ts +67 -0
  244. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/spacing-structure.ts +58 -0
  245. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/state-specs.ts +79 -0
  246. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/states.ts +50 -0
  247. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/type-hierarchy.ts +33 -0
  248. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/typography.ts +55 -0
  249. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/usage-guidelines.ts +73 -0
  250. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/variants.ts +81 -0
  251. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/types.ts +409 -0
  252. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/index.ts +198 -0
  253. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/renderer.ts +701 -0
  254. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/types.ts +88 -0
  255. package/figma-intelligence-layer/src/tools/phase5-governance/decision-log/index.ts +135 -0
  256. package/figma-intelligence-layer/src/tools/phase5-governance/design-decision-log/index.ts +491 -0
  257. package/figma-intelligence-layer/src/tools/phase5-governance/ds-primitives/index.ts +416 -0
  258. package/figma-intelligence-layer/src/tools/phase5-governance/ds-scaffolder/index.ts +722 -0
  259. package/figma-intelligence-layer/src/tools/phase5-governance/ds-variables/index.ts +449 -0
  260. package/figma-intelligence-layer/src/tools/phase5-governance/health-report/index.ts +393 -0
  261. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/index.ts +406 -0
  262. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/figma-page.ts +292 -0
  263. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/json.ts +24 -0
  264. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/markdown.ts +172 -0
  265. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/naming-guide.ts +409 -0
  266. package/figma-intelligence-layer/src/tools/phase5-governance/token-analytics/index.ts +594 -0
  267. package/figma-intelligence-layer/src/tools/phase5-governance/token-docs/index.ts +710 -0
  268. package/figma-intelligence-layer/src/tools/phase5-governance/token-migrate/index.ts +458 -0
  269. package/figma-intelligence-layer/src/tools/phase5-governance/token-naming/index.ts +134 -0
  270. package/figma-intelligence-layer/tests/apg-doc.test.ts +101 -0
  271. package/figma-intelligence-layer/tests/design-system-context.test.ts +152 -0
  272. package/figma-intelligence-layer/tests/design-system-matcher.test.ts +144 -0
  273. package/figma-intelligence-layer/tests/figma-bridge.test.ts +83 -0
  274. package/figma-intelligence-layer/tests/generate-image-and-insert.test.ts +56 -0
  275. package/figma-intelligence-layer/tests/screen-cloner-regression.test.ts +69 -0
  276. package/figma-intelligence-layer/tests/smoke.test.ts +174 -0
  277. package/figma-intelligence-layer/tests/spec-generator.test.ts +127 -0
  278. package/figma-intelligence-layer/tests/token-migrate.test.ts +21 -0
  279. package/figma-intelligence-layer/tests/token-naming.test.ts +30 -0
  280. package/figma-intelligence-layer/tsconfig.json +19 -0
  281. package/package.json +35 -0
  282. package/scripts/clean-existing-chunks.js +179 -0
  283. package/scripts/connect-ai-tool.js +490 -0
  284. package/scripts/convert-hub-pdfs.js +425 -0
  285. package/scripts/figma-mcp-status.js +349 -0
  286. package/scripts/register-codex-mcp.js +96 -0
@@ -0,0 +1,2019 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // Page Architect
3
+ // Parses a natural-language flow description into a sequence of screens and
4
+ // builds them in Figma using Auto Layout frames populated with DS components.
5
+ // Optionally wires prototype connections and generates a flow-map page.
6
+ // ─────────────────────────────────────────────────────────────────────────────
7
+
8
+ import Fuse from "fuse.js";
9
+ import { getBridge } from "../../../shared/figma-bridge.js";
10
+ import { decisionLog } from "../../../shared/decision-log.js";
11
+ import { ComponentSet, Token } from "../../../shared/types.js";
12
+ import { buildWireScript } from "../../../shared/prototype-script-builder.js";
13
+ import {
14
+ resolveDesignPalette,
15
+ resolveFloatToken,
16
+ ResolvedPalette,
17
+ } from "../../../shared/token-binder.js";
18
+ import {
19
+ fetchRemoteImageAsDataUri,
20
+ searchUnsplashPhotos,
21
+ trackUnsplashDownload,
22
+ UnsplashOrientation,
23
+ } from "../../../shared/unsplash.js";
24
+ import { FontConfig, resolveFontConfig, generateFontLoadScript, fontNameLiteral } from "../../../shared/font-config.js";
25
+ import { generateValidatorScript, generateValidatorCall, generateDocumentRepairScript, generateDocumentRepairCall } from "../../../shared/auto-layout-validator.js";
26
+
27
+ // ─── Public types ─────────────────────────────────────────────────────────────
28
+
29
+ export interface PageArchitectArgs {
30
+ productContext: string;
31
+ flow: string;
32
+ platform: "web" | "mobile" | "both";
33
+ width?: number;
34
+ wireframeMode?: boolean;
35
+ includeFlowMap?: boolean;
36
+ contentMode: "placeholder" | "realistic";
37
+ useStockImages?: boolean;
38
+ imageQuery?: string;
39
+ fonts?: Partial<FontConfig>;
40
+ }
41
+
42
+ export interface ScreenSpec {
43
+ name: string;
44
+ purpose: string;
45
+ template: ScreenTemplate;
46
+ requiredComponents: string[];
47
+ layoutPattern: string;
48
+ realisticContent?: ScreenContent;
49
+ }
50
+
51
+ export interface ScreenContent {
52
+ heading?: string;
53
+ subheading?: string;
54
+ bodyText?: string;
55
+ ctaLabel?: string;
56
+ listItems?: string[];
57
+ sectionTitle?: string;
58
+ summaryItems?: string[];
59
+ helperText?: string;
60
+ }
61
+
62
+ export interface CreatedScreen {
63
+ frameId: string;
64
+ name: string;
65
+ template: ScreenTemplate;
66
+ instantiatedComponents: string[];
67
+ }
68
+
69
+ export interface PrototypeConnection {
70
+ fromFrameId: string;
71
+ toFrameId: string;
72
+ fromName: string;
73
+ toName: string;
74
+ }
75
+
76
+ export interface PageArchitectResult {
77
+ screens: CreatedScreen[];
78
+ prototypeConnections: PrototypeConnection[];
79
+ flowMapPageId: string | null;
80
+ frameIds: string[];
81
+ prototypeConnectionCount: number;
82
+ logEntryId: string;
83
+ }
84
+
85
+ // ─── Screen templates ─────────────────────────────────────────────────────────
86
+
87
+ type ScreenTemplate =
88
+ | "auth"
89
+ | "dashboard"
90
+ | "list"
91
+ | "detail"
92
+ | "settings"
93
+ | "onboarding"
94
+ | "checkout-cart"
95
+ | "checkout-address"
96
+ | "checkout-shipping"
97
+ | "checkout-payment"
98
+ | "checkout-review"
99
+ | "checkout-success"
100
+ | "document"
101
+ | "generic";
102
+
103
+ interface TemplateDefinition {
104
+ template: ScreenTemplate;
105
+ keywords: string[];
106
+ components: string[];
107
+ layoutPattern: string;
108
+ }
109
+
110
+ const SCREEN_TEMPLATES: TemplateDefinition[] = [
111
+ {
112
+ template: "auth",
113
+ keywords: ["login", "sign in", "signup", "register", "auth", "password", "email", "forgot"],
114
+ components: ["TextInput", "Button", "Heading", "Link", "Divider", "SocialButton", "Checkbox", "Logo", "Avatar"],
115
+ layoutPattern: "Centered single-column with email + password inputs and primary CTA",
116
+ },
117
+ {
118
+ template: "dashboard",
119
+ keywords: ["dashboard", "home", "overview", "main", "hub"],
120
+ components: ["Navigation", "Header", "Card", "Chart", "Badge", "Avatar", "Table"],
121
+ layoutPattern: "Sidebar nav + top header + main content grid",
122
+ },
123
+ {
124
+ template: "list",
125
+ keywords: ["list", "feed", "search", "browse", "explore", "results", "index", "directory"],
126
+ components: ["SearchBar", "Filter", "ListItem", "Pagination", "FilterChip", "Thumbnail"],
127
+ layoutPattern: "Search/filter header + scrollable list of items",
128
+ },
129
+ {
130
+ template: "detail",
131
+ keywords: ["detail", "view", "show", "profile", "product", "article", "post", "item"],
132
+ components: ["Image", "Heading", "Body", "Button", "ActionBar", "BackButton", "ShareButton", "Rating", "Tag"],
133
+ layoutPattern: "Hero image + content area + sticky action bar",
134
+ },
135
+ {
136
+ template: "settings",
137
+ keywords: ["settings", "preferences", "account", "profile edit", "configuration", "options"],
138
+ components: ["SectionHeader", "FormField", "Toggle", "Button"],
139
+ layoutPattern: "Grouped form sections with labels and inputs",
140
+ },
141
+ {
142
+ template: "onboarding",
143
+ keywords: ["onboarding", "welcome", "intro", "get started", "tutorial", "step", "walkthrough"],
144
+ components: ["Illustration", "Heading", "Body", "Button"],
145
+ layoutPattern: "Full-bleed illustration + centered heading + body + next button",
146
+ },
147
+ {
148
+ template: "checkout-cart",
149
+ keywords: ["cart", "bag", "basket"],
150
+ components: ["Navigation", "ProductCard", "QuantityStepper", "PromoCode", "OrderSummary", "Button"],
151
+ layoutPattern: "Product list with summary card and bottom CTA",
152
+ },
153
+ {
154
+ template: "checkout-address",
155
+ keywords: ["address", "delivery address", "shipping address"],
156
+ components: ["Navigation", "AddressCard", "TextInput", "Button"],
157
+ layoutPattern: "Saved address selection with editable delivery form",
158
+ },
159
+ {
160
+ template: "checkout-shipping",
161
+ keywords: ["shipping", "delivery", "method"],
162
+ components: ["Navigation", "RadioOption", "OrderSummary", "Button"],
163
+ layoutPattern: "Shipping method options with delivery ETA and summary",
164
+ },
165
+ {
166
+ template: "checkout-payment",
167
+ keywords: ["payment", "card", "billing", "wallet"],
168
+ components: ["Navigation", "PaymentMethodRow", "TextInput", "Button"],
169
+ layoutPattern: "Saved payment methods, billing fields, and trust messaging",
170
+ },
171
+ {
172
+ template: "checkout-review",
173
+ keywords: ["review", "confirm", "place order", "order review"],
174
+ components: ["Navigation", "OrderSummary", "AddressCard", "PaymentMethodRow", "Button"],
175
+ layoutPattern: "Sectioned review screen with final totals and place-order CTA",
176
+ },
177
+ {
178
+ template: "checkout-success",
179
+ keywords: ["success", "confirmation", "complete", "thank you"],
180
+ components: ["Illustration", "Heading", "OrderSummary", "Button"],
181
+ layoutPattern: "Confirmation state with receipt summary and next steps",
182
+ },
183
+ {
184
+ template: "document",
185
+ keywords: ["document", "documentation", "spec", "specification", "guidelines", "style guide", "reference", "wiki", "readme", "changelog", "api doc", "design doc"],
186
+ components: ["Heading", "Body", "Divider", "Table", "Badge"],
187
+ layoutPattern: "Full-width document page with header, TOC, and content sections",
188
+ },
189
+ ];
190
+
191
+ // ─── Platform widths ──────────────────────────────────────────────────────────
192
+
193
+ function platformWidth(platform: "web" | "mobile" | "both", userWidth?: number): number {
194
+ if (userWidth) return userWidth;
195
+ switch (platform) {
196
+ case "mobile": return 390;
197
+ case "web": return 1440;
198
+ case "both": return 1440;
199
+ }
200
+ }
201
+
202
+ function mobileWidth(_platform: "web" | "mobile" | "both"): number | null {
203
+ return null; // single-frame output — no duplicate (Mobile) frames
204
+ }
205
+
206
+ // ─── Parse flow into screen specs (template matching) ────────────────────────
207
+
208
+ function parseFlowToScreens(
209
+ productContext: string,
210
+ flow: string,
211
+ contentMode: "placeholder" | "realistic"
212
+ ): ScreenSpec[] {
213
+ const rawParts = flow.split(/\s*(?:→|->|>>|,|\n)\s*/);
214
+ let screenNames = rawParts
215
+ .map((s) => s.trim())
216
+ .filter((s) => s.length > 2 && s.length < 60)
217
+ .filter((s) => {
218
+ const lower = s.toLowerCase();
219
+ // Keep if it matches any template keyword
220
+ const hasKeyword = SCREEN_TEMPLATES.some((t) =>
221
+ t.keywords.some((kw) => lower.includes(kw))
222
+ );
223
+ // Keep if it looks like a proper name (capital letter or common UI term)
224
+ const looksLikeName =
225
+ /[A-Z]/.test(s) ||
226
+ /^(home|profile|search|feed|inbox|cart|map)$/i.test(lower);
227
+ return hasKeyword || looksLikeName || s.split(/\s+/).length <= 3;
228
+ })
229
+ .slice(0, 8);
230
+
231
+ // If we filtered out most fragments, treat the whole input as one screen
232
+ if (
233
+ screenNames.length === 0 ||
234
+ (rawParts.length > 3 && screenNames.length <= 1)
235
+ ) {
236
+ screenNames = [flow.trim().slice(0, 80)];
237
+ }
238
+
239
+ const parsed = screenNames.map((name) => ({
240
+ name: name.replace(/\s+/g, " ").trim(),
241
+ purpose: `User navigates to ${name}`,
242
+ templateHint: "generic" as string | undefined,
243
+ requiredComponents: ["Heading", "Button"] as string[],
244
+ layoutPattern: "Generic content frame",
245
+ }));
246
+
247
+ return parsed.map((item, index) => {
248
+ const templateDef =
249
+ SCREEN_TEMPLATES.find(
250
+ (t) =>
251
+ t.template === item.templateHint ||
252
+ t.keywords.some((kw) => item.name.toLowerCase().includes(kw))
253
+ ) ?? null;
254
+
255
+ const resolvedTemplate =
256
+ (item.templateHint as ScreenTemplate) ?? templateDef?.template ?? "generic";
257
+
258
+ return {
259
+ name: item.name,
260
+ purpose: item.purpose,
261
+ template: resolvedTemplate,
262
+ requiredComponents:
263
+ item.requiredComponents ??
264
+ templateDef?.components ??
265
+ ["Heading", "Button"],
266
+ layoutPattern:
267
+ item.layoutPattern ??
268
+ templateDef?.layoutPattern ??
269
+ "Vertical stack",
270
+ realisticContent:
271
+ contentMode === "realistic"
272
+ ? buildRealisticContent(resolvedTemplate, item.name, productContext, index)
273
+ : undefined,
274
+ };
275
+ });
276
+ }
277
+
278
+ function buildRealisticContent(
279
+ template: ScreenTemplate,
280
+ name: string,
281
+ productContext: string,
282
+ index: number
283
+ ): ScreenContent {
284
+ const brandLabel = /skincare|beauty|cosmetic/.test(productContext.toLowerCase())
285
+ ? "Lumin Daily"
286
+ : /fashion|apparel/.test(productContext.toLowerCase())
287
+ ? "Studio North"
288
+ : "Northstar";
289
+
290
+ switch (template) {
291
+ case "checkout-cart":
292
+ return {
293
+ heading: "Your bag",
294
+ subheading: "Review items, delivery timing, and savings before checkout.",
295
+ ctaLabel: "Continue to address",
296
+ sectionTitle: `${brandLabel} favorites`,
297
+ listItems: ["Vitamin C Serum x1 $48", "Barrier Repair Cream x1 $36", "Free sample kit included"],
298
+ summaryItems: ["Subtotal $84", "Shipping Free", "Tax $6", "Total $90"],
299
+ helperText: "Promo code SAVE10 applied",
300
+ };
301
+ case "checkout-address":
302
+ return {
303
+ heading: "Delivery address",
304
+ subheading: "Choose where your order should arrive.",
305
+ ctaLabel: "Continue to shipping",
306
+ sectionTitle: "Saved addresses",
307
+ listItems: ["Home 21 Lake Shore Drive, Apt 6B", "Office 101 Market Street, Floor 9"],
308
+ helperText: "Add delivery instructions for the courier",
309
+ };
310
+ case "checkout-shipping":
311
+ return {
312
+ heading: "Shipping method",
313
+ subheading: "Select the delivery speed that works best for you.",
314
+ ctaLabel: "Continue to payment",
315
+ sectionTitle: "Delivery options",
316
+ listItems: ["Standard Free Arrives Tue, Mar 17", "Express $12 Arrives Mon, Mar 16", "Same day $18 Arrives today by 9 PM"],
317
+ summaryItems: ["Items 2", "Estimated delivery 2-3 business days"],
318
+ };
319
+ case "checkout-payment":
320
+ return {
321
+ heading: "Payment method",
322
+ subheading: "Your card details are encrypted and secure.",
323
+ ctaLabel: "Review order",
324
+ sectionTitle: "Saved methods",
325
+ listItems: ["Visa ending in 4242", "Apple Pay", "Add new card"],
326
+ helperText: "Billing address same as delivery",
327
+ };
328
+ case "checkout-review":
329
+ return {
330
+ heading: "Review your order",
331
+ subheading: "Double-check delivery, payment, and totals before placing the order.",
332
+ ctaLabel: "Place order",
333
+ sectionTitle: "Order summary",
334
+ listItems: ["Delivery to Maya Patel", "Payment Visa 4242", "Standard shipping Free"],
335
+ summaryItems: ["Subtotal $84", "Discount -$8", "Tax $6", "Total $82"],
336
+ helperText: "By placing this order, you agree to the terms and refund policy.",
337
+ };
338
+ case "checkout-success":
339
+ return {
340
+ heading: "Order confirmed",
341
+ subheading: "Thanks for shopping with us. A receipt has been sent to your email.",
342
+ ctaLabel: "Track shipment",
343
+ sectionTitle: "What happens next",
344
+ listItems: ["Order #NS-2048", "Estimated arrival Tue, Mar 17", "Receipt sent to maya@example.com"],
345
+ summaryItems: ["Need help? Contact support 24/7"],
346
+ };
347
+ case "detail":
348
+ return {
349
+ heading: name,
350
+ subheading: "Thoughtful product details, benefits, and a clear path to continue.",
351
+ ctaLabel: "Continue",
352
+ };
353
+ case "document":
354
+ return {
355
+ heading: name,
356
+ subheading: `Comprehensive documentation for ${productContext}.`,
357
+ sectionTitle: "Table of Contents",
358
+ listItems: ["1. Overview & Purpose", "2. Usage Guidelines", "3. Properties & API", "4. Accessibility", "5. Examples"],
359
+ bodyText: "This document provides detailed specifications, usage patterns, and implementation guidance.",
360
+ };
361
+ default:
362
+ return {
363
+ heading: name,
364
+ subheading: `Designed for ${productContext}.`,
365
+ ctaLabel: index === 0 ? "Get started" : "Continue",
366
+ };
367
+ }
368
+ }
369
+
370
+ // ─── AI Content Bundle ────────────────────────────────────────────────────────
371
+
372
+ interface ContentBundle {
373
+ brand: { name: string; tagline: string };
374
+ user: { name: string; email: string };
375
+ order: {
376
+ number: string;
377
+ items: Array<{ name: string; price: string; qty: number }>;
378
+ subtotal: string;
379
+ shipping: string;
380
+ tax: string;
381
+ total: string;
382
+ discount?: string;
383
+ estimatedDelivery: string;
384
+ };
385
+ address: { home: string; work: string };
386
+ imageQuery: string;
387
+ screens: Array<{
388
+ screenName: string;
389
+ heading: string;
390
+ subheading: string;
391
+ ctaLabel: string;
392
+ listItems?: string[];
393
+ summaryItems?: string[];
394
+ sectionTitle?: string;
395
+ helperText?: string;
396
+ bodyText?: string;
397
+ }>;
398
+ }
399
+
400
+ async function generateContentBundle(
401
+ productContext: string,
402
+ screenNames: string[]
403
+ ): Promise<ContentBundle | null> {
404
+ const apiKey = process.env.ANTHROPIC_API_KEY?.trim();
405
+ if (!apiKey) return null;
406
+
407
+ const prompt = `You are a UI content writer. Generate a realistic content bundle for a "${productContext}" app.
408
+
409
+ Screens: ${screenNames.join(", ")}
410
+
411
+ Return ONLY valid JSON (no markdown, no extra text):
412
+ {
413
+ "brand": { "name": "string", "tagline": "string" },
414
+ "user": { "name": "string", "email": "string" },
415
+ "order": {
416
+ "number": "string",
417
+ "items": [{ "name": "string", "price": "string", "qty": 1 }],
418
+ "subtotal": "string", "shipping": "string", "tax": "string", "total": "string",
419
+ "discount": "string", "estimatedDelivery": "string"
420
+ },
421
+ "address": { "home": "string", "work": "string" },
422
+ "imageQuery": "string",
423
+ "screens": [
424
+ {
425
+ "screenName": "string",
426
+ "heading": "string",
427
+ "subheading": "string",
428
+ "ctaLabel": "string",
429
+ "listItems": ["string"],
430
+ "summaryItems": ["string"],
431
+ "sectionTitle": "string",
432
+ "helperText": "string",
433
+ "bodyText": "string"
434
+ }
435
+ ]
436
+ }
437
+
438
+ Rules:
439
+ - Brand name, user name, products, and prices must fit the product context
440
+ - imageQuery: a specific Unsplash-friendly photographic term (e.g. "artisan coffee overhead shot")
441
+ - listItems and summaryItems: real relevant data, not generic placeholder text
442
+ - Keep text concise like a real production app (subheading max 120 chars, bodyText max 150 chars)
443
+ - screens array must have one entry per screen in the same order provided`;
444
+
445
+ try {
446
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
447
+ method: "POST",
448
+ headers: {
449
+ "Content-Type": "application/json",
450
+ "x-api-key": apiKey,
451
+ "anthropic-version": "2023-06-01",
452
+ },
453
+ body: JSON.stringify({
454
+ model: "claude-haiku-4-5-20251001",
455
+ max_tokens: 2048,
456
+ messages: [{ role: "user", content: prompt }],
457
+ }),
458
+ });
459
+
460
+ if (!response.ok) {
461
+ console.warn(`generateContentBundle: API error ${response.status}`);
462
+ return null;
463
+ }
464
+
465
+ const data = await response.json() as { content: Array<{ type: string; text: string }> };
466
+ const text = data.content?.[0]?.text ?? "";
467
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
468
+ if (!jsonMatch) return null;
469
+
470
+ return JSON.parse(jsonMatch[0]) as ContentBundle;
471
+ } catch (error) {
472
+ console.warn(`generateContentBundle: ${error instanceof Error ? error.message : String(error)}`);
473
+ return null;
474
+ }
475
+ }
476
+
477
+ function applyBundleToScreen(
478
+ template: ScreenTemplate,
479
+ screenName: string,
480
+ bundle: ContentBundle,
481
+ index: number
482
+ ): ScreenContent {
483
+ const screenData =
484
+ bundle.screens.find((s) => s.screenName.toLowerCase() === screenName.toLowerCase()) ??
485
+ bundle.screens[index] ??
486
+ bundle.screens[0];
487
+
488
+ if (!screenData) return buildRealisticContent(template, screenName, "", index);
489
+
490
+ switch (template) {
491
+ case "checkout-cart":
492
+ return {
493
+ heading: screenData.heading || "Your bag",
494
+ subheading: screenData.subheading,
495
+ ctaLabel: screenData.ctaLabel || "Continue to address",
496
+ sectionTitle: screenData.sectionTitle || bundle.brand.name,
497
+ listItems: bundle.order.items.map((i) => `${i.name} x${i.qty} ${i.price}`),
498
+ summaryItems: [
499
+ `Subtotal ${bundle.order.subtotal}`,
500
+ `Shipping ${bundle.order.shipping}`,
501
+ `Tax ${bundle.order.tax}`,
502
+ `Total ${bundle.order.total}`,
503
+ ],
504
+ helperText: screenData.helperText,
505
+ };
506
+ case "checkout-address":
507
+ return {
508
+ heading: screenData.heading || "Delivery address",
509
+ subheading: screenData.subheading,
510
+ ctaLabel: screenData.ctaLabel || "Continue to shipping",
511
+ sectionTitle: screenData.sectionTitle || "Saved addresses",
512
+ listItems: [`Home ${bundle.address.home}`, `Work ${bundle.address.work}`],
513
+ helperText: screenData.helperText,
514
+ };
515
+ case "checkout-shipping":
516
+ return {
517
+ heading: screenData.heading || "Shipping method",
518
+ subheading: screenData.subheading,
519
+ ctaLabel: screenData.ctaLabel || "Continue to payment",
520
+ sectionTitle: screenData.sectionTitle || "Delivery options",
521
+ listItems: screenData.listItems ?? [
522
+ `Standard Free Arrives ${bundle.order.estimatedDelivery}`,
523
+ "Express $12 Arrives tomorrow",
524
+ "Same day $18 Arrives today by 9 PM",
525
+ ],
526
+ summaryItems: screenData.summaryItems ?? [
527
+ `Items ${bundle.order.items.length}`,
528
+ `Estimated delivery ${bundle.order.estimatedDelivery}`,
529
+ ],
530
+ };
531
+ case "checkout-payment":
532
+ return {
533
+ heading: screenData.heading || "Payment method",
534
+ subheading: screenData.subheading,
535
+ ctaLabel: screenData.ctaLabel || "Review order",
536
+ sectionTitle: screenData.sectionTitle || "Saved methods",
537
+ listItems: screenData.listItems ?? ["Visa ending in 4242", "Apple Pay", "Add new card"],
538
+ helperText: screenData.helperText,
539
+ };
540
+ case "checkout-review":
541
+ return {
542
+ heading: screenData.heading || "Review your order",
543
+ subheading: screenData.subheading,
544
+ ctaLabel: screenData.ctaLabel || "Place order",
545
+ sectionTitle: screenData.sectionTitle || "Order summary",
546
+ listItems: [
547
+ `Delivery to ${bundle.user.name}`,
548
+ "Payment Visa 4242",
549
+ bundle.order.shipping === "Free" ? "Standard shipping Free" : `Shipping ${bundle.order.shipping}`,
550
+ ],
551
+ summaryItems: [
552
+ `Subtotal ${bundle.order.subtotal}`,
553
+ ...(bundle.order.discount ? [`Discount -${bundle.order.discount}`] : []),
554
+ `Tax ${bundle.order.tax}`,
555
+ `Total ${bundle.order.total}`,
556
+ ],
557
+ helperText: screenData.helperText,
558
+ };
559
+ case "checkout-success":
560
+ return {
561
+ heading: screenData.heading || "Order confirmed",
562
+ subheading: screenData.subheading || `Thanks for shopping with ${bundle.brand.name}. A receipt has been sent to your email.`,
563
+ ctaLabel: screenData.ctaLabel || "Track shipment",
564
+ sectionTitle: screenData.sectionTitle || "What happens next",
565
+ listItems: [
566
+ `Order ${bundle.order.number}`,
567
+ `Estimated arrival ${bundle.order.estimatedDelivery}`,
568
+ `Receipt sent to ${bundle.user.email}`,
569
+ ],
570
+ summaryItems: ["Need help? Contact support 24/7"],
571
+ };
572
+ default:
573
+ return {
574
+ heading: screenData.heading,
575
+ subheading: screenData.subheading,
576
+ ctaLabel: screenData.ctaLabel,
577
+ sectionTitle: screenData.sectionTitle,
578
+ listItems: screenData.listItems,
579
+ summaryItems: screenData.summaryItems,
580
+ helperText: screenData.helperText,
581
+ bodyText: screenData.bodyText,
582
+ };
583
+ }
584
+ }
585
+
586
+ // ─── DS component matching ────────────────────────────────────────────────────
587
+
588
+ function buildFuse(sets: ComponentSet[]): Fuse<ComponentSet> {
589
+ return new Fuse(sets, {
590
+ keys: ["name", "description"],
591
+ threshold: 0.45,
592
+ includeScore: true,
593
+ minMatchCharLength: 2,
594
+ });
595
+ }
596
+
597
+ function resolveComponents(
598
+ requiredNames: string[],
599
+ fuse: Fuse<ComponentSet>
600
+ ): Array<{ name: string; nodeId: string | null }> {
601
+ return requiredNames.map((name) => {
602
+ const results = fuse.search(name);
603
+ if (results.length === 0) return { name, nodeId: null };
604
+ const best = results[0];
605
+ const firstChild = best.item.children[0];
606
+ return {
607
+ name: best.item.name,
608
+ nodeId: firstChild?.id ?? null,
609
+ };
610
+ });
611
+ }
612
+
613
+ // ─── Figma script: build screen frame ────────────────────────────────────────
614
+
615
+ interface FrameSpec {
616
+ name: string;
617
+ purpose: string;
618
+ width: number;
619
+ template: ScreenTemplate;
620
+ wireframeMode: boolean;
621
+ contentMode: "placeholder" | "realistic";
622
+ content?: ScreenContent;
623
+ resolvedComponents: Array<{ name: string; nodeId: string | null }>;
624
+ xOffset: number;
625
+ imageHash?: string | null;
626
+ resolvedPalette?: ResolvedPalette;
627
+ fontConfig: FontConfig;
628
+ }
629
+
630
+ interface ScreenImagery {
631
+ imageHash: string;
632
+ sourceUrl: string;
633
+ photographerName: string;
634
+ photographerProfileUrl: string;
635
+ }
636
+
637
+ function isImageHeavyContext(productContext: string, flow: string): boolean {
638
+ const text = `${productContext} ${flow}`.toLowerCase();
639
+ return /(ecommerce|shop|store|retail|product|travel|hotel|hospitality|restaurant|food|beauty|fashion|wellness|fitness|interior|furniture|lifestyle|editorial|marketplace|booking)/.test(text);
640
+ }
641
+
642
+ function guessImageQuery(productContext: string, flow: string): string {
643
+ const text = `${productContext} ${flow}`.toLowerCase();
644
+ if (/(skincare|beauty|cosmetic|serum|makeup)/.test(text)) return "premium skincare product";
645
+ if (/(fashion|clothing|apparel|shoe|jewelry)/.test(text)) return "premium fashion product";
646
+ if (/(travel|hotel|hospitality|booking|resort)/.test(text)) return "boutique hotel travel";
647
+ if (/(food|restaurant|meal|grocery)/.test(text)) return "premium food photography";
648
+ if (/(fitness|wellness|health|yoga)/.test(text)) return "wellness lifestyle";
649
+ if (/(interior|furniture|home decor|real estate)/.test(text)) return "modern interior design";
650
+ return productContext.trim();
651
+ }
652
+
653
+ async function loadStockImagery(
654
+ query: string,
655
+ flow: string,
656
+ count: number,
657
+ orientation: UnsplashOrientation
658
+ ): Promise<ScreenImagery[]> {
659
+ const search = await searchUnsplashPhotos({
660
+ query,
661
+ perPage: Math.min(Math.max(count, 1), 6),
662
+ orientation,
663
+ contentFilter: "high",
664
+ });
665
+
666
+ const imagery: ScreenImagery[] = [];
667
+ const bridge = await getBridge();
668
+
669
+ for (const photo of search.results) {
670
+ const dataUri = await fetchRemoteImageAsDataUri(photo.urls.regular || photo.urls.small);
671
+ const imported = await bridge.importImage(dataUri);
672
+ await trackUnsplashDownload(photo.links.downloadLocation);
673
+ imagery.push({
674
+ imageHash: imported.imageHash,
675
+ sourceUrl: photo.links.html,
676
+ photographerName: photo.photographer.name,
677
+ photographerProfileUrl: photo.photographer.profileUrl,
678
+ });
679
+ }
680
+
681
+ return imagery;
682
+ }
683
+
684
+ // ─── Shimmer skeleton script ──────────────────────────────────────────────────
685
+ // Creates the outer frame immediately with loading-skeleton rectangles so the
686
+ // user sees the frame appear in the viewport before content is injected.
687
+ function buildShimmerScript(spec: FrameSpec, isFirst: boolean): string {
688
+ const { name, width, xOffset, wireframeMode } = spec;
689
+ const bgColor = wireframeMode ? "{ r: 0.97, g: 0.97, b: 0.97 }" : "{ r: 1, g: 1, b: 1 }";
690
+
691
+ const navigateSnippet = isFirst
692
+ ? `figma.viewport.scrollAndZoomIntoView([frame]);`
693
+ : "";
694
+
695
+ return `
696
+ (async () => {
697
+ const frame = figma.createFrame();
698
+ frame.name = ${JSON.stringify(name)};
699
+ frame.resize(${width}, 800);
700
+ frame.layoutMode = 'VERTICAL';
701
+ frame.primaryAxisSizingMode = 'AUTO';
702
+ frame.counterAxisSizingMode = 'FIXED';
703
+ frame.itemSpacing = 16;
704
+ frame.paddingLeft = 24;
705
+ frame.paddingRight = 24;
706
+ frame.paddingTop = 24;
707
+ frame.paddingBottom = 32;
708
+ frame.fills = [{ type: 'SOLID', color: ${bgColor} }];
709
+ frame.x = ${xOffset};
710
+ frame.y = 0;
711
+ figma.currentPage.appendChild(frame);
712
+
713
+ const skeletonFill = [{ type: 'SOLID', color: { r: 0.91, g: 0.92, b: 0.95 } }];
714
+ const lightFill = [{ type: 'SOLID', color: { r: 0.95, g: 0.96, b: 0.98 } }];
715
+ const shimmerItems = [
716
+ { h: 32, radius: 6, fill: skeletonFill },
717
+ { h: 18, radius: 4, fill: lightFill, w: 0.6 },
718
+ { h: 120, radius: 12, fill: lightFill },
719
+ { h: 72, radius: 8, fill: skeletonFill },
720
+ { h: 72, radius: 8, fill: skeletonFill },
721
+ { h: 48, radius: 24, fill: [{ type: 'SOLID', color: { r: 0.55, g: 0.40, b: 0.95 }, opacity: 0.18 }] },
722
+ ];
723
+ for (const item of shimmerItems) {
724
+ const r = figma.createRectangle();
725
+ r.name = '__shimmer__';
726
+ const rw = item.w ? Math.round((${width} - 48) * item.w) : ${width} - 48;
727
+ r.resize(rw, item.h);
728
+ r.cornerRadius = item.radius;
729
+ r.fills = item.fill;
730
+ frame.appendChild(r);
731
+ if (!item.w && 'layoutSizingHorizontal' in r) r.layoutSizingHorizontal = 'FILL';
732
+ }
733
+
734
+ ${navigateSnippet}
735
+ return { frameId: frame.id };
736
+ })();
737
+ `.trim();
738
+ }
739
+
740
+ function buildScreenScript(spec: FrameSpec): string {
741
+ const {
742
+ name,
743
+ width,
744
+ template,
745
+ wireframeMode,
746
+ contentMode,
747
+ content,
748
+ resolvedComponents,
749
+ xOffset,
750
+ imageHash,
751
+ resolvedPalette,
752
+ fontConfig: fc,
753
+ } = spec;
754
+
755
+ // Font literals for template string interpolation
756
+ const fontLoadBlock = generateFontLoadScript(fc);
757
+ const headingBoldFont = fontNameLiteral("heading", "Bold", fc);
758
+ const bodyRegularFont = fontNameLiteral("body", "Regular", fc);
759
+ const uiMediumFont = fontNameLiteral("ui", "Medium", fc);
760
+
761
+ const bgColor = wireframeMode
762
+ ? "{ r: 0.97, g: 0.97, b: 0.97 }"
763
+ : "{ r: 1, g: 1, b: 1 }";
764
+
765
+ const headerText =
766
+ contentMode === "realistic" && content?.heading
767
+ ? content.heading
768
+ : name;
769
+
770
+ const subText =
771
+ contentMode === "realistic" && content?.subheading
772
+ ? content.subheading
773
+ : spec.purpose ?? "";
774
+
775
+ const ctaText =
776
+ contentMode === "realistic" && content?.ctaLabel
777
+ ? content.ctaLabel
778
+ : "Continue";
779
+ const bodyText =
780
+ contentMode === "realistic" && content?.bodyText
781
+ ? content.bodyText
782
+ : "";
783
+ const sectionTitle =
784
+ contentMode === "realistic" && content?.sectionTitle
785
+ ? content.sectionTitle
786
+ : "";
787
+ const listItems = contentMode === "realistic" ? (content?.listItems ?? []) : [];
788
+ const summaryItems = contentMode === "realistic" ? (content?.summaryItems ?? []) : [];
789
+ const helperText =
790
+ contentMode === "realistic" && content?.helperText
791
+ ? content.helperText
792
+ : "";
793
+
794
+ // Template-specific skeleton builders
795
+ const templateBody = buildTemplateBody(
796
+ template,
797
+ headerText,
798
+ subText,
799
+ ctaText,
800
+ wireframeMode,
801
+ imageHash,
802
+ bodyText,
803
+ sectionTitle,
804
+ listItems,
805
+ summaryItems,
806
+ helperText,
807
+ resolvedComponents,
808
+ resolvedPalette,
809
+ fc
810
+ );
811
+
812
+ // Build variable binding script for CTA button and other key elements
813
+ const bindingScript = buildPostCreationBindings(resolvedPalette);
814
+
815
+ // Document template uses wider frame, larger padding/gap, semantic naming
816
+ const isDocTemplate = template === "document";
817
+ const frameWidth = isDocTemplate ? 1200 : width;
818
+ const framePadding = isDocTemplate ? 56 : 24;
819
+ const frameGap = isDocTemplate ? 48 : 16;
820
+ const frameName = isDocTemplate ? `Document Page - ${name}` : name;
821
+
822
+ return `
823
+ (async () => {
824
+ ${fontLoadBlock}
825
+
826
+ // Create new frame
827
+ const frame = figma.createFrame();
828
+ frame.name = ${JSON.stringify(frameName)};
829
+ frame.resize(${frameWidth}, 900);
830
+ frame.layoutMode = 'VERTICAL';
831
+ frame.primaryAxisSizingMode = 'AUTO';
832
+ frame.counterAxisSizingMode = 'FIXED';
833
+ frame.itemSpacing = ${frameGap};
834
+ frame.paddingLeft = ${framePadding};
835
+ frame.paddingRight = ${framePadding};
836
+ frame.paddingTop = ${framePadding};
837
+ frame.paddingBottom = ${isDocTemplate ? 72 : 32};
838
+ frame.fills = [{ type: 'SOLID', color: ${bgColor} }];
839
+ frame.x = ${xOffset};
840
+ frame.y = 0;
841
+ figma.currentPage.appendChild(frame);
842
+
843
+ ${templateBody}
844
+
845
+ ${bindingScript}
846
+
847
+ // ── Auto-Layout Safety Validator (inline, zero extra bridge calls) ──
848
+ ${generateValidatorScript()}
849
+ ${generateValidatorCall('frame')}
850
+
851
+ // ── Document Repair Pass (runs only on document-type pages) ──
852
+ ${generateDocumentRepairScript()}
853
+ ${generateDocumentRepairCall('frame')}
854
+
855
+ return { frameId: frame.id };
856
+ })();
857
+ `.trim();
858
+ }
859
+
860
+ /**
861
+ * Generate variable binding code that runs after all elements are created.
862
+ * Walks the frame's children and binds matching variables to fills/strokes.
863
+ */
864
+ function buildPostCreationBindings(resolvedPalette?: ResolvedPalette): string {
865
+ if (!resolvedPalette) return "";
866
+
867
+ // Collect all variable IDs we want to bind
868
+ const varBindings: Array<{
869
+ namePattern: string;
870
+ field: "fills" | "strokes";
871
+ variableId: string;
872
+ }> = [];
873
+
874
+ if (resolvedPalette.primary.variableId) {
875
+ varBindings.push({ namePattern: "CTA", field: "fills", variableId: resolvedPalette.primary.variableId });
876
+ }
877
+ if (resolvedPalette.border.variableId) {
878
+ varBindings.push({ namePattern: "InputField|SearchBar|SettingsGroup|AddressForm|CardDetails|ListItem", field: "strokes", variableId: resolvedPalette.border.variableId });
879
+ }
880
+ if (resolvedPalette.surface.variableId) {
881
+ varBindings.push({ namePattern: "InputField|SettingsGroup|AddressForm|CardDetails", field: "fills", variableId: resolvedPalette.surface.variableId });
882
+ }
883
+
884
+ if (varBindings.length === 0) return "";
885
+
886
+ return `
887
+ // ── Bind design-system variables to generated elements ──
888
+ try {
889
+ const __bindings = ${JSON.stringify(varBindings)};
890
+ const __bindChildren = async (parent) => {
891
+ if (!parent.children) return;
892
+ for (const child of parent.children) {
893
+ for (const b of __bindings) {
894
+ const patterns = b.namePattern.split('|');
895
+ if (patterns.some(p => child.name === p || child.name.startsWith(p))) {
896
+ const v = await figma.variables.getVariableByIdAsync(b.variableId);
897
+ if (v && child[b.field] && child[b.field].length > 0) {
898
+ const paints = [...child[b.field]];
899
+ if (paints[0].type === 'SOLID') {
900
+ paints[0] = figma.variables.setBoundVariableForPaint(paints[0], 'color', v);
901
+ child[b.field] = paints;
902
+ }
903
+ }
904
+ }
905
+ }
906
+ await __bindChildren(child);
907
+ }
908
+ };
909
+ await __bindChildren(frame);
910
+
911
+ // Bind CTA button label text color
912
+ ${resolvedPalette.primaryText.variableId ? `
913
+ const __ctaNode = frame.findOne(n => n.name === 'CTA');
914
+ if (__ctaNode && __ctaNode.children) {
915
+ for (const c of __ctaNode.children) {
916
+ if (c.type === 'TEXT' && c.fills && c.fills.length > 0) {
917
+ const v = await figma.variables.getVariableByIdAsync(${JSON.stringify(resolvedPalette.primaryText.variableId)});
918
+ if (v) {
919
+ const paints = [...c.fills];
920
+ paints[0] = figma.variables.setBoundVariableForPaint(paints[0], 'color', v);
921
+ c.fills = paints;
922
+ }
923
+ }
924
+ }
925
+ }` : ""}
926
+ } catch (e) {
927
+ // Token binding is best-effort — frame still renders with hardcoded values
928
+ }`;
929
+ }
930
+
931
+ function buildTemplateBody(
932
+ template: ScreenTemplate,
933
+ headerText: string,
934
+ subText: string,
935
+ ctaText: string,
936
+ wireframeMode: boolean,
937
+ imageHash?: string | null,
938
+ bodyText?: string,
939
+ sectionTitle?: string,
940
+ listItems: string[] = [],
941
+ summaryItems: string[] = [],
942
+ helperText?: string,
943
+ resolvedComponents: Array<{ name: string; nodeId: string | null }> = [],
944
+ resolvedPalette?: ResolvedPalette,
945
+ fc?: FontConfig
946
+ ): string {
947
+ // Font literals for template string interpolation
948
+ const _fc = fc ?? resolveFontConfig();
949
+ const headingBoldFont = fontNameLiteral("heading", "Bold", _fc);
950
+ const bodyRegularFont = fontNameLiteral("body", "Regular", _fc);
951
+ const uiMediumFont = fontNameLiteral("ui", "Medium", _fc);
952
+ // ── Centralized color palette — resolved from DS tokens when available ──
953
+ const palette = {
954
+ primary: resolvedPalette?.primary.rgb ?? "{ r: 0.09, g: 0.09, b: 0.09 }",
955
+ primaryText: resolvedPalette?.primaryText.rgb ?? "{ r: 1, g: 1, b: 1 }",
956
+ surface: resolvedPalette?.surface.rgb ?? "{ r: 0.98, g: 0.98, b: 0.99 }",
957
+ border: resolvedPalette?.border.rgb ?? "{ r: 0.90, g: 0.91, b: 0.93 }",
958
+ muted: resolvedPalette?.muted.rgb ?? "{ r: 0.45, g: 0.45, b: 0.50 }",
959
+ accent: resolvedPalette?.accent.rgb ?? "{ r: 0.22, g: 0.35, b: 0.96 }",
960
+ };
961
+
962
+ // Collect variable IDs for post-creation binding
963
+ const bindings: Array<{ elementName: string; field: string; variableId: string }> = [];
964
+ if (resolvedPalette) {
965
+ if (resolvedPalette.primary.variableId) {
966
+ bindings.push({ elementName: "CTA", field: "fills", variableId: resolvedPalette.primary.variableId });
967
+ }
968
+ if (resolvedPalette.primaryText.variableId) {
969
+ bindings.push({ elementName: "CTA__label", field: "fills", variableId: resolvedPalette.primaryText.variableId });
970
+ }
971
+ if (resolvedPalette.surface.variableId) {
972
+ bindings.push({ elementName: "__surface__", field: "fills", variableId: resolvedPalette.surface.variableId });
973
+ }
974
+ if (resolvedPalette.border.variableId) {
975
+ bindings.push({ elementName: "__border__", field: "strokes", variableId: resolvedPalette.border.variableId });
976
+ }
977
+ }
978
+
979
+ const rectColor = wireframeMode
980
+ ? "{ r: 0.88, g: 0.88, b: 0.88 }"
981
+ : "{ r: 0.94, g: 0.95, b: 1 }";
982
+
983
+ // Helper: try to instantiate a DS component, falling back to manual code
984
+ const tryInstantiate = (componentName: string, fallbackCode: string): string => {
985
+ const match = resolvedComponents.find(
986
+ (c) => c.nodeId && c.name.toLowerCase().includes(componentName.toLowerCase())
987
+ );
988
+ if (match) {
989
+ return `{
990
+ const comp = await figma.getNodeByIdAsync(${JSON.stringify(match.nodeId)});
991
+ if (comp && 'createInstance' in comp) {
992
+ const inst = comp.createInstance();
993
+ frame.appendChild(inst);
994
+ if ('layoutSizingHorizontal' in inst) inst.layoutSizingHorizontal = 'FILL';
995
+ } else { ${fallbackCode} }
996
+ }`;
997
+ }
998
+ return fallbackCode;
999
+ };
1000
+
1001
+ const addHeading = `
1002
+ const heading = figma.createText();
1003
+ heading.characters = ${JSON.stringify(headerText)};
1004
+ heading.fontSize = 28;
1005
+ heading.fontName = ${headingBoldFont};
1006
+ frame.appendChild(heading);
1007
+ if ('layoutSizingHorizontal' in heading) heading.layoutSizingHorizontal = 'FILL';`;
1008
+
1009
+ const addSubheading = subText
1010
+ ? `
1011
+ const subheading = figma.createText();
1012
+ subheading.characters = ${JSON.stringify(subText.slice(0, 140))};
1013
+ subheading.fontSize = 16;
1014
+ subheading.fontName = ${bodyRegularFont};
1015
+ subheading.opacity = 0.65;
1016
+ frame.appendChild(subheading);
1017
+ if ('layoutSizingHorizontal' in subheading) subheading.layoutSizingHorizontal = 'FILL';`
1018
+ : "";
1019
+
1020
+ const addSectionTitle = sectionTitle
1021
+ ? `
1022
+ const sectionTitleNode = figma.createText();
1023
+ sectionTitleNode.characters = ${JSON.stringify(sectionTitle)};
1024
+ sectionTitleNode.fontSize = 14;
1025
+ sectionTitleNode.fontName = ${headingBoldFont};
1026
+ sectionTitleNode.opacity = 0.8;
1027
+ frame.appendChild(sectionTitleNode);
1028
+ if ('layoutSizingHorizontal' in sectionTitleNode) sectionTitleNode.layoutSizingHorizontal = 'FILL';`
1029
+ : "";
1030
+
1031
+ const addBodyText = bodyText
1032
+ ? `
1033
+ const bodyNode = figma.createText();
1034
+ bodyNode.characters = ${JSON.stringify(bodyText.slice(0, 180))};
1035
+ bodyNode.fontSize = 15;
1036
+ bodyNode.fontName = ${bodyRegularFont};
1037
+ bodyNode.opacity = 0.75;
1038
+ frame.appendChild(bodyNode);
1039
+ if ('layoutSizingHorizontal' in bodyNode) bodyNode.layoutSizingHorizontal = 'FILL';`
1040
+ : "";
1041
+
1042
+ const addTextRows = (rows: string[], title: string) => {
1043
+ if (rows.length === 0) return "";
1044
+ return `
1045
+ {
1046
+ const group = figma.createFrame();
1047
+ group.name = ${JSON.stringify(title)};
1048
+ group.layoutMode = 'VERTICAL';
1049
+ group.primaryAxisSizingMode = 'AUTO';
1050
+ group.counterAxisSizingMode = 'AUTO';
1051
+ group.itemSpacing = 12;
1052
+ group.paddingLeft = 16;
1053
+ group.paddingRight = 16;
1054
+ group.paddingTop = 16;
1055
+ group.paddingBottom = 16;
1056
+ group.cornerRadius = 12;
1057
+ group.fills = [{ type: 'SOLID', color: { r: 0.98, g: 0.98, b: 0.99 } }];
1058
+ ${rows
1059
+ .map(
1060
+ (row) => `
1061
+ {
1062
+ const t = figma.createText();
1063
+ t.characters = ${JSON.stringify(row)};
1064
+ t.fontSize = 15;
1065
+ t.fontName = ${bodyRegularFont};
1066
+ group.appendChild(t);
1067
+ if ('layoutSizingHorizontal' in t) t.layoutSizingHorizontal = 'FILL';
1068
+ }`
1069
+ )
1070
+ .join("\n")}
1071
+ frame.appendChild(group);
1072
+ if ('layoutSizingHorizontal' in group) group.layoutSizingHorizontal = 'FILL';
1073
+ }`;
1074
+ };
1075
+
1076
+ const addHelperText = helperText
1077
+ ? `
1078
+ const helperNode = figma.createText();
1079
+ helperNode.characters = ${JSON.stringify(helperText.slice(0, 180))};
1080
+ helperNode.fontSize = 13;
1081
+ helperNode.fontName = ${bodyRegularFont};
1082
+ helperNode.opacity = 0.6;
1083
+ frame.appendChild(helperNode);
1084
+ if ('layoutSizingHorizontal' in helperNode) helperNode.layoutSizingHorizontal = 'FILL';`
1085
+ : "";
1086
+
1087
+ // Wireframe-only grey block — only used in wireframeMode
1088
+ const addPlaceholderRect = (label: string, height: number) => `
1089
+ {
1090
+ const r = figma.createRectangle();
1091
+ r.name = ${JSON.stringify(label)};
1092
+ r.resize(frame.width - 48, ${height});
1093
+ r.fills = [{ type: 'SOLID', color: ${rectColor} }];
1094
+ r.cornerRadius = 8;
1095
+ frame.appendChild(r);
1096
+ if ('layoutSizingHorizontal' in r) r.layoutSizingHorizontal = 'FILL';
1097
+ }`;
1098
+
1099
+ const addImageRect = (label: string, height: number) => `
1100
+ {
1101
+ const r = figma.createRectangle();
1102
+ r.name = ${JSON.stringify(label)};
1103
+ r.resize(frame.width - 48, ${height});
1104
+ ${imageHash
1105
+ ? `r.fills = [{ type: 'IMAGE', imageHash: ${JSON.stringify(imageHash)}, scaleMode: 'FILL' }];`
1106
+ : `r.fills = [{ type: 'SOLID', color: ${rectColor} }];`}
1107
+ r.cornerRadius = 12;
1108
+ frame.appendChild(r);
1109
+ if ('layoutSizingHorizontal' in r) r.layoutSizingHorizontal = 'FILL';
1110
+ }`;
1111
+
1112
+ const ctaFallback = `
1113
+ const btn = figma.createFrame();
1114
+ btn.name = 'CTA';
1115
+ btn.resize(frame.width - 48, 52);
1116
+ btn.layoutMode = 'HORIZONTAL';
1117
+ btn.primaryAxisAlignItems = 'CENTER';
1118
+ btn.counterAxisAlignItems = 'CENTER';
1119
+ btn.fills = [{ type: 'SOLID', color: ${palette.primary} }];
1120
+ btn.cornerRadius = 12;
1121
+ const btnLabel = figma.createText();
1122
+ btnLabel.characters = ${JSON.stringify(ctaText)};
1123
+ btnLabel.fontSize = 16;
1124
+ btnLabel.fontName = ${uiMediumFont};
1125
+ btnLabel.fills = [{ type: 'SOLID', color: ${palette.primaryText} }];
1126
+ btn.appendChild(btnLabel);
1127
+ frame.appendChild(btn);
1128
+ if ('layoutSizingHorizontal' in btn) btn.layoutSizingHorizontal = 'FILL';
1129
+ `;
1130
+ const addCTA = tryInstantiate("Button", ctaFallback);
1131
+
1132
+ // ── Styled elements (replace grey rects in realistic / non-wireframe mode) ──
1133
+
1134
+ const addInputField = (label: string, placeholder: string) => wireframeMode
1135
+ ? addPlaceholderRect(label, 52)
1136
+ : `
1137
+ {
1138
+ const inp = figma.createFrame();
1139
+ inp.name = ${JSON.stringify(label)};
1140
+ inp.layoutMode = 'HORIZONTAL';
1141
+ inp.counterAxisAlignItems = 'CENTER';
1142
+ inp.paddingLeft = 16; inp.paddingRight = 16;
1143
+ inp.primaryAxisSizingMode = 'FIXED'; inp.counterAxisSizingMode = 'FIXED';
1144
+ inp.resize(frame.width - 48, 52);
1145
+ inp.fills = [{ type: 'SOLID', color: { r: 0.99, g: 0.99, b: 1 } }];
1146
+ inp.cornerRadius = 10;
1147
+ inp.strokes = [{ type: 'SOLID', color: { r: 0.87, g: 0.88, b: 0.91 } }];
1148
+ inp.strokeWeight = 1.5;
1149
+ const ph = figma.createText();
1150
+ ph.characters = ${JSON.stringify(placeholder)};
1151
+ ph.fontSize = 15;
1152
+ ph.fontName = ${bodyRegularFont};
1153
+ ph.fills = [{ type: 'SOLID', color: { r: 0.63, g: 0.64, b: 0.68 } }];
1154
+ inp.appendChild(ph);
1155
+ if ('layoutSizingHorizontal' in ph) ph.layoutSizingHorizontal = 'FILL';
1156
+ frame.appendChild(inp);
1157
+ if ('layoutSizingHorizontal' in inp) inp.layoutSizingHorizontal = 'FILL';
1158
+ }`;
1159
+
1160
+ const addNavBar = (brandText: string) => wireframeMode
1161
+ ? addPlaceholderRect("Navigation Bar", 56)
1162
+ : `
1163
+ {
1164
+ const nav = figma.createFrame();
1165
+ nav.name = 'NavBar';
1166
+ nav.layoutMode = 'HORIZONTAL';
1167
+ nav.counterAxisAlignItems = 'CENTER';
1168
+ nav.primaryAxisAlignItems = 'SPACE_BETWEEN';
1169
+ nav.paddingLeft = 0; nav.paddingRight = 0;
1170
+ nav.primaryAxisSizingMode = 'FIXED'; nav.counterAxisSizingMode = 'FIXED';
1171
+ nav.resize(frame.width - 48, 56);
1172
+ nav.fills = [];
1173
+ const bLabel = figma.createText();
1174
+ bLabel.characters = ${JSON.stringify(brandText)};
1175
+ bLabel.fontSize = 17;
1176
+ bLabel.fontName = ${headingBoldFont};
1177
+ nav.appendChild(bLabel);
1178
+ const mIcon = figma.createText();
1179
+ mIcon.characters = '\u22EF';
1180
+ mIcon.fontSize = 20;
1181
+ mIcon.fontName = ${headingBoldFont};
1182
+ mIcon.opacity = 0.4;
1183
+ nav.appendChild(mIcon);
1184
+ frame.appendChild(nav);
1185
+ if ('layoutSizingHorizontal' in nav) nav.layoutSizingHorizontal = 'FILL';
1186
+ }`;
1187
+
1188
+ const addStatRow = () => wireframeMode
1189
+ ? addPlaceholderRect("Stats Row", 96)
1190
+ : `
1191
+ {
1192
+ const row = figma.createFrame();
1193
+ row.name = 'StatsRow';
1194
+ row.layoutMode = 'HORIZONTAL';
1195
+ row.itemSpacing = 12;
1196
+ row.primaryAxisSizingMode = 'FIXED'; row.counterAxisSizingMode = 'AUTO';
1197
+ row.resize(frame.width - 48, 1);
1198
+ row.fills = [];
1199
+ ${[["24", "Active"], ["8", "Pending"], ["142", "Total"]]
1200
+ .map(
1201
+ ([val, lbl]) => `
1202
+ {
1203
+ const sc = figma.createFrame();
1204
+ sc.name = ${JSON.stringify(lbl)};
1205
+ sc.layoutMode = 'VERTICAL';
1206
+ sc.primaryAxisSizingMode = 'AUTO'; sc.counterAxisSizingMode = 'AUTO';
1207
+ sc.paddingLeft = 16; sc.paddingRight = 16;
1208
+ sc.paddingTop = 14; sc.paddingBottom = 14;
1209
+ sc.itemSpacing = 2;
1210
+ sc.cornerRadius = 12;
1211
+ sc.fills = [{ type: 'SOLID', color: { r: 0.96, g: 0.97, b: 1 } }];
1212
+ const sv = figma.createText(); sv.characters = ${JSON.stringify(val)};
1213
+ sv.fontSize = 24; sv.fontName = ${headingBoldFont};
1214
+ sc.appendChild(sv);
1215
+ const sl = figma.createText(); sl.characters = ${JSON.stringify(lbl)};
1216
+ sl.fontSize = 12; sl.fontName = ${bodyRegularFont}; sl.opacity = 0.55;
1217
+ sc.appendChild(sl);
1218
+ row.appendChild(sc);
1219
+ if ('layoutSizingHorizontal' in sc) sc.layoutSizingHorizontal = 'FILL';
1220
+ }`
1221
+ )
1222
+ .join("\n")}
1223
+ frame.appendChild(row);
1224
+ if ('layoutSizingHorizontal' in row) row.layoutSizingHorizontal = 'FILL';
1225
+ }`;
1226
+
1227
+ const addSearchBar = () => wireframeMode
1228
+ ? addPlaceholderRect("Search Bar", 48)
1229
+ : `
1230
+ {
1231
+ const sb = figma.createFrame();
1232
+ sb.name = 'SearchBar';
1233
+ sb.layoutMode = 'HORIZONTAL';
1234
+ sb.counterAxisAlignItems = 'CENTER';
1235
+ sb.paddingLeft = 16; sb.paddingRight = 16;
1236
+ sb.itemSpacing = 8;
1237
+ sb.primaryAxisSizingMode = 'FIXED'; sb.counterAxisSizingMode = 'FIXED';
1238
+ sb.resize(frame.width - 48, 48);
1239
+ sb.fills = [{ type: 'SOLID', color: { r: 0.95, g: 0.95, b: 0.97 } }];
1240
+ sb.cornerRadius = 24;
1241
+ const sIcon = figma.createText();
1242
+ sIcon.characters = '\uD83D\uDD0D';
1243
+ sIcon.fontSize = 14; sIcon.fontName = ${bodyRegularFont};
1244
+ sIcon.opacity = 0.4;
1245
+ sb.appendChild(sIcon);
1246
+ const sph = figma.createText();
1247
+ sph.characters = 'Search\u2026';
1248
+ sph.fontSize = 15; sph.fontName = ${bodyRegularFont};
1249
+ sph.opacity = 0.45;
1250
+ sb.appendChild(sph);
1251
+ if ('layoutSizingHorizontal' in sph) sph.layoutSizingHorizontal = 'FILL';
1252
+ frame.appendChild(sb);
1253
+ if ('layoutSizingHorizontal' in sb) sb.layoutSizingHorizontal = 'FILL';
1254
+ }`;
1255
+
1256
+ const addListItemRows = (items: string[]) => wireframeMode
1257
+ ? items.map(() => addPlaceholderRect("List Item", 72)).join("\n")
1258
+ : items
1259
+ .map(
1260
+ (item) => `
1261
+ {
1262
+ const row = figma.createFrame();
1263
+ row.name = 'ListItem';
1264
+ row.layoutMode = 'HORIZONTAL';
1265
+ row.counterAxisAlignItems = 'CENTER';
1266
+ row.paddingLeft = 16; row.paddingRight = 16;
1267
+ row.itemSpacing = 12;
1268
+ row.primaryAxisSizingMode = 'FIXED'; row.counterAxisSizingMode = 'FIXED';
1269
+ row.resize(frame.width - 48, 68);
1270
+ row.cornerRadius = 10;
1271
+ row.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }];
1272
+ row.strokes = [{ type: 'SOLID', color: { r: 0.92, g: 0.93, b: 0.96 } }];
1273
+ row.strokeWeight = 1;
1274
+ const avatar = figma.createEllipse();
1275
+ avatar.resize(40, 40);
1276
+ avatar.fills = [{ type: 'SOLID', color: { r: 0.91, g: 0.92, b: 0.97 } }];
1277
+ row.appendChild(avatar);
1278
+ const textCol = figma.createFrame();
1279
+ textCol.name = 'Labels';
1280
+ textCol.layoutMode = 'VERTICAL';
1281
+ textCol.primaryAxisSizingMode = 'AUTO'; textCol.counterAxisSizingMode = 'AUTO';
1282
+ textCol.itemSpacing = 3; textCol.fills = [];
1283
+ const titleT = figma.createText();
1284
+ titleT.characters = ${JSON.stringify(item)};
1285
+ titleT.fontSize = 15; titleT.fontName = ${uiMediumFont};
1286
+ textCol.appendChild(titleT);
1287
+ const subT = figma.createText();
1288
+ subT.characters = 'Tap to view details';
1289
+ subT.fontSize = 13; subT.fontName = ${bodyRegularFont};
1290
+ subT.opacity = 0.5;
1291
+ textCol.appendChild(subT);
1292
+ row.appendChild(textCol);
1293
+ if ('layoutSizingHorizontal' in textCol) textCol.layoutSizingHorizontal = 'FILL';
1294
+ const arrowT = figma.createText();
1295
+ arrowT.characters = '\u203A';
1296
+ arrowT.fontSize = 18; arrowT.fontName = ${bodyRegularFont};
1297
+ arrowT.opacity = 0.35;
1298
+ row.appendChild(arrowT);
1299
+ frame.appendChild(row);
1300
+ if ('layoutSizingHorizontal' in row) row.layoutSizingHorizontal = 'FILL';
1301
+ }`
1302
+ )
1303
+ .join("\n");
1304
+
1305
+ const addSettingsGroup = (items: string[]) => wireframeMode
1306
+ ? addPlaceholderRect("Settings Group", items.length * 52)
1307
+ : `
1308
+ {
1309
+ const grp = figma.createFrame();
1310
+ grp.name = 'SettingsGroup';
1311
+ grp.layoutMode = 'VERTICAL';
1312
+ grp.primaryAxisSizingMode = 'AUTO'; grp.counterAxisSizingMode = 'FIXED';
1313
+ grp.resize(frame.width - 48, 1);
1314
+ grp.cornerRadius = 12;
1315
+ grp.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }];
1316
+ grp.strokes = [{ type: 'SOLID', color: { r: 0.92, g: 0.93, b: 0.96 } }];
1317
+ grp.strokeWeight = 1;
1318
+ ${items
1319
+ .map(
1320
+ (item, i) => `
1321
+ {
1322
+ const ri = figma.createFrame();
1323
+ ri.name = 'SettingsRow';
1324
+ ri.layoutMode = 'HORIZONTAL';
1325
+ ri.counterAxisAlignItems = 'CENTER';
1326
+ ri.primaryAxisAlignItems = 'SPACE_BETWEEN';
1327
+ ri.paddingLeft = 16; ri.paddingRight = 16;
1328
+ ri.primaryAxisSizingMode = 'FIXED'; ri.counterAxisSizingMode = 'FIXED';
1329
+ ri.resize(frame.width - 48, 52);
1330
+ ri.fills = [];
1331
+ ${i > 0 ? `ri.strokes = [{ type: 'SOLID', color: { r: 0.93, g: 0.94, b: 0.96 } }]; ri.strokeWeight = 1; ri.strokeAlign = 'INSIDE';` : ""}
1332
+ const rl = figma.createText();
1333
+ rl.characters = ${JSON.stringify(item)};
1334
+ rl.fontSize = 15; rl.fontName = ${bodyRegularFont};
1335
+ ri.appendChild(rl);
1336
+ const ra = figma.createText();
1337
+ ra.characters = '\u203A';
1338
+ ra.fontSize = 18; ra.fontName = ${bodyRegularFont};
1339
+ ra.opacity = 0.35;
1340
+ ri.appendChild(ra);
1341
+ grp.appendChild(ri);
1342
+ if ('layoutSizingHorizontal' in ri) ri.layoutSizingHorizontal = 'FILL';
1343
+ }`
1344
+ )
1345
+ .join("\n")}
1346
+ frame.appendChild(grp);
1347
+ if ('layoutSizingHorizontal' in grp) grp.layoutSizingHorizontal = 'FILL';
1348
+ }`;
1349
+
1350
+ const addAddressForm = () => wireframeMode
1351
+ ? addPlaceholderRect("Address Form", 180)
1352
+ : `
1353
+ {
1354
+ const form = figma.createFrame();
1355
+ form.name = 'AddressForm';
1356
+ form.layoutMode = 'VERTICAL';
1357
+ form.primaryAxisSizingMode = 'AUTO'; form.counterAxisSizingMode = 'FIXED';
1358
+ form.resize(frame.width - 48, 1);
1359
+ form.itemSpacing = 12;
1360
+ form.paddingLeft = 16; form.paddingRight = 16;
1361
+ form.paddingTop = 16; form.paddingBottom = 16;
1362
+ form.cornerRadius = 12;
1363
+ form.fills = [{ type: 'SOLID', color: { r: 0.99, g: 0.99, b: 1 } }];
1364
+ form.strokes = [{ type: 'SOLID', color: { r: 0.9, g: 0.91, b: 0.94 } }];
1365
+ form.strokeWeight = 1;
1366
+ ['Street address', 'City', 'Postcode'].forEach(function(lbl) {
1367
+ const fi = figma.createFrame();
1368
+ fi.layoutMode = 'HORIZONTAL'; fi.counterAxisAlignItems = 'CENTER';
1369
+ fi.paddingLeft = 12; fi.paddingRight = 12;
1370
+ fi.primaryAxisSizingMode = 'FIXED'; fi.counterAxisSizingMode = 'FIXED';
1371
+ fi.resize(form.width - 32, 46);
1372
+ fi.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }];
1373
+ fi.cornerRadius = 8;
1374
+ fi.strokes = [{ type: 'SOLID', color: { r: 0.87, g: 0.88, b: 0.91 } }];
1375
+ fi.strokeWeight = 1;
1376
+ const ft = figma.createText();
1377
+ ft.characters = lbl;
1378
+ ft.fontSize = 14; ft.fontName = ${bodyRegularFont};
1379
+ ft.opacity = 0.5;
1380
+ fi.appendChild(ft);
1381
+ form.appendChild(fi);
1382
+ if ('layoutSizingHorizontal' in fi) fi.layoutSizingHorizontal = 'FILL';
1383
+ });
1384
+ frame.appendChild(form);
1385
+ if ('layoutSizingHorizontal' in form) form.layoutSizingHorizontal = 'FILL';
1386
+ }`;
1387
+
1388
+ const addCardDetailsForm = () => wireframeMode
1389
+ ? addPlaceholderRect("Card Details", 140)
1390
+ : `
1391
+ {
1392
+ const cdf = figma.createFrame();
1393
+ cdf.name = 'CardDetails';
1394
+ cdf.layoutMode = 'VERTICAL';
1395
+ cdf.primaryAxisSizingMode = 'AUTO'; cdf.counterAxisSizingMode = 'FIXED';
1396
+ cdf.resize(frame.width - 48, 1);
1397
+ cdf.itemSpacing = 12;
1398
+ cdf.paddingLeft = 16; cdf.paddingRight = 16;
1399
+ cdf.paddingTop = 16; cdf.paddingBottom = 16;
1400
+ cdf.cornerRadius = 12;
1401
+ cdf.fills = [{ type: 'SOLID', color: { r: 0.99, g: 0.99, b: 1 } }];
1402
+ cdf.strokes = [{ type: 'SOLID', color: { r: 0.9, g: 0.91, b: 0.94 } }];
1403
+ cdf.strokeWeight = 1;
1404
+ ['Card number', 'MM / YY', 'CVV'].forEach(function(lbl) {
1405
+ const fi = figma.createFrame();
1406
+ fi.layoutMode = 'HORIZONTAL'; fi.counterAxisAlignItems = 'CENTER';
1407
+ fi.paddingLeft = 12; fi.paddingRight = 12;
1408
+ fi.primaryAxisSizingMode = 'FIXED'; fi.counterAxisSizingMode = 'FIXED';
1409
+ fi.resize(cdf.width - 32, 46);
1410
+ fi.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }];
1411
+ fi.cornerRadius = 8;
1412
+ fi.strokes = [{ type: 'SOLID', color: { r: 0.87, g: 0.88, b: 0.91 } }];
1413
+ fi.strokeWeight = 1;
1414
+ const ft = figma.createText();
1415
+ ft.characters = lbl;
1416
+ ft.fontSize = 14; ft.fontName = ${bodyRegularFont};
1417
+ ft.opacity = 0.5;
1418
+ fi.appendChild(ft);
1419
+ cdf.appendChild(fi);
1420
+ if ('layoutSizingHorizontal' in fi) fi.layoutSizingHorizontal = 'FILL';
1421
+ });
1422
+ frame.appendChild(cdf);
1423
+ if ('layoutSizingHorizontal' in cdf) cdf.layoutSizingHorizontal = 'FILL';
1424
+ }`;
1425
+
1426
+ switch (template) {
1427
+ case "auth":
1428
+ return `
1429
+ ${addHeading}
1430
+ ${addSubheading}
1431
+ ${addInputField("Email", "Email address")}
1432
+ ${addInputField("Password", "Password")}
1433
+ ${addCTA}
1434
+ `;
1435
+
1436
+ case "dashboard":
1437
+ return `
1438
+ ${addNavBar(sectionTitle || headerText)}
1439
+ ${addHeading}
1440
+ ${addSubheading}
1441
+ ${addStatRow()}
1442
+ ${addImageRect("Main Content", 300)}
1443
+ `;
1444
+
1445
+ case "list": {
1446
+ const displayItems = listItems.length > 0 ? listItems.slice(0, 3) : ["Item One", "Item Two", "Item Three"];
1447
+ return `
1448
+ ${addHeading}
1449
+ ${addSearchBar()}
1450
+ ${addImageRect("Featured Item", 180)}
1451
+ ${addListItemRows(displayItems)}
1452
+ `;
1453
+ }
1454
+
1455
+ case "detail":
1456
+ return `
1457
+ ${addImageRect("Hero Image", 240)}
1458
+ ${addHeading}
1459
+ ${addSubheading}
1460
+ ${addBodyText}
1461
+ ${addTextRows(listItems.length > 0 ? listItems : ["Key feature or detail", "Another relevant point", "Why users should care"], "Details")}
1462
+ ${addCTA}
1463
+ `;
1464
+
1465
+ case "settings": {
1466
+ const settingsItems = listItems.length > 0 ? listItems : ["Notifications", "Privacy & Security", "Language", "Help & Support", "Sign out"];
1467
+ const half = Math.ceil(settingsItems.length / 2);
1468
+ return `
1469
+ ${addHeading}
1470
+ ${addSubheading}
1471
+ ${addSettingsGroup(settingsItems.slice(0, half))}
1472
+ ${addSettingsGroup(settingsItems.slice(half))}
1473
+ ${addCTA}
1474
+ `;
1475
+ }
1476
+
1477
+ case "onboarding":
1478
+ return `
1479
+ ${addImageRect("Illustration", 280)}
1480
+ ${addHeading}
1481
+ ${addSubheading}
1482
+ ${addCTA}
1483
+ `;
1484
+
1485
+ case "checkout-cart":
1486
+ return `
1487
+ ${addHeading}
1488
+ ${addSubheading}
1489
+ ${addSectionTitle}
1490
+ ${addImageRect("Product Hero", 160)}
1491
+ ${addTextRows(listItems, "Cart Items")}
1492
+ ${addHelperText}
1493
+ ${addTextRows(summaryItems, "Order Summary")}
1494
+ ${addCTA}
1495
+ `;
1496
+
1497
+ case "checkout-address":
1498
+ return `
1499
+ ${addHeading}
1500
+ ${addSubheading}
1501
+ ${addSectionTitle}
1502
+ ${addTextRows(listItems, "Saved Addresses")}
1503
+ ${addAddressForm()}
1504
+ ${addHelperText}
1505
+ ${addCTA}
1506
+ `;
1507
+
1508
+ case "checkout-shipping":
1509
+ return `
1510
+ ${addHeading}
1511
+ ${addSubheading}
1512
+ ${addSectionTitle}
1513
+ ${addTextRows(listItems, "Shipping Options")}
1514
+ ${addTextRows(summaryItems, "Delivery Summary")}
1515
+ ${addCTA}
1516
+ `;
1517
+
1518
+ case "checkout-payment":
1519
+ return `
1520
+ ${addHeading}
1521
+ ${addSubheading}
1522
+ ${addSectionTitle}
1523
+ ${addTextRows(listItems, "Payment Methods")}
1524
+ ${addCardDetailsForm()}
1525
+ ${addHelperText}
1526
+ ${addCTA}
1527
+ `;
1528
+
1529
+ case "checkout-review":
1530
+ return `
1531
+ ${addHeading}
1532
+ ${addSubheading}
1533
+ ${addSectionTitle}
1534
+ ${addTextRows(listItems, "Review Sections")}
1535
+ ${addTextRows(summaryItems, "Final Totals")}
1536
+ ${addHelperText}
1537
+ ${addCTA}
1538
+ `;
1539
+
1540
+ case "checkout-success":
1541
+ return `
1542
+ ${addImageRect("Success Illustration", 220)}
1543
+ ${addHeading}
1544
+ ${addSubheading}
1545
+ ${addTextRows(listItems, "Confirmation Details")}
1546
+ ${addTextRows(summaryItems, "Support")}
1547
+ ${addCTA}
1548
+ `;
1549
+
1550
+ case "document": {
1551
+ // Document-style page with proper auto-layout structure
1552
+ const tocItems = listItems.length > 0 ? listItems : ["1. Overview", "2. Usage", "3. API", "4. Accessibility"];
1553
+
1554
+ const addTocSection = `
1555
+ {
1556
+ // ── TOC Section ──
1557
+ const tocSection = figma.createFrame();
1558
+ tocSection.name = 'Section Block - Table of Contents';
1559
+ tocSection.layoutMode = 'VERTICAL';
1560
+ tocSection.primaryAxisSizingMode = 'AUTO';
1561
+ tocSection.counterAxisSizingMode = 'AUTO';
1562
+ tocSection.itemSpacing = 20;
1563
+ tocSection.fills = [];
1564
+ frame.appendChild(tocSection);
1565
+ if ('layoutSizingHorizontal' in tocSection) tocSection.layoutSizingHorizontal = 'FILL';
1566
+ if ('layoutAlign' in tocSection) tocSection.layoutAlign = 'STRETCH';
1567
+
1568
+ ${sectionTitle ? `
1569
+ const tocTitle = figma.createText();
1570
+ tocTitle.characters = ${JSON.stringify(sectionTitle)};
1571
+ tocTitle.fontSize = 20;
1572
+ tocTitle.fontName = ${headingBoldFont};
1573
+ tocSection.appendChild(tocTitle);
1574
+ if ('layoutSizingHorizontal' in tocTitle) tocTitle.layoutSizingHorizontal = 'FILL';
1575
+ ` : ''}
1576
+
1577
+ ${tocItems.map((item, idx) => `
1578
+ {
1579
+ const tocRow = figma.createFrame();
1580
+ tocRow.name = 'TOC Row';
1581
+ tocRow.layoutMode = 'HORIZONTAL';
1582
+ tocRow.primaryAxisSizingMode = 'AUTO';
1583
+ tocRow.counterAxisSizingMode = 'AUTO';
1584
+ tocRow.itemSpacing = 12;
1585
+ tocRow.fills = [];
1586
+ frame.children.length; // force layout
1587
+ tocSection.appendChild(tocRow);
1588
+ if ('layoutSizingHorizontal' in tocRow) tocRow.layoutSizingHorizontal = 'FILL';
1589
+
1590
+ const tocLabel = figma.createText();
1591
+ tocLabel.characters = ${JSON.stringify(item)};
1592
+ tocLabel.fontSize = 16;
1593
+ tocLabel.fontName = ${bodyRegularFont};
1594
+ tocRow.appendChild(tocLabel);
1595
+ if ('layoutSizingHorizontal' in tocLabel) tocLabel.layoutSizingHorizontal = 'FILL';
1596
+ }`).join('\n')}
1597
+ }`;
1598
+
1599
+ const addDivider = `
1600
+ {
1601
+ const div = figma.createFrame();
1602
+ div.name = 'Divider';
1603
+ div.resize(frame.width - 112, 1);
1604
+ div.fills = [{ type: 'SOLID', color: { r: 0.88, g: 0.88, b: 0.9 } }];
1605
+ frame.appendChild(div);
1606
+ if ('layoutSizingHorizontal' in div) div.layoutSizingHorizontal = 'FILL';
1607
+ if ('layoutAlign' in div) div.layoutAlign = 'STRETCH';
1608
+ }`;
1609
+
1610
+ const addDocBody = bodyText ? `
1611
+ {
1612
+ const bodySection = figma.createFrame();
1613
+ bodySection.name = 'Section Block - Overview';
1614
+ bodySection.layoutMode = 'VERTICAL';
1615
+ bodySection.primaryAxisSizingMode = 'AUTO';
1616
+ bodySection.counterAxisSizingMode = 'AUTO';
1617
+ bodySection.itemSpacing = 16;
1618
+ bodySection.fills = [];
1619
+ frame.appendChild(bodySection);
1620
+ if ('layoutSizingHorizontal' in bodySection) bodySection.layoutSizingHorizontal = 'FILL';
1621
+ if ('layoutAlign' in bodySection) bodySection.layoutAlign = 'STRETCH';
1622
+
1623
+ const overviewTitle = figma.createText();
1624
+ overviewTitle.characters = 'Overview';
1625
+ overviewTitle.fontSize = 24;
1626
+ overviewTitle.fontName = ${headingBoldFont};
1627
+ bodySection.appendChild(overviewTitle);
1628
+ if ('layoutSizingHorizontal' in overviewTitle) overviewTitle.layoutSizingHorizontal = 'FILL';
1629
+
1630
+ const bodyNode = figma.createText();
1631
+ bodyNode.characters = ${JSON.stringify(bodyText.slice(0, 300))};
1632
+ bodyNode.fontSize = 15;
1633
+ bodyNode.fontName = ${bodyRegularFont};
1634
+ bodyNode.opacity = 0.75;
1635
+ bodySection.appendChild(bodyNode);
1636
+ if ('layoutSizingHorizontal' in bodyNode) bodyNode.layoutSizingHorizontal = 'FILL';
1637
+ }` : '';
1638
+
1639
+ return `
1640
+ // ── Document Header Block ──
1641
+ {
1642
+ const headerBlock = figma.createFrame();
1643
+ headerBlock.name = 'Header Block';
1644
+ headerBlock.layoutMode = 'VERTICAL';
1645
+ headerBlock.primaryAxisSizingMode = 'AUTO';
1646
+ headerBlock.counterAxisSizingMode = 'AUTO';
1647
+ headerBlock.itemSpacing = 8;
1648
+ headerBlock.fills = [];
1649
+ frame.appendChild(headerBlock);
1650
+ if ('layoutSizingHorizontal' in headerBlock) headerBlock.layoutSizingHorizontal = 'FILL';
1651
+ if ('layoutAlign' in headerBlock) headerBlock.layoutAlign = 'STRETCH';
1652
+
1653
+ const hTitle = figma.createText();
1654
+ hTitle.characters = ${JSON.stringify(headerText)};
1655
+ hTitle.fontSize = 40;
1656
+ hTitle.fontName = ${headingBoldFont};
1657
+ headerBlock.appendChild(hTitle);
1658
+ if ('layoutSizingHorizontal' in hTitle) hTitle.layoutSizingHorizontal = 'FILL';
1659
+
1660
+ ${subText ? `
1661
+ const hSub = figma.createText();
1662
+ hSub.characters = ${JSON.stringify(subText.slice(0, 140))};
1663
+ hSub.fontSize = 16;
1664
+ hSub.fontName = ${bodyRegularFont};
1665
+ hSub.opacity = 0.6;
1666
+ headerBlock.appendChild(hSub);
1667
+ if ('layoutSizingHorizontal' in hSub) hSub.layoutSizingHorizontal = 'FILL';
1668
+ ` : ''}
1669
+ }
1670
+ ${addDivider}
1671
+ ${addTocSection}
1672
+ ${addDivider}
1673
+ ${addDocBody}
1674
+ `;
1675
+ }
1676
+
1677
+ default:
1678
+ return `
1679
+ ${addHeading}
1680
+ ${addSubheading}
1681
+ ${addImageRect("Content Visual", 220)}
1682
+ ${addTextRows(listItems.length > 0 ? listItems : ["Primary content area"], "Content")}
1683
+ ${addCTA}
1684
+ `;
1685
+ }
1686
+ }
1687
+
1688
+ // ─── Prototype connection script (uses shared builder) ──────────────────────
1689
+
1690
+ function buildPrototypeScript(fromId: string, toId: string): string {
1691
+ return buildWireScript([{
1692
+ fromNodeId: fromId,
1693
+ toNodeId: toId,
1694
+ trigger: { type: "ON_CLICK" },
1695
+ animation: { type: "SMART_ANIMATE", duration: 0.3, easing: "EASE_IN_AND_OUT" },
1696
+ }]);
1697
+ }
1698
+
1699
+ // ─── Flow map page ────────────────────────────────────────────────────────────
1700
+
1701
+ function buildFlowMapScript(screens: CreatedScreen[], fc: FontConfig): string {
1702
+ const fontLoadBlock = generateFontLoadScript(fc);
1703
+ const headingBoldFont = fontNameLiteral("heading", "Bold", fc);
1704
+
1705
+ const screenData = screens.map((s, i) => ({
1706
+ id: s.frameId,
1707
+ name: s.name,
1708
+ x: i * 280,
1709
+ y: 0,
1710
+ }));
1711
+
1712
+ return `
1713
+ (async () => {
1714
+ ${fontLoadBlock}
1715
+
1716
+ // Create or find flow map page
1717
+ let flowPage = figma.root.children.find(p => p.name === '[Flow Map]');
1718
+ if (!flowPage) {
1719
+ flowPage = figma.createPage();
1720
+ flowPage.name = '[Flow Map]';
1721
+ }
1722
+ await figma.setCurrentPageAsync(flowPage);
1723
+
1724
+ const screenData = ${JSON.stringify(screenData)};
1725
+ const createdBoxes = [];
1726
+
1727
+ for (const s of screenData) {
1728
+ const box = figma.createFrame();
1729
+ box.name = s.name;
1730
+ box.resize(240, 140);
1731
+ box.x = s.x;
1732
+ box.y = 0;
1733
+ box.fills = [{ type: 'SOLID', color: { r: 0.93, g: 0.96, b: 1 } }];
1734
+ box.cornerRadius = 12;
1735
+ box.strokeWeight = 2;
1736
+ box.strokes = [{ type: 'SOLID', color: { r: 0.24, g: 0.37, b: 1 } }];
1737
+
1738
+ const label = figma.createText();
1739
+ label.characters = s.name;
1740
+ label.fontSize = 14;
1741
+ label.fontName = ${headingBoldFont};
1742
+ label.x = 16;
1743
+ label.y = 16;
1744
+ box.appendChild(label);
1745
+
1746
+ flowPage.appendChild(box);
1747
+ createdBoxes.push(box);
1748
+ }
1749
+
1750
+ // Draw connector lines between sequential screens
1751
+ for (let i = 0; i < createdBoxes.length - 1; i++) {
1752
+ const from = createdBoxes[i];
1753
+ const to = createdBoxes[i + 1];
1754
+ const line = figma.createLine();
1755
+ line.x = from.x + from.width;
1756
+ line.y = from.y + from.height / 2;
1757
+ line.resize(to.x - (from.x + from.width), 0);
1758
+ line.strokes = [{ type: 'SOLID', color: { r: 0.24, g: 0.37, b: 1 } }];
1759
+ line.strokeWeight = 2;
1760
+ flowPage.appendChild(line);
1761
+ }
1762
+
1763
+ figma.viewport.scrollAndZoomIntoView(createdBoxes);
1764
+ return { pageId: flowPage.id };
1765
+ })();
1766
+ `.trim();
1767
+ }
1768
+
1769
+ // ─── Main handler ─────────────────────────────────────────────────────────────
1770
+
1771
+ export async function pageArchitectHandler(
1772
+ args: PageArchitectArgs
1773
+ ): Promise<PageArchitectResult> {
1774
+ const {
1775
+ productContext,
1776
+ flow,
1777
+ platform,
1778
+ width: userWidth,
1779
+ wireframeMode = false,
1780
+ includeFlowMap = false,
1781
+ contentMode,
1782
+ useStockImages = true,
1783
+ imageQuery,
1784
+ } = args;
1785
+
1786
+ if (!flow) throw new Error("pageArchitect: `flow` is required.");
1787
+ if (!productContext) throw new Error("pageArchitect: `productContext` is required.");
1788
+
1789
+ const bridge = await getBridge();
1790
+ const fontConfig = resolveFontConfig(args.fonts, bridge.getActiveDesignSystemId());
1791
+
1792
+ // 1. Parse flow into screen specs
1793
+ const screenSpecs = parseFlowToScreens(productContext, flow, contentMode);
1794
+
1795
+ if (screenSpecs.length === 0) {
1796
+ throw new Error("pageArchitect: Could not parse any screens from the flow description.");
1797
+ }
1798
+
1799
+ // 1.5 Generate AI content bundle for realistic mode
1800
+ let contentBundle: ContentBundle | null = null;
1801
+ if (contentMode === "realistic") {
1802
+ try {
1803
+ contentBundle = await generateContentBundle(productContext, screenSpecs.map((s) => s.name));
1804
+ } catch (error) {
1805
+ console.warn(`pageArchitect: content bundle generation failed: ${error instanceof Error ? error.message : String(error)}`);
1806
+ }
1807
+ if (contentBundle) {
1808
+ for (const [index, spec] of screenSpecs.entries()) {
1809
+ spec.realisticContent = applyBundleToScreen(spec.template, spec.name, contentBundle, index);
1810
+ }
1811
+ }
1812
+ }
1813
+
1814
+ // 2. Load DS components for matching + tokens for palette resolution
1815
+ const componentSets = await bridge.getComponentSets();
1816
+ const fuse = buildFuse(componentSets);
1817
+
1818
+ // Fetch design tokens to resolve palette colors and bind variables
1819
+ let dsTokens: Token[] = [];
1820
+ let palette: ResolvedPalette | undefined;
1821
+ const dsId = bridge.getActiveDesignSystemId();
1822
+ try {
1823
+ dsTokens = await bridge.getTokens();
1824
+ // When a DS is selected, its tokens are authoritative (no fallback to file tokens)
1825
+ if (dsId || dsTokens.length > 0) {
1826
+ palette = resolveDesignPalette(dsTokens, dsId);
1827
+ }
1828
+ } catch {
1829
+ // If DS is selected, still resolve palette from DS tokens even if getTokens fails
1830
+ if (dsId) {
1831
+ palette = resolveDesignPalette([], dsId);
1832
+ }
1833
+ }
1834
+
1835
+ // 2.5 Optionally prepare imagery for image-heavy flows
1836
+ const shouldUseStockImages =
1837
+ useStockImages &&
1838
+ !wireframeMode;
1839
+
1840
+ let stockImagery: ScreenImagery[] = [];
1841
+ if (shouldUseStockImages) {
1842
+ try {
1843
+ const querySource = imageQuery?.trim()
1844
+ ? imageQuery
1845
+ : contentBundle?.imageQuery
1846
+ ? contentBundle.imageQuery
1847
+ : guessImageQuery(productContext, flow);
1848
+ stockImagery = await loadStockImagery(querySource, flow, screenSpecs.length, platform === "mobile" ? "portrait" : "landscape");
1849
+ } catch (error) {
1850
+ console.warn(`pageArchitect: stock imagery lookup failed: ${error instanceof Error ? error.message : String(error)}`);
1851
+ }
1852
+ }
1853
+
1854
+ // 3. Build each screen frame
1855
+ const createdScreens: CreatedScreen[] = [];
1856
+ const frameWidth = platformWidth(platform, userWidth);
1857
+ const FRAME_GAP = 80;
1858
+
1859
+ // Find rightmost existing frame so new screens don't overlap prior work
1860
+ let xOffset = 0;
1861
+ try {
1862
+ const posScript = `(async () => {
1863
+ const frames = figma.currentPage.children.filter(n =>
1864
+ n.type === 'FRAME' && !n.name.startsWith('__agent_')
1865
+ );
1866
+ const maxX = frames.reduce((max, f) => Math.max(max, f.x + f.width), 0);
1867
+ return { maxX };
1868
+ })();`;
1869
+ const posResult = await bridge.execute(posScript);
1870
+ const posMax = (posResult.result as { maxX?: number })?.maxX ?? 0;
1871
+ if (posResult.success && posMax > 0) {
1872
+ xOffset = posMax + FRAME_GAP;
1873
+ }
1874
+ } catch {
1875
+ // ignore — start at 0
1876
+ }
1877
+
1878
+ for (const [index, spec] of screenSpecs.entries()) {
1879
+ const resolved = resolveComponents(spec.requiredComponents, fuse);
1880
+ const assignedImage = stockImagery[index % Math.max(stockImagery.length, 1)];
1881
+
1882
+ const frameSpec: FrameSpec = {
1883
+ name: spec.name,
1884
+ width: frameWidth,
1885
+ template: spec.template,
1886
+ wireframeMode,
1887
+ contentMode,
1888
+ content: spec.realisticContent,
1889
+ resolvedComponents: resolved,
1890
+ xOffset,
1891
+ purpose: spec.purpose,
1892
+ imageHash: assignedImage?.imageHash ?? null,
1893
+ resolvedPalette: palette,
1894
+ fontConfig,
1895
+ };
1896
+
1897
+ // Build final frame directly in a single execute call (no shimmer phase —
1898
+ // eliminates one WebSocket round-trip and prevents duplicate/overlapping frames).
1899
+ const script = buildScreenScript(frameSpec);
1900
+ const execResult = await bridge.execute(script);
1901
+
1902
+ if (execResult.success && execResult.result) {
1903
+ const res = execResult.result as { frameId: string };
1904
+ if (res.frameId) {
1905
+ createdScreens.push({
1906
+ frameId: res.frameId,
1907
+ name: spec.name,
1908
+ template: spec.template,
1909
+ instantiatedComponents: resolved
1910
+ .filter((c) => c.nodeId !== null)
1911
+ .map((c) => c.name),
1912
+ });
1913
+ xOffset += frameWidth + FRAME_GAP;
1914
+ }
1915
+ } else {
1916
+ console.error(
1917
+ `pageArchitect: Failed to create frame "${spec.name}": ${execResult.error}`
1918
+ );
1919
+ }
1920
+
1921
+ // If "both" platforms, also create mobile frame
1922
+ const mw = mobileWidth(platform);
1923
+ if (mw && mw !== frameWidth) {
1924
+ const mobileSpec: FrameSpec = {
1925
+ ...frameSpec,
1926
+ width: mw,
1927
+ name: `${spec.name} (Mobile)`,
1928
+ xOffset,
1929
+ };
1930
+ const mobileScript = buildScreenScript(mobileSpec);
1931
+ const mobileResult = await bridge.execute(mobileScript);
1932
+ if (mobileResult.success && mobileResult.result) {
1933
+ const res = mobileResult.result as { frameId: string };
1934
+ if (res.frameId) {
1935
+ createdScreens.push({
1936
+ frameId: res.frameId,
1937
+ name: `${spec.name} (Mobile)`,
1938
+ template: spec.template,
1939
+ instantiatedComponents: resolved
1940
+ .filter((c) => c.nodeId !== null)
1941
+ .map((c) => c.name),
1942
+ });
1943
+ xOffset += mw + FRAME_GAP;
1944
+ }
1945
+ }
1946
+ }
1947
+ }
1948
+
1949
+ // 4. Wire prototype connections between consecutive screens
1950
+ const prototypeConnections: PrototypeConnection[] = [];
1951
+ const primaryScreens = createdScreens.filter((s) => !s.name.includes("(Mobile)"));
1952
+
1953
+ for (let i = 0; i < primaryScreens.length - 1; i++) {
1954
+ const from = primaryScreens[i];
1955
+ const to = primaryScreens[i + 1];
1956
+ const script = buildPrototypeScript(from.frameId, to.frameId);
1957
+ const result = await bridge.execute(script);
1958
+ if (result.success) {
1959
+ prototypeConnections.push({
1960
+ fromFrameId: from.frameId,
1961
+ toFrameId: to.frameId,
1962
+ fromName: from.name,
1963
+ toName: to.name,
1964
+ });
1965
+ }
1966
+ }
1967
+
1968
+ // 5. Optionally create flow map page
1969
+ let flowMapPageId: string | null = null;
1970
+ if (includeFlowMap && createdScreens.length > 0) {
1971
+ const flowScript = buildFlowMapScript(primaryScreens, fontConfig);
1972
+ const flowResult = await bridge.execute(flowScript);
1973
+ if (flowResult.success && flowResult.result) {
1974
+ const res = flowResult.result as { pageId: string };
1975
+ flowMapPageId = res.pageId;
1976
+ }
1977
+ }
1978
+
1979
+ // 6. Log the decision
1980
+ const logEntry = await decisionLog.log({
1981
+ tool: "page-architect",
1982
+ nodeIds: createdScreens.map((s) => s.frameId),
1983
+ rationale: `Built ${createdScreens.length} screen frame(s) for flow: "${flow.slice(0, 100)}". Platform: ${platform}. Content: ${contentMode}. AI content bundle: ${!!contentBundle}. Wireframe: ${wireframeMode}. Prototype connections: ${prototypeConnections.length}. Flow map: ${!!flowMapPageId}. Token binding: ${palette ? "active" : "none"} (${dsTokens.length} tokens resolved).`,
1984
+ tokens: dsTokens.slice(0, 20).map((t) => t.name),
1985
+ reversible: true,
1986
+ metadata: {
1987
+ productContext,
1988
+ platform,
1989
+ wireframeMode,
1990
+ contentMode,
1991
+ useStockImages: shouldUseStockImages,
1992
+ stockImageCount: stockImagery.length,
1993
+ stockImageSources: stockImagery.map((item) => item.sourceUrl),
1994
+ includeFlowMap,
1995
+ screenCount: createdScreens.length,
1996
+ prototypeConnectionCount: prototypeConnections.length,
1997
+ flowMapPageId,
1998
+ },
1999
+ });
2000
+
2001
+ // 7. Navigate viewport to the first created screen so the user lands there
2002
+ const primaryScreens2 = createdScreens.filter((s) => !s.name.includes("(Mobile)"));
2003
+ if (primaryScreens2.length > 0) {
2004
+ try {
2005
+ await bridge.navigate(primaryScreens2[0].frameId);
2006
+ } catch {
2007
+ // Non-critical navigation error — ignore
2008
+ }
2009
+ }
2010
+
2011
+ return {
2012
+ screens: createdScreens,
2013
+ prototypeConnections,
2014
+ flowMapPageId,
2015
+ frameIds: createdScreens.map((s) => s.frameId),
2016
+ prototypeConnectionCount: prototypeConnections.length,
2017
+ logEntryId: logEntry.id,
2018
+ };
2019
+ }