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.
- package/README.md +4 -2
- package/dist/core/design-code-tools.d.ts.map +1 -1
- package/dist/core/design-code-tools.js +143 -90
- package/dist/core/design-code-tools.js.map +1 -1
- package/dist/core/figma-connector.d.ts +8 -2
- package/dist/core/figma-connector.d.ts.map +1 -1
- package/dist/core/figma-tools.d.ts.map +1 -1
- package/dist/core/figma-tools.js +5 -3
- package/dist/core/figma-tools.js.map +1 -1
- package/dist/core/identity.d.ts +16 -0
- package/dist/core/identity.d.ts.map +1 -1
- package/dist/core/identity.js +48 -1
- package/dist/core/identity.js.map +1 -1
- package/dist/core/tokens-tools.js +1 -1
- package/dist/core/websocket-connector.d.ts +2 -2
- package/dist/core/websocket-connector.d.ts.map +1 -1
- package/dist/core/websocket-connector.js +4 -4
- package/dist/core/websocket-connector.js.map +1 -1
- package/dist/core/websocket-server.d.ts +2 -0
- package/dist/core/websocket-server.d.ts.map +1 -1
- package/dist/core/websocket-server.js +4 -0
- package/dist/core/websocket-server.js.map +1 -1
- package/dist/local.d.ts.map +1 -1
- package/dist/local.js +3 -2
- package/dist/local.js.map +1 -1
- package/package.json +4 -3
- package/dist/cloudflare/apps/design-system-dashboard/scoring/accessibility.js +0 -408
- package/dist/cloudflare/apps/design-system-dashboard/scoring/component-metadata.js +0 -366
- package/dist/cloudflare/apps/design-system-dashboard/scoring/consistency.js +0 -407
- package/dist/cloudflare/apps/design-system-dashboard/scoring/coverage.js +0 -233
- package/dist/cloudflare/apps/design-system-dashboard/scoring/engine.js +0 -92
- package/dist/cloudflare/apps/design-system-dashboard/scoring/naming-semantics.js +0 -342
- package/dist/cloudflare/apps/design-system-dashboard/scoring/remediation.js +0 -168
- package/dist/cloudflare/apps/design-system-dashboard/scoring/token-architecture.js +0 -412
- package/dist/cloudflare/apps/design-system-dashboard/scoring/types.js +0 -40
- package/dist/cloudflare/apps/design-system-dashboard/server.js +0 -346
- package/dist/cloudflare/apps/token-browser/server.js +0 -136
- package/dist/cloudflare/browser-manager.js +0 -157
- package/dist/cloudflare/core/accessibility-tools.js +0 -361
- package/dist/cloudflare/core/annotation-tools.js +0 -230
- package/dist/cloudflare/core/cloud-websocket-connector.js +0 -430
- package/dist/cloudflare/core/cloud-websocket-relay.js +0 -198
- package/dist/cloudflare/core/comment-tools.js +0 -292
- package/dist/cloudflare/core/config.js +0 -153
- package/dist/cloudflare/core/console-monitor.js +0 -427
- package/dist/cloudflare/core/deep-component-tools.js +0 -128
- package/dist/cloudflare/core/design-code-tools.js +0 -3702
- package/dist/cloudflare/core/design-system-extraction-tools.js +0 -611
- package/dist/cloudflare/core/design-system-manifest.js +0 -283
- package/dist/cloudflare/core/design-system-tools.js +0 -882
- package/dist/cloudflare/core/diagnose-tool.js +0 -100
- package/dist/cloudflare/core/diff/changelog-formatter.js +0 -275
- package/dist/cloudflare/core/diff/diff-engine.js +0 -334
- package/dist/cloudflare/core/diff/property-compare.js +0 -36
- package/dist/cloudflare/core/diff/version-cache.js +0 -74
- package/dist/cloudflare/core/enrichment/enrichment-service.js +0 -374
- package/dist/cloudflare/core/enrichment/index.js +0 -7
- package/dist/cloudflare/core/enrichment/relationship-mapper.js +0 -351
- package/dist/cloudflare/core/enrichment/style-resolver.js +0 -346
- package/dist/cloudflare/core/extraction/analyzer.js +0 -161
- package/dist/cloudflare/core/extraction/architecture.js +0 -159
- package/dist/cloudflare/core/extraction/component-inventory.js +0 -485
- package/dist/cloudflare/core/extraction/detectors.js +0 -148
- package/dist/cloudflare/core/extraction/scaffolder.js +0 -614
- package/dist/cloudflare/core/extraction/storybook-preset.js +0 -349
- package/dist/cloudflare/core/extraction/token-extractor.js +0 -759
- package/dist/cloudflare/core/extraction/types.js +0 -14
- package/dist/cloudflare/core/extraction/verifier.js +0 -137
- package/dist/cloudflare/core/extraction/walker.js +0 -118
- package/dist/cloudflare/core/figjam-tools.js +0 -547
- package/dist/cloudflare/core/figma-api.js +0 -509
- package/dist/cloudflare/core/figma-connector.js +0 -7
- package/dist/cloudflare/core/figma-reconstruction-spec.js +0 -402
- package/dist/cloudflare/core/figma-style-extractor.js +0 -311
- package/dist/cloudflare/core/figma-tools.js +0 -3302
- package/dist/cloudflare/core/history/component-history.js +0 -388
- package/dist/cloudflare/core/history/git-history.js +0 -161
- package/dist/cloudflare/core/history/history-formatter.js +0 -116
- package/dist/cloudflare/core/identity.js +0 -96
- package/dist/cloudflare/core/library-tools.js +0 -629
- package/dist/cloudflare/core/logger.js +0 -53
- package/dist/cloudflare/core/multi-file-tools.js +0 -161
- package/dist/cloudflare/core/port-discovery.js +0 -1016
- package/dist/cloudflare/core/resolve-package-root.js +0 -11
- package/dist/cloudflare/core/slides-tools.js +0 -714
- package/dist/cloudflare/core/slot-tools.js +0 -334
- package/dist/cloudflare/core/snippet-injector.js +0 -96
- package/dist/cloudflare/core/tokens/alias-resolver.js +0 -205
- package/dist/cloudflare/core/tokens/config.js +0 -294
- package/dist/cloudflare/core/tokens/dialect.js +0 -232
- package/dist/cloudflare/core/tokens/figma-converter.js +0 -323
- package/dist/cloudflare/core/tokens/formatters/css-vars.js +0 -345
- package/dist/cloudflare/core/tokens/formatters/dtcg.js +0 -376
- package/dist/cloudflare/core/tokens/formatters/index.js +0 -45
- package/dist/cloudflare/core/tokens/formatters/json.js +0 -205
- package/dist/cloudflare/core/tokens/formatters/less.js +0 -4
- package/dist/cloudflare/core/tokens/formatters/scss.js +0 -264
- package/dist/cloudflare/core/tokens/formatters/stubs.js +0 -13
- package/dist/cloudflare/core/tokens/formatters/style-dictionary-v3.js +0 -213
- package/dist/cloudflare/core/tokens/formatters/tailwind-v3.js +0 -237
- package/dist/cloudflare/core/tokens/formatters/tailwind-v4.js +0 -341
- package/dist/cloudflare/core/tokens/formatters/tokens-studio.js +0 -256
- package/dist/cloudflare/core/tokens/formatters/ts-module.js +0 -198
- package/dist/cloudflare/core/tokens/index.js +0 -16
- package/dist/cloudflare/core/tokens/parsers/css-vars.js +0 -4
- package/dist/cloudflare/core/tokens/parsers/dtcg.js +0 -280
- package/dist/cloudflare/core/tokens/parsers/index.js +0 -138
- package/dist/cloudflare/core/tokens/parsers/json.js +0 -7
- package/dist/cloudflare/core/tokens/parsers/scss.js +0 -4
- package/dist/cloudflare/core/tokens/parsers/stubs.js +0 -20
- package/dist/cloudflare/core/tokens/parsers/style-dictionary-v3.js +0 -4
- package/dist/cloudflare/core/tokens/parsers/tailwind-v3.js +0 -4
- package/dist/cloudflare/core/tokens/parsers/tailwind-v4.js +0 -4
- package/dist/cloudflare/core/tokens/parsers/tokens-studio.js +0 -4
- package/dist/cloudflare/core/tokens/schemas.js +0 -152
- package/dist/cloudflare/core/tokens/transforms/color.js +0 -12
- package/dist/cloudflare/core/tokens/transforms/index.js +0 -29
- package/dist/cloudflare/core/tokens/transforms/size.js +0 -7
- package/dist/cloudflare/core/tokens/types.js +0 -18
- package/dist/cloudflare/core/tokens-tools.js +0 -2238
- package/dist/cloudflare/core/types/design-code.js +0 -4
- package/dist/cloudflare/core/types/enriched.js +0 -5
- package/dist/cloudflare/core/types/index.js +0 -4
- package/dist/cloudflare/core/variable-resolver.js +0 -85
- package/dist/cloudflare/core/version-tools.js +0 -1199
- package/dist/cloudflare/core/websocket-connector.js +0 -439
- package/dist/cloudflare/core/websocket-server.js +0 -1050
- package/dist/cloudflare/core/write-tools.js +0 -2505
- 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.
|
|
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.
|
|
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
|
-
|
|
381
|
-
|
|
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 ${
|
|
386
|
+
return `layer blur ${radius}px`;
|
|
385
387
|
if (effect.type === "BACKGROUND_BLUR")
|
|
386
|
-
return `background blur ${
|
|
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
|
-
|
|
631
|
-
const
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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 =
|
|
684
|
-
|
|
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
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
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
|
-
|
|
2036
|
-
|
|
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
|
-
|
|
2043
|
-
|
|
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
|
-
|
|
2071
|
+
section.push(`Built on ${baseLink}, ${codeInfo.baseComponent.description}`);
|
|
2052
2072
|
}
|
|
2053
2073
|
else {
|
|
2054
|
-
|
|
2074
|
+
section.push(`Built on ${baseLink}.`);
|
|
2055
2075
|
}
|
|
2056
|
-
|
|
2076
|
+
section.push("");
|
|
2057
2077
|
}
|
|
2058
2078
|
// When to Use
|
|
2059
2079
|
if (parsedDesc.whenToUse.length > 0) {
|
|
2060
|
-
|
|
2061
|
-
|
|
2080
|
+
section.push("### When to Use");
|
|
2081
|
+
section.push("");
|
|
2062
2082
|
for (const item of parsedDesc.whenToUse) {
|
|
2063
|
-
|
|
2083
|
+
section.push(`- ${item}`);
|
|
2064
2084
|
}
|
|
2065
|
-
|
|
2085
|
+
section.push("");
|
|
2066
2086
|
}
|
|
2067
2087
|
// When NOT to Use
|
|
2068
2088
|
if (parsedDesc.whenNotToUse.length > 0) {
|
|
2069
|
-
|
|
2070
|
-
|
|
2089
|
+
section.push("### When NOT to Use");
|
|
2090
|
+
section.push("");
|
|
2071
2091
|
for (const item of parsedDesc.whenNotToUse) {
|
|
2072
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|