@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.
- package/README.md +69 -36
- package/dist/bin/cli.js +2 -0
- package/dist/design-bridge/bridge.js +2 -0
- package/dist/figma-bridge-plugin/bridge-relay.js +2 -0
- package/dist/figma-bridge-plugin/code.js +1 -0
- package/{figma-bridge-plugin → dist/figma-bridge-plugin}/package-lock.json +0 -3
- package/dist/figma-bridge-plugin/ui.html +4970 -0
- package/dist/figma-intelligence-layer/dist/index.js +2 -0
- package/dist/scripts/clean-existing-chunks.js +2 -0
- package/dist/scripts/connect-ai-tool.js +2 -0
- package/dist/scripts/convert-hub-pdfs.js +2 -0
- package/dist/scripts/figma-mcp-status.js +2 -0
- package/dist/scripts/register-codex-mcp.js +2 -0
- package/dist/scripts/test-copilot-chat.js +2 -0
- package/package.json +11 -8
- package/bin/cli.js +0 -859
- package/design-bridge/bridge.js +0 -196
- package/design-bridge/lib/assets.js +0 -367
- package/design-bridge/lib/prompt.js +0 -85
- package/design-bridge/lib/server.js +0 -66
- package/design-bridge/lib/stitch.js +0 -37
- package/design-bridge/lib/tokens.js +0 -82
- package/design-bridge/package-lock.json +0 -579
- package/figma-bridge-plugin/README.md +0 -97
- package/figma-bridge-plugin/anthropic-chat-runner.js +0 -192
- package/figma-bridge-plugin/bridge-relay.js +0 -2505
- package/figma-bridge-plugin/chat-runner.js +0 -485
- package/figma-bridge-plugin/code.js +0 -1534
- package/figma-bridge-plugin/codex-runner.js +0 -505
- package/figma-bridge-plugin/component-schemas.js +0 -110
- package/figma-bridge-plugin/content-context.js +0 -869
- package/figma-bridge-plugin/create-button.js +0 -216
- package/figma-bridge-plugin/gemini-cli-runner.js +0 -291
- package/figma-bridge-plugin/gemini-runner.js +0 -187
- package/figma-bridge-plugin/html-to-figma.js +0 -927
- package/figma-bridge-plugin/knowledge-hub/.gitkeep +0 -0
- package/figma-bridge-plugin/knowledge-hub/uspec-references/anatomy-spec.md +0 -159
- package/figma-bridge-plugin/knowledge-hub/uspec-references/api-spec.md +0 -162
- package/figma-bridge-plugin/knowledge-hub/uspec-references/color-spec.md +0 -148
- package/figma-bridge-plugin/knowledge-hub/uspec-references/full-spec-template.md +0 -314
- package/figma-bridge-plugin/knowledge-hub/uspec-references/property-spec.md +0 -175
- package/figma-bridge-plugin/knowledge-hub/uspec-references/screen-reader-spec.md +0 -180
- package/figma-bridge-plugin/knowledge-hub/uspec-references/structure-spec.md +0 -165
- package/figma-bridge-plugin/perplexity-runner.js +0 -188
- package/figma-bridge-plugin/references/SKILL.md +0 -178
- package/figma-bridge-plugin/references/anatomy-spec.md +0 -159
- package/figma-bridge-plugin/references/api-spec.md +0 -162
- package/figma-bridge-plugin/references/color-spec.md +0 -148
- package/figma-bridge-plugin/references/full-spec-template.md +0 -314
- package/figma-bridge-plugin/references/property-spec.md +0 -175
- package/figma-bridge-plugin/references/screen-reader-spec.md +0 -180
- package/figma-bridge-plugin/references/structure-spec.md +0 -165
- package/figma-bridge-plugin/shared-prompt-config.js +0 -645
- package/figma-bridge-plugin/spec-helpers/build-table.js +0 -269
- package/figma-bridge-plugin/spec-helpers/classify-elements.js +0 -189
- package/figma-bridge-plugin/spec-helpers/index.js +0 -35
- package/figma-bridge-plugin/spec-helpers/parse-figma-link.js +0 -49
- package/figma-bridge-plugin/spec-helpers/position-markers.js +0 -158
- package/figma-bridge-plugin/stitch-auth.js +0 -322
- package/figma-bridge-plugin/stitch-runner.js +0 -1427
- package/figma-bridge-plugin/token-resolver.js +0 -107
- package/figma-bridge-plugin/ui.html +0 -4542
- package/figma-intelligence-layer/.env.example +0 -39
- package/figma-intelligence-layer/docs/local-image-generation.md +0 -60
- package/figma-intelligence-layer/examples/comfyui-workflow-template.example.json +0 -101
- package/figma-intelligence-layer/jest.config.js +0 -14
- package/figma-intelligence-layer/mcp-config.json +0 -19
- package/figma-intelligence-layer/package-lock.json +0 -5892
- package/figma-intelligence-layer/scripts/setup-comfyui-local.sh +0 -67
- package/figma-intelligence-layer/scripts/start-comfyui.sh +0 -33
- package/figma-intelligence-layer/src/index.ts +0 -2233
- package/figma-intelligence-layer/src/shared/auto-layout-validator.ts +0 -404
- package/figma-intelligence-layer/src/shared/cache.ts +0 -187
- package/figma-intelligence-layer/src/shared/color-operations.ts +0 -533
- package/figma-intelligence-layer/src/shared/color-utils.ts +0 -138
- package/figma-intelligence-layer/src/shared/component-script-builder.ts +0 -413
- package/figma-intelligence-layer/src/shared/component-templates.ts +0 -2767
- package/figma-intelligence-layer/src/shared/concept-taxonomy.ts +0 -694
- package/figma-intelligence-layer/src/shared/decision-log.ts +0 -128
- package/figma-intelligence-layer/src/shared/design-system-context.ts +0 -568
- package/figma-intelligence-layer/src/shared/design-system-intelligence.ts +0 -131
- package/figma-intelligence-layer/src/shared/design-system-matcher.ts +0 -184
- package/figma-intelligence-layer/src/shared/design-system-normalizers.ts +0 -196
- package/figma-intelligence-layer/src/shared/design-system-tokens.ts +0 -295
- package/figma-intelligence-layer/src/shared/dtcg-validator.ts +0 -530
- package/figma-intelligence-layer/src/shared/enrichment-pipeline.ts +0 -671
- package/figma-intelligence-layer/src/shared/figma-bridge.ts +0 -1418
- package/figma-intelligence-layer/src/shared/font-config.ts +0 -126
- package/figma-intelligence-layer/src/shared/icon-catalog.ts +0 -360
- package/figma-intelligence-layer/src/shared/icon-fetch.ts +0 -80
- package/figma-intelligence-layer/src/shared/prototype-script-builder.ts +0 -162
- package/figma-intelligence-layer/src/shared/response-compression.ts +0 -440
- package/figma-intelligence-layer/src/shared/semantic-token-catalog.ts +0 -324
- package/figma-intelligence-layer/src/shared/token-binder.ts +0 -505
- package/figma-intelligence-layer/src/shared/token-math.ts +0 -427
- package/figma-intelligence-layer/src/shared/token-naming.ts +0 -468
- package/figma-intelligence-layer/src/shared/token-utils.ts +0 -420
- package/figma-intelligence-layer/src/shared/types.ts +0 -346
- package/figma-intelligence-layer/src/shared/typography-presets.ts +0 -94
- package/figma-intelligence-layer/src/shared/unsplash.ts +0 -165
- package/figma-intelligence-layer/src/shared/vision-client.ts +0 -607
- package/figma-intelligence-layer/src/shared/vision-provider-anthropic.ts +0 -334
- package/figma-intelligence-layer/src/shared/vision-provider-openai.ts +0 -446
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotate-handler.ts +0 -782
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotate-renderer.ts +0 -496
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotation-kit.ts +0 -230
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/colorblind-sim.ts +0 -66
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/index.ts +0 -810
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-analyzer.ts +0 -1191
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-figma-page.ts +0 -1346
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-handler.ts +0 -148
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/vpat-figma-page.ts +0 -499
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/vpat-report.ts +0 -910
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/wcag-checker.ts +0 -989
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/wcag-criteria.ts +0 -1160
- package/figma-intelligence-layer/src/tools/phase1-vision/design-from-ref/index.ts +0 -424
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/component-recognizer.ts +0 -38
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/ds-matcher.ts +0 -111
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/font-matcher.ts +0 -114
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/icon-resolver.ts +0 -103
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/index.ts +0 -1060
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/layout-segmenter.ts +0 -18
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/token-inferencer.ts +0 -39
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/vision-pipeline.ts +0 -58
- package/figma-intelligence-layer/src/tools/phase1-vision/sketch-to-design/index.ts +0 -298
- package/figma-intelligence-layer/src/tools/phase1-vision/visual-audit/index.ts +0 -197
- package/figma-intelligence-layer/src/tools/phase2-accuracy/component-audit/index.ts +0 -494
- package/figma-intelligence-layer/src/tools/phase2-accuracy/intent-translator/index.ts +0 -356
- package/figma-intelligence-layer/src/tools/phase2-accuracy/layout-intelligence/container-patterns.ts +0 -123
- package/figma-intelligence-layer/src/tools/phase2-accuracy/layout-intelligence/index.ts +0 -663
- package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/built-in-rules.yaml +0 -56
- package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/index.ts +0 -614
- package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/rule-engine.ts +0 -113
- package/figma-intelligence-layer/src/tools/phase2-accuracy/theme-generator/color-theory.ts +0 -178
- package/figma-intelligence-layer/src/tools/phase2-accuracy/theme-generator/index.ts +0 -470
- package/figma-intelligence-layer/src/tools/phase2-accuracy/variant-expander/index.ts +0 -429
- package/figma-intelligence-layer/src/tools/phase2-accuracy/variant-expander/token-override-maps.ts +0 -226
- package/figma-intelligence-layer/src/tools/phase3-generation/ai-image-insert/index.ts +0 -535
- package/figma-intelligence-layer/src/tools/phase3-generation/component-archaeologist/index.ts +0 -660
- package/figma-intelligence-layer/src/tools/phase3-generation/component-archaeologist/pattern-fingerprints.ts +0 -209
- package/figma-intelligence-layer/src/tools/phase3-generation/composition-builder/index.ts +0 -540
- package/figma-intelligence-layer/src/tools/phase3-generation/figma-animated-build.ts +0 -391
- package/figma-intelligence-layer/src/tools/phase3-generation/page-architect/index.ts +0 -2019
- package/figma-intelligence-layer/src/tools/phase3-generation/page-architect/screen-templates.ts +0 -131
- package/figma-intelligence-layer/src/tools/phase3-generation/prototype-map/index.ts +0 -381
- package/figma-intelligence-layer/src/tools/phase3-generation/prototype-wire/index.ts +0 -565
- package/figma-intelligence-layer/src/tools/phase3-generation/swarm-build/index.ts +0 -764
- package/figma-intelligence-layer/src/tools/phase3-generation/system-drift/index.ts +0 -535
- package/figma-intelligence-layer/src/tools/phase3-generation/unsplash-search/index.ts +0 -84
- package/figma-intelligence-layer/src/tools/phase3-generation/url-to-frame/index.ts +0 -401
- package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/css-animations.ts +0 -68
- package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/framer-motion.ts +0 -78
- package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/swift-animations.ts +0 -93
- package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/index.ts +0 -596
- package/figma-intelligence-layer/src/tools/phase4-sync/ci-check/index.ts +0 -462
- package/figma-intelligence-layer/src/tools/phase4-sync/export-tokens/index.ts +0 -1470
- package/figma-intelligence-layer/src/tools/phase4-sync/generate-component-code/index.ts +0 -829
- package/figma-intelligence-layer/src/tools/phase4-sync/handoff-spec/index.ts +0 -702
- package/figma-intelligence-layer/src/tools/phase4-sync/icon-library-sync/index.ts +0 -483
- package/figma-intelligence-layer/src/tools/phase4-sync/sync-from-code/index.ts +0 -501
- package/figma-intelligence-layer/src/tools/phase4-sync/sync-from-code/storybook-parser.ts +0 -106
- package/figma-intelligence-layer/src/tools/phase4-sync/watch-docs/index.ts +0 -676
- package/figma-intelligence-layer/src/tools/phase4-sync/webhook-listener/index.ts +0 -560
- package/figma-intelligence-layer/src/tools/phase5-governance/apg-doc/index.ts +0 -1043
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/component-detection.ts +0 -620
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/anatomy.ts +0 -331
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/color-tokens.ts +0 -77
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/properties.ts +0 -54
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/snapshot.ts +0 -287
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/spacing.ts +0 -71
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/states.ts +0 -43
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/typography.ts +0 -71
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/index.ts +0 -221
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/_default.ts +0 -166
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/accordion.ts +0 -232
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/alert.ts +0 -234
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/avatar-group.ts +0 -270
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/avatar.ts +0 -249
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/badge.ts +0 -231
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/banner.ts +0 -293
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/breadcrumb.ts +0 -240
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/button.ts +0 -243
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/calendar.ts +0 -307
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/card.ts +0 -143
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/checkbox.ts +0 -227
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/chip.ts +0 -233
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/combobox.ts +0 -282
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/datepicker.ts +0 -276
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/divider.ts +0 -223
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/drawer.ts +0 -255
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/dropdown-menu.ts +0 -289
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/empty-state.ts +0 -261
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/file-uploader.ts +0 -290
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/form.ts +0 -265
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/grid.ts +0 -238
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/icon.ts +0 -255
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/index.ts +0 -128
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/inline-edit.ts +0 -286
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/inline-message.ts +0 -255
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/input.ts +0 -330
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/link.ts +0 -247
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/list.ts +0 -250
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/menu.ts +0 -247
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/modal.ts +0 -144
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/navbar.ts +0 -264
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/navigation.ts +0 -251
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/number-input.ts +0 -261
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/pagination.ts +0 -248
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/popover.ts +0 -270
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/progress.ts +0 -251
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/radio.ts +0 -142
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/range-slider.ts +0 -282
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/rating.ts +0 -250
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/search.ts +0 -258
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/segmented-control.ts +0 -265
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/select.ts +0 -319
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/skeleton.ts +0 -256
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/slider.ts +0 -232
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/spinner.ts +0 -239
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/status-dot.ts +0 -252
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/stepper.ts +0 -270
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/table.ts +0 -244
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tabs.ts +0 -143
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tag.ts +0 -243
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/textarea.ts +0 -259
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/time-picker.ts +0 -293
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toast.ts +0 -144
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toggle.ts +0 -289
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toolbar.ts +0 -267
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tooltip.ts +0 -232
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/treeview.ts +0 -257
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/typography.ts +0 -319
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/legacy-compat.ts +0 -121
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/anatomy-diagram.ts +0 -430
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/figma-page.ts +0 -312
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/json.ts +0 -129
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/markdown.ts +0 -78
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/visual-doc.ts +0 -2333
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/accessibility.ts +0 -100
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/anatomy.ts +0 -32
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/color-tokens.ts +0 -59
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/content-guidance.ts +0 -18
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/design-tokens.ts +0 -53
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/interaction-rules.ts +0 -19
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/overview.ts +0 -91
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/properties-api.ts +0 -71
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/qa-criteria.ts +0 -19
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/related-components.ts +0 -110
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/responsive.ts +0 -19
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/size-specs.ts +0 -67
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/spacing-structure.ts +0 -58
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/state-specs.ts +0 -79
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/states.ts +0 -50
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/type-hierarchy.ts +0 -33
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/typography.ts +0 -55
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/usage-guidelines.ts +0 -73
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/variants.ts +0 -81
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/types.ts +0 -409
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/index.ts +0 -198
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/renderer.ts +0 -701
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/types.ts +0 -88
- package/figma-intelligence-layer/src/tools/phase5-governance/decision-log/index.ts +0 -135
- package/figma-intelligence-layer/src/tools/phase5-governance/design-decision-log/index.ts +0 -491
- package/figma-intelligence-layer/src/tools/phase5-governance/ds-primitives/index.ts +0 -416
- package/figma-intelligence-layer/src/tools/phase5-governance/ds-scaffolder/index.ts +0 -722
- package/figma-intelligence-layer/src/tools/phase5-governance/ds-variables/index.ts +0 -449
- package/figma-intelligence-layer/src/tools/phase5-governance/health-report/index.ts +0 -393
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/index.ts +0 -406
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/figma-page.ts +0 -292
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/json.ts +0 -24
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/markdown.ts +0 -172
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/naming-guide.ts +0 -409
- package/figma-intelligence-layer/src/tools/phase5-governance/token-analytics/index.ts +0 -594
- package/figma-intelligence-layer/src/tools/phase5-governance/token-docs/index.ts +0 -710
- package/figma-intelligence-layer/src/tools/phase5-governance/token-migrate/index.ts +0 -458
- package/figma-intelligence-layer/src/tools/phase5-governance/token-naming/index.ts +0 -134
- package/figma-intelligence-layer/tests/apg-doc.test.ts +0 -101
- package/figma-intelligence-layer/tests/design-system-context.test.ts +0 -152
- package/figma-intelligence-layer/tests/design-system-matcher.test.ts +0 -144
- package/figma-intelligence-layer/tests/figma-bridge.test.ts +0 -83
- package/figma-intelligence-layer/tests/generate-image-and-insert.test.ts +0 -56
- package/figma-intelligence-layer/tests/screen-cloner-regression.test.ts +0 -69
- package/figma-intelligence-layer/tests/smoke.test.ts +0 -174
- package/figma-intelligence-layer/tests/spec-generator.test.ts +0 -127
- package/figma-intelligence-layer/tests/token-migrate.test.ts +0 -21
- package/figma-intelligence-layer/tests/token-naming.test.ts +0 -30
- package/figma-intelligence-layer/tsconfig.json +0 -19
- package/scripts/clean-existing-chunks.js +0 -179
- package/scripts/connect-ai-tool.js +0 -490
- package/scripts/convert-hub-pdfs.js +0 -425
- package/scripts/figma-mcp-status.js +0 -349
- package/scripts/register-codex-mcp.js +0 -96
- /package/{design-bridge → dist/design-bridge}/.env.example +0 -0
- /package/{design-bridge → dist/design-bridge}/package.json +0 -0
- /package/{figma-bridge-plugin → dist/figma-bridge-plugin}/manifest.json +0 -0
- /package/{figma-bridge-plugin → dist/figma-bridge-plugin}/package.json +0 -0
- /package/{figma-intelligence-layer → dist/figma-intelligence-layer}/package.json +0 -0
package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotate-handler.ts
DELETED
|
@@ -1,782 +0,0 @@
|
|
|
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
|
-
}
|