figma-console-mcp 1.40.3 → 1.40.5

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 (129) hide show
  1. package/README.md +4 -2
  2. package/dist/core/design-code-tools.d.ts.map +1 -1
  3. package/dist/core/design-code-tools.js +143 -90
  4. package/dist/core/design-code-tools.js.map +1 -1
  5. package/dist/core/figma-connector.d.ts +8 -2
  6. package/dist/core/figma-connector.d.ts.map +1 -1
  7. package/dist/core/figma-tools.d.ts.map +1 -1
  8. package/dist/core/figma-tools.js +5 -3
  9. package/dist/core/figma-tools.js.map +1 -1
  10. package/dist/core/identity.d.ts +16 -0
  11. package/dist/core/identity.d.ts.map +1 -1
  12. package/dist/core/identity.js +48 -1
  13. package/dist/core/identity.js.map +1 -1
  14. package/dist/core/tokens-tools.js +1 -1
  15. package/dist/core/websocket-connector.d.ts +2 -2
  16. package/dist/core/websocket-connector.d.ts.map +1 -1
  17. package/dist/core/websocket-connector.js +4 -4
  18. package/dist/core/websocket-connector.js.map +1 -1
  19. package/dist/core/websocket-server.d.ts +2 -0
  20. package/dist/core/websocket-server.d.ts.map +1 -1
  21. package/dist/core/websocket-server.js +4 -0
  22. package/dist/core/websocket-server.js.map +1 -1
  23. package/dist/local.d.ts.map +1 -1
  24. package/dist/local.js +3 -2
  25. package/dist/local.js.map +1 -1
  26. package/package.json +4 -3
  27. package/dist/cloudflare/apps/design-system-dashboard/scoring/accessibility.js +0 -408
  28. package/dist/cloudflare/apps/design-system-dashboard/scoring/component-metadata.js +0 -366
  29. package/dist/cloudflare/apps/design-system-dashboard/scoring/consistency.js +0 -407
  30. package/dist/cloudflare/apps/design-system-dashboard/scoring/coverage.js +0 -233
  31. package/dist/cloudflare/apps/design-system-dashboard/scoring/engine.js +0 -92
  32. package/dist/cloudflare/apps/design-system-dashboard/scoring/naming-semantics.js +0 -342
  33. package/dist/cloudflare/apps/design-system-dashboard/scoring/remediation.js +0 -168
  34. package/dist/cloudflare/apps/design-system-dashboard/scoring/token-architecture.js +0 -412
  35. package/dist/cloudflare/apps/design-system-dashboard/scoring/types.js +0 -40
  36. package/dist/cloudflare/apps/design-system-dashboard/server.js +0 -346
  37. package/dist/cloudflare/apps/token-browser/server.js +0 -136
  38. package/dist/cloudflare/browser-manager.js +0 -157
  39. package/dist/cloudflare/core/accessibility-tools.js +0 -361
  40. package/dist/cloudflare/core/annotation-tools.js +0 -230
  41. package/dist/cloudflare/core/cloud-websocket-connector.js +0 -430
  42. package/dist/cloudflare/core/cloud-websocket-relay.js +0 -198
  43. package/dist/cloudflare/core/comment-tools.js +0 -292
  44. package/dist/cloudflare/core/config.js +0 -153
  45. package/dist/cloudflare/core/console-monitor.js +0 -427
  46. package/dist/cloudflare/core/deep-component-tools.js +0 -128
  47. package/dist/cloudflare/core/design-code-tools.js +0 -3702
  48. package/dist/cloudflare/core/design-system-extraction-tools.js +0 -611
  49. package/dist/cloudflare/core/design-system-manifest.js +0 -283
  50. package/dist/cloudflare/core/design-system-tools.js +0 -882
  51. package/dist/cloudflare/core/diagnose-tool.js +0 -100
  52. package/dist/cloudflare/core/diff/changelog-formatter.js +0 -275
  53. package/dist/cloudflare/core/diff/diff-engine.js +0 -334
  54. package/dist/cloudflare/core/diff/property-compare.js +0 -36
  55. package/dist/cloudflare/core/diff/version-cache.js +0 -74
  56. package/dist/cloudflare/core/enrichment/enrichment-service.js +0 -374
  57. package/dist/cloudflare/core/enrichment/index.js +0 -7
  58. package/dist/cloudflare/core/enrichment/relationship-mapper.js +0 -351
  59. package/dist/cloudflare/core/enrichment/style-resolver.js +0 -346
  60. package/dist/cloudflare/core/extraction/analyzer.js +0 -161
  61. package/dist/cloudflare/core/extraction/architecture.js +0 -159
  62. package/dist/cloudflare/core/extraction/component-inventory.js +0 -485
  63. package/dist/cloudflare/core/extraction/detectors.js +0 -148
  64. package/dist/cloudflare/core/extraction/scaffolder.js +0 -614
  65. package/dist/cloudflare/core/extraction/storybook-preset.js +0 -349
  66. package/dist/cloudflare/core/extraction/token-extractor.js +0 -759
  67. package/dist/cloudflare/core/extraction/types.js +0 -14
  68. package/dist/cloudflare/core/extraction/verifier.js +0 -137
  69. package/dist/cloudflare/core/extraction/walker.js +0 -118
  70. package/dist/cloudflare/core/figjam-tools.js +0 -547
  71. package/dist/cloudflare/core/figma-api.js +0 -509
  72. package/dist/cloudflare/core/figma-connector.js +0 -7
  73. package/dist/cloudflare/core/figma-reconstruction-spec.js +0 -402
  74. package/dist/cloudflare/core/figma-style-extractor.js +0 -311
  75. package/dist/cloudflare/core/figma-tools.js +0 -3302
  76. package/dist/cloudflare/core/history/component-history.js +0 -388
  77. package/dist/cloudflare/core/history/git-history.js +0 -161
  78. package/dist/cloudflare/core/history/history-formatter.js +0 -116
  79. package/dist/cloudflare/core/identity.js +0 -96
  80. package/dist/cloudflare/core/library-tools.js +0 -629
  81. package/dist/cloudflare/core/logger.js +0 -53
  82. package/dist/cloudflare/core/multi-file-tools.js +0 -161
  83. package/dist/cloudflare/core/port-discovery.js +0 -1016
  84. package/dist/cloudflare/core/resolve-package-root.js +0 -11
  85. package/dist/cloudflare/core/slides-tools.js +0 -714
  86. package/dist/cloudflare/core/slot-tools.js +0 -334
  87. package/dist/cloudflare/core/snippet-injector.js +0 -96
  88. package/dist/cloudflare/core/tokens/alias-resolver.js +0 -205
  89. package/dist/cloudflare/core/tokens/config.js +0 -294
  90. package/dist/cloudflare/core/tokens/dialect.js +0 -232
  91. package/dist/cloudflare/core/tokens/figma-converter.js +0 -323
  92. package/dist/cloudflare/core/tokens/formatters/css-vars.js +0 -345
  93. package/dist/cloudflare/core/tokens/formatters/dtcg.js +0 -376
  94. package/dist/cloudflare/core/tokens/formatters/index.js +0 -45
  95. package/dist/cloudflare/core/tokens/formatters/json.js +0 -205
  96. package/dist/cloudflare/core/tokens/formatters/less.js +0 -4
  97. package/dist/cloudflare/core/tokens/formatters/scss.js +0 -264
  98. package/dist/cloudflare/core/tokens/formatters/stubs.js +0 -13
  99. package/dist/cloudflare/core/tokens/formatters/style-dictionary-v3.js +0 -213
  100. package/dist/cloudflare/core/tokens/formatters/tailwind-v3.js +0 -237
  101. package/dist/cloudflare/core/tokens/formatters/tailwind-v4.js +0 -341
  102. package/dist/cloudflare/core/tokens/formatters/tokens-studio.js +0 -256
  103. package/dist/cloudflare/core/tokens/formatters/ts-module.js +0 -198
  104. package/dist/cloudflare/core/tokens/index.js +0 -16
  105. package/dist/cloudflare/core/tokens/parsers/css-vars.js +0 -4
  106. package/dist/cloudflare/core/tokens/parsers/dtcg.js +0 -280
  107. package/dist/cloudflare/core/tokens/parsers/index.js +0 -138
  108. package/dist/cloudflare/core/tokens/parsers/json.js +0 -7
  109. package/dist/cloudflare/core/tokens/parsers/scss.js +0 -4
  110. package/dist/cloudflare/core/tokens/parsers/stubs.js +0 -20
  111. package/dist/cloudflare/core/tokens/parsers/style-dictionary-v3.js +0 -4
  112. package/dist/cloudflare/core/tokens/parsers/tailwind-v3.js +0 -4
  113. package/dist/cloudflare/core/tokens/parsers/tailwind-v4.js +0 -4
  114. package/dist/cloudflare/core/tokens/parsers/tokens-studio.js +0 -4
  115. package/dist/cloudflare/core/tokens/schemas.js +0 -152
  116. package/dist/cloudflare/core/tokens/transforms/color.js +0 -12
  117. package/dist/cloudflare/core/tokens/transforms/index.js +0 -29
  118. package/dist/cloudflare/core/tokens/transforms/size.js +0 -7
  119. package/dist/cloudflare/core/tokens/types.js +0 -18
  120. package/dist/cloudflare/core/tokens-tools.js +0 -2238
  121. package/dist/cloudflare/core/types/design-code.js +0 -4
  122. package/dist/cloudflare/core/types/enriched.js +0 -5
  123. package/dist/cloudflare/core/types/index.js +0 -4
  124. package/dist/cloudflare/core/variable-resolver.js +0 -85
  125. package/dist/cloudflare/core/version-tools.js +0 -1199
  126. package/dist/cloudflare/core/websocket-connector.js +0 -439
  127. package/dist/cloudflare/core/websocket-server.js +0 -1050
  128. package/dist/cloudflare/core/write-tools.js +0 -2505
  129. package/dist/cloudflare/index.js +0 -3010
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  > **Your design system as an API.** Model Context Protocol server that bridges design and development—giving AI assistants complete access to Figma for **extraction**, **creation**, **debugging**, and **bidirectional token sync**.
10
10
 
