@fprad0/skill-master-mcp 0.0.9 → 0.0.10

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 (195) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +16 -2
  3. package/VERSION.md +3 -3
  4. package/bin/lib/menu-core.mjs +651 -16
  5. package/bin/skill-master-bootstrap-global.mjs +14 -1
  6. package/bin/skill-master-doctor.mjs +168 -0
  7. package/bin/skill-master-install-global-skills.mjs +30 -10
  8. package/bin/skill-master-menu.mjs +184 -36
  9. package/bin/skill-master-register-clients.mjs +26 -3
  10. package/dist/index.js +30 -5
  11. package/dist/index.js.map +1 -1
  12. package/docs/skill-candidates/v0.0.10/cli-creator/LICENSE.txt +201 -0
  13. package/docs/skill-candidates/v0.0.10/cli-creator/SKILL.md +160 -0
  14. package/docs/skill-candidates/v0.0.10/cli-creator/agents/openai.yaml +4 -0
  15. package/docs/skill-candidates/v0.0.10/cli-creator/references/agent-cli-patterns.md +154 -0
  16. package/docs/skill-candidates/v0.0.10/developer-workstation-ops/SKILL.md +32 -0
  17. package/docs/skill-candidates/v0.0.10/figma/LICENSE.txt +2 -0
  18. package/docs/skill-candidates/v0.0.10/figma/SKILL.md +42 -0
  19. package/docs/skill-candidates/v0.0.10/figma/agents/openai.yaml +14 -0
  20. package/docs/skill-candidates/v0.0.10/figma/assets/figma-small.svg +3 -0
  21. package/docs/skill-candidates/v0.0.10/figma/assets/figma.png +0 -0
  22. package/docs/skill-candidates/v0.0.10/figma/assets/icon.svg +28 -0
  23. package/docs/skill-candidates/v0.0.10/figma/references/figma-mcp-config.md +35 -0
  24. package/docs/skill-candidates/v0.0.10/figma/references/figma-tools-and-prompts.md +34 -0
  25. package/docs/skill-candidates/v0.0.10/figma-code-connect-components/LICENSE.TXT +2 -0
  26. package/docs/skill-candidates/v0.0.10/figma-code-connect-components/SKILL.md +349 -0
  27. package/docs/skill-candidates/v0.0.10/figma-code-connect-components/agents/openai.yaml +14 -0
  28. package/docs/skill-candidates/v0.0.10/figma-code-connect-components/assets/figma-small.svg +3 -0
  29. package/docs/skill-candidates/v0.0.10/figma-code-connect-components/assets/figma.png +0 -0
  30. package/docs/skill-candidates/v0.0.10/figma-code-connect-components/assets/icon.svg +28 -0
  31. package/docs/skill-candidates/v0.0.10/figma-code-connect-components/references/mapping-checklist.md +7 -0
  32. package/docs/skill-candidates/v0.0.10/figma-code-connect-components/scripts/normalize_node_id.py +25 -0
  33. package/docs/skill-candidates/v0.0.10/figma-create-design-system-rules/LICENSE.TXT +2 -0
  34. package/docs/skill-candidates/v0.0.10/figma-create-design-system-rules/SKILL.md +537 -0
  35. package/docs/skill-candidates/v0.0.10/figma-create-design-system-rules/agents/openai.yaml +14 -0
  36. package/docs/skill-candidates/v0.0.10/figma-create-design-system-rules/assets/figma-small.svg +3 -0
  37. package/docs/skill-candidates/v0.0.10/figma-create-design-system-rules/assets/figma.png +0 -0
  38. package/docs/skill-candidates/v0.0.10/figma-create-design-system-rules/assets/icon.svg +28 -0
  39. package/docs/skill-candidates/v0.0.10/figma-create-design-system-rules/references/rule-template.md +15 -0
  40. package/docs/skill-candidates/v0.0.10/figma-create-design-system-rules/scripts/check_agents_md.sh +9 -0
  41. package/docs/skill-candidates/v0.0.10/figma-generate-design/LICENSE.TXT +2 -0
  42. package/docs/skill-candidates/v0.0.10/figma-generate-design/SKILL.md +341 -0
  43. package/docs/skill-candidates/v0.0.10/figma-generate-design/agents/openai.yaml +14 -0
  44. package/docs/skill-candidates/v0.0.10/figma-generate-design/assets/figma-small.svg +3 -0
  45. package/docs/skill-candidates/v0.0.10/figma-generate-design/assets/figma.png +0 -0
  46. package/docs/skill-candidates/v0.0.10/figma-generate-design/assets/icon.svg +28 -0
  47. package/docs/skill-candidates/v0.0.10/figma-generate-design/maintainers.yml +1 -0
  48. package/docs/skill-candidates/v0.0.10/figma-generate-library/LICENSE.TXT +2 -0
  49. package/docs/skill-candidates/v0.0.10/figma-generate-library/SKILL.md +314 -0
  50. package/docs/skill-candidates/v0.0.10/figma-generate-library/agents/openai.yaml +14 -0
  51. package/docs/skill-candidates/v0.0.10/figma-generate-library/assets/figma-small.svg +3 -0
  52. package/docs/skill-candidates/v0.0.10/figma-generate-library/assets/figma.png +0 -0
  53. package/docs/skill-candidates/v0.0.10/figma-generate-library/assets/icon.svg +28 -0
  54. package/docs/skill-candidates/v0.0.10/figma-generate-library/maintainers.yml +3 -0
  55. package/docs/skill-candidates/v0.0.10/figma-generate-library/references/code-connect-setup.md +260 -0
  56. package/docs/skill-candidates/v0.0.10/figma-generate-library/references/component-creation.md +1014 -0
  57. package/docs/skill-candidates/v0.0.10/figma-generate-library/references/discovery-phase.md +518 -0
  58. package/docs/skill-candidates/v0.0.10/figma-generate-library/references/documentation-creation.md +834 -0
  59. package/docs/skill-candidates/v0.0.10/figma-generate-library/references/error-recovery.md +540 -0
  60. package/docs/skill-candidates/v0.0.10/figma-generate-library/references/naming-conventions.md +527 -0
  61. package/docs/skill-candidates/v0.0.10/figma-generate-library/references/token-creation.md +962 -0
  62. package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/bindVariablesToComponent.js +110 -0
  63. package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/cleanupOrphans.js +127 -0
  64. package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/createComponentWithVariants.js +148 -0
  65. package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/createDocumentationPage.js +139 -0
  66. package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/createSemanticTokens.js +108 -0
  67. package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/createVariableCollection.js +49 -0
  68. package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/inspectFileStructure.js +121 -0
  69. package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/rehydrateState.js +92 -0
  70. package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/validateCreation.js +83 -0
  71. package/docs/skill-candidates/v0.0.10/figma-implement-design/LICENSE.txt +2 -0
  72. package/docs/skill-candidates/v0.0.10/figma-implement-design/SKILL.md +258 -0
  73. package/docs/skill-candidates/v0.0.10/figma-implement-design/agents/openai.yaml +14 -0
  74. package/docs/skill-candidates/v0.0.10/figma-implement-design/assets/figma-small.svg +3 -0
  75. package/docs/skill-candidates/v0.0.10/figma-implement-design/assets/figma.png +0 -0
  76. package/docs/skill-candidates/v0.0.10/figma-implement-design/assets/icon.svg +28 -0
  77. package/docs/skill-candidates/v0.0.10/figma-use/LICENSE.TXT +2 -0
  78. package/docs/skill-candidates/v0.0.10/figma-use/SKILL.md +233 -0
  79. package/docs/skill-candidates/v0.0.10/figma-use/agents/openai.yaml +14 -0
  80. package/docs/skill-candidates/v0.0.10/figma-use/assets/figma-small.svg +3 -0
  81. package/docs/skill-candidates/v0.0.10/figma-use/assets/figma.png +0 -0
  82. package/docs/skill-candidates/v0.0.10/figma-use/assets/icon.svg +28 -0
  83. package/docs/skill-candidates/v0.0.10/figma-use/maintainers.yml +1 -0
  84. package/docs/skill-candidates/v0.0.10/figma-use/references/api-reference.md +301 -0
  85. package/docs/skill-candidates/v0.0.10/figma-use/references/common-patterns.md +512 -0
  86. package/docs/skill-candidates/v0.0.10/figma-use/references/component-patterns.md +488 -0
  87. package/docs/skill-candidates/v0.0.10/figma-use/references/effect-style-patterns.md +123 -0
  88. package/docs/skill-candidates/v0.0.10/figma-use/references/gotchas.md +599 -0
  89. package/docs/skill-candidates/v0.0.10/figma-use/references/maintainers.yml +12 -0
  90. package/docs/skill-candidates/v0.0.10/figma-use/references/plugin-api-patterns.md +513 -0
  91. package/docs/skill-candidates/v0.0.10/figma-use/references/plugin-api-standalone.d.ts +11293 -0
  92. package/docs/skill-candidates/v0.0.10/figma-use/references/plugin-api-standalone.index.md +441 -0
  93. package/docs/skill-candidates/v0.0.10/figma-use/references/text-style-patterns.md +203 -0
  94. package/docs/skill-candidates/v0.0.10/figma-use/references/validation-and-recovery.md +109 -0
  95. package/docs/skill-candidates/v0.0.10/figma-use/references/variable-patterns.md +354 -0
  96. package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/maintainers.yml +9 -0
  97. package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-components--creating.md +17 -0
  98. package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-components--using.md +17 -0
  99. package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-components.md +50 -0
  100. package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-effect-styles.md +52 -0
  101. package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-text-styles.md +90 -0
  102. package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-variables--creating.md +13 -0
  103. package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-variables--using.md +13 -0
  104. package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-variables.md +64 -0
  105. package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds.md +41 -0
  106. package/docs/skill-candidates/v0.0.10/frontend-design/LICENSE.txt +177 -0
  107. package/docs/skill-candidates/v0.0.10/frontend-design/SKILL.md +55 -0
  108. package/docs/skill-candidates/v0.0.10/frontend-ui-ux-systems/SKILL.md +32 -0
  109. package/docs/skill-candidates/v0.0.10/github/SKILL.md +74 -0
  110. package/docs/skill-candidates/v0.0.10/github/agents/openai.yaml +6 -0
  111. package/docs/skill-candidates/v0.0.10/github/assets/github-small.svg +3 -0
  112. package/docs/skill-candidates/v0.0.10/github/assets/github.png +0 -0
  113. package/docs/skill-candidates/v0.0.10/image-graphic-design-rendering/SKILL.md +28 -0
  114. package/docs/skill-candidates/v0.0.10/language-quality-pt-en-fr-it-ru/SKILL.md +28 -0
  115. package/docs/skill-candidates/v0.0.10/math-physics-reasoning/SKILL.md +28 -0
  116. package/docs/skill-candidates/v0.0.10/mcp-builder/LICENSE.txt +202 -0
  117. package/docs/skill-candidates/v0.0.10/mcp-builder/SKILL.md +236 -0
  118. package/docs/skill-candidates/v0.0.10/mcp-builder/reference/evaluation.md +602 -0
  119. package/docs/skill-candidates/v0.0.10/mcp-builder/reference/mcp_best_practices.md +249 -0
  120. package/docs/skill-candidates/v0.0.10/mcp-builder/reference/node_mcp_server.md +970 -0
  121. package/docs/skill-candidates/v0.0.10/mcp-builder/reference/python_mcp_server.md +719 -0
  122. package/docs/skill-candidates/v0.0.10/mcp-builder/scripts/connections.py +151 -0
  123. package/docs/skill-candidates/v0.0.10/mcp-builder/scripts/evaluation.py +373 -0
  124. package/docs/skill-candidates/v0.0.10/mcp-builder/scripts/example_evaluation.xml +22 -0
  125. package/docs/skill-candidates/v0.0.10/mcp-builder/scripts/requirements.txt +2 -0
  126. package/docs/skill-candidates/v0.0.10/mcp-client-readiness/SKILL.md +31 -0
  127. package/docs/skill-candidates/v0.0.10/openai-docs/LICENSE.txt +201 -0
  128. package/docs/skill-candidates/v0.0.10/openai-docs/SKILL.md +161 -0
  129. package/docs/skill-candidates/v0.0.10/openai-docs/agents/openai.yaml +14 -0
  130. package/docs/skill-candidates/v0.0.10/openai-docs/assets/openai-small.svg +3 -0
  131. package/docs/skill-candidates/v0.0.10/openai-docs/assets/openai.png +0 -0
  132. package/docs/skill-candidates/v0.0.10/openai-docs/references/latest-model.md +37 -0
  133. package/docs/skill-candidates/v0.0.10/openai-docs/references/prompting-guide.md +244 -0
  134. package/docs/skill-candidates/v0.0.10/openai-docs/references/upgrade-guide.md +181 -0
  135. package/docs/skill-candidates/v0.0.10/openai-docs/scripts/fetch-codex-manual.mjs +598 -0
  136. package/docs/skill-candidates/v0.0.10/openai-docs/scripts/resolve-latest-model-info.js +147 -0
  137. package/docs/skill-candidates/v0.0.10/playwright/LICENSE.txt +201 -0
  138. package/docs/skill-candidates/v0.0.10/playwright/NOTICE.txt +14 -0
  139. package/docs/skill-candidates/v0.0.10/playwright/SKILL.md +147 -0
  140. package/docs/skill-candidates/v0.0.10/playwright/agents/openai.yaml +6 -0
  141. package/docs/skill-candidates/v0.0.10/playwright/assets/playwright-small.svg +3 -0
  142. package/docs/skill-candidates/v0.0.10/playwright/assets/playwright.png +0 -0
  143. package/docs/skill-candidates/v0.0.10/playwright/references/cli.md +116 -0
  144. package/docs/skill-candidates/v0.0.10/playwright/references/workflows.md +95 -0
  145. package/docs/skill-candidates/v0.0.10/playwright/scripts/playwright_cli.sh +25 -0
  146. package/docs/skill-candidates/v0.0.10/polyglot-backend-engineering/SKILL.md +32 -0
  147. package/docs/skill-candidates/v0.0.10/screenshot/LICENSE.txt +201 -0
  148. package/docs/skill-candidates/v0.0.10/screenshot/SKILL.md +267 -0
  149. package/docs/skill-candidates/v0.0.10/screenshot/agents/openai.yaml +6 -0
  150. package/docs/skill-candidates/v0.0.10/screenshot/assets/screenshot-small.svg +5 -0
  151. package/docs/skill-candidates/v0.0.10/screenshot/assets/screenshot.png +0 -0
  152. package/docs/skill-candidates/v0.0.10/screenshot/scripts/ensure_macos_permissions.sh +54 -0
  153. package/docs/skill-candidates/v0.0.10/screenshot/scripts/macos_display_info.swift +22 -0
  154. package/docs/skill-candidates/v0.0.10/screenshot/scripts/macos_permissions.swift +40 -0
  155. package/docs/skill-candidates/v0.0.10/screenshot/scripts/macos_window_info.swift +126 -0
  156. package/docs/skill-candidates/v0.0.10/screenshot/scripts/take_screenshot.ps1 +163 -0
  157. package/docs/skill-candidates/v0.0.10/screenshot/scripts/take_screenshot.py +585 -0
  158. package/docs/skill-candidates/v0.0.10/skill-master-orchestrator/SKILL.md +62 -0
  159. package/docs/skill-candidates/v0.0.10/skill-master-orchestrator/agents/openai.yaml +4 -0
  160. package/docs/skill-candidates/v0.0.10/skill-master-orchestrator/references/activation-policy.md +77 -0
  161. package/docs/skill-candidates/v0.0.10/skill-master-orchestrator/references/human-approval-policy.md +83 -0
  162. package/docs/skill-candidates/v0.0.10/skill-master-orchestrator/references/persona-dev-senior-master.md +46 -0
  163. package/docs/skill-candidates/v0.0.10/terminal-menu-operations/SKILL.md +30 -0
  164. package/docs/skill-candidates/v0.0.10/terminal-pixel-art-tui/SKILL.md +43 -0
  165. package/docs/skill-candidates/v0.0.10/webapp-testing/LICENSE.txt +202 -0
  166. package/docs/skill-candidates/v0.0.10/webapp-testing/SKILL.md +96 -0
  167. package/docs/skill-candidates/v0.0.10/webapp-testing/examples/console_logging.py +35 -0
  168. package/docs/skill-candidates/v0.0.10/webapp-testing/examples/element_discovery.py +40 -0
  169. package/docs/skill-candidates/v0.0.10/webapp-testing/examples/static_html_automation.py +33 -0
  170. package/docs/skill-candidates/v0.0.10/webapp-testing/scripts/with_server.py +106 -0
  171. package/docs/skill-candidates/v0.0.10/winui-app/LICENSE.txt +202 -0
  172. package/docs/skill-candidates/v0.0.10/winui-app/SKILL.md +94 -0
  173. package/docs/skill-candidates/v0.0.10/winui-app/agents/openai.yaml +5 -0
  174. package/docs/skill-candidates/v0.0.10/winui-app/assets/winui.png +0 -0
  175. package/docs/skill-candidates/v0.0.10/winui-app/config.yaml +50 -0
  176. package/docs/skill-candidates/v0.0.10/winui-app/references/_sections.md +96 -0
  177. package/docs/skill-candidates/v0.0.10/winui-app/references/accessibility-input-and-localization.md +51 -0
  178. package/docs/skill-candidates/v0.0.10/winui-app/references/build-run-and-launch-verification.md +72 -0
  179. package/docs/skill-candidates/v0.0.10/winui-app/references/community-toolkit-controls-and-helpers.md +57 -0
  180. package/docs/skill-candidates/v0.0.10/winui-app/references/controls-layout-and-adaptive-ui.md +84 -0
  181. package/docs/skill-candidates/v0.0.10/winui-app/references/foundation-environment-audit-and-remediation.md +82 -0
  182. package/docs/skill-candidates/v0.0.10/winui-app/references/foundation-setup-and-project-selection.md +67 -0
  183. package/docs/skill-candidates/v0.0.10/winui-app/references/foundation-template-first-recovery.md +62 -0
  184. package/docs/skill-candidates/v0.0.10/winui-app/references/foundation-winui-app-structure.md +62 -0
  185. package/docs/skill-candidates/v0.0.10/winui-app/references/motion-animations-and-polish.md +45 -0
  186. package/docs/skill-candidates/v0.0.10/winui-app/references/performance-diagnostics-and-responsiveness.md +46 -0
  187. package/docs/skill-candidates/v0.0.10/winui-app/references/sample-source-map.md +37 -0
  188. package/docs/skill-candidates/v0.0.10/winui-app/references/shell-navigation-and-windowing.md +67 -0
  189. package/docs/skill-candidates/v0.0.10/winui-app/references/styling-theming-materials-and-icons.md +71 -0
  190. package/docs/skill-candidates/v0.0.10/winui-app/references/testing-debugging-and-review-checklists.md +77 -0
  191. package/docs/skill-candidates/v0.0.10/winui-app/references/windows-app-sdk-lifecycle-notifications-and-deployment.md +52 -0
  192. package/manifests/channels/beta.json +7 -7
  193. package/manifests/channels/stable.json +8 -8
  194. package/network/unapproved-skill-candidates.json +34 -1
  195. package/package.json +2 -1
