@sarjallab09/figma-intelligence 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. package/README.md +69 -36
  2. package/dist/bin/cli.js +2 -0
  3. package/dist/design-bridge/bridge.js +2 -0
  4. package/dist/figma-bridge-plugin/bridge-relay.js +2 -0
  5. package/dist/figma-bridge-plugin/code.js +1 -0
  6. package/{figma-bridge-plugin → dist/figma-bridge-plugin}/package-lock.json +0 -3
  7. package/dist/figma-bridge-plugin/ui.html +4970 -0
  8. package/dist/figma-intelligence-layer/dist/index.js +2 -0
  9. package/dist/scripts/clean-existing-chunks.js +2 -0
  10. package/dist/scripts/connect-ai-tool.js +2 -0
  11. package/dist/scripts/convert-hub-pdfs.js +2 -0
  12. package/dist/scripts/figma-mcp-status.js +2 -0
  13. package/dist/scripts/register-codex-mcp.js +2 -0
  14. package/dist/scripts/test-copilot-chat.js +2 -0
  15. package/package.json +11 -8
  16. package/bin/cli.js +0 -859
  17. package/design-bridge/bridge.js +0 -196
  18. package/design-bridge/lib/assets.js +0 -367
  19. package/design-bridge/lib/prompt.js +0 -85
  20. package/design-bridge/lib/server.js +0 -66
  21. package/design-bridge/lib/stitch.js +0 -37
  22. package/design-bridge/lib/tokens.js +0 -82
  23. package/design-bridge/package-lock.json +0 -579
  24. package/figma-bridge-plugin/README.md +0 -97
  25. package/figma-bridge-plugin/anthropic-chat-runner.js +0 -192
  26. package/figma-bridge-plugin/bridge-relay.js +0 -2505
  27. package/figma-bridge-plugin/chat-runner.js +0 -485
  28. package/figma-bridge-plugin/code.js +0 -1534
  29. package/figma-bridge-plugin/codex-runner.js +0 -505
  30. package/figma-bridge-plugin/component-schemas.js +0 -110
  31. package/figma-bridge-plugin/content-context.js +0 -869
  32. package/figma-bridge-plugin/create-button.js +0 -216
  33. package/figma-bridge-plugin/gemini-cli-runner.js +0 -291
  34. package/figma-bridge-plugin/gemini-runner.js +0 -187
  35. package/figma-bridge-plugin/html-to-figma.js +0 -927
  36. package/figma-bridge-plugin/knowledge-hub/.gitkeep +0 -0
  37. package/figma-bridge-plugin/knowledge-hub/uspec-references/anatomy-spec.md +0 -159
  38. package/figma-bridge-plugin/knowledge-hub/uspec-references/api-spec.md +0 -162
  39. package/figma-bridge-plugin/knowledge-hub/uspec-references/color-spec.md +0 -148
  40. package/figma-bridge-plugin/knowledge-hub/uspec-references/full-spec-template.md +0 -314
  41. package/figma-bridge-plugin/knowledge-hub/uspec-references/property-spec.md +0 -175
  42. package/figma-bridge-plugin/knowledge-hub/uspec-references/screen-reader-spec.md +0 -180
  43. package/figma-bridge-plugin/knowledge-hub/uspec-references/structure-spec.md +0 -165
  44. package/figma-bridge-plugin/perplexity-runner.js +0 -188
  45. package/figma-bridge-plugin/references/SKILL.md +0 -178
  46. package/figma-bridge-plugin/references/anatomy-spec.md +0 -159
  47. package/figma-bridge-plugin/references/api-spec.md +0 -162
  48. package/figma-bridge-plugin/references/color-spec.md +0 -148
  49. package/figma-bridge-plugin/references/full-spec-template.md +0 -314
  50. package/figma-bridge-plugin/references/property-spec.md +0 -175
  51. package/figma-bridge-plugin/references/screen-reader-spec.md +0 -180
  52. package/figma-bridge-plugin/references/structure-spec.md +0 -165
  53. package/figma-bridge-plugin/shared-prompt-config.js +0 -645
  54. package/figma-bridge-plugin/spec-helpers/build-table.js +0 -269
  55. package/figma-bridge-plugin/spec-helpers/classify-elements.js +0 -189
  56. package/figma-bridge-plugin/spec-helpers/index.js +0 -35
  57. package/figma-bridge-plugin/spec-helpers/parse-figma-link.js +0 -49
  58. package/figma-bridge-plugin/spec-helpers/position-markers.js +0 -158
  59. package/figma-bridge-plugin/stitch-auth.js +0 -322
  60. package/figma-bridge-plugin/stitch-runner.js +0 -1427
  61. package/figma-bridge-plugin/token-resolver.js +0 -107
  62. package/figma-bridge-plugin/ui.html +0 -4542
  63. package/figma-intelligence-layer/.env.example +0 -39
  64. package/figma-intelligence-layer/docs/local-image-generation.md +0 -60
  65. package/figma-intelligence-layer/examples/comfyui-workflow-template.example.json +0 -101
  66. package/figma-intelligence-layer/jest.config.js +0 -14
  67. package/figma-intelligence-layer/mcp-config.json +0 -19
  68. package/figma-intelligence-layer/package-lock.json +0 -5892
  69. package/figma-intelligence-layer/scripts/setup-comfyui-local.sh +0 -67
  70. package/figma-intelligence-layer/scripts/start-comfyui.sh +0 -33
  71. package/figma-intelligence-layer/src/index.ts +0 -2233
  72. package/figma-intelligence-layer/src/shared/auto-layout-validator.ts +0 -404
  73. package/figma-intelligence-layer/src/shared/cache.ts +0 -187
  74. package/figma-intelligence-layer/src/shared/color-operations.ts +0 -533
  75. package/figma-intelligence-layer/src/shared/color-utils.ts +0 -138
  76. package/figma-intelligence-layer/src/shared/component-script-builder.ts +0 -413
  77. package/figma-intelligence-layer/src/shared/component-templates.ts +0 -2767
  78. package/figma-intelligence-layer/src/shared/concept-taxonomy.ts +0 -694
  79. package/figma-intelligence-layer/src/shared/decision-log.ts +0 -128
  80. package/figma-intelligence-layer/src/shared/design-system-context.ts +0 -568
  81. package/figma-intelligence-layer/src/shared/design-system-intelligence.ts +0 -131
  82. package/figma-intelligence-layer/src/shared/design-system-matcher.ts +0 -184
  83. package/figma-intelligence-layer/src/shared/design-system-normalizers.ts +0 -196
  84. package/figma-intelligence-layer/src/shared/design-system-tokens.ts +0 -295
  85. package/figma-intelligence-layer/src/shared/dtcg-validator.ts +0 -530
  86. package/figma-intelligence-layer/src/shared/enrichment-pipeline.ts +0 -671
  87. package/figma-intelligence-layer/src/shared/figma-bridge.ts +0 -1418
  88. package/figma-intelligence-layer/src/shared/font-config.ts +0 -126
  89. package/figma-intelligence-layer/src/shared/icon-catalog.ts +0 -360
  90. package/figma-intelligence-layer/src/shared/icon-fetch.ts +0 -80
  91. package/figma-intelligence-layer/src/shared/prototype-script-builder.ts +0 -162
  92. package/figma-intelligence-layer/src/shared/response-compression.ts +0 -440
  93. package/figma-intelligence-layer/src/shared/semantic-token-catalog.ts +0 -324
  94. package/figma-intelligence-layer/src/shared/token-binder.ts +0 -505
  95. package/figma-intelligence-layer/src/shared/token-math.ts +0 -427
  96. package/figma-intelligence-layer/src/shared/token-naming.ts +0 -468
  97. package/figma-intelligence-layer/src/shared/token-utils.ts +0 -420
  98. package/figma-intelligence-layer/src/shared/types.ts +0 -346
  99. package/figma-intelligence-layer/src/shared/typography-presets.ts +0 -94
  100. package/figma-intelligence-layer/src/shared/unsplash.ts +0 -165
  101. package/figma-intelligence-layer/src/shared/vision-client.ts +0 -607
  102. package/figma-intelligence-layer/src/shared/vision-provider-anthropic.ts +0 -334
  103. package/figma-intelligence-layer/src/shared/vision-provider-openai.ts +0 -446
  104. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotate-handler.ts +0 -782
  105. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotate-renderer.ts +0 -496
  106. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotation-kit.ts +0 -230
  107. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/colorblind-sim.ts +0 -66
  108. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/index.ts +0 -810
  109. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-analyzer.ts +0 -1191
  110. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-figma-page.ts +0 -1346
  111. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-handler.ts +0 -148
  112. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/vpat-figma-page.ts +0 -499
  113. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/vpat-report.ts +0 -910
  114. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/wcag-checker.ts +0 -989
  115. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/wcag-criteria.ts +0 -1160
  116. package/figma-intelligence-layer/src/tools/phase1-vision/design-from-ref/index.ts +0 -424
  117. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/component-recognizer.ts +0 -38
  118. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/ds-matcher.ts +0 -111
  119. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/font-matcher.ts +0 -114
  120. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/icon-resolver.ts +0 -103
  121. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/index.ts +0 -1060
  122. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/layout-segmenter.ts +0 -18
  123. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/token-inferencer.ts +0 -39
  124. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/vision-pipeline.ts +0 -58
  125. package/figma-intelligence-layer/src/tools/phase1-vision/sketch-to-design/index.ts +0 -298
  126. package/figma-intelligence-layer/src/tools/phase1-vision/visual-audit/index.ts +0 -197
  127. package/figma-intelligence-layer/src/tools/phase2-accuracy/component-audit/index.ts +0 -494
  128. package/figma-intelligence-layer/src/tools/phase2-accuracy/intent-translator/index.ts +0 -356
  129. package/figma-intelligence-layer/src/tools/phase2-accuracy/layout-intelligence/container-patterns.ts +0 -123
  130. package/figma-intelligence-layer/src/tools/phase2-accuracy/layout-intelligence/index.ts +0 -663
  131. package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/built-in-rules.yaml +0 -56
  132. package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/index.ts +0 -614
  133. package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/rule-engine.ts +0 -113
  134. package/figma-intelligence-layer/src/tools/phase2-accuracy/theme-generator/color-theory.ts +0 -178
  135. package/figma-intelligence-layer/src/tools/phase2-accuracy/theme-generator/index.ts +0 -470
  136. package/figma-intelligence-layer/src/tools/phase2-accuracy/variant-expander/index.ts +0 -429
  137. package/figma-intelligence-layer/src/tools/phase2-accuracy/variant-expander/token-override-maps.ts +0 -226
  138. package/figma-intelligence-layer/src/tools/phase3-generation/ai-image-insert/index.ts +0 -535
  139. package/figma-intelligence-layer/src/tools/phase3-generation/component-archaeologist/index.ts +0 -660
  140. package/figma-intelligence-layer/src/tools/phase3-generation/component-archaeologist/pattern-fingerprints.ts +0 -209
  141. package/figma-intelligence-layer/src/tools/phase3-generation/composition-builder/index.ts +0 -540
  142. package/figma-intelligence-layer/src/tools/phase3-generation/figma-animated-build.ts +0 -391
  143. package/figma-intelligence-layer/src/tools/phase3-generation/page-architect/index.ts +0 -2019
  144. package/figma-intelligence-layer/src/tools/phase3-generation/page-architect/screen-templates.ts +0 -131
  145. package/figma-intelligence-layer/src/tools/phase3-generation/prototype-map/index.ts +0 -381
  146. package/figma-intelligence-layer/src/tools/phase3-generation/prototype-wire/index.ts +0 -565
  147. package/figma-intelligence-layer/src/tools/phase3-generation/swarm-build/index.ts +0 -764
  148. package/figma-intelligence-layer/src/tools/phase3-generation/system-drift/index.ts +0 -535
  149. package/figma-intelligence-layer/src/tools/phase3-generation/unsplash-search/index.ts +0 -84
  150. package/figma-intelligence-layer/src/tools/phase3-generation/url-to-frame/index.ts +0 -401
  151. package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/css-animations.ts +0 -68
  152. package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/framer-motion.ts +0 -78
  153. package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/swift-animations.ts +0 -93
  154. package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/index.ts +0 -596
  155. package/figma-intelligence-layer/src/tools/phase4-sync/ci-check/index.ts +0 -462
  156. package/figma-intelligence-layer/src/tools/phase4-sync/export-tokens/index.ts +0 -1470
  157. package/figma-intelligence-layer/src/tools/phase4-sync/generate-component-code/index.ts +0 -829
  158. package/figma-intelligence-layer/src/tools/phase4-sync/handoff-spec/index.ts +0 -702
  159. package/figma-intelligence-layer/src/tools/phase4-sync/icon-library-sync/index.ts +0 -483
  160. package/figma-intelligence-layer/src/tools/phase4-sync/sync-from-code/index.ts +0 -501
  161. package/figma-intelligence-layer/src/tools/phase4-sync/sync-from-code/storybook-parser.ts +0 -106
  162. package/figma-intelligence-layer/src/tools/phase4-sync/watch-docs/index.ts +0 -676
  163. package/figma-intelligence-layer/src/tools/phase4-sync/webhook-listener/index.ts +0 -560
  164. package/figma-intelligence-layer/src/tools/phase5-governance/apg-doc/index.ts +0 -1043
  165. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/component-detection.ts +0 -620
  166. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/anatomy.ts +0 -331
  167. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/color-tokens.ts +0 -77
  168. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/properties.ts +0 -54
  169. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/snapshot.ts +0 -287
  170. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/spacing.ts +0 -71
  171. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/states.ts +0 -43
  172. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/typography.ts +0 -71
  173. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/index.ts +0 -221
  174. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/_default.ts +0 -166
  175. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/accordion.ts +0 -232
  176. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/alert.ts +0 -234
  177. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/avatar-group.ts +0 -270
  178. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/avatar.ts +0 -249
  179. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/badge.ts +0 -231
  180. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/banner.ts +0 -293
  181. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/breadcrumb.ts +0 -240
  182. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/button.ts +0 -243
  183. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/calendar.ts +0 -307
  184. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/card.ts +0 -143
  185. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/checkbox.ts +0 -227
  186. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/chip.ts +0 -233
  187. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/combobox.ts +0 -282
  188. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/datepicker.ts +0 -276
  189. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/divider.ts +0 -223
  190. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/drawer.ts +0 -255
  191. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/dropdown-menu.ts +0 -289
  192. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/empty-state.ts +0 -261
  193. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/file-uploader.ts +0 -290
  194. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/form.ts +0 -265
  195. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/grid.ts +0 -238
  196. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/icon.ts +0 -255
  197. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/index.ts +0 -128
  198. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/inline-edit.ts +0 -286
  199. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/inline-message.ts +0 -255
  200. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/input.ts +0 -330
  201. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/link.ts +0 -247
  202. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/list.ts +0 -250
  203. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/menu.ts +0 -247
  204. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/modal.ts +0 -144
  205. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/navbar.ts +0 -264
  206. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/navigation.ts +0 -251
  207. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/number-input.ts +0 -261
  208. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/pagination.ts +0 -248
  209. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/popover.ts +0 -270
  210. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/progress.ts +0 -251
  211. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/radio.ts +0 -142
  212. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/range-slider.ts +0 -282
  213. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/rating.ts +0 -250
  214. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/search.ts +0 -258
  215. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/segmented-control.ts +0 -265
  216. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/select.ts +0 -319
  217. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/skeleton.ts +0 -256
  218. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/slider.ts +0 -232
  219. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/spinner.ts +0 -239
  220. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/status-dot.ts +0 -252
  221. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/stepper.ts +0 -270
  222. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/table.ts +0 -244
  223. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tabs.ts +0 -143
  224. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tag.ts +0 -243
  225. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/textarea.ts +0 -259
  226. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/time-picker.ts +0 -293
  227. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toast.ts +0 -144
  228. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toggle.ts +0 -289
  229. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toolbar.ts +0 -267
  230. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tooltip.ts +0 -232
  231. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/treeview.ts +0 -257
  232. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/typography.ts +0 -319
  233. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/legacy-compat.ts +0 -121
  234. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/anatomy-diagram.ts +0 -430
  235. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/figma-page.ts +0 -312
  236. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/json.ts +0 -129
  237. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/markdown.ts +0 -78
  238. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/visual-doc.ts +0 -2333
  239. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/accessibility.ts +0 -100
  240. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/anatomy.ts +0 -32
  241. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/color-tokens.ts +0 -59
  242. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/content-guidance.ts +0 -18
  243. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/design-tokens.ts +0 -53
  244. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/interaction-rules.ts +0 -19
  245. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/overview.ts +0 -91
  246. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/properties-api.ts +0 -71
  247. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/qa-criteria.ts +0 -19
  248. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/related-components.ts +0 -110
  249. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/responsive.ts +0 -19
  250. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/size-specs.ts +0 -67
  251. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/spacing-structure.ts +0 -58
  252. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/state-specs.ts +0 -79
  253. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/states.ts +0 -50
  254. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/type-hierarchy.ts +0 -33
  255. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/typography.ts +0 -55
  256. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/usage-guidelines.ts +0 -73
  257. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/variants.ts +0 -81
  258. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/types.ts +0 -409
  259. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/index.ts +0 -198
  260. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/renderer.ts +0 -701
  261. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/types.ts +0 -88
  262. package/figma-intelligence-layer/src/tools/phase5-governance/decision-log/index.ts +0 -135
  263. package/figma-intelligence-layer/src/tools/phase5-governance/design-decision-log/index.ts +0 -491
  264. package/figma-intelligence-layer/src/tools/phase5-governance/ds-primitives/index.ts +0 -416
  265. package/figma-intelligence-layer/src/tools/phase5-governance/ds-scaffolder/index.ts +0 -722
  266. package/figma-intelligence-layer/src/tools/phase5-governance/ds-variables/index.ts +0 -449
  267. package/figma-intelligence-layer/src/tools/phase5-governance/health-report/index.ts +0 -393
  268. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/index.ts +0 -406
  269. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/figma-page.ts +0 -292
  270. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/json.ts +0 -24
  271. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/markdown.ts +0 -172
  272. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/naming-guide.ts +0 -409
  273. package/figma-intelligence-layer/src/tools/phase5-governance/token-analytics/index.ts +0 -594
  274. package/figma-intelligence-layer/src/tools/phase5-governance/token-docs/index.ts +0 -710
  275. package/figma-intelligence-layer/src/tools/phase5-governance/token-migrate/index.ts +0 -458
  276. package/figma-intelligence-layer/src/tools/phase5-governance/token-naming/index.ts +0 -134
  277. package/figma-intelligence-layer/tests/apg-doc.test.ts +0 -101
  278. package/figma-intelligence-layer/tests/design-system-context.test.ts +0 -152
  279. package/figma-intelligence-layer/tests/design-system-matcher.test.ts +0 -144
  280. package/figma-intelligence-layer/tests/figma-bridge.test.ts +0 -83
  281. package/figma-intelligence-layer/tests/generate-image-and-insert.test.ts +0 -56
  282. package/figma-intelligence-layer/tests/screen-cloner-regression.test.ts +0 -69
  283. package/figma-intelligence-layer/tests/smoke.test.ts +0 -174
  284. package/figma-intelligence-layer/tests/spec-generator.test.ts +0 -127
  285. package/figma-intelligence-layer/tests/token-migrate.test.ts +0 -21
  286. package/figma-intelligence-layer/tests/token-naming.test.ts +0 -30
  287. package/figma-intelligence-layer/tsconfig.json +0 -19
  288. package/scripts/clean-existing-chunks.js +0 -179
  289. package/scripts/connect-ai-tool.js +0 -490
  290. package/scripts/convert-hub-pdfs.js +0 -425
  291. package/scripts/figma-mcp-status.js +0 -349
  292. package/scripts/register-codex-mcp.js +0 -96
  293. /package/{design-bridge → dist/design-bridge}/.env.example +0 -0
  294. /package/{design-bridge → dist/design-bridge}/package.json +0 -0
  295. /package/{figma-bridge-plugin → dist/figma-bridge-plugin}/manifest.json +0 -0
  296. /package/{figma-bridge-plugin → dist/figma-bridge-plugin}/package.json +0 -0
  297. /package/{figma-intelligence-layer → dist/figma-intelligence-layer}/package.json +0 -0