11
- > **🆕 Design System Extraction (v1.40.0, patched v1.40.3):** Turn a production codebase into a design system. Seven new `figma_ds_*` tools (Local Mode) scan one or more app codebases — framework, styling-method, and vendor-layer detection; a usage-ranked component inventory classified vendored / wrapped / pure-vendor / bespoke; variant inference from real call sites; duplicate detection; and an architecture pass that separates a UI kit from a design system (`FollowButton` is really a `Button` — and here are the missing generic primitives). Then they mine the app's de-facto styling into DTCG tokens with per-token provenance — multi-mode CSS custom properties (`.dark`, `[data-theme]`, and friends), SCSS variables, Tailwind config values, shadcn HSL triples, Tailwind utility-class frequency mining valued from the app's own theme, and frequency-promoted raw values — scaffold the design-system package with token/typography/iconography showcase pages, wire a fresh Storybook workshop to the app's real theme layers and fonts, deep-extract components for porting with CSF3 story scaffolds, gate the result behind deterministic fidelity evals, and persist porting progress across sessions. The extracted `tokens/tokens.json` imports straight into Figma variables via `figma_import_tokens` — a full code → design system → Figma round-trip. Also fixes the token formatters quoting CSS functional expressions like `cubic-bezier(...)` (a quoted easing silently kills transitions — affects `figma_export_tokens` output too). Server-only — no plugin re-import needed. [See what's new →](CHANGELOG.md#1400---2026-08-16)
11
+ > **🆕 Design System Extraction (v1.40.0, patched v1.40.5):** Turn a production codebase into a design system. Seven new `figma_ds_*` tools (Local Mode) scan one or more app codebases — framework, styling-method, and vendor-layer detection; a usage-ranked component inventory classified vendored / wrapped / pure-vendor / bespoke; variant inference from real call sites; duplicate detection; and an architecture pass that separates a UI kit from a design system (`FollowButton` is really a `Button` — and here are the missing generic primitives). Then they mine the app's de-facto styling into DTCG tokens with per-token provenance — multi-mode CSS custom properties (`.dark`, `[data-theme]`, and friends), SCSS variables, Tailwind config values, shadcn HSL triples, Tailwind utility-class frequency mining valued from the app's own theme, and frequency-promoted raw values — scaffold the design-system package with token/typography/iconography showcase pages, wire a fresh Storybook workshop to the app's real theme layers and fonts, deep-extract components for porting with CSF3 story scaffolds, gate the result behind deterministic fidelity evals, and persist porting progress across sessions. The extracted `tokens/tokens.json` imports straight into Figma variables via `figma_import_tokens` — a full code → design system → Figma round-trip. Also fixes the token formatters quoting CSS functional expressions like `cubic-bezier(...)` (a quoted easing silently kills transitions — affects `figma_export_tokens` output too). Server-only — no plugin re-import needed. [See what's new →](CHANGELOG.md#1400---2026-08-16)
12
12
 
13
13
  ## What is this?
14
14
 
@@ -840,9 +840,11 @@ The architecture supports adding new apps with minimal boilerplate — each app
840
840
 
841
841
  ## 🛤️ Roadmap
842
842
 
843
- **Current Status:** v1.40.3 (Stable) - Production-ready. Latest: fidelity fixes for generated component docs on harder components — hidden layers are labeled instead of documented as rendered, one-sided borders report the weight that actually renders, typography and layer structure are compared across every variant instead of read from the first, gradients/shadows/opacity are no longer silently dropped, truncated trees say so, and `SLOT` properties are listed (server-only, no plugin re-import). On top of v1.40.2's data-loss fix for `figma_export_tokens` — with a different file active in Figma than intended, an export could replace a real token file with an empty one (or with another file's tokens) and report success; exports now refuse to write when a requested collection is missing, when the export is empty, when the target was generated from a different Figma file, or when overwriting would destroy tokens the export doesn't manage, and `strategy: "merge"` — documented as preserving code-only tokens but never implemented — is now a genuinely safe default (server-only, no plugin re-import). On top of v1.40.1's accuracy fixes for generated component docs across variants — a descendant's fill (typically an icon's) could be documented as a variant's Background, Color Token headings kept only a property literally named `Variant`, spacing was read from the first variant alone, and icons were only detected by layer name — plus `figma_export_tokens` accepting a file path as `outputPath` instead of failing with a raw `EEXIST` or silently creating a directory (server-only, no plugin re-import). On top of v1.40.0's design system extraction from production codebases — seven Local-Mode `figma_ds_*` tools that scan one or more apps (framework/styling/vendor detection, usage-ranked component inventory classified vendored/wrapped/pure-vendor/bespoke, variant inference from real call sites, duplicate detection, and an architecture pass separating UI-kit specializations from the missing generic primitives), mine the de-facto styling into DTCG tokens with per-token provenance (multi-mode CSS custom properties, SCSS, Tailwind config, shadcn HSL triples, utility-class frequency mining valued from the app's own theme), scaffold the design-system package with showcase docs pages, wire a fresh Storybook workshop to the app's real theme layers and fonts, deep-extract components for porting with CSF3 story scaffolds, verify with deterministic fidelity evals, and persist porting progress across sessions — the extracted tokens import straight into Figma variables via `figma_import_tokens`. On top of v1.39.1's fix for a plugin-update banner that could never be cleared — an older server instance nagging a newer plugin to re-import, which only ever installs the same or a newer plugin. On top of v1.39.0's multi-file execution — `figma_execute_across_files` runs one script against several Desktop Bridge-connected files concurrently (four files x 2s of work in ~3s, not ~8s), and `figma_execute` takes an optional `fileKey` to target a single file without moving the active file or releasing target lock; targeting must be explicit (`fileKeys` or `allFiles: true`), so nothing fans out to files you did not name. From community PR #107 by @Wolfr. Ships with a relay fix: `handleResult()` in the plugin's ui.html rebuilt each response field by field and silently dropped `resultAnalysis` and `fileContext`, so the `resultAnalysis.warning` check that `figma_execute`'s own description tells callers to perform has never been possible (re-import `manifest.json` for those two fields; everything else works without it). On top of v1.38.2's connection-stability fix — the server's own orphan reaper was terminating healthy MCP servers because its liveness probe used an IPv4 literal while the server binds the IPv6 loopback, which turned every kill-safety gate into a rubber stamp; this was the cause of recurring "Server disconnected" errors across all MCP clients. Alongside v1.38.1's fix for the shared-library variable tools, which had silently reported zero collections since v1.29.0 and reported failed imports as successes. On top of v1.38.0's ongoing component changelogs in generated docs — `figma_generate_component_doc` takes an opt-in `history` parameter that pulls per-component **design history** from Figma version history (each version diffed and scoped to that component, so renames, added properties, and token bindings show up as rows) alongside **code history** from `git log` on the component's source files, rendered as a `## History` section with design, code, and release-note tables. Prefers labeled versions, falls back to auto-saves when a file has none, and is off by default so existing calls are unchanged (server-only, no plugin re-import). On top of v1.37.x's design-system health audit for every client — `figma_audit_design_system_report` runs the dashboard's deterministic six-category scoring engine and returns the report as data with per-finding remediation, live-first fileKey-verified data (source disclosed), chunked per-category drill-down, and a 5-minute cache (server-only, no plugin re-import). On top of v1.36.0's target lock for multi-file parallel work — — `figma_navigate` takes a `lock: true` flag that pins the active file so an AI agent can work in one file while you work in another without commands routing to the wrong file. On top of v1.35.0's Figma Slots write support — create, inspect, populate, and reset Slots (GA at Config 2026) via 5 new tools (`figma_create_slot`, `figma_get_slots`, `figma_append_to_slot`, `figma_reset_slot`, `figma_add_slot_property`), live-validated against the GA Plugin API and based on community PR #77. On top of v1.34.0's Bidirectional Token Sync v2 + DTCG 2025.10 — `figma_import_tokens` applies the complete diff plan (creates missing collections/variables, applies renames, writes real `VARIABLE_ALIAS` references, and deletes only under explicit `replace`), `figma_export_tokens` speaks the DTCG 2025.10 dialect on request (legacy default byte-identical), variable scopes/codeSyntax round-trip via `$extensions`, `figma_setup_design_tokens` accepts alias values via DTCG brace references, and `figma_create_component_set` builds a full variant set from an axes matrix in one call. On top of the v1.33.x line: version-handshake fix (re-import banner only fires when plugin files actually changed), security dependency sweep, and the v1.33.0 connection UX overhaul (honest status pill derived from live connection state, `/health` auto-discovery with self-healing reconnect) + a 33-fix full-codebase audit (lossless DTCG multi-mode round-trips, cross-collection alias resolution, branch-URL correctness across REST tools, cache-poisoning and CSWSH fixes, bridge-first screenshots). Built on WCAG-accurate accessibility auditing (line height below 1.5× is no longer mis-flagged as a failure; readability hints decoupled from conformance checks and scoped to multi-line text; code-side WCAG 1.4.12 check), a self-healing Desktop Bridge connection (zombie-process reaper + auto-reconnect watchdog — fixes the recurring "not connected until restart" bug), native variable binding on fills/strokes + typography control in the write tools, shared-library inspection (key-based component resolution + library variable read/import without Enterprise plan), 10-format token export pipeline (DTCG, CSS, Tailwind v4, Tailwind v3, SCSS, TS module, JSON flat/nested, Style Dictionary v3, Tokens Studio), bidirectional Figma↔code token sync, version history & time-series awareness, FigJam + Slides support, Cloud Write Relay, Design System Kit, WebSocket-only connectivity, smart multi-file tracking, **121 tools** (Local) / **101 tools** (Cloud) / **9 tools** (Remote read-only), Comments API, cross-MCP identity disambiguation, and MCP Apps.
843
+ **Current Status:** v1.40.5 (Stable) - Production-ready. Latest: a clean dependency audit for the npm package — Worker-only dependencies (`@cloudflare/puppeteer`, which pulled in a high-severity `extract-zip` advisory with no upstream fix, and `agents`) no longer install for npm users, so `npm install figma-console-mcp` audits at 0 vulnerabilities and is 64 MB instead of 173 MB; the server also reports its real version to MCP clients instead of `0.1.0` (server-only, no plugin re-import). On top of v1.40.4's fixes found by live-testing v1.40.3 on hard production components — tools given a `fileUrl` now read variables, descriptions and annotations from THAT file rather than whichever file is active (previously every token name could vanish, or on an id collision come from the wrong file; `figma_get_component` could return a different component), a tool result over 16 MB no longer disconnects the server, anatomy labels sizing by the layout's real axes, and the icon/typography/anatomy output stays readable on large components (server-only, no plugin re-import). On top of v1.40.3's fidelity fixes for generated component docs on harder components — hidden layers are labeled instead of documented as rendered, one-sided borders report the weight that actually renders, typography and layer structure are compared across every variant instead of read from the first, gradients/shadows/opacity are no longer silently dropped, truncated trees say so, and `SLOT` properties are listed (server-only, no plugin re-import). On top of v1.40.2's data-loss fix for `figma_export_tokens` — with a different file active in Figma than intended, an export could replace a real token file with an empty one (or with another file's tokens) and report success; exports now refuse to write when a requested collection is missing, when the export is empty, when the target was generated from a different Figma file, or when overwriting would destroy tokens the export doesn't manage, and `strategy: "merge"` — documented as preserving code-only tokens but never implemented — is now a genuinely safe default (server-only, no plugin re-import). On top of v1.40.1's accuracy fixes for generated component docs across variants — a descendant's fill (typically an icon's) could be documented as a variant's Background, Color Token headings kept only a property literally named `Variant`, spacing was read from the first variant alone, and icons were only detected by layer name — plus `figma_export_tokens` accepting a file path as `outputPath` instead of failing with a raw `EEXIST` or silently creating a directory (server-only, no plugin re-import). On top of v1.40.0's design system extraction from production codebases — seven Local-Mode `figma_ds_*` tools that scan one or more apps (framework/styling/vendor detection, usage-ranked component inventory classified vendored/wrapped/pure-vendor/bespoke, variant inference from real call sites, duplicate detection, and an architecture pass separating UI-kit specializations from the missing generic primitives), mine the de-facto styling into DTCG tokens with per-token provenance (multi-mode CSS custom properties, SCSS, Tailwind config, shadcn HSL triples, utility-class frequency mining valued from the app's own theme), scaffold the design-system package with showcase docs pages, wire a fresh Storybook workshop to the app's real theme layers and fonts, deep-extract components for porting with CSF3 story scaffolds, verify with deterministic fidelity evals, and persist porting progress across sessions — the extracted tokens import straight into Figma variables via `figma_import_tokens`. On top of v1.39.1's fix for a plugin-update banner that could never be cleared — an older server instance nagging a newer plugin to re-import, which only ever installs the same or a newer plugin. On top of v1.39.0's multi-file execution — `figma_execute_across_files` runs one script against several Desktop Bridge-connected files concurrently (four files x 2s of work in ~3s, not ~8s), and `figma_execute` takes an optional `fileKey` to target a single file without moving the active file or releasing target lock; targeting must be explicit (`fileKeys` or `allFiles: true`), so nothing fans out to files you did not name. From community PR #107 by @Wolfr. Ships with a relay fix: `handleResult()` in the plugin's ui.html rebuilt each response field by field and silently dropped `resultAnalysis` and `fileContext`, so the `resultAnalysis.warning` check that `figma_execute`'s own description tells callers to perform has never been possible (re-import `manifest.json` for those two fields; everything else works without it). On top of v1.38.2's connection-stability fix — the server's own orphan reaper was terminating healthy MCP servers because its liveness probe used an IPv4 literal while the server binds the IPv6 loopback, which turned every kill-safety gate into a rubber stamp; this was the cause of recurring "Server disconnected" errors across all MCP clients. Alongside v1.38.1's fix for the shared-library variable tools, which had silently reported zero collections since v1.29.0 and reported failed imports as successes. On top of v1.38.0's ongoing component changelogs in generated docs — `figma_generate_component_doc` takes an opt-in `history` parameter that pulls per-component **design history** from Figma version history (each version diffed and scoped to that component, so renames, added properties, and token bindings show up as rows) alongside **code history** from `git log` on the component's source files, rendered as a `## History` section with design, code, and release-note tables. Prefers labeled versions, falls back to auto-saves when a file has none, and is off by default so existing calls are unchanged (server-only, no plugin re-import). On top of v1.37.x's design-system health audit for every client — `figma_audit_design_system_report` runs the dashboard's deterministic six-category scoring engine and returns the report as data with per-finding remediation, live-first fileKey-verified data (source disclosed), chunked per-category drill-down, and a 5-minute cache (server-only, no plugin re-import). On top of v1.36.0's target lock for multi-file parallel work — — `figma_navigate` takes a `lock: true` flag that pins the active file so an AI agent can work in one file while you work in another without commands routing to the wrong file. On top of v1.35.0's Figma Slots write support — create, inspect, populate, and reset Slots (GA at Config 2026) via 5 new tools (`figma_create_slot`, `figma_get_slots`, `figma_append_to_slot`, `figma_reset_slot`, `figma_add_slot_property`), live-validated against the GA Plugin API and based on community PR #77. On top of v1.34.0's Bidirectional Token Sync v2 + DTCG 2025.10 — `figma_import_tokens` applies the complete diff plan (creates missing collections/variables, applies renames, writes real `VARIABLE_ALIAS` references, and deletes only under explicit `replace`), `figma_export_tokens` speaks the DTCG 2025.10 dialect on request (legacy default byte-identical), variable scopes/codeSyntax round-trip via `$extensions`, `figma_setup_design_tokens` accepts alias values via DTCG brace references, and `figma_create_component_set` builds a full variant set from an axes matrix in one call. On top of the v1.33.x line: version-handshake fix (re-import banner only fires when plugin files actually changed), security dependency sweep, and the v1.33.0 connection UX overhaul (honest status pill derived from live connection state, `/health` auto-discovery with self-healing reconnect) + a 33-fix full-codebase audit (lossless DTCG multi-mode round-trips, cross-collection alias resolution, branch-URL correctness across REST tools, cache-poisoning and CSWSH fixes, bridge-first screenshots). Built on WCAG-accurate accessibility auditing (line height below 1.5× is no longer mis-flagged as a failure; readability hints decoupled from conformance checks and scoped to multi-line text; code-side WCAG 1.4.12 check), a self-healing Desktop Bridge connection (zombie-process reaper + auto-reconnect watchdog — fixes the recurring "not connected until restart" bug), native variable binding on fills/strokes + typography control in the write tools, shared-library inspection (key-based component resolution + library variable read/import without Enterprise plan), 10-format token export pipeline (DTCG, CSS, Tailwind v4, Tailwind v3, SCSS, TS module, JSON flat/nested, Style Dictionary v3, Tokens Studio), bidirectional Figma↔code token sync, version history & time-series awareness, FigJam + Slides support, Cloud Write Relay, Design System Kit, WebSocket-only connectivity, smart multi-file tracking, **121 tools** (Local) / **101 tools** (Cloud) / **9 tools** (Remote read-only), Comments API, cross-MCP identity disambiguation, and MCP Apps.
844
844
 
845
845
  **Recent Releases:**
846
+ - [x] **v1.40.5** - Clean dependency audit. `@cloudflare/puppeteer` (→ `@puppeteer/browsers` → `extract-zip`, high, no upstream fix) and `agents` were regular dependencies but are only used by the Cloudflare Worker; they are now devDependencies and the Worker build is excluded from the npm package, so a fresh install audits at 0 vulnerabilities (was 4 high) and drops from 173 MB to 64 MB. The local server now reports its real version to MCP clients instead of `0.1.0`. A new test guards both directions of the dependency split. Server-only. Reported by Daniel Westerlund.
847
+ - [x] **v1.40.4** - Live-test fixes. Tools given a `fileUrl` (`figma_generate_component_doc`, `figma_get_component`, `figma_get_component_for_development`) read bridge data from the ACTIVE file instead of the named one — token names vanished, and on an id collision another file's names, description, annotations or even component were returned; they now target the named file and fall back to REST. A tool result over 16 MB disconnected the server for the session (the client closes the transport); results are now size-capped centrally with an actionable error. Anatomy sizing labels follow the real layout axes (vertical layouts were mislabeled); icons are grouped, counted and named by the glyph in the slot; typography rows are identified by (element, style); identical sibling layers collapse to `×N`. Found by testing on hard production components. Server-only.
846
848
  - [x] **v1.40.3** - Component-doc fidelity on harder components. `figma_generate_component_doc`: hidden layers labeled rather than documented as rendered; border width honors `individualStrokeWeights` (a tab underline is `4px bottom`, not the vestigial scalar) and is reported only where a stroke is painted; typography and anatomy compared across every variant (an *Applies to* column; one tree per distinct layer structure) instead of read from the first; gradients, images, shadows, blurs and opacity reported instead of dropped; fetch depth raised from 4 to 8 with an explicit note when a tree may be cut off; boolean-valued variant properties keep their name; Configurable Properties renders for variant-only sets and lists `SLOT` properties; every icon shown, not just the first; Design-Code Parity compares property values (it could report a property *name* as a missing variant); placeholder `status`/`version`/description no longer asserted. Server-only. Reported by Robin Di Capua.
847
849
  - [x] **v1.40.2** - Token export data-loss fix. `figma_export_tokens` reads from whichever file is active in Figma Desktop; with the wrong one focused it could overwrite a real token file with an empty document — or, for CSS/SCSS/Tailwind/TS outputs, with another file's tokens — and report `success: true`. Exports now refuse (writing nothing) when a requested collection isn't in the file that was read, when the export is empty, when the target was generated from a different Figma file (generated files now record their source), when a DTCG target can't be parsed or would be replaced by a different kind of output, or when overwriting would destroy hand-added tokens or another collection's tokens. `strategy: "merge"` had been documented as preserving code-only tokens but was never implemented; it is now the safe default and `replace` is the explicit override. Every response reports the `source` Figma file. Server-only. Reported by Isabella Minzly; two further loss paths found in code review.
848
850
  - [x] **v1.40.1** - Component-doc variant accuracy + token export file paths. `figma_generate_component_doc`: Background now comes only from the variant's own fills (a transparent outline/ghost variant no longer borrows its icon's color), Color Token headings name every variant property under any property names, Spacing Tokens are compared across all variants (`varies by **Size**: …`, with a Spacing Inconsistencies list for properties tokenized on some variants and hardcoded on others), and icons resolve from the main component and `INSTANCE_SWAP` slots rather than a layer name containing "icon". `figma_export_tokens`: an `outputPath` that is an existing file or ends in a token-file extension is written as that exact file; multi-file exports into a file path are rejected before anything is written. `figma_search_components` no longer reports a failed component load as a successful empty search. Server-only. Reported by Robin Di Capua and Isabella Minzly.
@@ -1 +1 @@
1
- {"version":3,"file":"design-code-tools.d.ts","sourceRoot":"","sources":["../../src/core/design-code-tools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"design-code-tools.d.ts","sourceRoot":"","sources":["../../src/core/design-code-tools.ts"],"names":[],"mappings":"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"}
@@ -376,14 +376,16 @@ function describeEffect(effect) {
376
376
  if (!effect || effect.visible === false || typeof effect.type !== "string")
377
377
  return null;
378
378
  if (effect.type === "DROP_SHADOW" || effect.type === "INNER_SHADOW") {
379
- const color = effect.color ? figmaRGBAToHex({ ...effect.color, a: effect.color.a ?? 1 }) : "";
380
- const x = effect.offset?.x ?? 0, y = effect.offset?.y ?? 0;
381
- return `${effect.type === "DROP_SHADOW" ? "drop" : "inner"} shadow: x ${x} · y ${y} · blur ${effect.radius ?? 0} · spread ${effect.spread ?? 0}${color ? ` · ${color}` : ""}`;
379
+ const color = effect.color ? figmaRGBAToHex({ ...effect.color, a: effect.color.a ?? 1 }).toUpperCase() : "";
380
+ // Scaled instances produce values like 0.39000001549720764
381
+ const n = (v) => Math.round((v ?? 0) * 100) / 100;
382
+ return `${effect.type === "DROP_SHADOW" ? "drop" : "inner"} shadow: x ${n(effect.offset?.x)} · y ${n(effect.offset?.y)} · blur ${n(effect.radius)} · spread ${n(effect.spread)}${color ? ` · ${color}` : ""}`;
382
383
  }
384
+ const radius = Math.round((effect.radius ?? 0) * 100) / 100;
383
385
  if (effect.type === "LAYER_BLUR")
384
- return `layer blur ${effect.radius ?? 0}px`;
386
+ return `layer blur ${radius}px`;
385
387
  if (effect.type === "BACKGROUND_BLUR")
386
- return `background blur ${effect.radius ?? 0}px`;
388
+ return `background blur ${radius}px`;
387
389
  return `${effect.type.toLowerCase().replace(/_/g, " ")} effect`;
388
390
  }
389
391
  /** Largest dimension (px) an instance-swap slot can have and still be treated as an icon */
@@ -414,13 +416,32 @@ function detectIconInstance(node, lookup) {
414
416
  if (!nameSaysIcon && !(swapProperty && iconSized))
415
417
  return null;
416
418
  const cleanedLayerName = layerName.replace(/^icon\s*\/?\s*/i, "").trim() || layerName;
419
+ // Many systems wrap the glyph: an "Icon (small)" instance whose only job is to
420
+ // hold a swappable "MagnifyingGlass". The wrapper says nothing about WHICH icon
421
+ // this is — the instance nested inside it does.
422
+ const glyph = findNestedGlyphName(node, lookup);
417
423
  return {
418
- name: mainName || cleanedLayerName,
424
+ name: glyph || mainName || cleanedLayerName,
419
425
  type: "instance",
420
426
  layerName,
421
427
  ...(swapProperty ? { swapProperty } : {}),
422
428
  };
423
429
  }
430
+ /** Name of the first instance nested inside an icon wrapper (breadth-first, ≤3 levels) */
431
+ function findNestedGlyphName(icon, lookup) {
432
+ let level = Array.isArray(icon.children) ? icon.children : [];
433
+ for (let depth = 0; depth < 3 && level.length > 0; depth++) {
434
+ for (const child of level) {
435
+ if (child.type === "INSTANCE") {
436
+ const name = resolveMainComponentName(child.componentId, lookup);
437
+ if (name)
438
+ return name;
439
+ }
440
+ }
441
+ level = level.flatMap((c) => (Array.isArray(c.children) ? c.children : []));
442
+ }
443
+ return undefined;
444
+ }
424
445
  /** True when the node has at least one visible paint (solid, gradient, image…) */
425
446
  function hasVisibleSolidFill(node) {
426
447
  return Array.isArray(node?.fills) && node.fills.some((f) => describePaint(f) !== null);
@@ -626,64 +647,43 @@ const typographySignature = (ts) => `${ts.fontFamily}|${ts.fontWeight}|${ts.font
626
647
  */
627
648
  export function collectTypographyAcrossVariants(setNode) {
628
649
  const variants = Array.isArray(setNode?.children) ? setNode.children : [];
650
+ // A row's identity is (element name, style) — NOT its position in the tree.
651
+ // Positional identity ("text-8 #4") made ten nav items' badges ten separate
652
+ // elements, each with its own scope, and printed the same fact three times.
653
+ const rowKey = (ts) => `${ts.nodeName}|${typographySignature(ts)}`;
629
654
  const perVariant = variants.map((v) => {
630
- // Same-named text layers within one variant are told apart by order
631
- const seen = new Map();
632
- const byElement = new Map();
633
- for (const ts of collectTypographyData(v)) {
634
- const n = (seen.get(ts.nodeName) ?? 0) + 1;
635
- seen.set(ts.nodeName, n);
636
- byElement.set(n === 1 ? ts.nodeName : `${ts.nodeName} #${n}`, ts);
637
- }
638
- return { name: cleanVariantName(v.name || "Unknown"), props: parseVariantProperties(v.name || ""), byElement };
655
+ const keys = new Map();
656
+ for (const ts of collectTypographyData(v))
657
+ if (!keys.has(rowKey(ts)))
658
+ keys.set(rowKey(ts), ts);
659
+ return { name: cleanVariantName(v.name || "Unknown"), props: parseVariantProperties(v.name || ""), keys };
639
660
  });
640
- const elements = [];
641
- for (const pv of perVariant)
642
- for (const el of pv.byElement.keys())
643
- if (!elements.includes(el))
644
- elements.push(el);
645
- const rows = [];
646
- for (const el of elements) {
647
- const having = perVariant.filter((pv) => pv.byElement.has(el));
648
- const distinct = new Map();
649
- for (const pv of having) {
650
- const style = pv.byElement.get(el);
651
- const sig = typographySignature(style);
652
- if (!distinct.has(sig))
653
- distinct.set(sig, { style, names: [] });
654
- distinct.get(sig).names.push(pv.name);
655
- }
656
- if (distinct.size === 1) {
657
- const only = [...distinct.values()][0];
658
- // Present in only some variants? Say which, rather than implying all.
659
- const scope = having.length === perVariant.length ? "" : scopeLabel(only.names, perVariant.length);
660
- rows.push({ style: { ...only.style, nodeName: el }, scope });
661
- continue;
661
+ const order = [];
662
+ const styles = new Map();
663
+ for (const pv of perVariant) {
664
+ for (const [key, ts] of pv.keys) {
665
+ if (!styles.has(key)) {
666
+ styles.set(key, ts);
667
+ order.push(key);
668
+ }
669
+ }
670
+ }
671
+ return order.map((key) => {
672
+ const style = styles.get(key);
673
+ const having = perVariant.filter((pv) => pv.keys.has(key));
674
+ let scope = "";
675
+ if (having.length < perVariant.length) {
676
+ // Which variant property decides where this (element, style) appears?
677
+ const explanation = explainByVariantProperty(perVariant.map((pv) => ({ props: pv.props, display: pv.keys.has(key) ? "yes" : "no" })));
678
+ const values = explanation?.groups.filter(([, display]) => display === "yes").map(([pv]) => pv) ?? [];
679
+ scope = explanation && values.length > 0
680
+ ? `${explanation.property}=${values.join(" | ")}`
681
+ : scopeLabel(having.map((pv) => pv.name), perVariant.length);
662
682
  }
663
- const explanation = explainByVariantProperty(having.map((pv) => ({ props: pv.props, display: typographySignature(pv.byElement.get(el)) })));
664
- for (const [sig, group] of distinct) {
665
- const propValue = explanation?.groups.filter(([, display]) => display === sig).map(([pv]) => pv);
666
- const scope = explanation && propValue && propValue.length > 0
667
- ? `${explanation.property}=${propValue.join(" | ")}`
668
- : scopeLabel(group.names, perVariant.length);
669
- rows.push({ style: { ...group.style, nodeName: el }, scope });
670
- }
671
- }
672
- // Same-named layers ("Label", "Label #2", "Label #3" — three tabs in a tab list)
673
- // that share a style and a scope are one fact, not three rows.
674
- const merged = [];
675
- const seenRows = new Set();
676
- for (const row of rows) {
677
- const baseName = row.style.nodeName.replace(/ #\d+$/, "");
678
- const key = `${baseName}|${typographySignature(row.style)}|${row.scope}|${row.style.hidden ? "h" : ""}`;
679
- if (seenRows.has(key))
680
- continue;
681
- seenRows.add(key);
682
683
  // Text layers are often auto-named after their content — keep the table readable
683
- const label = baseName.length > 40 ? `${baseName.slice(0, 40)}…` : baseName;
684
- merged.push({ ...row, style: { ...row.style, nodeName: label } });
685
- }
686
- return merged;
684
+ const label = style.nodeName.length > 40 ? `${style.nodeName.slice(0, 40)}…` : style.nodeName;
685
+ return { style: { ...style, nodeName: label }, scope };
686
+ });
687
687
  }
688
688
  /** "Primary / Default, Primary / Hover, +3 more" — enough to identify, short enough to read */
689
689
  function scopeLabel(names, total) {
@@ -729,7 +729,9 @@ function countChildDepth(node) {
729
729
  }
730
730
  return 1 + max;
731
731
  }
732
- function buildAnatomyLines(node, lines, prefix, isLast, depth, maxDepth) {
732
+ function buildAnatomyLines(node, lines, prefix, isLast, depth, maxDepth,
733
+ /** Appended to this node's own line — " ×7" for a collapsed run of identical siblings */
734
+ suffix = "") {
733
735
  if (depth > maxDepth)
734
736
  return;
735
737
  const connector = depth === 0 ? "" : (isLast ? "└── " : "├── ");
@@ -755,12 +757,16 @@ function buildAnatomyLines(node, lines, prefix, isLast, depth, maxDepth) {
755
757
  let sizingInfo = "";
756
758
  if (node.primaryAxisSizingMode || node.counterAxisSizingMode) {
757
759
  const parts = [];
760
+ // The PRIMARY axis is width only in a horizontal layout — in a vertical one it
761
+ // is height. (These were hard-wired to width/height, mislabeling every
762
+ // vertical auto-layout: a fixed-width sidebar printed as "fixed-height".)
763
+ const vertical = node.layoutMode === "VERTICAL";
758
764
  if (node.primaryAxisSizingMode === "FIXED")
759
- parts.push("fixed-width");
765
+ parts.push(vertical ? "fixed-height" : "fixed-width");
760
766
  if (node.primaryAxisSizingMode === "AUTO")
761
767
  parts.push("hug-content");
762
768
  if (node.counterAxisSizingMode === "FIXED")
763
- parts.push("fixed-height");
769
+ parts.push(vertical ? "fixed-width" : "fixed-height");
764
770
  if (node.layoutGrow === 1)
765
771
  parts.push("fill");
766
772
  if (parts.length > 0)
@@ -769,14 +775,28 @@ function buildAnatomyLines(node, lines, prefix, isLast, depth, maxDepth) {
769
775
  // Hidden layers stay in the tree, marked: the color table names them (a hidden
770
776
  // focus ring is real design intent), so the tree must not pretend they don't exist.
771
777
  const hiddenHint = node.visible === false ? " (hidden)" : "";
772
- lines.push(`${prefix}${connector}${label}${typeHint}${hiddenHint}${layoutInfo}${sizingInfo}`);
773
- // Recurse into children
774
- if (node.children && Array.isArray(node.children)) {
775
- const visibleChildren = node.children;
776
- for (let i = 0; i < visibleChildren.length; i++) {
777
- const isChildLast = i === visibleChildren.length - 1;
778
- buildAnatomyLines(visibleChildren[i], lines, prefix + childPrefix, isChildLast, depth + 1, maxDepth);
779
- }
778
+ lines.push(`${prefix}${connector}${label}${typeHint}${suffix}${hiddenHint}${layoutInfo}${sizingInfo}`);
779
+ // Recurse into children, collapsing RUNS of siblings that would print
780
+ // identically (ten nav items, five list rows) into one entry marked ×N. Compared
781
+ // on the rendered text, so anything the tree shows — a different gap, one hidden
782
+ // caret — keeps a sibling separate.
783
+ if (node.children && Array.isArray(node.children) && depth < maxDepth) {
784
+ const rendered = node.children.map((child) => {
785
+ const own = [];
786
+ buildAnatomyLines(child, own, "", true, depth + 1, maxDepth);
787
+ return own.join("\n");
788
+ });
789
+ const runs = [];
790
+ node.children.forEach((child, i) => {
791
+ const last = runs[runs.length - 1];
792
+ if (last && rendered[i] === rendered[i - 1])
793
+ last.count++;
794
+ else
795
+ runs.push({ child, count: 1 });
796
+ });
797
+ runs.forEach((run, i) => {
798
+ buildAnatomyLines(run.child, lines, prefix + childPrefix, i === runs.length - 1, depth + 1, maxDepth, run.count > 1 ? ` ×${run.count}` : "");
799
+ });
780
800
  }
781
801
  }
782
802
  /**
@@ -2032,15 +2052,15 @@ export function generateOverviewSection(componentName, description, fileUrl, par
2032
2052
  }
2033
2053
  lines.push(links.join(" | "));
2034
2054
  lines.push("");
2035
- lines.push("## Overview");
2036
- lines.push("");
2055
+ // Collected separately so an empty overview doesn't leave an orphaned heading
2056
+ const section = [];
2037
2057
  // Use parsed overview or fall back to raw description
2038
2058
  // No filler: libraries that deliberately keep descriptions empty would get a
2039
2059
  // sentence carrying no information on every page. Nothing says more than that.
2040
2060
  const overviewText = parsedDesc.overview || description?.split("\n")[0] || "";
2041
2061
  if (overviewText.trim()) {
2042
- lines.push(overviewText);
2043
- lines.push("");
2062
+ section.push(overviewText);
2063
+ section.push("");
2044
2064
  }
2045
2065
  // Base component attribution
2046
2066
  if (codeInfo?.baseComponent) {
@@ -2048,30 +2068,35 @@ export function generateOverviewSection(componentName, description, fileUrl, par
2048
2068
  ? `[${codeInfo.baseComponent.name}](${codeInfo.baseComponent.url})`
2049
2069
  : codeInfo.baseComponent.name;
2050
2070
  if (codeInfo.baseComponent.description) {
2051
- lines.push(`Built on ${baseLink}, ${codeInfo.baseComponent.description}`);
2071
+ section.push(`Built on ${baseLink}, ${codeInfo.baseComponent.description}`);
2052
2072
  }
2053
2073
  else {
2054
- lines.push(`Built on ${baseLink}.`);
2074
+ section.push(`Built on ${baseLink}.`);
2055
2075
  }
2056
- lines.push("");
2076
+ section.push("");
2057
2077
  }
2058
2078
  // When to Use
2059
2079
  if (parsedDesc.whenToUse.length > 0) {
2060
- lines.push("### When to Use");
2061
- lines.push("");
2080
+ section.push("### When to Use");
2081
+ section.push("");
2062
2082
  for (const item of parsedDesc.whenToUse) {
2063
- lines.push(`- ${item}`);
2083
+ section.push(`- ${item}`);
2064
2084
  }
2065
- lines.push("");
2085
+ section.push("");
2066
2086
  }
2067
2087
  // When NOT to Use
2068
2088
  if (parsedDesc.whenNotToUse.length > 0) {
2069
- lines.push("### When NOT to Use");
2070
- lines.push("");
2089
+ section.push("### When NOT to Use");
2090
+ section.push("");
2071
2091
  for (const item of parsedDesc.whenNotToUse) {
2072
- lines.push(`- ${item}`);
2092
+ section.push(`- ${item}`);
2073
2093
  }
2094
+ section.push("");
2095
+ }
2096
+ if (section.length > 0) {
2097
+ lines.push("## Overview");
2074
2098
  lines.push("");
2099
+ lines.push(...section);
2075
2100
  }
2076
2101
  return lines.join("\n");
2077
2102
  }
@@ -2079,8 +2104,30 @@ export function generateOverviewSection(componentName, description, fileUrl, par
2079
2104
  function formatIconCell(icons) {
2080
2105
  if (!icons || icons.length === 0)
2081
2106
  return "—";
2082
- // A chip with a leading AND a trailing icon has two — show both
2083
- return icons.map(formatOneIcon).join("; ");
2107
+ // A chip with a leading AND a trailing icon has two — show both. A nav with
2108
+ // twenty repeats of the same one shows it once, counted.
2109
+ const groups = new Map();
2110
+ for (const icon of icons) {
2111
+ const key = `${icon.name}|${icon.swapProperty ?? ""}`;
2112
+ if (!groups.has(key))
2113
+ groups.set(key, { icon, total: 0, hidden: 0 });
2114
+ const g = groups.get(key);
2115
+ g.total++;
2116
+ if (icon.hidden)
2117
+ g.hidden++;
2118
+ }
2119
+ const MAX_ICON_GROUPS = 5;
2120
+ const parts = [...groups.values()].slice(0, MAX_ICON_GROUPS).map(({ icon, total, hidden }) => {
2121
+ if (total === 1)
2122
+ return formatOneIcon(icon);
2123
+ // Count first, then the note: "Placeholder ×9 _(all hidden)_"
2124
+ const label = formatOneIcon({ ...icon, hidden: false });
2125
+ const note = hidden === 0 ? "" : hidden === total ? " _(all hidden)_" : ` _(${hidden} hidden)_`;
2126
+ return `${label} ×${total}${note}`;
2127
+ });
2128
+ if (groups.size > MAX_ICON_GROUPS)
2129
+ parts.push(`+${groups.size - MAX_ICON_GROUPS} more`);
2130
+ return parts.join("; ");
2084
2131
  }
2085
2132
  function formatOneIcon(icon) {
2086
2133
  const notes = [];
@@ -2318,6 +2365,8 @@ setNode) {
2318
2365
  };
2319
2366
  // Multi-path icons and repeated shapes would otherwise emit identical rows
2320
2367
  const emitted = new Set();
2368
+ // Text layers are often auto-named after their content ("Lorem ipsum dolor…")
2369
+ const short = (name) => (name.length > 40 ? `${name.slice(0, 40)}…` : name);
2321
2370
  const pushRow = (label, c) => {
2322
2371
  const row = `| ${label}${c.hidden ? " _(hidden layer)_" : ""} | ${tokenCell(c)} | ${c.hex} |`;
2323
2372
  if (emitted.has(row))
@@ -2340,7 +2389,7 @@ setNode) {
2340
2389
  pushRow(c.iconLabel ? `Icon (${c.iconLabel})` : "Icon", c);
2341
2390
  // Text colors
2342
2391
  for (const text of vd.textColors)
2343
- pushRow(`Text (${text.nodeName})`, text);
2392
+ pushRow(`Text (${short(text.nodeName)})`, text);
2344
2393
  // Strokes — through the same writer, so a visible underline and a hidden
2345
2394
  // focus ring sharing one color don't print as two indistinguishable rows
2346
2395
  for (const stroke of vd.strokes)
@@ -3405,7 +3454,11 @@ export function registerDesignCodeTools(server, getFigmaAPI, getCurrentUrl, vari
3405
3454
  if (getDesktopConnector) {
3406
3455
  try {
3407
3456
  const connector = await getDesktopConnector();
3408
- const varsResult = await connector.getVariables();
3457
+ // Target the file being DOCUMENTED, not whichever file is active:
3458
+ // variable ids are file-local, so the active file's variables either
3459
+ // resolve nothing (every token prints as "—") or, on an id collision,
3460
+ // resolve to the wrong names. Rejects → caught → hex fallback.
3461
+ const varsResult = await connector.getVariables(fileKey);
3409
3462
  const varList = varsResult?.variables || varsResult?.result?.variables;
3410
3463
  if (Array.isArray(varList)) {
3411
3464
  for (const [id, name] of buildVariableNameMap(varList, varsResult?.variableCollections || varsResult?.result?.variableCollections))
@@ -3434,7 +3487,7 @@ export function registerDesignCodeTools(server, getFigmaAPI, getCurrentUrl, vari
3434
3487
  if (getDesktopConnector) {
3435
3488
  try {
3436
3489
  const connector = await getDesktopConnector();
3437
- const bridgeResult = await connector.getComponentFromPluginUI(nodeId);
3490
+ const bridgeResult = await connector.getComponentFromPluginUI(nodeId, fileKey);
3438
3491
  if (bridgeResult.success && bridgeResult.component) {
3439
3492
  // Fetch description from bridge if REST API returned empty
3440
3493
  if (!description) {