@@ -0,0 +1,962 @@
1
+ > Part of the [figma-generate-library skill](../SKILL.md).
2
+
3
+ # Token Creation Reference
4
+
5
+ This document covers Phase 1: creating variable collections, modes, primitives, semantic aliases, scopes, code syntax, styles, and validation. All code is copy-paste ready for `use_figma`.
6
+
7
+ ---
8
+
9
+ ## 1. Collection Architecture
10
+
11
+ Choose the pattern that matches your token count and complexity:
12
+
13
+ ### Simple Pattern (< 50 tokens)
14
+
15
+ One collection, 2 modes. Appropriate for small projects or brand kits.
16
+
17
+ ```
18
+ Collection: "Tokens" modes: ["Light", "Dark"]
19
+ color/bg/primary → Light: #FFFFFF, Dark: #1A1A1A
20
+ spacing/sm = 8
21
+ ```
22
+
23
+ ### Standard Pattern (50–200 tokens) — Recommended Starting Point
24
+
25
+ Separate primitives from semantics. The real-world reference is Figma's Simple Design System (SDS): 7 collections, 368 variables, light/dark modes on semantic colors, single-mode primitives.
26
+
27
+ ```
28
+ Collection: "Primitives" modes: ["Value"] ← raw hex values, no modes
29
+ blue/500 = #3B82F6
30
+ gray/900 = #111827
31
+ white/1000 = #FFFFFF
32
+
33
+ Collection: "Color" modes: ["Light", "Dark"] ← aliases to Primitives
34
+ color/bg/primary → Light: alias Primitives/white/1000, Dark: alias Primitives/gray/900
35
+ color/text/primary → Light: alias Primitives/gray/900, Dark: alias Primitives/white/1000
36
+
37
+ Collection: "Spacing" modes: ["Value"]
38
+ spacing/xs = 4, spacing/sm = 8, spacing/md = 16, spacing/lg = 24, spacing/xl = 32
39
+
40
+ Collection: "Typography Primitives" modes: ["Value"]
41
+ family/sans = "Inter", scale/01 = 12, scale/02 = 14, scale/03 = 16, weight/regular = 400
42
+
43
+ Collection: "Typography" modes: ["Value"] ← aliases to Typography Primitives
44
+ body/font-family → alias family/sans
45
+ body/size-md → alias scale/03
46
+ ```
47
+
48
+ ### Advanced Pattern (200+ tokens) — M3 Model
49
+
50
+ Multiple semantic collections, 4–8 modes. Use when you need light/dark × contrast × brand or responsive breakpoints.
51
+
52
+ ```
53
+ Collection: "M3" modes: ["Light", "Dark", "Light High Contrast", "Dark High Contrast", ...]
54
+ Collection: "Typeface" modes: ["Baseline", "Wireframe"]
55
+ Collection: "Typescale" modes: ["Value"] ← aliases into Typeface
56
+ Collection: "Shape" modes: ["Value"]
57
+ ```
58
+
59
+ Key insight from M3: ALL 196 semantic color variables live in a SINGLE collection with 8 modes. Switching a frame's mode once updates every color simultaneously.
60
+
61
+ ---
62
+
63
+ ## 2. Creating Collections + Modes
64
+
65
+ ### Creating a Primitives Collection
66
+
67
+ ```javascript
68
+ (async () => {
69
+ try {
70
+ const RUN_ID = "ds-build-2024-001"; // use the same RUN_ID throughout the build
71
+
72
+ // Create the collection
73
+ const primColl = figma.variables.createVariableCollection("Primitives");
74
+
75
+ // Rename the default "Mode 1" to "Value"
76
+ primColl.renameMode(primColl.modes[0].modeId, "Value");
77
+ const valueMode = primColl.modes[0].modeId;
78
+
79
+ // Tag for idempotency
80
+ primColl.setPluginData('dsb_run_id', RUN_ID);
81
+ primColl.setPluginData('dsb_key', 'collection/primitives');
82
+
83
+ figma.closePlugin(JSON.stringify({
84
+ collectionId: primColl.id,
85
+ modeId: valueMode,
86
+ name: primColl.name
87
+ }));
88
+ } catch(e) { figma.closePluginWithFailure(e.toString()); }
89
+ })();
90
+ ```
91
+
92
+ ### Creating a Semantic Color Collection with Light/Dark Modes
93
+
94
+ ```javascript
95
+ (async () => {
96
+ try {
97
+ const RUN_ID = "ds-build-2024-001";
98
+
99
+ const colorColl = figma.variables.createVariableCollection("Color");
100
+
101
+ // Rename default "Mode 1" to "Light"
102
+ colorColl.renameMode(colorColl.modes[0].modeId, "Light");
103
+ const lightModeId = colorColl.modes[0].modeId;
104
+
105
+ // Add "Dark" mode — requires Professional plan or higher
106
+ // Throws "in addMode: Limited to N modes only" on Starter plan
107
+ const darkModeId = colorColl.addMode("Dark");
108
+
109
+ colorColl.setPluginData('dsb_run_id', RUN_ID);
110
+ colorColl.setPluginData('dsb_key', 'collection/color');
111
+
112
+ figma.closePlugin(JSON.stringify({
113
+ collectionId: colorColl.id,
114
+ lightModeId,
115
+ darkModeId
116
+ }));
117
+ } catch(e) { figma.closePluginWithFailure(e.toString()); }
118
+ })();
119
+ ```
120
+
121
+ **Mode plan limits:** Starter = 1 mode, Professional = 4 modes, Organization/Enterprise = 40 modes. If `addMode` throws, the file is on a Starter plan — tell the user and ask how to proceed.
122
+
123
+ ### Creating a Spacing Collection (single mode)
124
+
125
+ ```javascript
126
+ (async () => {
127
+ try {
128
+ const RUN_ID = "ds-build-2024-001";
129
+
130
+ const spacingColl = figma.variables.createVariableCollection("Spacing");
131
+ spacingColl.renameMode(spacingColl.modes[0].modeId, "Value");
132
+ const valueMode = spacingColl.modes[0].modeId;
133
+
134
+ spacingColl.setPluginData('dsb_run_id', RUN_ID);
135
+ spacingColl.setPluginData('dsb_key', 'collection/spacing');
136
+
137
+ figma.closePlugin(JSON.stringify({
138
+ collectionId: spacingColl.id,
139
+ modeId: valueMode
140
+ }));
141
+ } catch(e) { figma.closePluginWithFailure(e.toString()); }
142
+ })();
143
+ ```
144
+
145
+ ---
146
+
147
+ ## 3. Creating All Variable Types
148
+
149
+ ### hex → {r, g, b} Conversion Helper
150
+
151
+ Colors in the Figma Plugin API are 0–1 range, not 0–255. Embed this helper in any script that creates color variables:
152
+
153
+ ```javascript
154
+ function hexToRgb(hex) {
155
+ const clean = hex.replace('#', '');
156
+ return {
157
+ r: parseInt(clean.substring(0, 2), 16) / 255,
158
+ g: parseInt(clean.substring(2, 4), 16) / 255,
159
+ b: parseInt(clean.substring(4, 6), 16) / 255
160
+ };
161
+ }
162
+
163
+ // With alpha channel (for semi-transparent primitives like Black/200 at 10%):
164
+ function hexToRgba(hex) {
165
+ const clean = hex.replace('#', '');
166
+ const hasAlpha = clean.length === 8;
167
+ return {
168
+ r: parseInt(clean.substring(0, 2), 16) / 255,
169
+ g: parseInt(clean.substring(2, 4), 16) / 255,
170
+ b: parseInt(clean.substring(4, 6), 16) / 255,
171
+ a: hasAlpha ? parseInt(clean.substring(6, 8), 16) / 255 : 1
172
+ };
173
+ }
174
+
175
+ // Usage:
176
+ // hexToRgb('#3B82F6') → {r: 0.231, g: 0.510, b: 0.965}
177
+ // hexToRgb('#14AE5C') → {r: 0.078, g: 0.682, b: 0.361}
178
+ // hexToRgba('#0c0c0d1a') → {r: 0.047, g: 0.047, b: 0.051, a: 0.102}
179
+ ```
180
+
181
+ ### Creating Primitive Color Variables (Real SDS Data)
182
+
183
+ This creates a subset of the Simple Design System's `Color Primitives` collection (Blue family, from the Standard pattern used by real design systems):
184
+
185
+ ```javascript
186
+ (async () => {
187
+ try {
188
+ function hexToRgb(hex) {
189
+ const c = hex.replace('#', '');
190
+ return { r: parseInt(c.slice(0,2),16)/255, g: parseInt(c.slice(2,4),16)/255, b: parseInt(c.slice(4,6),16)/255 };
191
+ }
192
+
193
+ const RUN_ID = "ds-build-2024-001";
194
+
195
+ // Get the Primitives collection created in the previous step
196
+ const collections = await figma.variables.getLocalVariableCollectionsAsync();
197
+ const primColl = collections.find(c => c.getPluginData('dsb_key') === 'collection/primitives');
198
+ if (!primColl) throw new Error("Primitives collection not found — run collection creation first");
199
+ const valueMode = primColl.modes[0].modeId;
200
+
201
+ // Define primitives — use real values from your codebase
202
+ const primitiveColors = [
203
+ // Blue scale
204
+ { name: 'blue/100', hex: '#EFF6FF' },
205
+ { name: 'blue/200', hex: '#DBEAFE' },
206
+ { name: 'blue/300', hex: '#93C5FD' },
207
+ { name: 'blue/400', hex: '#60A5FA' },
208
+ { name: 'blue/500', hex: '#3B82F6' },
209
+ { name: 'blue/600', hex: '#2563EB' },
210
+ { name: 'blue/700', hex: '#1D4ED8' },
211
+ { name: 'blue/800', hex: '#1E40AF' },
212
+ { name: 'blue/900', hex: '#1E3A8A' },
213
+ // Gray scale
214
+ { name: 'gray/100', hex: '#F9FAFB' },
215
+ { name: 'gray/200', hex: '#F3F4F6' },
216
+ { name: 'gray/300', hex: '#D1D5DB' },
217
+ { name: 'gray/400', hex: '#9CA3AF' },
218
+ { name: 'gray/500', hex: '#6B7280' },
219
+ { name: 'gray/600', hex: '#4B5563' },
220
+ { name: 'gray/700', hex: '#374151' },
221
+ { name: 'gray/800', hex: '#1F2937' },
222
+ { name: 'gray/900', hex: '#111827' },
223
+ // White / Black
224
+ { name: 'white/1000', hex: '#FFFFFF' },
225
+ { name: 'black/1000', hex: '#000000' },
226
+ ];
227
+
228
+ const created = [];
229
+ for (const { name, hex } of primitiveColors) {
230
+ const v = figma.variables.createVariable(name, primColl, 'COLOR');
231
+ v.setValueForMode(valueMode, hexToRgb(hex));
232
+ // Primitives: EMPTY scopes (hidden from all pickers — designers use semantics)
233
+ v.scopes = [];
234
+ // Code syntax from the actual CSS variable name
235
+ v.setVariableCodeSyntax('WEB', `var(--color-${name.replace('/', '-')})`);
236
+ v.setPluginData('dsb_run_id', RUN_ID);
237
+ v.setPluginData('dsb_key', `primitive/${name}`);
238
+ created.push({ name, id: v.id });
239
+ }
240
+
241
+ figma.closePlugin(JSON.stringify({ created, count: created.length }));
242
+ } catch(e) { figma.closePluginWithFailure(e.toString()); }
243
+ })();
244
+ ```
245
+
246
+ **Critical scope rule for primitives:** Set `v.scopes = []`. This hides primitives from every picker. Designers should only see semantic tokens. The exception is semi-transparent overlay primitives (Black/White with alpha) — those get `["EFFECT_COLOR"]` so they appear in shadow pickers.
247
+
248
+ ### Creating FLOAT Variables (Spacing, Radius, Font Size)
249
+
250
+ ```javascript
251
+ (async () => {
252
+ try {
253
+ const RUN_ID = "ds-build-2024-001";
254
+ const collections = await figma.variables.getLocalVariableCollectionsAsync();
255
+ const spacingColl = collections.find(c => c.getPluginData('dsb_key') === 'collection/spacing');
256
+ if (!spacingColl) throw new Error("Spacing collection not found");
257
+ const valueMode = spacingColl.modes[0].modeId;
258
+
259
+ const spacingTokens = [
260
+ { name: 'spacing/xs', value: 4, scope: 'GAP', cssVar: '--spacing-xs' },
261
+ { name: 'spacing/sm', value: 8, scope: 'GAP', cssVar: '--spacing-sm' },
262
+ { name: 'spacing/md', value: 16, scope: 'GAP', cssVar: '--spacing-md' },
263
+ { name: 'spacing/lg', value: 24, scope: 'GAP', cssVar: '--spacing-lg' },
264
+ { name: 'spacing/xl', value: 32, scope: 'GAP', cssVar: '--spacing-xl' },
265
+ { name: 'spacing/2xl', value: 48, scope: 'GAP', cssVar: '--spacing-2xl' },
266
+ ];
267
+
268
+ const radiusTokens = [
269
+ { name: 'radius/none', value: 0, scope: 'CORNER_RADIUS', cssVar: '--radius-none' },
270
+ { name: 'radius/sm', value: 4, scope: 'CORNER_RADIUS', cssVar: '--radius-sm' },
271
+ { name: 'radius/md', value: 8, scope: 'CORNER_RADIUS', cssVar: '--radius-md' },
272
+ { name: 'radius/lg', value: 16, scope: 'CORNER_RADIUS', cssVar: '--radius-lg' },
273
+ { name: 'radius/full', value: 9999, scope: 'CORNER_RADIUS', cssVar: '--radius-full' },
274
+ ];
275
+
276
+ const created = [];
277
+ for (const { name, value, scope, cssVar } of [...spacingTokens, ...radiusTokens]) {
278
+ const v = figma.variables.createVariable(name, spacingColl, 'FLOAT');
279
+ v.setValueForMode(valueMode, value);
280
+ v.scopes = [scope];
281
+ v.setVariableCodeSyntax('WEB', `var(${cssVar})`);
282
+ v.setPluginData('dsb_run_id', RUN_ID);
283
+ v.setPluginData('dsb_key', name);
284
+ created.push({ name, value, id: v.id });
285
+ }
286
+
287
+ figma.closePlugin(JSON.stringify({ created, count: created.length }));
288
+ } catch(e) { figma.closePluginWithFailure(e.toString()); }
289
+ })();
290
+ ```
291
+
292
+ ### Creating STRING Variables (Font Family, Font Style)
293
+
294
+ ```javascript
295
+ (async () => {
296
+ try {
297
+ const RUN_ID = "ds-build-2024-001";
298
+ const collections = await figma.variables.getLocalVariableCollectionsAsync();
299
+ const typoPrimColl = collections.find(c => c.getPluginData('dsb_key') === 'collection/typography-primitives');
300
+ if (!typoPrimColl) throw new Error("Typography Primitives collection not found");
301
+ const valueMode = typoPrimColl.modes[0].modeId;
302
+
303
+ const fontTokens = [
304
+ { name: 'family/sans', value: 'Inter', scope: 'FONT_FAMILY', cssVar: '--font-family-sans' },
305
+ { name: 'family/mono', value: 'Roboto Mono', scope: 'FONT_FAMILY', cssVar: '--font-family-mono' },
306
+ // Font style strings — these are the Figma fontName.style values:
307
+ { name: 'weight/regular', value: 'Regular', scope: 'FONT_STYLE', cssVar: '--font-weight-regular' },
308
+ { name: 'weight/medium', value: 'Medium', scope: 'FONT_STYLE', cssVar: '--font-weight-medium' },
309
+ { name: 'weight/semibold', value: 'Semi Bold', scope: 'FONT_STYLE', cssVar: '--font-weight-semibold' },
310
+ { name: 'weight/bold', value: 'Bold', scope: 'FONT_STYLE', cssVar: '--font-weight-bold' },
311
+ ];
312
+
313
+ const created = [];
314
+ for (const { name, value, scope, cssVar } of fontTokens) {
315
+ const v = figma.variables.createVariable(name, typoPrimColl, 'STRING');
316
+ v.setValueForMode(valueMode, value);
317
+ v.scopes = [scope];
318
+ v.setVariableCodeSyntax('WEB', `var(${cssVar})`);
319
+ v.setPluginData('dsb_run_id', RUN_ID);
320
+ v.setPluginData('dsb_key', `typo-prim/${name}`);
321
+ created.push({ name, value, id: v.id });
322
+ }
323
+
324
+ figma.closePlugin(JSON.stringify({ created, count: created.length }));
325
+ } catch(e) { figma.closePluginWithFailure(e.toString()); }
326
+ })();
327
+ ```
328
+
329
+ ### Creating BOOLEAN Variables
330
+
331
+ BOOLEAN variables have no scopes (scopes are not supported for BOOLEAN type).
332
+
333
+ ```javascript
334
+ (async () => {
335
+ try {
336
+ const RUN_ID = "ds-build-2024-001";
337
+ const collections = await figma.variables.getLocalVariableCollectionsAsync();
338
+ const coll = collections.find(c => c.getPluginData('dsb_key') === 'collection/tokens');
339
+ if (!coll) throw new Error("Collection not found");
340
+ const valueMode = coll.modes[0].modeId;
341
+
342
+ const v = figma.variables.createVariable('feature-flags/show-beta-badge', coll, 'BOOLEAN');
343
+ v.setValueForMode(valueMode, false);
344
+ // No scopes — BOOLEAN does not support scopes
345
+ v.setPluginData('dsb_run_id', RUN_ID);
346
+ v.setPluginData('dsb_key', 'feature-flags/show-beta-badge');
347
+
348
+ figma.closePlugin(JSON.stringify({ id: v.id, name: v.name }));
349
+ } catch(e) { figma.closePluginWithFailure(e.toString()); }
350
+ })();
351
+ ```
352
+
353
+ ---
354
+
355
+ ## 4. Variable Aliasing (VARIABLE_ALIAS) — Primitive → Semantic Chain
356
+
357
+ Semantic tokens reference primitives via `VARIABLE_ALIAS`. This is the core pattern that makes light/dark theming work.
358
+
359
+ **Architecture:**
360
+ ```
361
+ Color Primitives collection (1 mode: Value)
362
+ blue/500 = #3B82F6 ← raw value
363
+
364
+ Color collection (2 modes: Light, Dark)
365
+ color/bg/accent/default:
366
+ Light → VARIABLE_ALIAS → Primitives/blue/500
367
+ Dark → VARIABLE_ALIAS → Primitives/blue/300
368
+ ```
369
+
370
+ ### Complete Semantic Alias Creation Script (SDS-style)
371
+
372
+ ```javascript
373
+ (async () => {
374
+ try {
375
+ function hexToRgb(hex) {
376
+ const c = hex.replace('#', '');
377
+ return { r: parseInt(c.slice(0,2),16)/255, g: parseInt(c.slice(2,4),16)/255, b: parseInt(c.slice(4,6),16)/255 };
378
+ }
379
+
380
+ const RUN_ID = "ds-build-2024-001";
381
+ const collections = await figma.variables.getLocalVariableCollectionsAsync();
382
+
383
+ const primColl = collections.find(c => c.getPluginData('dsb_key') === 'collection/primitives');
384
+ const colorColl = collections.find(c => c.getPluginData('dsb_key') === 'collection/color');
385
+ if (!primColl || !colorColl) throw new Error("Collections not found — run primitive/color collection creation first");
386
+
387
+ const primValueMode = primColl.modes[0].modeId;
388
+ const lightModeId = colorColl.modes.find(m => m.name === 'Light').modeId;
389
+ const darkModeId = colorColl.modes.find(m => m.name === 'Dark').modeId;
390
+
391
+ // Load all primitive variables for lookup
392
+ const allVars = await figma.variables.getLocalVariablesAsync();
393
+ const primsByKey = {};
394
+ for (const v of allVars) {
395
+ if (v.variableCollectionId === primColl.id) {
396
+ primsByKey[v.getPluginData('dsb_key')] = v;
397
+ }
398
+ }
399
+
400
+ function getPrim(name) {
401
+ const v = primsByKey[`primitive/${name}`];
402
+ if (!v) throw new Error(`Primitive not found: primitive/${name}`);
403
+ return v;
404
+ }
405
+
406
+ // Define semantic → [lightPrimitiveName, darkPrimitiveName]
407
+ // Following the SDS pattern: Background/{Intent}/{Emphasis}
408
+ const semanticColors = [
409
+ // Background
410
+ { name: 'color/bg/default/default', lightPrim: 'white/1000', darkPrim: 'gray/900',
411
+ cssVar: '--color-bg-default-default', scopes: ['FRAME_FILL', 'SHAPE_FILL'] },
412
+ { name: 'color/bg/default/secondary', lightPrim: 'gray/100', darkPrim: 'gray/800',
413
+ cssVar: '--color-bg-default-secondary', scopes: ['FRAME_FILL', 'SHAPE_FILL'] },
414
+ { name: 'color/bg/brand/default', lightPrim: 'blue/600', darkPrim: 'blue/300',
415
+ cssVar: '--color-bg-brand-default', scopes: ['FRAME_FILL', 'SHAPE_FILL'] },
416
+ // Text
417
+ { name: 'color/text/default/default', lightPrim: 'gray/900', darkPrim: 'white/1000',
418
+ cssVar: '--color-text-default-default', scopes: ['TEXT_FILL'] },
419
+ { name: 'color/text/default/secondary', lightPrim: 'gray/500', darkPrim: 'gray/400',
420
+ cssVar: '--color-text-default-secondary', scopes: ['TEXT_FILL'] },
421
+ { name: 'color/text/brand/default', lightPrim: 'blue/700', darkPrim: 'blue/200',
422
+ cssVar: '--color-text-brand-default', scopes: ['TEXT_FILL'] },
423
+ // Border
424
+ { name: 'color/border/default/default', lightPrim: 'gray/300', darkPrim: 'gray/600',
425
+ cssVar: '--color-border-default-default', scopes: ['STROKE_COLOR'] },
426
+ { name: 'color/border/brand/default', lightPrim: 'blue/500', darkPrim: 'blue/400',
427
+ cssVar: '--color-border-brand-default', scopes: ['STROKE_COLOR'] },
428
+ ];
429
+
430
+ const created = [];
431
+ for (const { name, lightPrim, darkPrim, cssVar, scopes } of semanticColors) {
432
+ const v = figma.variables.createVariable(name, colorColl, 'COLOR');
433
+ // Alias to primitive in Light mode
434
+ v.setValueForMode(lightModeId, figma.variables.createVariableAlias(getPrim(lightPrim)));
435
+ // Alias to primitive in Dark mode
436
+ v.setValueForMode(darkModeId, figma.variables.createVariableAlias(getPrim(darkPrim)));
437
+ // Set scopes (semantic layer — these ARE shown in pickers)
438
+ v.scopes = scopes;
439
+ // Code syntax
440
+ v.setVariableCodeSyntax('WEB', `var(${cssVar})`);
441
+ v.setPluginData('dsb_run_id', RUN_ID);
442
+ v.setPluginData('dsb_key', name);
443
+ created.push({ name, id: v.id });
444
+ }
445
+
446
+ figma.closePlugin(JSON.stringify({ created, count: created.length }));
447
+ } catch(e) { figma.closePluginWithFailure(e.toString()); }
448
+ })();
449
+ ```
450
+
451
+ **Key API points:**
452
+ - `figma.variables.createVariableAlias(variable)` — takes a Variable object, returns `{type:'VARIABLE_ALIAS', id: variable.id}`
453
+ - The aliased variable MUST have the same `resolvedType` as the semantic variable
454
+ - Never duplicate raw values in the semantic layer — always alias
455
+
456
+ ---
457
+
458
+ ## 5. Variable Scopes — Complete Reference Table
459
+
460
+ | Semantic Role | Recommended Scopes | Variable Type |
461
+ |---|---|---|
462
+ | Primitive colors (raw) | `[]` — empty, hidden from all pickers | COLOR |
463
+ | Semi-transparent overlay primitives | `["EFFECT_COLOR"]` | COLOR |
464
+ | Background fills (frame, shape) | `["FRAME_FILL", "SHAPE_FILL"]` | COLOR |
465
+ | Text color | `["TEXT_FILL"]` | COLOR |
466
+ | Icon / shape fill | `["SHAPE_FILL", "STROKE_COLOR"]` | COLOR |
467
+ | Border / stroke color | `["STROKE_COLOR"]` | COLOR |
468
+ | Background + border combined | `["FRAME_FILL", "SHAPE_FILL", "STROKE_COLOR"]` | COLOR |
469
+ | Shadow color | `["EFFECT_COLOR"]` | COLOR |
470
+ | Spacing / gap between items | `["GAP"]` | FLOAT |
471
+ | Padding (if separate from gap) | `["GAP"]` | FLOAT |
472
+ | Corner radius | `["CORNER_RADIUS"]` | FLOAT |
473
+ | Width / height dimensions | `["WIDTH_HEIGHT"]` | FLOAT |
474
+ | Font size | `["FONT_SIZE"]` | FLOAT |
475
+ | Line height | `["LINE_HEIGHT"]` | FLOAT |
476
+ | Letter spacing | `["LETTER_SPACING"]` | FLOAT |
477
+ | Font weight (numeric) | `["FONT_WEIGHT"]` | FLOAT |
478
+ | Stroke width | `["STROKE_FLOAT"]` | FLOAT |
479
+ | Effect blur radius | `["EFFECT_FLOAT"]` | FLOAT |
480
+ | Opacity | `["OPACITY"]` | FLOAT |
481
+ | Font family | `["FONT_FAMILY"]` | STRING |
482
+ | Font style (e.g. "Semi Bold") | `["FONT_STYLE"]` | STRING |
483
+ | Boolean flags | *(scopes not supported)* | BOOLEAN |
484
+
485
+ **Never use `ALL_SCOPES`** on any variable. It pollutes every picker with irrelevant tokens. The Simple Design System (SDS), the gold standard, uses targeted scopes on every variable.
486
+
487
+ **`ALL_FILLS` note:** `ALL_FILLS` is exclusive among fill scopes — it covers `FRAME_FILL`, `SHAPE_FILL`, and `TEXT_FILL` together. If set, you cannot also add individual fill scopes. Prefer specifying individual scopes for precision.
488
+
489
+ ### Batch Scope-Setting (After Variables are Created)
490
+
491
+ If you created variables without scopes and need to set them in batch:
492
+
493
+ ```javascript
494
+ (async () => {
495
+ try {
496
+ const allVars = await figma.variables.getLocalVariablesAsync();
497
+
498
+ // Scope mapping: partial name match → scopes
499
+ const scopeRules = [
500
+ { match: 'color/bg/', scopes: ['FRAME_FILL', 'SHAPE_FILL'] },
501
+ { match: 'color/text/', scopes: ['TEXT_FILL'] },
502
+ { match: 'color/icon/', scopes: ['SHAPE_FILL', 'STROKE_COLOR'] },
503
+ { match: 'color/border/', scopes: ['STROKE_COLOR'] },
504
+ { match: 'spacing/', scopes: ['GAP'] },
505
+ { match: 'radius/', scopes: ['CORNER_RADIUS'] },
506
+ { match: 'blue/', scopes: [] }, // primitives — hide
507
+ { match: 'gray/', scopes: [] },
508
+ { match: 'white/', scopes: [] },
509
+ { match: 'black/', scopes: [] },
510
+ ];
511
+
512
+ const updated = [];
513
+ for (const v of allVars) {
514
+ if (v.remote) continue; // skip library variables
515
+ for (const rule of scopeRules) {
516
+ if (v.name.startsWith(rule.match)) {
517
+ v.scopes = rule.scopes;
518
+ updated.push({ name: v.name, scopes: rule.scopes });
519
+ break;
520
+ }
521
+ }
522
+ }
523
+
524
+ figma.closePlugin(JSON.stringify({ updated, count: updated.length }));
525
+ } catch(e) { figma.closePluginWithFailure(e.toString()); }
526
+ })();
527
+ ```
528
+
529
+ ---
530
+
531
+ ## 6. Code Syntax — WEB/ANDROID/iOS
532
+
533
+ Every variable must have code syntax set. This is what powers the developer handoff experience:
534
+
535
+ **What code syntax does:** When a developer inspects any element in Figma Dev Mode that has a variable-bound property (fill, padding, radius, etc.), the code snippet shown uses the variable's code syntax name — not the Figma variable name. For example, a button's background fill bound to `color/bg/primary` will show `background: var(--color-bg-primary)` in the CSS snippet, not `color/bg/primary`. Without code syntax set, Dev Mode shows raw hex values or nothing useful.
536
+
537
+ You can set up to **3 syntaxes per variable** — one per platform (Web, iOS, Android). Set all three if the codebase targets multiple platforms; set only WEB if it's a web-only project.
538
+
539
+ ```javascript
540
+ // WEB: MUST include the var() wrapper — this is the full CSS function syntax
541
+ variable.setVariableCodeSyntax('WEB', 'var(--color-bg-primary)');
542
+ // ^^^^ ^
543
+ // var() wrapper is REQUIRED
544
+
545
+ // ANDROID: Kotlin property name — camelCase, no wrapper
546
+ variable.setVariableCodeSyntax('ANDROID', 'colorBgPrimary');
547
+
548
+ // iOS: Swift property — dot-notation, no wrapper
549
+ variable.setVariableCodeSyntax('iOS', 'Color.bgPrimary');
550
+ ```
551
+
552
+ > **CRITICAL — WEB code syntax MUST use the `var()` wrapper.** Setting just `--color-bg-primary` (without `var()`) will cause Dev Mode to show raw hex values instead of the CSS variable reference. Always use the full `var(--name)` form. ANDROID and iOS do NOT use a wrapper.
553
+
554
+ **Platform derivation rules from the CSS variable name:**
555
+
556
+ | Platform | Pattern | Example |
557
+ |---|---|---|
558
+ | WEB | **`var(--{css-var-name})`** — `var()` wrapper required | `var(--sds-color-bg-primary)` |
559
+ | ANDROID | camelCase, no wrapper, strip `--` prefix | `sdsColorBgPrimary` |
560
+ | iOS | PascalCase after `.`, no wrapper, strip `--` prefix | `Color.SdsColorBgPrimary` or `Color.bgPrimary` |
561
+
562
+ **Always use the actual CSS variable name from the codebase** — do not derive it from the Figma variable name. If the code uses `--sds-color-background-brand-default`, that exact string is the WEB code syntax (minus the `var()` wrapper that you add).
563
+
564
+ ### Batch Code Syntax Setting
565
+
566
+ ```javascript
567
+ (async () => {
568
+ try {
569
+ const allVars = await figma.variables.getLocalVariablesAsync();
570
+ const updated = [];
571
+
572
+ for (const v of allVars) {
573
+ if (v.remote) continue;
574
+ // If code syntax already set, skip
575
+ if (v.codeSyntax['WEB']) continue;
576
+
577
+ // FALLBACK: derive from Figma name: color/bg/primary → var(--color-bg-primary)
578
+ // PREFERRED: pass in a cssVarMap built from actual codebase CSS variable names
579
+ // e.g. cssVarMap = { 'color/bg/primary': '--color-bg-primary', ... }
580
+ const cssName = cssVarMap?.[v.name]
581
+ ?? v.name.replace(/\//g, '-').replace(/\s/g, '-').toLowerCase();
582
+ v.setVariableCodeSyntax('WEB', `var(--${cssName})`);
583
+ updated.push({ name: v.name, web: `var(--${cssName})` });
584
+ }
585
+
586
+ figma.closePlugin(JSON.stringify({ updated, count: updated.length }));
587
+ } catch(e) { figma.closePluginWithFailure(e.toString()); }
588
+ })();
589
+ ```
590
+
591
+ Note: derived names are a fallback only. Always prefer overriding with actual CSS variable names from the codebase when they are known.
592
+
593
+ ---
594
+
595
+ ## 7. Effect Styles (Shadows) and Text Styles
596
+
597
+ Shadows and composite typography cannot be variables — they are Styles.
598
+
599
+ ### Creating Effect Styles (Shadows)
600
+
601
+ Reference from SDS (15 effect styles) and the SDS shadow pattern `Shadow/{Level}`:
602
+
603
+ ```javascript
604
+ (async () => {
605
+ try {
606
+ const RUN_ID = "ds-build-2024-001";
607
+
608
+ // Shadow definitions — CSS equivalent in comments
609
+ // CSS: 0 1px 2px rgba(0,0,0,0.05)
610
+ const shadows = [
611
+ {
612
+ name: 'Shadow/Subtle',
613
+ effects: [{
614
+ type: 'DROP_SHADOW',
615
+ color: { r: 0, g: 0, b: 0, a: 0.05 },
616
+ offset: { x: 0, y: 1 },
617
+ radius: 2,
618
+ spread: 0,
619
+ visible: true,
620
+ blendMode: 'NORMAL'
621
+ }]
622
+ },
623
+ {
624
+ // CSS: 0 4px 6px -1px rgba(0,0,0,0.10), 0 2px 4px -1px rgba(0,0,0,0.06)
625
+ name: 'Shadow/Medium',
626
+ effects: [
627
+ {
628
+ type: 'DROP_SHADOW',
629
+ color: { r: 0, g: 0, b: 0, a: 0.10 },
630
+ offset: { x: 0, y: 4 },
631
+ radius: 6,
632
+ spread: -1,
633
+ visible: true,
634
+ blendMode: 'NORMAL'
635
+ },
636
+ {
637
+ type: 'DROP_SHADOW',
638
+ color: { r: 0, g: 0, b: 0, a: 0.06 },
639
+ offset: { x: 0, y: 2 },
640
+ radius: 4,
641
+ spread: -1,
642
+ visible: true,
643
+ blendMode: 'NORMAL'
644
+ }
645
+ ]
646
+ },
647
+ {
648
+ // CSS: 0 10px 15px -3px rgba(0,0,0,0.10), 0 4px 6px -2px rgba(0,0,0,0.05)
649
+ name: 'Shadow/Strong',
650
+ effects: [
651
+ {
652
+ type: 'DROP_SHADOW',
653
+ color: { r: 0, g: 0, b: 0, a: 0.10 },
654
+ offset: { x: 0, y: 10 },
655
+ radius: 15,
656
+ spread: -3,
657
+ visible: true,
658
+ blendMode: 'NORMAL'
659
+ },
660
+ {
661
+ type: 'DROP_SHADOW',
662
+ color: { r: 0, g: 0, b: 0, a: 0.05 },
663
+ offset: { x: 0, y: 4 },
664
+ radius: 6,
665
+ spread: -2,
666
+ visible: true,
667
+ blendMode: 'NORMAL'
668
+ }
669
+ ]
670
+ }
671
+ ];
672
+
673
+ // M3-style dual shadow (umbra + penumbra pattern):
674
+ const m3Shadows = [
675
+ {
676
+ name: 'Elevation/1',
677
+ effects: [
678
+ { type: 'DROP_SHADOW', color: {r:0,g:0,b:0,a:0.30}, offset:{x:0,y:1}, radius:2, spread:0, visible:true, blendMode:'NORMAL' },
679
+ { type: 'DROP_SHADOW', color: {r:0,g:0,b:0,a:0.15}, offset:{x:0,y:1}, radius:3, spread:1, visible:true, blendMode:'NORMAL' }
680
+ ]
681
+ },
682
+ {
683
+ name: 'Elevation/2',
684
+ effects: [
685
+ { type: 'DROP_SHADOW', color: {r:0,g:0,b:0,a:0.30}, offset:{x:0,y:1}, radius:2, spread:0, visible:true, blendMode:'NORMAL' },
686
+ { type: 'DROP_SHADOW', color: {r:0,g:0,b:0,a:0.15}, offset:{x:0,y:2}, radius:6, spread:2, visible:true, blendMode:'NORMAL' }
687
+ ]
688
+ },
689
+ {
690
+ name: 'Elevation/3',
691
+ effects: [
692
+ { type: 'DROP_SHADOW', color: {r:0,g:0,b:0,a:0.30}, offset:{x:0,y:1}, radius:3, spread:0, visible:true, blendMode:'NORMAL' },
693
+ { type: 'DROP_SHADOW', color: {r:0,g:0,b:0,a:0.15}, offset:{x:0,y:4}, radius:8, spread:3, visible:true, blendMode:'NORMAL' }
694
+ ]
695
+ }
696
+ ];
697
+
698
+ const created = [];
699
+ for (const { name, effects } of shadows) {
700
+ const style = figma.createEffectStyle();
701
+ style.name = name;
702
+ style.effects = effects;
703
+ style.setPluginData('dsb_run_id', RUN_ID);
704
+ style.setPluginData('dsb_key', `effect-style/${name}`);
705
+ created.push({ name, id: style.id });
706
+ }
707
+
708
+ figma.closePlugin(JSON.stringify({ created, count: created.length }));
709
+ } catch(e) { figma.closePluginWithFailure(e.toString()); }
710
+ })();
711
+ ```
712
+
713
+ ### Creating Text Styles
714
+
715
+ Fonts must be loaded before creating text styles.
716
+
717
+ ```javascript
718
+ (async () => {
719
+ try {
720
+ const RUN_ID = "ds-build-2024-001";
721
+
722
+ // Define text styles — based on SDS typography hierarchy
723
+ const textStyles = [
724
+ // Display / Hero
725
+ { name: 'Display/Hero', family: 'Inter', style: 'Bold', size: 72, lineHeight: 80, letterSpacing: -1.5 },
726
+ // Headings
727
+ { name: 'Heading/H1', family: 'Inter', style: 'Bold', size: 48, lineHeight: 56, letterSpacing: -1.0 },
728
+ { name: 'Heading/H2', family: 'Inter', style: 'Bold', size: 40, lineHeight: 48, letterSpacing: -0.5 },
729
+ { name: 'Heading/H3', family: 'Inter', style: 'Semi Bold', size: 32, lineHeight: 40, letterSpacing: 0 },
730
+ { name: 'Heading/H4', family: 'Inter', style: 'Semi Bold', size: 24, lineHeight: 32, letterSpacing: 0 },
731
+ // Body
732
+ { name: 'Body/Large', family: 'Inter', style: 'Regular', size: 18, lineHeight: 28, letterSpacing: 0 },
733
+ { name: 'Body/Medium', family: 'Inter', style: 'Regular', size: 16, lineHeight: 24, letterSpacing: 0 },
734
+ { name: 'Body/Small', family: 'Inter', style: 'Regular', size: 14, lineHeight: 20, letterSpacing: 0 },
735
+ // Label
736
+ { name: 'Label/Large', family: 'Inter', style: 'Medium', size: 14, lineHeight: 20, letterSpacing: 0.1 },
737
+ { name: 'Label/Medium', family: 'Inter', style: 'Medium', size: 12, lineHeight: 16, letterSpacing: 0.5 },
738
+ { name: 'Label/Small', family: 'Inter', style: 'Medium', size: 11, lineHeight: 16, letterSpacing: 0.5 },
739
+ // Code
740
+ { name: 'Code/Base', family: 'Roboto Mono', style: 'Regular', size: 14, lineHeight: 20, letterSpacing: 0 },
741
+ ];
742
+
743
+ // Load all required fonts first
744
+ const fontSet = new Set(textStyles.map(s => JSON.stringify({ family: s.family, style: s.style })));
745
+ await Promise.all([...fontSet].map(f => figma.loadFontAsync(JSON.parse(f))));
746
+
747
+ const created = [];
748
+ for (const { name, family, style, size, lineHeight, letterSpacing } of textStyles) {
749
+ const ts = figma.createTextStyle();
750
+ ts.name = name;
751
+ ts.fontName = { family, style };
752
+ ts.fontSize = size;
753
+ ts.lineHeight = { value: lineHeight, unit: 'PIXELS' };
754
+ ts.letterSpacing = { value: letterSpacing, unit: 'PIXELS' };
755
+ ts.setPluginData('dsb_run_id', RUN_ID);
756
+ ts.setPluginData('dsb_key', `text-style/${name}`);
757
+ created.push({ name, id: ts.id });
758
+ }
759
+
760
+ figma.closePlugin(JSON.stringify({ created, count: created.length }));
761
+ } catch(e) { figma.closePluginWithFailure(e.toString()); }
762
+ })();
763
+ ```
764
+
765
+ ---
766
+
767
+ ## 8. Idempotency — Check-Before-Create Pattern
768
+
769
+ Every creation script should check whether the entity already exists before creating it. This prevents duplicates when a script is re-run after partial failure.
770
+
771
+ ### Check-Before-Create for Collections
772
+
773
+ ```javascript
774
+ (async () => {
775
+ try {
776
+ const DSB_KEY = 'collection/primitives';
777
+ const RUN_ID = "ds-build-2024-001";
778
+
779
+ // Check if already exists
780
+ const existing = await figma.variables.getLocalVariableCollectionsAsync();
781
+ let primColl = existing.find(c => c.getPluginData('dsb_key') === DSB_KEY);
782
+
783
+ if (primColl) {
784
+ figma.closePlugin(JSON.stringify({ status: 'already_exists', collectionId: primColl.id, name: primColl.name }));
785
+ return;
786
+ }
787
+
788
+ // Create only if not found
789
+ primColl = figma.variables.createVariableCollection("Primitives");
790
+ primColl.renameMode(primColl.modes[0].modeId, "Value");
791
+ primColl.setPluginData('dsb_run_id', RUN_ID);
792
+ primColl.setPluginData('dsb_key', DSB_KEY);
793
+
794
+ figma.closePlugin(JSON.stringify({ status: 'created', collectionId: primColl.id }));
795
+ } catch(e) { figma.closePluginWithFailure(e.toString()); }
796
+ })();
797
+ ```
798
+
799
+ ### Check-Before-Create for Variables
800
+
801
+ ```javascript
802
+ (async () => {
803
+ try {
804
+ const VARIABLE_KEY = 'primitive/blue/500';
805
+ const RUN_ID = "ds-build-2024-001";
806
+
807
+ // Check if already exists by pluginData key
808
+ const allVars = await figma.variables.getLocalVariablesAsync();
809
+ const existing = allVars.find(v => v.getPluginData('dsb_key') === VARIABLE_KEY);
810
+
811
+ if (existing) {
812
+ figma.closePlugin(JSON.stringify({ status: 'already_exists', id: existing.id, name: existing.name }));
813
+ return;
814
+ }
815
+
816
+ // ... create the variable ...
817
+ figma.closePlugin(JSON.stringify({ status: 'created' }));
818
+ } catch(e) { figma.closePluginWithFailure(e.toString()); }
819
+ })();
820
+ ```
821
+
822
+ ### pluginData Tagging Strategy
823
+
824
+ Tag every created node immediately after creation. The `dsb_key` is the stable logical identifier used for idempotency checks. The `dsb_run_id` identifies which build run created it (useful for cleanup).
825
+
826
+ ```javascript
827
+ node.setPluginData('dsb_run_id', RUN_ID); // build run ID
828
+ node.setPluginData('dsb_phase', 'phase1'); // which phase
829
+ node.setPluginData('dsb_key', 'color/bg/primary'); // stable logical key
830
+ ```
831
+
832
+ **Cleanup by run ID (safe — targets only tagged nodes, never user-owned nodes):**
833
+
834
+ ```javascript
835
+ (async () => {
836
+ try {
837
+ const TARGET_RUN_ID = "ds-build-2024-001"; // run to remove
838
+ const allVars = await figma.variables.getLocalVariablesAsync();
839
+ const removed = [];
840
+ for (const v of allVars) {
841
+ if (v.getPluginData('dsb_run_id') === TARGET_RUN_ID) {
842
+ removed.push(v.name);
843
+ v.remove();
844
+ }
845
+ }
846
+ figma.closePlugin(JSON.stringify({ removed, count: removed.length }));
847
+ } catch(e) { figma.closePluginWithFailure(e.toString()); }
848
+ })();
849
+ ```
850
+
851
+ **Never clean up by name prefix** (e.g., deleting everything starting with `color/`). This will destroy user-created variables that happen to share the prefix.
852
+
853
+ ---
854
+
855
+ ## 9. Validation — Verify Counts, Aliases, and Scopes
856
+
857
+ Run these scripts after Phase 1 to verify everything was created correctly before proceeding to Phase 2.
858
+
859
+ ### Verify Collection and Variable Counts
860
+
861
+ ```javascript
862
+ (async () => {
863
+ try {
864
+ const collections = await figma.variables.getLocalVariableCollectionsAsync();
865
+ const allVars = await figma.variables.getLocalVariablesAsync();
866
+
867
+ const summary = collections.map(c => {
868
+ const vars = allVars.filter(v => v.variableCollectionId === c.id);
869
+ return {
870
+ name: c.name,
871
+ id: c.id,
872
+ modes: c.modes.map(m => m.name),
873
+ variableCount: vars.length,
874
+ missingScopes: vars.filter(v => v.scopes.length === 0 && v.resolvedType !== 'BOOLEAN').length,
875
+ missingCodeSyntax: vars.filter(v => !v.codeSyntax['WEB'] && !v.remote).length,
876
+ sampleVariables: vars.slice(0, 3).map(v => v.name)
877
+ };
878
+ });
879
+
880
+ figma.closePlugin(JSON.stringify({
881
+ collectionCount: collections.length,
882
+ totalVariables: allVars.length,
883
+ collections: summary
884
+ }));
885
+ } catch(e) { figma.closePluginWithFailure(e.toString()); }
886
+ })();
887
+ ```
888
+
889
+ Interpret: `missingScopes > 0` (for non-primitives and non-BOOLEANs) → scope-setting failed, re-run scope script. `missingCodeSyntax > 0` → code syntax not set, run batch code syntax script.
890
+
891
+ Note: primitives correctly have `scopes = []` (empty, hidden). `missingScopes` above counts non-BOOLEAN variables with empty scopes — review the list to confirm they are all primitives.
892
+
893
+ ### Verify Aliases Resolve
894
+
895
+ ```javascript
896
+ (async () => {
897
+ try {
898
+ const allVars = await figma.variables.getLocalVariablesAsync();
899
+ const collections = await figma.variables.getLocalVariableCollectionsAsync();
900
+
901
+ const brokenAliases = [];
902
+ const aliasedVars = [];
903
+
904
+ for (const v of allVars) {
905
+ if (v.remote) continue;
906
+ const coll = collections.find(c => c.id === v.variableCollectionId);
907
+ if (!coll) continue;
908
+
909
+ for (const [modeId, val] of Object.entries(v.valuesByMode)) {
910
+ if (val && typeof val === 'object' && val.type === 'VARIABLE_ALIAS') {
911
+ aliasedVars.push({ name: v.name, aliasTargetId: val.id });
912
+ // Verify the target exists
913
+ const target = allVars.find(t => t.id === val.id);
914
+ if (!target) {
915
+ brokenAliases.push({ variable: v.name, modeId, missingTargetId: val.id });
916
+ }
917
+ }
918
+ }
919
+ }
920
+
921
+ figma.closePlugin(JSON.stringify({
922
+ totalAliased: aliasedVars.length,
923
+ brokenAliases,
924
+ brokenCount: brokenAliases.length,
925
+ status: brokenAliases.length === 0 ? 'all_aliases_resolve' : 'BROKEN_ALIASES_FOUND'
926
+ }));
927
+ } catch(e) { figma.closePluginWithFailure(e.toString()); }
928
+ })();
929
+ ```
930
+
931
+ Interpret: `brokenCount > 0` means a semantic variable references a primitive that was deleted or not yet created. Create the missing primitives, then re-run alias creation for the affected semantic variables.
932
+
933
+ ### Verify Style Counts
934
+
935
+ ```javascript
936
+ (async () => {
937
+ try {
938
+ const [textStyles, effectStyles] = await Promise.all([
939
+ figma.getLocalTextStylesAsync(),
940
+ figma.getLocalEffectStylesAsync()
941
+ ]);
942
+
943
+ figma.closePlugin(JSON.stringify({
944
+ textStyles: textStyles.map(s => ({ name: s.name, fontSize: s.fontSize, fontFamily: s.fontName.family })),
945
+ effectStyles: effectStyles.map(s => ({ name: s.name, effectCount: s.effects.length })),
946
+ counts: { text: textStyles.length, effect: effectStyles.length }
947
+ }));
948
+ } catch(e) { figma.closePluginWithFailure(e.toString()); }
949
+ })();
950
+ ```
951
+
952
+ ### Phase 1 Exit Criteria Checklist
953
+
954
+ Before proceeding to Phase 2, verify all of the following:
955
+
956
+ - Every planned collection exists with the correct number of modes
957
+ - Primitive variables: `scopes = []`, code syntax set
958
+ - Semantic variables: targeted scopes set, code syntax set, aliases pointing to primitives (not raw values)
959
+ - All broken alias count = 0
960
+ - All planned text styles exist with correct font family/size/weight
961
+ - All planned effect styles exist with correct shadow values
962
+ - No variable has `ALL_SCOPES` unless explicitly approved by the user