@@ -1,1191 +0,0 @@
1
- /**
2
- * Keyboard & Screen Reader Order Analyzer
3
- * Pure analysis engine that extracts interactive elements from a Figma node tree,
4
- * infers ARIA roles/labels, computes keyboard tab order and screen reader reading
5
- * order, and produces all 10 annotation sections as structured data.
6
- */
7
-
8
- import { FigmaNode, WCAGIssue } from "../../../shared/types.js";
9
- import {
10
- checkMeaningfulSequence,
11
- checkFocusOrder,
12
- checkNameRoleValue,
13
- checkLabelsOrInstructions,
14
- extractSolidColor,
15
- findParentBgColor,
16
- checkTextContrast,
17
- checkTouchTarget,
18
- checkTargetSizeMinimum,
19
- } from "./wcag-checker.js";
20
-
21
- // ─── Types ──────────────────────────────────────────────────────────────────
22
-
23
- export interface TabOrderEntry {
24
- order: number;
25
- elementDescription: string;
26
- role: string;
27
- label: string;
28
- notes: string;
29
- nodeId: string;
30
- }
31
-
32
- export interface ReadingOrderNode {
33
- landmarkRole?: string;
34
- landmarkLabel?: string;
35
- content: string;
36
- role?: string;
37
- depth: number;
38
- children?: ReadingOrderNode[];
39
- nodeId: string;
40
- }
41
-
42
- export interface FocusAnnouncement {
43
- element: string;
44
- announcement: string;
45
- }
46
-
47
- export interface StateChangeRule {
48
- trigger: string;
49
- behavior: string;
50
- }
51
-
52
- export interface FocusRule {
53
- scenario: string;
54
- rule: string;
55
- }
56
-
57
- export interface AriaReq {
58
- element: string;
59
- attribute: string;
60
- value: string;
61
- }
62
-
63
- export interface KBRule {
64
- component: string;
65
- key: string;
66
- action: string;
67
- }
68
-
69
- export interface Warning {
70
- id: number;
71
- severity: "Critical" | "Warning" | "Info";
72
- title: string;
73
- description: string;
74
- }
75
-
76
- export interface AuditSummaryRow {
77
- severity: string;
78
- count: number;
79
- category: string;
80
- }
81
-
82
- export interface A11yOrderAnalysis {
83
- header: { frameName: string; nodeId: string; date: string; standard: string };
84
- scope: string;
85
- assumptions: string[];
86
- keyboardTabOrder: TabOrderEntry[];
87
- screenReaderReadingOrder: ReadingOrderNode[];
88
- interactionAnnouncements: {
89
- onFocus: FocusAnnouncement[];
90
- stateChanges: StateChangeRule[];
91
- };
92
- focusManagement: FocusRule[];
93
- implementationNotes: {
94
- ariaTable: AriaReq[];
95
- keyboardBehavior: KBRule[];
96
- dosDonts: string[];
97
- };
98
- warnings: Warning[];
99
- auditSummary: AuditSummaryRow[];
100
- }
101
-
102
- // ─── Role Inference ─────────────────────────────────────────────────────────
103
-
104
- const ROLE_PATTERNS: [RegExp, string][] = [
105
- [/\b(button|btn|cta)\b/i, "button"],
106
- [/\b(link|anchor|href)\b/i, "link"],
107
- [/\b(input|field|textbox|text[-_ ]?field)\b/i, "textbox"],
108
- [/\b(checkbox|check[-_ ]?box)\b/i, "checkbox"],
109
- [/\b(radio)\b/i, "radio"],
110
- [/\b(toggle|switch)\b/i, "switch"],
111
- [/\b(select|dropdown|combo[-_ ]?box)\b/i, "listbox"],
112
- [/\b(slider|range)\b/i, "slider"],
113
- [/\b(spinner|stepper|quantity)\b/i, "spinbutton"],
114
- [/\b(tab)\b/i, "tab"],
115
- [/\b(search)\b/i, "searchbox"],
116
- [/\b(menu[-_ ]?item)\b/i, "menuitem"],
117
- [/\b(menu)\b/i, "menu"],
118
- [/\b(modal|dialog)\b/i, "dialog"],
119
- [/\b(image|photo|avatar|thumbnail)\b/i, "img"],
120
- ];
121
-
122
- const LANDMARK_PATTERNS: [RegExp, string, string][] = [
123
- [/\b(nav|navigation)\b/i, "navigation", "Navigation"],
124
- [/\b(header|top[-_ ]?bar|app[-_ ]?bar|navbar)\b/i, "banner", "Site header"],
125
- [/\b(footer|bottom[-_ ]?bar)\b/i, "contentinfo", "Site footer"],
126
- [/\b(sidebar|side[-_ ]?nav|drawer)\b/i, "complementary", "Sidebar"],
127
- [/\b(main|content|body)\b/i, "main", "Main content"],
128
- [/\b(banner|promo|offer|announcement)\b/i, "status", "Announcement"],
129
- [/\b(form)\b/i, "form", "Form"],
130
- [/\b(search)\b/i, "search", "Search"],
131
- ];
132
-
133
- function inferRole(node: FigmaNode): string | null {
134
- const nameLower = node.name.toLowerCase();
135
-
136
- // Check name-based patterns
137
- for (const [pattern, role] of ROLE_PATTERNS) {
138
- if (pattern.test(nameLower)) return role;
139
- }
140
-
141
- // INSTANCE/COMPONENT nodes: only assign a role if the name strongly
142
- // indicates interactivity. Decorative icons (heart, percent, trailing
143
- // icon, etc.) must NOT be promoted to "button" just because they are
144
- // component instances.
145
- return null;
146
- }
147
-
148
- function inferLandmark(node: FigmaNode): { role: string; label: string } | null {
149
- const nameLower = node.name.toLowerCase();
150
- for (const [pattern, role, label] of LANDMARK_PATTERNS) {
151
- if (pattern.test(nameLower)) return { role, label };
152
- }
153
- return null;
154
- }
155
-
156
- // ─── Label Inference ────────────────────────────────────────────────────────
157
-
158
- function hasTextChild(node: FigmaNode): boolean {
159
- if (node.type === "TEXT" && node.characters) return true;
160
- return node.children?.some((c) => hasTextChild(c)) ?? false;
161
- }
162
-
163
- function findTextContent(node: FigmaNode): string {
164
- if (node.type === "TEXT" && node.characters) return node.characters.trim();
165
- if (!node.children) return "";
166
- const texts: string[] = [];
167
- for (const child of node.children) {
168
- const t = findTextContent(child);
169
- if (t) texts.push(t);
170
- }
171
- return texts.join(" ");
172
- }
173
-
174
- function cleanNodeName(name: string): string {
175
- return name
176
- .replace(/^(icon|icn|ic)[-_ /]*/i, "")
177
- .replace(/[-_/]/g, " ")
178
- .replace(/\s+/g, " ")
179
- .trim();
180
- }
181
-
182
- function findParentContext(
183
- nodeId: string,
184
- allNodes: FigmaNode[],
185
- depth: number = 0
186
- ): string | null {
187
- if (depth > 6) return null;
188
- for (const n of allNodes) {
189
- if (n.children?.some((c) => c.id === nodeId)) {
190
- // Look for a text child that could be a product/item name
191
- const nameText = n.children?.find(
192
- (c) =>
193
- c.type === "TEXT" &&
194
- c.characters &&
195
- c.characters.length > 2 &&
196
- c.characters.length < 80
197
- );
198
- if (nameText?.characters) return nameText.characters.trim();
199
- // Walk further up
200
- return findParentContext(n.id, allNodes, depth + 1);
201
- }
202
- if (n.children) {
203
- const found = findParentContext(nodeId, n.children, depth);
204
- if (found) return found;
205
- }
206
- }
207
- return null;
208
- }
209
-
210
- function inferLabel(
211
- node: FigmaNode,
212
- role: string,
213
- allNodes: FigmaNode[]
214
- ): string {
215
- // Text content inside the element
216
- const textContent = findTextContent(node);
217
-
218
- // For elements with text, use it
219
- if (textContent && textContent.length < 80) {
220
- // Enhance with context for actions
221
- if (
222
- role === "button" &&
223
- /^(remove|delete|close|add|minus|plus|decrease|increase)$/i.test(
224
- textContent
225
- )
226
- ) {
227
- const context = findParentContext(node.id, allNodes);
228
- if (context) {
229
- const action = textContent.toLowerCase();
230
- if (/remove|delete/i.test(action)) return `Remove ${context} from cart`;
231
- if (/decrease|minus/i.test(action))
232
- return `Decrease quantity for ${context}`;
233
- if (/increase|plus|add/i.test(action))
234
- return `Increase quantity for ${context}`;
235
- }
236
- }
237
- return textContent;
238
- }
239
-
240
- // Icon-only elements: clean up name
241
- if (role === "button" || role === "link") {
242
- const cleaned = cleanNodeName(node.name);
243
- if (cleaned) {
244
- // Capitalize first letter
245
- return cleaned.charAt(0).toUpperCase() + cleaned.slice(1);
246
- }
247
- }
248
-
249
- // Use node name as fallback
250
- return cleanNodeName(node.name) || node.name;
251
- }
252
-
253
- // ─── Node Collection Helpers ────────────────────────────────────────────────
254
-
255
- function collectAll(node: FigmaNode, acc: FigmaNode[] = []): FigmaNode[] {
256
- acc.push(node);
257
- if (node.children) {
258
- for (const child of node.children) collectAll(child, acc);
259
- }
260
- return acc;
261
- }
262
-
263
- function isInsideNavigation(node: FigmaNode, rootNode: FigmaNode): boolean {
264
- // Walk up parent chain to check if this node is inside a navigation landmark
265
- function findParent(current: FigmaNode, targetId: string): FigmaNode | null {
266
- if (current.children) {
267
- for (const child of current.children) {
268
- if (child.id === targetId) return current;
269
- const found = findParent(child, targetId);
270
- if (found) return found;
271
- }
272
- }
273
- return null;
274
- }
275
-
276
- let parentNode = findParent(rootNode, node.id);
277
- let depth = 0;
278
- while (parentNode && depth < 8) {
279
- if (inferLandmark(parentNode)?.role === "navigation" ||
280
- inferLandmark(parentNode)?.role === "banner") {
281
- return true;
282
- }
283
- const nextParent = findParent(rootNode, parentNode.id);
284
- if (!nextParent || nextParent.id === parentNode.id) break;
285
- parentNode = nextParent;
286
- depth++;
287
- }
288
- return false;
289
- }
290
-
291
- // Cache root node for navigation lookups in isInteractive
292
- let _analysisRootNode: FigmaNode | null = null;
293
-
294
- function isInteractive(node: FigmaNode): boolean {
295
- const role = inferRole(node);
296
- if (role && !["img", "dialog", "menu"].includes(role)) return true;
297
-
298
- // INSTANCE/COMPONENT with explicitly interactive names only.
299
- // DO NOT treat all leaf instances as interactive — many are decorative
300
- // icons (heart, star, percent, trailing icon, etc.).
301
- if (
302
- (node.type === "INSTANCE" || node.type === "COMPONENT") &&
303
- /button|btn|link|cta|click/i.test(node.name)
304
- )
305
- return true;
306
-
307
- // TEXT nodes inside navigation/banner landmarks are likely links
308
- if (
309
- node.type === "TEXT" &&
310
- node.characters &&
311
- node.characters.trim().length > 0 &&
312
- node.characters.trim().length < 40 &&
313
- _analysisRootNode &&
314
- isInsideNavigation(node, _analysisRootNode)
315
- ) {
316
- return true;
317
- }
318
-
319
- return false;
320
- }
321
-
322
- function hasInteractiveDescendant(node: FigmaNode): boolean {
323
- if (!node.children) return false;
324
- for (const child of node.children) {
325
- const role = inferRole(child);
326
- if (role && !["img", "dialog", "menu"].includes(role)) return true;
327
- if (
328
- (child.type === "INSTANCE" || child.type === "COMPONENT") &&
329
- /button|btn|link|cta|click/i.test(child.name)
330
- )
331
- return true;
332
- if (hasInteractiveDescendant(child)) return true;
333
- }
334
- return false;
335
- }
336
-
337
- function isHeading(node: FigmaNode): boolean {
338
- if (node.type !== "TEXT") return false;
339
- const fontSize = node.style?.fontSize ?? 16;
340
- const fontWeight = node.style?.fontWeight ?? 400;
341
- return fontSize >= 20 || fontWeight >= 700;
342
- }
343
-
344
- // ─── Tab Order Computation ──────────────────────────────────────────────────
345
-
346
- const ROW_THRESHOLD = 10; // px tolerance for same-row grouping
347
-
348
- function computeTabOrder(
349
- interactiveNodes: FigmaNode[],
350
- allNodes: FigmaNode[]
351
- ): TabOrderEntry[] {
352
- // Filter nodes with bounding boxes
353
- const withBounds = interactiveNodes.filter((n) => n.absoluteBoundingBox);
354
-
355
- // Sort by visual position: row-group by Y, then left-to-right within row
356
- withBounds.sort((a, b) => {
357
- const ay = a.absoluteBoundingBox!.y;
358
- const by = b.absoluteBoundingBox!.y;
359
- if (Math.abs(ay - by) <= ROW_THRESHOLD) {
360
- return a.absoluteBoundingBox!.x - b.absoluteBoundingBox!.x;
361
- }
362
- return ay - by;
363
- });
364
-
365
- return withBounds.map((node, idx) => {
366
- let role = inferRole(node) || (node.type === "TEXT" ? "link" : "button");
367
- const label = inferLabel(node, role, allNodes);
368
- const notes = generateNotes(node, role);
369
-
370
- return {
371
- order: idx + 1,
372
- elementDescription: generateElementDescription(node),
373
- role,
374
- label,
375
- notes,
376
- nodeId: node.id,
377
- };
378
- });
379
- }
380
-
381
- function generateElementDescription(node: FigmaNode): string {
382
- let name = node.name;
383
- // Clean up Figma-style naming
384
- name = name.replace(/^(component|instance|frame)[-_ /]*/i, "");
385
- name = name.replace(/[-_/]/g, " ").replace(/\s+/g, " ").trim();
386
- return name || node.name;
387
- }
388
-
389
- function generateNotes(node: FigmaNode, role: string): string {
390
- const notes: string[] = [];
391
-
392
- if (role === "spinbutton") {
393
- notes.push('aria-valuenow required; keyboard: Arrow Up/Down to adjust');
394
- }
395
- if (role === "textbox") {
396
- notes.push("Pair with visible label or aria-label");
397
- }
398
- if (role === "link" && node.name.toLowerCase().includes("logo")) {
399
- notes.push("Navigates to homepage");
400
- }
401
- if (role === "button" && /search/i.test(node.name)) {
402
- notes.push("Opens search overlay; announce expanded/collapsed state");
403
- }
404
- if (role === "button" && /menu|hamburger|nav/i.test(node.name)) {
405
- notes.push("Opens navigation menu; see Focus Management for trap behavior");
406
- }
407
- if (role === "button" && /remove|delete/i.test(node.name)) {
408
- notes.push("See Focus Management for post-removal focus");
409
- }
410
- if (role === "button" && /decrease|minus/i.test(node.name)) {
411
- notes.push("Disabled when qty = 1; announce current qty on activation");
412
- }
413
- if (role === "button" && /increase|plus/i.test(node.name)) {
414
- notes.push("Announce new qty on activation");
415
- }
416
-
417
- // Touch target warning
418
- if (node.width && node.height && (node.width < 44 || node.height < 44)) {
419
- notes.push(`Touch target ${node.width}x${node.height}px below 44x44px minimum`);
420
- }
421
-
422
- return notes.join("; ") || "\u2014";
423
- }
424
-
425
- // ─── Screen Reader Reading Order ────────────────────────────────────────────
426
-
427
- function buildReadingOrder(
428
- node: FigmaNode,
429
- depth: number = 0
430
- ): ReadingOrderNode[] {
431
- const results: ReadingOrderNode[] = [];
432
-
433
- // Check if this node is a landmark
434
- const landmark = inferLandmark(node);
435
-
436
- if (node.type === "TEXT" && node.characters) {
437
- results.push({
438
- content: node.characters.trim(),
439
- role: isHeading(node)
440
- ? `heading (h${estimateHeadingLevel(node)})`
441
- : "text",
442
- depth,
443
- nodeId: node.id,
444
- });
445
- return results;
446
- }
447
-
448
- // Interactive leaf nodes
449
- const role = inferRole(node);
450
- if (role && !node.children?.length) {
451
- const label = inferLabel(node, role, []);
452
- results.push({
453
- content: `${label} (${role})`,
454
- role,
455
- depth,
456
- nodeId: node.id,
457
- });
458
- return results;
459
- }
460
-
461
- // Container/group nodes
462
- if (node.children && node.children.length > 0) {
463
- // Sort children by visual position (top-to-bottom, left-to-right)
464
- // instead of relying on Figma's layer order which may be inverted
465
- const sortedChildren = [...node.children].sort((a, b) => {
466
- const ay = a.absoluteBoundingBox?.y ?? 0;
467
- const by = b.absoluteBoundingBox?.y ?? 0;
468
- if (Math.abs(ay - by) <= ROW_THRESHOLD) {
469
- return (a.absoluteBoundingBox?.x ?? 0) - (b.absoluteBoundingBox?.x ?? 0);
470
- }
471
- return ay - by;
472
- });
473
-
474
- const childResults: ReadingOrderNode[] = [];
475
- for (const child of sortedChildren) {
476
- childResults.push(...buildReadingOrder(child, depth + 1));
477
- }
478
-
479
- if (landmark) {
480
- // Wrap children in landmark
481
- results.push({
482
- landmarkRole: landmark.role,
483
- landmarkLabel: landmark.label,
484
- content: node.name,
485
- depth,
486
- children: childResults,
487
- nodeId: node.id,
488
- });
489
- } else if (
490
- role === "dialog" ||
491
- (node.name.toLowerCase().includes("product") &&
492
- childResults.length > 0)
493
- ) {
494
- // Group container
495
- results.push({
496
- content: node.name,
497
- role: role || "group",
498
- depth,
499
- children: childResults,
500
- nodeId: node.id,
501
- });
502
- } else {
503
- // Flatten into parent
504
- results.push(...childResults);
505
- }
506
- }
507
-
508
- return results;
509
- }
510
-
511
- function estimateHeadingLevel(node: FigmaNode): number {
512
- const fontSize = node.style?.fontSize ?? 16;
513
- if (fontSize >= 32) return 1;
514
- if (fontSize >= 24) return 2;
515
- if (fontSize >= 20) return 3;
516
- if (fontSize >= 18) return 4;
517
- if (fontSize >= 16) return 5;
518
- return 6;
519
- }
520
-
521
- // ─── Interaction Announcements ──────────────────────────────────────────────
522
-
523
- function generateFocusAnnouncements(
524
- tabOrder: TabOrderEntry[]
525
- ): FocusAnnouncement[] {
526
- return tabOrder.map((entry) => {
527
- let announcement = `${entry.label}, ${entry.role}`;
528
-
529
- // Add state info
530
- if (entry.notes.includes("Disabled")) {
531
- announcement += ", disabled";
532
- }
533
- if (entry.notes.includes("current page")) {
534
- announcement += ", current page";
535
- }
536
- if (entry.role === "textbox") {
537
- announcement += ", edit text, blank";
538
- }
539
- if (entry.role === "spinbutton") {
540
- announcement += ", spin button, 1";
541
- }
542
-
543
- return { element: entry.elementDescription, announcement };
544
- });
545
- }
546
-
547
- function generateStateChanges(
548
- tabOrder: TabOrderEntry[]
549
- ): StateChangeRule[] {
550
- const rules: StateChangeRule[] = [];
551
-
552
- // Quantity controls
553
- const hasSpinbutton = tabOrder.some((e) => e.role === "spinbutton");
554
- if (hasSpinbutton) {
555
- rules.push({
556
- trigger: "Quantity change (spinbutton / button activation)",
557
- behavior:
558
- 'Announce via aria-live="assertive": "Quantity updated to [N] for [Product Name]". Update order summary total via aria-live="polite".',
559
- });
560
- rules.push({
561
- trigger: "Decrease at minimum quantity",
562
- behavior:
563
- '"Minimum quantity reached" — button becomes aria-disabled="true"',
564
- });
565
- }
566
-
567
- // Remove actions
568
- const hasRemove = tabOrder.some((e) =>
569
- /remove|delete/i.test(e.elementDescription)
570
- );
571
- if (hasRemove) {
572
- rules.push({
573
- trigger: "Item removed",
574
- behavior:
575
- 'Announce "[Item Name] removed from cart". Update item count via aria-live="polite". Update order summary.',
576
- });
577
- }
578
-
579
- // Form submission
580
- const hasTextbox = tabOrder.some((e) => e.role === "textbox");
581
- if (hasTextbox) {
582
- rules.push({
583
- trigger: "Form validation error",
584
- behavior:
585
- 'Announce error via role="alert" or aria-live="assertive". Input receives aria-invalid="true" and aria-describedby pointing to error.',
586
- });
587
- rules.push({
588
- trigger: "Form submission success",
589
- behavior:
590
- 'Announce success via aria-live="polite". Focus remains on input or moves to success message.',
591
- });
592
- }
593
-
594
- // Navigation
595
- const hasPrimaryCTA = tabOrder.some(
596
- (e) =>
597
- e.role === "button" &&
598
- /proceed|checkout|submit|continue|next/i.test(e.elementDescription)
599
- );
600
- if (hasPrimaryCTA) {
601
- rules.push({
602
- trigger: "Primary CTA activation",
603
- behavior:
604
- 'Announce "Navigating to [destination]". Standard page navigation.',
605
- });
606
- }
607
-
608
- // Toggle/switch
609
- const hasSwitch = tabOrder.some((e) => e.role === "switch");
610
- if (hasSwitch) {
611
- rules.push({
612
- trigger: "Toggle state change",
613
- behavior: 'Announce new state: "[Label], switch, on/off"',
614
- });
615
- }
616
-
617
- return rules;
618
- }
619
-
620
- // ─── Focus Management ───────────────────────────────────────────────────────
621
-
622
- function generateFocusRules(tabOrder: TabOrderEntry[]): FocusRule[] {
623
- const rules: FocusRule[] = [];
624
-
625
- rules.push({
626
- scenario: "Initial page load",
627
- rule: "Focus on the <main> landmark or the primary heading (<h1>).",
628
- });
629
-
630
- const hasSpinbutton = tabOrder.some((e) => e.role === "spinbutton");
631
- if (hasSpinbutton) {
632
- rules.push({
633
- scenario: "After quantity change",
634
- rule: "Focus stays on the activated button (plus or minus). Do NOT move focus.",
635
- });
636
- }
637
-
638
- const hasRemove = tabOrder.some((e) =>
639
- /remove|delete/i.test(e.elementDescription)
640
- );
641
- if (hasRemove) {
642
- rules.push({
643
- scenario: "After item removal",
644
- rule: "If items remain: move focus to the next item's name. If removed item was last: move to previous item's name. If list empty: move to empty state message or \"Continue Shopping\" link.",
645
- });
646
- }
647
-
648
- const hasMenu = tabOrder.some(
649
- (e) => e.role === "button" && /menu|hamburger|nav/i.test(e.elementDescription)
650
- );
651
- if (hasMenu) {
652
- rules.push({
653
- scenario: "Menu/drawer activation",
654
- rule: 'Focus traps inside the drawer. Escape closes and returns focus to the menu button. Drawer has role="dialog", aria-modal="true".',
655
- });
656
- }
657
-
658
- const hasSearch = tabOrder.some(
659
- (e) => e.role === "button" && /search/i.test(e.elementDescription)
660
- );
661
- if (hasSearch) {
662
- rules.push({
663
- scenario: "Search overlay activation",
664
- rule: "Focus moves to the search input. Escape closes and returns focus to the search button.",
665
- });
666
- }
667
-
668
- const hasTextbox = tabOrder.some((e) => e.role === "textbox");
669
- if (hasTextbox) {
670
- rules.push({
671
- scenario: "Form validation failure",
672
- rule: 'Focus moves to the input. Input marked aria-invalid="true". Error associated via aria-describedby.',
673
- });
674
- }
675
-
676
- return rules;
677
- }
678
-
679
- // ─── Implementation Notes ───────────────────────────────────────────────────
680
-
681
- const APG_KEYBOARD_MAP: Record<string, KBRule[]> = {
682
- button: [
683
- { component: "Button", key: "Enter / Space", action: "Activate" },
684
- ],
685
- link: [{ component: "Link", key: "Enter", action: "Navigate" }],
686
- textbox: [
687
- { component: "Text input", key: "Enter", action: "Submit (if applicable)" },
688
- ],
689
- spinbutton: [
690
- { component: "Spinbutton", key: "Arrow Up", action: "Increment value" },
691
- {
692
- component: "Spinbutton",
693
- key: "Arrow Down",
694
- action: "Decrement value (min bound)",
695
- },
696
- { component: "Spinbutton", key: "Home", action: "Set to minimum" },
697
- { component: "Spinbutton", key: "End", action: "Set to maximum" },
698
- ],
699
- checkbox: [
700
- { component: "Checkbox", key: "Space", action: "Toggle checked state" },
701
- ],
702
- radio: [
703
- {
704
- component: "Radio",
705
- key: "Arrow Up/Down",
706
- action: "Move within radio group",
707
- },
708
- { component: "Radio", key: "Space", action: "Select focused option" },
709
- ],
710
- switch: [
711
- { component: "Switch", key: "Space", action: "Toggle on/off" },
712
- ],
713
- listbox: [
714
- {
715
- component: "Listbox",
716
- key: "Arrow Up/Down",
717
- action: "Navigate options",
718
- },
719
- { component: "Listbox", key: "Enter", action: "Select option" },
720
- { component: "Listbox", key: "Escape", action: "Close dropdown" },
721
- ],
722
- slider: [
723
- {
724
- component: "Slider",
725
- key: "Arrow Left/Right",
726
- action: "Adjust value",
727
- },
728
- { component: "Slider", key: "Home", action: "Set to minimum" },
729
- { component: "Slider", key: "End", action: "Set to maximum" },
730
- ],
731
- tab: [
732
- {
733
- component: "Tab",
734
- key: "Arrow Left/Right",
735
- action: "Move between tabs",
736
- },
737
- ],
738
- dialog: [
739
- {
740
- component: "Dialog",
741
- key: "Escape",
742
- action: "Close dialog, return focus to trigger",
743
- },
744
- ],
745
- menuitem: [
746
- {
747
- component: "Menu item",
748
- key: "Arrow Up/Down",
749
- action: "Navigate items",
750
- },
751
- { component: "Menu item", key: "Enter", action: "Activate item" },
752
- { component: "Menu item", key: "Escape", action: "Close menu" },
753
- ],
754
- };
755
-
756
- function generateAriaRequirements(
757
- tabOrder: TabOrderEntry[],
758
- readingOrder: ReadingOrderNode[]
759
- ): AriaReq[] {
760
- const reqs: AriaReq[] = [];
761
- const seenElements = new Set<string>();
762
-
763
- for (const entry of tabOrder) {
764
- const key = `${entry.role}-${entry.elementDescription}`;
765
- if (seenElements.has(key)) continue;
766
- seenElements.add(key);
767
-
768
- // Icon-only buttons need aria-label
769
- if (
770
- (entry.role === "button" || entry.role === "link") &&
771
- /icon|search|cart|menu|hamburger|close|minus|plus|heart/i.test(
772
- entry.elementDescription
773
- )
774
- ) {
775
- reqs.push({
776
- element: entry.elementDescription,
777
- attribute: "aria-label",
778
- value: entry.label,
779
- });
780
- }
781
-
782
- // Spinbutton requirements
783
- if (entry.role === "spinbutton") {
784
- reqs.push({
785
- element: entry.elementDescription,
786
- attribute: "role",
787
- value: "spinbutton",
788
- });
789
- reqs.push({
790
- element: entry.elementDescription,
791
- attribute: "aria-valuenow",
792
- value: "current quantity",
793
- });
794
- reqs.push({
795
- element: entry.elementDescription,
796
- attribute: "aria-valuemin",
797
- value: '"1"',
798
- });
799
- }
800
-
801
- // Current page indicator
802
- if (/cart/i.test(entry.elementDescription) && entry.role === "link") {
803
- reqs.push({
804
- element: entry.elementDescription,
805
- attribute: "aria-current",
806
- value: '"page"',
807
- });
808
- }
809
- }
810
-
811
- // Landmark regions
812
- for (const rNode of readingOrder) {
813
- if (rNode.landmarkRole) {
814
- reqs.push({
815
- element: rNode.content || rNode.landmarkLabel || "",
816
- attribute: "role",
817
- value: rNode.landmarkRole,
818
- });
819
- if (rNode.landmarkLabel) {
820
- reqs.push({
821
- element: rNode.content || rNode.landmarkLabel,
822
- attribute: "aria-label",
823
- value: rNode.landmarkLabel,
824
- });
825
- }
826
- }
827
- }
828
-
829
- // Live regions
830
- const hasQuantity = tabOrder.some((e) => e.role === "spinbutton");
831
- if (hasQuantity) {
832
- reqs.push({
833
- element: "Item count text",
834
- attribute: "aria-live",
835
- value: '"polite"',
836
- });
837
- reqs.push({
838
- element: "Total amount",
839
- attribute: "aria-live",
840
- value: '"polite"',
841
- });
842
- }
843
-
844
- // Decorative dividers
845
- reqs.push({
846
- element: "Decorative dividers",
847
- attribute: "aria-hidden",
848
- value: '"true"',
849
- });
850
-
851
- return reqs;
852
- }
853
-
854
- function generateKeyboardBehavior(tabOrder: TabOrderEntry[]): KBRule[] {
855
- const rules: KBRule[] = [];
856
- const seenRoles = new Set<string>();
857
-
858
- // Global navigation
859
- rules.push({
860
- component: "Entire page",
861
- key: "Tab",
862
- action: "Move to next focusable element",
863
- });
864
- rules.push({
865
- component: "Entire page",
866
- key: "Shift+Tab",
867
- action: "Move to previous focusable element",
868
- });
869
-
870
- for (const entry of tabOrder) {
871
- if (seenRoles.has(entry.role)) continue;
872
- seenRoles.add(entry.role);
873
-
874
- const apgRules = APG_KEYBOARD_MAP[entry.role];
875
- if (apgRules) {
876
- rules.push(...apgRules);
877
- }
878
- }
879
-
880
- return rules;
881
- }
882
-
883
- function generateDosDonts(tabOrder: TabOrderEntry[]): string[] {
884
- const rules: string[] = [];
885
-
886
- // Dos
887
- rules.push(
888
- 'Do: Use <button> for all action elements (submit, remove, toggle, increase/decrease)'
889
- );
890
- rules.push(
891
- 'Do: Use <a> for elements that navigate to other pages (product links, logo)'
892
- );
893
-
894
- const hasTextbox = tabOrder.some((e) => e.role === "textbox");
895
- if (hasTextbox) {
896
- rules.push(
897
- 'Do: Use <input type="text"> with a visible <label> for text inputs'
898
- );
899
- }
900
-
901
- const hasSpinbutton = tabOrder.some((e) => e.role === "spinbutton");
902
- if (hasSpinbutton) {
903
- rules.push(
904
- 'Do: Group quantity controls (minus, value, plus) in a <div role="group" aria-label="Quantity for [Item]">'
905
- );
906
- }
907
-
908
- rules.push(
909
- 'Do: Use aria-live="polite" on elements that update dynamically (counts, totals)'
910
- );
911
- rules.push(
912
- "Do: Provide aria-label on icon-only buttons and links"
913
- );
914
-
915
- // Don'ts
916
- rules.push(
917
- "Don't: Use <div> or <span> for interactive elements -- always use semantic HTML"
918
- );
919
- rules.push(
920
- "Don't: Rely on color alone for conveying status or state changes"
921
- );
922
- if (hasTextbox) {
923
- rules.push(
924
- "Don't: Use placeholder as the only label for inputs"
925
- );
926
- }
927
- rules.push(
928
- "Don't: Remove focus outline on any interactive element"
929
- );
930
- rules.push(
931
- "Don't: Use tabindex values greater than 0 -- let DOM order control tab sequence"
932
- );
933
- rules.push(
934
- "Don't: Make decorative dividers or purely decorative images focusable"
935
- );
936
-
937
- return rules;
938
- }
939
-
940
- // ─── Warnings & Audit Summary ───────────────────────────────────────────────
941
-
942
- function generateWarnings(
943
- allNodes: FigmaNode[],
944
- interactiveNodes: FigmaNode[],
945
- textNodes: FigmaNode[]
946
- ): { warnings: Warning[]; summary: AuditSummaryRow[] } {
947
- const warnings: Warning[] = [];
948
- const issues: WCAGIssue[] = [];
949
- let warnId = 1;
950
-
951
- // Contrast checks
952
- for (const node of textNodes) {
953
- const issue = checkTextContrast(node, allNodes, "AA");
954
- if (issue) issues.push(issue);
955
- }
956
-
957
- // Touch target checks
958
- for (const node of interactiveNodes) {
959
- const ttIssue = checkTouchTarget(node);
960
- if (ttIssue) issues.push(ttIssue);
961
- const tsIssue = checkTargetSizeMinimum(node);
962
- if (tsIssue) issues.push(tsIssue);
963
- }
964
-
965
- // Reading/focus order checks
966
- issues.push(...checkMeaningfulSequence(allNodes));
967
- issues.push(...checkFocusOrder(interactiveNodes));
968
- issues.push(...checkNameRoleValue(interactiveNodes));
969
- issues.push(...checkLabelsOrInstructions(interactiveNodes, allNodes));
970
-
971
- // Group issues into warnings
972
- const contrastErrors = issues.filter(
973
- (i) => i.criterion.includes("1.4.3") && i.severity === "error"
974
- );
975
- const contrastWarnings = issues.filter(
976
- (i) => i.criterion.includes("1.4.3") && i.severity === "warning"
977
- );
978
- const touchTargetIssues = issues.filter(
979
- (i) =>
980
- i.criterion.includes("2.5.5") || i.criterion.includes("2.5.8")
981
- );
982
- const nameRoleIssues = issues.filter((i) =>
983
- i.criterion.includes("4.1.2")
984
- );
985
- const focusOrderIssues = issues.filter((i) =>
986
- i.criterion.includes("2.4.3")
987
- );
988
-
989
- if (contrastErrors.length > 0) {
990
- warnings.push({
991
- id: warnId++,
992
- severity: "Critical",
993
- title: `Contrast failures (${contrastErrors.length} elements)`,
994
- description: `${contrastErrors.length} text elements fail WCAG AA contrast minimum. Fix by darkening text or lightening background to achieve at least 4.5:1 ratio.`,
995
- });
996
- }
997
-
998
- if (touchTargetIssues.length > 0) {
999
- warnings.push({
1000
- id: warnId++,
1001
- severity: "Critical",
1002
- title: `Touch target violations (${touchTargetIssues.length} elements)`,
1003
- description: `${touchTargetIssues.length} interactive elements are below the 44x44px minimum. Apply invisible padding to achieve compliant tap zones.`,
1004
- });
1005
- }
1006
-
1007
- if (nameRoleIssues.length > 0) {
1008
- warnings.push({
1009
- id: warnId++,
1010
- severity: "Critical",
1011
- title: `Missing accessible names (${nameRoleIssues.length} elements)`,
1012
- description: `${nameRoleIssues.length} interactive elements lack unique accessible names. Screen readers cannot distinguish them. Add aria-label with contextual information.`,
1013
- });
1014
- }
1015
-
1016
- if (contrastWarnings.length > 0) {
1017
- warnings.push({
1018
- id: warnId++,
1019
- severity: "Warning",
1020
- title: `Contrast near-misses (${contrastWarnings.length} elements)`,
1021
- description: `${contrastWarnings.length} text elements have contrast between 3:1 and 4.5:1. While they pass for large text, they fail for normal text.`,
1022
- });
1023
- }
1024
-
1025
- if (focusOrderIssues.length > 0) {
1026
- warnings.push({
1027
- id: warnId++,
1028
- severity: "Warning",
1029
- title: `Focus order mismatches (${focusOrderIssues.length})`,
1030
- description: `${focusOrderIssues.length} interactive elements have a tab order that does not match visual layout. Verify DOM order matches intended reading order.`,
1031
- });
1032
- }
1033
-
1034
- // Build summary
1035
- const errorCount = issues.filter((i) => i.severity === "error").length;
1036
- const warningCount = issues.filter((i) => i.severity === "warning").length;
1037
- const suggestionCount = issues.filter(
1038
- (i) => i.severity === "suggestion"
1039
- ).length;
1040
-
1041
- const summary: AuditSummaryRow[] = [];
1042
- if (errorCount > 0) {
1043
- summary.push({
1044
- severity: "Error",
1045
- count: errorCount,
1046
- category: `Critical issues (contrast, touch targets, missing names)`,
1047
- });
1048
- }
1049
- if (warningCount > 0) {
1050
- summary.push({
1051
- severity: "Warning",
1052
- count: warningCount,
1053
- category: `Potential issues (near-miss contrast, focus order, fixed containers)`,
1054
- });
1055
- }
1056
- if (suggestionCount > 0) {
1057
- summary.push({
1058
- severity: "Suggestion",
1059
- count: suggestionCount,
1060
- category: `Improvement opportunities`,
1061
- });
1062
- }
1063
-
1064
- const totalIssues = errorCount + warningCount + suggestionCount;
1065
- const totalChecks = allNodes.length;
1066
- const passRate =
1067
- totalChecks > 0
1068
- ? Math.round(
1069
- ((totalChecks - totalIssues) / totalChecks) * 100
1070
- )
1071
- : 100;
1072
- summary.push({
1073
- severity: "Total",
1074
- count: totalIssues,
1075
- category: `issues across ${totalChecks} checks (${passRate}% pass rate)`,
1076
- });
1077
-
1078
- return { warnings, summary };
1079
- }
1080
-
1081
- // ─── Scope & Assumptions Generation ─────────────────────────────────────────
1082
-
1083
- function generateScope(
1084
- node: FigmaNode,
1085
- interactiveNodes: FigmaNode[],
1086
- landmarks: ReadingOrderNode[]
1087
- ): string {
1088
- const parts: string[] = [];
1089
- parts.push(`Complete ${node.name} page including:`);
1090
-
1091
- const landmarkNames = landmarks
1092
- .filter((l) => l.landmarkRole)
1093
- .map((l) => l.landmarkLabel || l.content);
1094
- if (landmarkNames.length > 0) {
1095
- parts.push(landmarkNames.join(", "));
1096
- }
1097
-
1098
- const roleGroups = new Map<string, number>();
1099
- for (const n of interactiveNodes) {
1100
- const role = inferRole(n) || "interactive element";
1101
- roleGroups.set(role, (roleGroups.get(role) || 0) + 1);
1102
- }
1103
-
1104
- const roleSummary = Array.from(roleGroups.entries())
1105
- .map(([role, count]) => `${count} ${role}${count > 1 ? "s" : ""}`)
1106
- .join(", ");
1107
- if (roleSummary) parts.push(roleSummary);
1108
-
1109
- return parts.join(" ");
1110
- }
1111
-
1112
- function generateAssumptions(node: FigmaNode): string[] {
1113
- return [
1114
- `Page is in default/resting state`,
1115
- `No validation errors are visible`,
1116
- `No modal dialogs are open`,
1117
- `All interactive elements are enabled unless noted`,
1118
- `Content matches the current Figma frame "${node.name}"`,
1119
- ];
1120
- }
1121
-
1122
- // ─── Main Analysis Function ─────────────────────────────────────────────────
1123
-
1124
- export function analyzeKeyboardAndScreenReaderOrder(
1125
- rootNode: FigmaNode,
1126
- nodeId: string
1127
- ): A11yOrderAnalysis {
1128
- // Set root node for navigation context lookups
1129
- _analysisRootNode = rootNode;
1130
-
1131
- // Collect all nodes
1132
- const allNodes = collectAll(rootNode);
1133
- const textNodes = allNodes.filter((n) => n.type === "TEXT");
1134
- const interactiveNodes = allNodes.filter((n) => isInteractive(n));
1135
-
1136
- // Compute tab order
1137
- const keyboardTabOrder = computeTabOrder(interactiveNodes, allNodes);
1138
-
1139
- // Build reading order
1140
- const screenReaderReadingOrder = buildReadingOrder(rootNode);
1141
-
1142
- // Generate interaction announcements
1143
- const onFocus = generateFocusAnnouncements(keyboardTabOrder);
1144
- const stateChanges = generateStateChanges(keyboardTabOrder);
1145
-
1146
- // Focus management rules
1147
- const focusManagement = generateFocusRules(keyboardTabOrder);
1148
-
1149
- // Implementation notes
1150
- const ariaTable = generateAriaRequirements(
1151
- keyboardTabOrder,
1152
- screenReaderReadingOrder
1153
- );
1154
- const keyboardBehavior = generateKeyboardBehavior(keyboardTabOrder);
1155
- const dosDonts = generateDosDonts(keyboardTabOrder);
1156
-
1157
- // Warnings & audit summary
1158
- const { warnings, summary } = generateWarnings(
1159
- allNodes,
1160
- interactiveNodes,
1161
- textNodes
1162
- );
1163
-
1164
- // Scope & assumptions
1165
- const scope = generateScope(
1166
- rootNode,
1167
- interactiveNodes,
1168
- screenReaderReadingOrder
1169
- );
1170
- const assumptions = generateAssumptions(rootNode);
1171
-
1172
- const today = new Date().toISOString().split("T")[0];
1173
-
1174
- return {
1175
- header: {
1176
- frameName: rootNode.name,
1177
- nodeId,
1178
- date: today,
1179
- standard: "WAI-ARIA APG + WCAG 2.1 AA",
1180
- },
1181
- scope,
1182
- assumptions,
1183
- keyboardTabOrder,
1184
- screenReaderReadingOrder,
1185
- interactionAnnouncements: { onFocus, stateChanges },
1186
- focusManagement,
1187
- implementationNotes: { ariaTable, keyboardBehavior, dosDonts },
1188
- warnings,
1189
- auditSummary: summary,
1190
- };
1191
- }