@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,782 @@
1
+ /**
2
+ * Accessibility Annotation Handler
3
+ * Analyzes a Figma frame and creates a new page with:
4
+ * 1. A clone of the design with numbered circle markers on interactive elements
5
+ * 2. A Tab Order Sequence table
6
+ * 3. Implementation Notes
7
+ *
8
+ * Supports 7 annotation types: focus-order, reading-order, input,
9
+ * landmark, heading, link, button.
10
+ */
11
+
12
+ import { getBridge } from "../../../shared/figma-bridge.js";
13
+ import { decisionLog } from "../../../shared/decision-log.js";
14
+ import { FigmaNode } from "../../../shared/types.js";
15
+ import {
16
+ ANNOTATION_TYPES,
17
+ AnnotationTypeKey,
18
+ roleToAnnotationType,
19
+ inferInputType,
20
+ } from "./a11y-annotation-kit.js";
21
+ import {
22
+ renderAnnotationPage,
23
+ MarkerDef,
24
+ TabOrderRow,
25
+ } from "./a11y-annotate-renderer.js";
26
+
27
+ // ─── Public Interface ──────────────────────────────────────────────────────
28
+
29
+ export interface A11yAnnotateArgs {
30
+ nodeId: string;
31
+ annotationType: AnnotationTypeKey | "all";
32
+ showDetails?: boolean;
33
+ showLasso?: boolean;
34
+ placement?: "left" | "right" | "auto";
35
+ }
36
+
37
+ export interface A11yAnnotateResult {
38
+ pageId: string;
39
+ annotationCount: number;
40
+ types: string[];
41
+ summary: string;
42
+ }
43
+
44
+ // ─── Module-level root for navigation lookups ────────────────────────────
45
+
46
+ let _annotateRootNode: FigmaNode | null = null;
47
+
48
+ // ─── Role / Landmark / Heading inference ───────────────────────────────────
49
+
50
+ const ROLE_PATTERNS: [RegExp, string][] = [
51
+ [/\b(button|btn|cta)\b/i, "button"],
52
+ [/\b(link|anchor|href)\b/i, "link"],
53
+ [/\b(input|field|textbox|text[-_ ]?field)\b/i, "textbox"],
54
+ [/\b(checkbox|check[-_ ]?box)\b/i, "checkbox"],
55
+ [/\b(radio)\b/i, "radio"],
56
+ [/\b(toggle|switch)\b/i, "switch"],
57
+ [/\b(select|dropdown|combo[-_ ]?box)\b/i, "listbox"],
58
+ [/\b(slider|range)\b/i, "slider"],
59
+ [/\b(spinner|stepper|quantity)\b/i, "spinbutton"],
60
+ [/\b(tab)\b/i, "tab"],
61
+ [/\b(search)\b/i, "searchbox"],
62
+ [/\b(menu[-_ ]?item)\b/i, "menuitem"],
63
+ [/\b(menu)\b/i, "menu"],
64
+ [/\b(modal|dialog)\b/i, "dialog"],
65
+ [/\b(image|photo|avatar|thumbnail)\b/i, "img"],
66
+ ];
67
+
68
+ const LANDMARK_PATTERNS: [RegExp, string, string][] = [
69
+ [/\b(nav|navigation)\b/i, "navigation", "Navigation"],
70
+ [/\b(header|top[-_ ]?bar|app[-_ ]?bar|navbar)\b/i, "banner", "Site header"],
71
+ [/\b(footer|bottom[-_ ]?bar)\b/i, "contentinfo", "Site footer"],
72
+ [/\b(sidebar|side[-_ ]?nav|drawer)\b/i, "complementary", "Sidebar"],
73
+ [/\b(main|content|body)\b/i, "main", "Main content"],
74
+ [/\b(form)\b/i, "form", "Form"],
75
+ [/\b(search)\b/i, "search", "Search"],
76
+ ];
77
+
78
+ function inferRole(node: FigmaNode): string | null {
79
+ const nameLower = node.name.toLowerCase();
80
+ for (const [pattern, role] of ROLE_PATTERNS) {
81
+ if (pattern.test(nameLower)) return role;
82
+ }
83
+ return null;
84
+ }
85
+
86
+ function inferLandmark(
87
+ node: FigmaNode
88
+ ): { role: string; label: string } | null {
89
+ const nameLower = node.name.toLowerCase();
90
+ for (const [pattern, role, label] of LANDMARK_PATTERNS) {
91
+ if (pattern.test(nameLower)) return { role, label };
92
+ }
93
+ return null;
94
+ }
95
+
96
+ function isHeading(node: FigmaNode): boolean {
97
+ if (node.type !== "TEXT") return false;
98
+ const fontSize = node.style?.fontSize ?? 16;
99
+ const fontWeight = node.style?.fontWeight ?? 400;
100
+ return fontSize >= 20 || fontWeight >= 700;
101
+ }
102
+
103
+ function estimateHeadingLevel(node: FigmaNode): number {
104
+ const fontSize = node.style?.fontSize ?? 16;
105
+ if (fontSize >= 32) return 1;
106
+ if (fontSize >= 24) return 2;
107
+ if (fontSize >= 20) return 3;
108
+ if (fontSize >= 18) return 4;
109
+ if (fontSize >= 16) return 5;
110
+ return 6;
111
+ }
112
+
113
+ // ─── Navigation context detection ────────────────────────────────────────
114
+
115
+ function isInsideNavigation(node: FigmaNode, rootNode: FigmaNode): boolean {
116
+ function findParent(current: FigmaNode, targetId: string): FigmaNode | null {
117
+ if (current.children) {
118
+ for (const child of current.children) {
119
+ if (child.id === targetId) return current;
120
+ const found = findParent(child, targetId);
121
+ if (found) return found;
122
+ }
123
+ }
124
+ return null;
125
+ }
126
+
127
+ let parentNode = findParent(rootNode, node.id);
128
+ let depth = 0;
129
+ while (parentNode && depth < 8) {
130
+ if (
131
+ inferLandmark(parentNode)?.role === "navigation" ||
132
+ inferLandmark(parentNode)?.role === "banner"
133
+ ) {
134
+ return true;
135
+ }
136
+ const nextParent = findParent(rootNode, parentNode.id);
137
+ if (!nextParent || nextParent.id === parentNode.id) break;
138
+ parentNode = nextParent;
139
+ depth++;
140
+ }
141
+ return false;
142
+ }
143
+
144
+ // ─── Interactive element helpers ─────────────────────────────────────────
145
+
146
+ function hasInteractiveDescendant(node: FigmaNode): boolean {
147
+ if (!node.children) return false;
148
+ for (const child of node.children) {
149
+ const role = inferRole(child);
150
+ if (role && !["img", "dialog", "menu"].includes(role) && child.type !== "TEXT") return true;
151
+ if (
152
+ (child.type === "INSTANCE" || child.type === "COMPONENT") &&
153
+ /button|btn|link|cta|click/i.test(child.name)
154
+ )
155
+ return true;
156
+ if (hasInteractiveDescendant(child)) return true;
157
+ }
158
+ return false;
159
+ }
160
+
161
+ function isInteractive(node: FigmaNode): boolean {
162
+ // TEXT nodes are never independently interactive — they are labels/content.
163
+ // Exception: text inside nav/banner landmarks acts as focusable links.
164
+ if (node.type === "TEXT") {
165
+ if (
166
+ _annotateRootNode &&
167
+ node.characters &&
168
+ node.characters.trim().length > 0 &&
169
+ node.characters.trim().length < 40 &&
170
+ isInsideNavigation(node, _annotateRootNode)
171
+ ) {
172
+ return true;
173
+ }
174
+ return false;
175
+ }
176
+
177
+ const role = inferRole(node);
178
+ if (role && !["img", "dialog", "menu"].includes(role)) {
179
+ // Skip containers (FRAME/GROUP) that wrap interactive children —
180
+ // the children will be collected individually
181
+ if (
182
+ (node.type === "FRAME" || node.type === "GROUP") &&
183
+ hasInteractiveDescendant(node)
184
+ ) {
185
+ return false;
186
+ }
187
+ return true;
188
+ }
189
+
190
+ if (
191
+ (node.type === "INSTANCE" || node.type === "COMPONENT") &&
192
+ /button|btn|link|cta|click/i.test(node.name)
193
+ ) {
194
+ // Skip component groups that contain interactive children
195
+ if (hasInteractiveDescendant(node)) return false;
196
+ return true;
197
+ }
198
+
199
+ return false;
200
+ }
201
+
202
+ function findTextContent(node: FigmaNode): string {
203
+ if (node.type === "TEXT" && node.characters) return node.characters.trim();
204
+ if (!node.children) return "";
205
+ const texts: string[] = [];
206
+ for (const child of node.children) {
207
+ const t = findTextContent(child);
208
+ if (t) texts.push(t);
209
+ }
210
+ return texts.join(" ");
211
+ }
212
+
213
+ function cleanNodeName(name: string): string {
214
+ return name
215
+ .replace(/^(icon|icn|ic)[-_ /]*/i, "")
216
+ .replace(/[-_/]/g, " ")
217
+ .replace(/\s+/g, " ")
218
+ .trim();
219
+ }
220
+
221
+ // ─── Node collection ──────────────────────────────────────────────────────
222
+
223
+ interface AnnotatableElement {
224
+ node: FigmaNode;
225
+ type: AnnotationTypeKey;
226
+ role: string;
227
+ label: string;
228
+ headingLevel?: number;
229
+ landmarkElement?: string;
230
+ inputType?: string;
231
+ }
232
+
233
+ // ─── Adaptive row threshold ──────────────────────────────────────────────
234
+
235
+ const MIN_ROW_THRESHOLD = 15;
236
+ const MAX_ROW_THRESHOLD = 40;
237
+
238
+ function computeRowThreshold(rootNode: FigmaNode): number {
239
+ const frameHeight = rootNode.absoluteBoundingBox?.height ?? 800;
240
+ return Math.max(
241
+ MIN_ROW_THRESHOLD,
242
+ Math.min(MAX_ROW_THRESHOLD, Math.round(frameHeight * 0.025))
243
+ );
244
+ }
245
+
246
+ // ─── Deduplication helper ────────────────────────────────────────────────
247
+
248
+ function isDescendantOf(
249
+ potentialChild: FigmaNode,
250
+ potentialParent: FigmaNode
251
+ ): boolean {
252
+ if (!potentialParent.children) return false;
253
+ for (const child of potentialParent.children) {
254
+ if (child.id === potentialChild.id) return true;
255
+ if (isDescendantOf(potentialChild, child)) return true;
256
+ }
257
+ return false;
258
+ }
259
+
260
+ function deduplicateElements(
261
+ elements: AnnotatableElement[]
262
+ ): AnnotatableElement[] {
263
+ const deduped: AnnotatableElement[] = [];
264
+ for (const el of elements) {
265
+ // If this element's node is a descendant of another collected element's node,
266
+ // skip it — keep only the outermost interactive ancestor
267
+ const isChildOfAnother = elements.some(
268
+ (other) =>
269
+ other.node.id !== el.node.id && isDescendantOf(el.node, other.node)
270
+ );
271
+ if (!isChildOfAnother) {
272
+ deduped.push(el);
273
+ }
274
+ }
275
+ return deduped;
276
+ }
277
+
278
+ function collectAnnotatableElements(
279
+ root: FigmaNode,
280
+ requestedTypes: AnnotationTypeKey[]
281
+ ): AnnotatableElement[] {
282
+ _annotateRootNode = root;
283
+ const rowThreshold = computeRowThreshold(root);
284
+ const elements: AnnotatableElement[] = [];
285
+ const allNodes: FigmaNode[] = [];
286
+ flattenAll(root, allNodes);
287
+
288
+ for (const node of allNodes) {
289
+ if (!node.absoluteBoundingBox) continue;
290
+
291
+ // Heading check
292
+ if (
293
+ requestedTypes.includes("heading") &&
294
+ isHeading(node)
295
+ ) {
296
+ const level = estimateHeadingLevel(node);
297
+ elements.push({
298
+ node,
299
+ type: "heading",
300
+ role: `h${level}`,
301
+ label: findTextContent(node) || node.name,
302
+ headingLevel: level,
303
+ });
304
+ continue;
305
+ }
306
+
307
+ // Landmark check
308
+ if (requestedTypes.includes("landmark")) {
309
+ const lm = inferLandmark(node);
310
+ if (lm) {
311
+ elements.push({
312
+ node,
313
+ type: "landmark",
314
+ role: lm.role,
315
+ label: lm.label,
316
+ landmarkElement: inferLandmarkElement(lm.role),
317
+ });
318
+ }
319
+ }
320
+
321
+ // Interactive element check
322
+ if (isInteractive(node)) {
323
+ const role = inferRole(node) || (node.type === "TEXT" ? "link" : "button");
324
+ const annotationType = roleToAnnotationType(role);
325
+
326
+ if (annotationType && requestedTypes.includes(annotationType)) {
327
+ const label =
328
+ findTextContent(node) || cleanNodeName(node.name) || node.name;
329
+ elements.push({
330
+ node,
331
+ type: annotationType,
332
+ role,
333
+ label,
334
+ inputType:
335
+ annotationType === "input" ? inferInputType(node.name) : undefined,
336
+ });
337
+ }
338
+
339
+ // Focus order and reading order include ALL interactive elements
340
+ if (requestedTypes.includes("focus-order")) {
341
+ const label =
342
+ findTextContent(node) || cleanNodeName(node.name) || node.name;
343
+ if (!annotationType || !requestedTypes.includes(annotationType)) {
344
+ elements.push({
345
+ node,
346
+ type: "focus-order",
347
+ role: role,
348
+ label,
349
+ });
350
+ }
351
+ }
352
+ }
353
+
354
+ // Reading order includes all visible text + interactive elements
355
+ if (
356
+ requestedTypes.includes("reading-order") &&
357
+ node.type === "TEXT" &&
358
+ node.characters &&
359
+ node.characters.trim().length > 0
360
+ ) {
361
+ elements.push({
362
+ node,
363
+ type: "reading-order",
364
+ role: isHeading(node) ? `h${estimateHeadingLevel(node)}` : "text",
365
+ label: node.characters.trim(),
366
+ });
367
+ }
368
+ }
369
+
370
+ // Deduplicate parent-child overlaps
371
+ const deduped = deduplicateElements(elements);
372
+
373
+ // Sort by visual position (top-to-bottom, left-to-right)
374
+ deduped.sort((a, b) => {
375
+ const ay = a.node.absoluteBoundingBox!.y;
376
+ const by = b.node.absoluteBoundingBox!.y;
377
+ if (Math.abs(ay - by) <= rowThreshold) {
378
+ return a.node.absoluteBoundingBox!.x - b.node.absoluteBoundingBox!.x;
379
+ }
380
+ return ay - by;
381
+ });
382
+
383
+ return deduped;
384
+ }
385
+
386
+ function flattenAll(node: FigmaNode, acc: FigmaNode[]): void {
387
+ acc.push(node);
388
+ if (node.children) {
389
+ for (const child of node.children) flattenAll(child, acc);
390
+ }
391
+ }
392
+
393
+ function inferLandmarkElement(role: string): string {
394
+ switch (role) {
395
+ case "navigation":
396
+ return "<nav>";
397
+ case "banner":
398
+ return "<header>";
399
+ case "contentinfo":
400
+ return "<footer>";
401
+ case "complementary":
402
+ return "<aside>";
403
+ case "main":
404
+ return "<main>";
405
+ case "form":
406
+ return "<form>";
407
+ case "search":
408
+ return "<search>";
409
+ default:
410
+ return "<div>";
411
+ }
412
+ }
413
+
414
+ // ─── Build ARIA note for table ────────────────────────────────────────────
415
+
416
+ function buildAriaNote(el: AnnotatableElement): string {
417
+ const role = el.role;
418
+ const label = el.label;
419
+
420
+ switch (el.type) {
421
+ case "focus-order":
422
+ case "button":
423
+ if (role === "button") return `role="button"`;
424
+ if (role === "link") return `role="link"`;
425
+ if (role === "tab") return `role="tab"`;
426
+ return `aria-label="${label}"`;
427
+
428
+ case "input": {
429
+ const inputType = el.inputType || "text";
430
+ if (inputType === "checkbox") return `role="checkbox"`;
431
+ if (inputType === "radio") return `role="radio"`;
432
+ return `aria-label="${label}"`;
433
+ }
434
+
435
+ case "link":
436
+ return `role="link"`;
437
+
438
+ case "heading":
439
+ return `aria-level="${el.headingLevel || 2}"`;
440
+
441
+ case "landmark":
442
+ return `role="${role}"`;
443
+
444
+ case "reading-order":
445
+ return role.startsWith("h") ? `aria-level="${role.slice(1)}"` : "";
446
+
447
+ default:
448
+ return `aria-label="${label}"`;
449
+ }
450
+ }
451
+
452
+ // ─── Build Implementation Notes ───────────────────────────────────────────
453
+
454
+ function buildImplementationNotes(elements: AnnotatableElement[]): string[] {
455
+ const notes: string[] = [];
456
+ const count = elements.length;
457
+ const roles = new Set(elements.map((e) => e.role));
458
+
459
+ notes.push(
460
+ "Focus ring must be visible (2px solid, 3:1 contrast ratio minimum)"
461
+ );
462
+ notes.push(
463
+ `Tab moves forward through items 1\u2192${count}; Shift+Tab moves backward`
464
+ );
465
+
466
+ if (roles.has("checkbox")) {
467
+ notes.push("Checkboxes toggle with Space key");
468
+ }
469
+
470
+ if (roles.has("button")) {
471
+ const buttonIdxs = elements
472
+ .map((e, i) => (e.role === "button" ? i + 1 : -1))
473
+ .filter((i) => i > 0);
474
+ if (buttonIdxs.length > 0) {
475
+ notes.push(
476
+ `Buttons (#${buttonIdxs.join(", #")}) activate with Enter or Space`
477
+ );
478
+ }
479
+ }
480
+
481
+ if (roles.has("link")) {
482
+ const linkIdxs = elements
483
+ .map((e, i) => (e.role === "link" ? i + 1 : -1))
484
+ .filter((i) => i > 0);
485
+ if (linkIdxs.length > 0) {
486
+ notes.push(`Links (#${linkIdxs.join(", #")}) activate with Enter`);
487
+ }
488
+ }
489
+
490
+ if (roles.has("textbox") || roles.has("searchbox")) {
491
+ notes.push(
492
+ "Text inputs are focusable via Tab; ensure visible labels or aria-label"
493
+ );
494
+ }
495
+
496
+ if (roles.has("radio")) {
497
+ notes.push("Radio buttons navigate within group using Arrow keys");
498
+ }
499
+
500
+ if (roles.has("spinbutton")) {
501
+ notes.push("Spinbuttons adjust with Arrow Up/Down; announce current value");
502
+ }
503
+
504
+ if (roles.has("switch")) {
505
+ notes.push("Toggle switches activate with Space key");
506
+ }
507
+
508
+ if (roles.has("listbox")) {
509
+ notes.push("Dropdowns/listboxes navigate options with Arrow keys");
510
+ }
511
+
512
+ if (roles.has("tab")) {
513
+ notes.push("Tabs switch with Arrow Left/Right; show associated panel");
514
+ }
515
+
516
+ if (count > 5) {
517
+ notes.push("Skip navigation link recommended before item #1");
518
+ }
519
+
520
+ return notes;
521
+ }
522
+
523
+ // ─── Keyboard support helper ─────────────────────────────────────────────
524
+
525
+ function getKeyboardSupport(role: string): string {
526
+ switch (role) {
527
+ case "button":
528
+ return "Enter or Space to activate";
529
+ case "link":
530
+ return "Enter to follow link";
531
+ case "textbox":
532
+ case "searchbox":
533
+ return "Focusable via Tab; editable";
534
+ case "checkbox":
535
+ return "Space to toggle";
536
+ case "radio":
537
+ return "Arrow keys to move within group";
538
+ case "switch":
539
+ return "Space to toggle";
540
+ case "listbox":
541
+ return "Arrow keys to navigate options";
542
+ case "slider":
543
+ return "Arrow keys to adjust value";
544
+ case "spinbutton":
545
+ return "Arrow Up/Down to adjust";
546
+ case "tab":
547
+ return "Arrow Left/Right to switch tabs";
548
+ default:
549
+ return "Focusable via Tab";
550
+ }
551
+ }
552
+
553
+ // ─── Main Handler ──────────────────────────────────────────────────────────
554
+
555
+ export async function a11yAnnotateHandler(
556
+ args: A11yAnnotateArgs
557
+ ): Promise<A11yAnnotateResult> {
558
+ const {
559
+ nodeId,
560
+ annotationType,
561
+ } = args;
562
+
563
+ const bridge = await getBridge();
564
+
565
+ // 1. Retrieve node tree
566
+ const treeScript = `
567
+ function serialize(n, depth) {
568
+ if (depth > 12) return null;
569
+ var fills = [];
570
+ try {
571
+ var rawFills = n.fills || [];
572
+ for (var i = 0; i < rawFills.length; i++) {
573
+ var f = rawFills[i];
574
+ fills.push({
575
+ type: f.type,
576
+ color: f.color ? { r: f.color.r, g: f.color.g, b: f.color.b, a: f.color.a } : null,
577
+ opacity: f.opacity || 1,
578
+ });
579
+ }
580
+ } catch(e) {}
581
+ var kids = [];
582
+ var rawChildren = n.children || [];
583
+ for (var j = 0; j < rawChildren.length; j++) {
584
+ var child = serialize(rawChildren[j], depth + 1);
585
+ if (child) kids.push(child);
586
+ }
587
+ var strokes = [];
588
+ try {
589
+ var rawStrokes = n.strokes || [];
590
+ for (var s = 0; s < rawStrokes.length; s++) {
591
+ var st = rawStrokes[s];
592
+ strokes.push({
593
+ type: st.type,
594
+ color: st.color ? { r: st.color.r, g: st.color.g, b: st.color.b, a: st.color.a } : null,
595
+ opacity: st.opacity || 1,
596
+ });
597
+ }
598
+ } catch(e) {}
599
+ return {
600
+ id: n.id,
601
+ name: n.name,
602
+ type: n.type,
603
+ width: n.width || null,
604
+ height: n.height || null,
605
+ absoluteBoundingBox: n.absoluteBoundingBox || null,
606
+ characters: n.characters || null,
607
+ style: n.style || null,
608
+ fills: fills,
609
+ strokes: strokes,
610
+ children: kids,
611
+ };
612
+ }
613
+ var root = await figma.getNodeByIdAsync(${JSON.stringify(nodeId)});
614
+ if (!root) throw new Error("Node not found: " + ${JSON.stringify(nodeId)});
615
+ return serialize(root, 0);
616
+ `;
617
+
618
+ const treeResult = await bridge.execute(treeScript);
619
+ if (!treeResult.success) {
620
+ throw new Error(
621
+ `a11yAnnotateHandler: could not retrieve node tree — ${treeResult.error}`
622
+ );
623
+ }
624
+
625
+ const rootNode = treeResult.result as FigmaNode;
626
+
627
+ // Set module-level root for navigation context lookups
628
+ _annotateRootNode = rootNode;
629
+
630
+ // 2. Determine which annotation types to generate
631
+ const ALL_TYPES: AnnotationTypeKey[] = [
632
+ "focus-order",
633
+ "reading-order",
634
+ "input",
635
+ "landmark",
636
+ "heading",
637
+ "link",
638
+ "button",
639
+ ];
640
+ const requestedTypes: AnnotationTypeKey[] =
641
+ annotationType === "all" ? ALL_TYPES : [annotationType];
642
+
643
+ // For focus-order, we want ALL interactive elements regardless of sub-type
644
+ if (annotationType === "focus-order" && !requestedTypes.includes("input")) {
645
+ requestedTypes.push("input", "button", "link");
646
+ }
647
+
648
+ // 3. Collect annotatable elements
649
+ const allElements = collectAnnotatableElements(rootNode, requestedTypes);
650
+
651
+ // For focus-order: merge all interactive into a single focus-order list
652
+ let elements: AnnotatableElement[];
653
+ if (annotationType === "focus-order") {
654
+ // Re-collect with just focus-order to get all interactive elements in one list
655
+ elements = collectFocusOrderElements(rootNode);
656
+ } else {
657
+ elements = allElements;
658
+ }
659
+
660
+ if (elements.length === 0) {
661
+ return {
662
+ pageId: "",
663
+ annotationCount: 0,
664
+ types: requestedTypes,
665
+ summary: "No annotatable elements found in the selected frame.",
666
+ };
667
+ }
668
+
669
+ // 4. Build markers, table rows, and implementation notes
670
+ const markers: MarkerDef[] = [];
671
+ const tableRows: TabOrderRow[] = [];
672
+
673
+ for (let i = 0; i < elements.length; i++) {
674
+ const el = elements[i];
675
+ const bbox = el.node.absoluteBoundingBox!;
676
+ const num = i + 1;
677
+
678
+ markers.push({
679
+ number: num,
680
+ elementX: bbox.x,
681
+ elementY: bbox.y,
682
+ elementW: bbox.width,
683
+ elementH: bbox.height,
684
+ });
685
+
686
+ // Determine the display role for the table
687
+ let displayRole = el.role;
688
+ if (el.type === "input" && el.inputType) {
689
+ displayRole = el.inputType;
690
+ }
691
+
692
+ tableRows.push({
693
+ number: num,
694
+ element: truncateLabel(el.label, 40),
695
+ role: displayRole,
696
+ ariaNote: buildAriaNote(el),
697
+ });
698
+ }
699
+
700
+ const implNotes = buildImplementationNotes(elements);
701
+
702
+ // 5. Render the annotation page
703
+ const { pageId } = await renderAnnotationPage(bridge, {
704
+ sourceNodeId: nodeId,
705
+ frameName: rootNode.name,
706
+ markers,
707
+ tableRows,
708
+ implNotes,
709
+ annotationType: annotationType,
710
+ });
711
+
712
+ // 6. Log action
713
+ const renderedTypes = annotationType === "all"
714
+ ? ALL_TYPES.map(t => `${t}`)
715
+ : [annotationType];
716
+
717
+ await decisionLog.log({
718
+ tool: "figma_a11y_annotate",
719
+ nodeIds: [nodeId],
720
+ rationale: `Created Keyboard Focus Order page for "${rootNode.name}" with ${elements.length} annotated elements.`,
721
+ reversible: false,
722
+ metadata: {
723
+ pageId,
724
+ annotationCount: elements.length,
725
+ types: renderedTypes,
726
+ },
727
+ });
728
+
729
+ return {
730
+ pageId,
731
+ annotationCount: elements.length,
732
+ types: renderedTypes,
733
+ summary: `Created "Keyboard Focus Order — ${rootNode.name}" page with ${elements.length} numbered markers and Tab Order Sequence table.`,
734
+ };
735
+ }
736
+
737
+ // ─── Focus Order: collect all interactive elements in tab order ────────────
738
+
739
+ function collectFocusOrderElements(root: FigmaNode): AnnotatableElement[] {
740
+ _annotateRootNode = root;
741
+ const rowThreshold = computeRowThreshold(root);
742
+ const elements: AnnotatableElement[] = [];
743
+ const allNodes: FigmaNode[] = [];
744
+ flattenAll(root, allNodes);
745
+
746
+ for (const node of allNodes) {
747
+ if (!node.absoluteBoundingBox) continue;
748
+ if (!isInteractive(node)) continue;
749
+
750
+ const role = inferRole(node) || (node.type === "TEXT" ? "link" : "button");
751
+ const label = findTextContent(node) || cleanNodeName(node.name) || node.name;
752
+ const annotationType = roleToAnnotationType(role);
753
+
754
+ elements.push({
755
+ node,
756
+ type: annotationType || "focus-order",
757
+ role,
758
+ label,
759
+ inputType: annotationType === "input" ? inferInputType(node.name) : undefined,
760
+ });
761
+ }
762
+
763
+ // Deduplicate parent-child overlaps
764
+ const deduped = deduplicateElements(elements);
765
+
766
+ // Sort by visual position (top-to-bottom, left-to-right)
767
+ deduped.sort((a, b) => {
768
+ const ay = a.node.absoluteBoundingBox!.y;
769
+ const by = b.node.absoluteBoundingBox!.y;
770
+ if (Math.abs(ay - by) <= rowThreshold) {
771
+ return a.node.absoluteBoundingBox!.x - b.node.absoluteBoundingBox!.x;
772
+ }
773
+ return ay - by;
774
+ });
775
+
776
+ return deduped;
777
+ }
778
+
779
+ function truncateLabel(text: string, maxLen: number): string {
780
+ if (text.length <= maxLen) return text;
781
+ return text.slice(0, maxLen - 3) + "...";
782
+ }