@mp3wizard/figma-console-mcp 1.29.2 → 1.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/cloudflare/core/cloud-websocket-connector.js +2 -0
- package/dist/cloudflare/core/design-code-tools.js +51 -5
- package/dist/cloudflare/core/tokens-tools.js +1 -1
- package/dist/cloudflare/core/websocket-connector.js +2 -0
- package/dist/cloudflare/core/write-tools.js +35 -8
- package/dist/cloudflare/index.js +3 -3
- package/dist/core/design-code-tools.d.ts.map +1 -1
- package/dist/core/design-code-tools.js +51 -5
- package/dist/core/design-code-tools.js.map +1 -1
- package/dist/core/tokens-tools.js +1 -1
- package/dist/core/websocket-connector.d.ts.map +1 -1
- package/dist/core/websocket-connector.js +2 -0
- package/dist/core/websocket-connector.js.map +1 -1
- package/dist/core/write-tools.d.ts.map +1 -1
- package/dist/core/write-tools.js +35 -8
- package/dist/core/write-tools.js.map +1 -1
- package/figma-desktop-bridge/code.js +156 -27
- package/figma-desktop-bridge/ui.html +2 -0
- package/package.json +5 -3
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
|
-
> **🆕
|
|
11
|
+
> **🆕 Native variable binding & typography (v1.30.0):** The structured write tools now handle the operations that used to force you into raw `figma_execute`. `figma_set_fills` / `figma_set_strokes` bind a fill or stroke to a color variable via a new `variableId` param (works on any Figma plan through the bridge — no Enterprise Variables API). `figma_set_text` gains `fontFamily` / `fontStyle` with space-insensitive normalization (`SemiBold` → `Semi Bold`) and graceful fallback. `figma_instantiate_component` now pre-loads instance fonts before text overrides (no more silently-skipped overrides) and reports failed overrides in a `warnings` array. Works on every Figma plan. [See what's new →](CHANGELOG.md#1300---2026-06-02)
|
|
12
12
|
|
|
13
13
|
## What is this?
|
|
14
14
|
|
|
@@ -808,9 +808,11 @@ The architecture supports adding new apps with minimal boilerplate — each app
|
|
|
808
808
|
|
|
809
809
|
## 🛤️ Roadmap
|
|
810
810
|
|
|
811
|
-
**Current Status:** v1.
|
|
811
|
+
**Current Status:** v1.30.0 (Stable) - Production-ready with 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, **106 tools** (Local) / **95 tools** (Cloud) / **9 tools** (Remote read-only), Comments API, cross-MCP identity disambiguation, and MCP Apps.
|
|
812
812
|
|
|
813
813
|
**Recent Releases:**
|
|
814
|
+
- [x] **v1.30.0** - Native variable binding + typography in the structured write tools, closing the Plugin API gaps that used to force raw `figma_execute`. `figma_set_fills` / `figma_set_strokes` accept a `variableId` to bind a fill/stroke to a color variable via `setBoundVariableForPaint` (any plan, via the bridge). `figma_set_text` gains `fontFamily` / `fontStyle` with space-insensitive normalization (`SemiBold` → `Semi Bold`) and graceful `Regular` fallback. `figma_instantiate_component` pre-loads instance text fonts before applying overrides (fixes silently-skipped text overrides on non-Regular weights) and returns a `warnings` array for failed overrides. Also fixes a mixed-font crash in `figma_set_text` and a `ui.html` relay that was dropping new message fields. No new tools; **plugin re-import required** (bridge `ui.html` + `code.js` changed). Validated live; 1185 tests passing.
|
|
815
|
+
- [x] **v1.29.2** - Bug fix: `figma_generate_component_doc` now renders Figma component descriptions faithfully and reliably tags atomic-design level. Single-`#` headings in descriptions render as real sections (Usage Guidelines, Implementation Considerations, Accessibility Requirements, Content Configuration) instead of leaking as `- # Heading` list items; frontmatter `description` takes the first sentence instead of truncating on the word "Accessibility"; the generated Figma URL no longer doubles `?node-id=`; and the component's atomic level (atom/molecule/organism/template) is auto-detected via a single `ids=<node>` file request + divider walk-back, with no dependency on library publishing. No new tools; plugin re-import not required.
|
|
814
816
|
- [x] **v1.29.1** - Bug fix: `figma_get_design_system_kit` now resolves variables bridge-first (Desktop Bridge / cloud relay → REST fallback) instead of calling the Enterprise-only Variables REST API directly. Non-Enterprise users no longer hit a 403 on the kit's token section when a bridge is connected, and a REST 403 now points the caller back to the bridge instead of dead-ending. 7 new tests, 1185 total passing. No new tools; plugin re-import not required.
|
|
815
817
|
- [x] **v1.29.0** - Shared library inspection: three new tools close the gap between "I have a component key" and "I can actually use it." `figma_get_library_component_by_key` resolves any 40-char component key to full `componentPropertyDefinitions` + variants (with their published keys) + per-variant visual specs — without needing the source library file's URL. `figma_get_library_variables` lists library tokens via Plugin API (works on every Figma plan; the REST equivalent is Enterprise-only). `figma_import_library_variable` imports a library token to the current file so it can be bound to nodes. 27 new tests, 1178 total passing. Plugin re-import optional.
|
|
816
818
|
- [x] **v1.28.1** - Bug fix patch surfacing from live-fire testing of the v1.28.0 formatters against multi-tier semantic-token design systems. Fixes: Tailwind v3 emitted empty `module.exports` for alias-only sets (now resolves alias chains to literal values); TypeScript module + JSON flat + JSON nested formatters emitted `"{alias.path}"` strings as literal values (now resolves); Tailwind v4 namespace-prefix doubling (`--color-theme-color-X` is now `--color-theme-X`). Adds `resolveAliasChain` public helper. 1151 tests still passing.
|
|
@@ -222,6 +222,8 @@ export class CloudWebSocketConnector {
|
|
|
222
222
|
params.fontWeight = options.fontWeight;
|
|
223
223
|
if (options.fontFamily)
|
|
224
224
|
params.fontFamily = options.fontFamily;
|
|
225
|
+
if (options.fontStyle)
|
|
226
|
+
params.fontStyle = options.fontStyle;
|
|
225
227
|
}
|
|
226
228
|
return this.sendCommand('SET_TEXT_CONTENT', params);
|
|
227
229
|
}
|
|
@@ -174,7 +174,7 @@ export function parseComponentDescription(description) {
|
|
|
174
174
|
for (const line of lines) {
|
|
175
175
|
const trimmed = line.trim();
|
|
176
176
|
// Detect section headers: bold text (**Header**), markdown headers (## Header), or plain text exact matches
|
|
177
|
-
const markdownHeaderMatch = trimmed.match(/^(
|
|
177
|
+
const markdownHeaderMatch = trimmed.match(/^(?:\*\*|#{1,6}\s*)(.+?)(?:\*\*)?$/);
|
|
178
178
|
const headerText = markdownHeaderMatch ? markdownHeaderMatch[1].trim().replace(/\*\*/g, "") : null;
|
|
179
179
|
// Check if this is a Figma per-property documentation block (e.g., "Show Left Icon: True – Purpose")
|
|
180
180
|
// These should be routed to "other" to avoid polluting content guidelines and accessibility sections
|
|
@@ -1380,7 +1380,47 @@ function buildParityInstruction(componentName, parityScore, counts, canonicalSou
|
|
|
1380
1380
|
// ============================================================================
|
|
1381
1381
|
// Documentation Section Generators
|
|
1382
1382
|
// ============================================================================
|
|
1383
|
-
|
|
1383
|
+
/**
|
|
1384
|
+
* Detect the atomic-design level (atom | molecule | organism | template) of a component
|
|
1385
|
+
* by finding its Figma page and walking the ordered page list back to the nearest
|
|
1386
|
+
* section-divider page (e.g. "ATOMS", "MOLECULES", "ORGANISMS"). Returns null when the
|
|
1387
|
+
* file doesn't use atomic-design page sections or the page can't be resolved — callers
|
|
1388
|
+
* then simply omit the `level` frontmatter. Best-effort and never throws.
|
|
1389
|
+
*/
|
|
1390
|
+
async function detectAtomicLevel(api, fileKey, nodeId, setNodeId, _componentMeta, _allComponentsMeta) {
|
|
1391
|
+
try {
|
|
1392
|
+
const targetId = setNodeId || nodeId;
|
|
1393
|
+
// Resolve the page the component lives on — independent of library-publish
|
|
1394
|
+
// status (published `containing_frame` metadata is empty for many files).
|
|
1395
|
+
// Requesting the file with `ids` returns every page in document order, but
|
|
1396
|
+
// prunes each page's children to only the path reaching the requested node,
|
|
1397
|
+
// so the single page whose subtree still contains the node is its home page.
|
|
1398
|
+
const pages = (await api.getFile(fileKey, { ids: [targetId] }))?.document?.children || [];
|
|
1399
|
+
const contains = (n) => n?.id === targetId || (Array.isArray(n?.children) && n.children.some(contains));
|
|
1400
|
+
const idx = pages.findIndex((p) => contains(p));
|
|
1401
|
+
if (idx < 0)
|
|
1402
|
+
return null;
|
|
1403
|
+
// Walk back to the nearest atomic-design divider page.
|
|
1404
|
+
const LEVELS = [
|
|
1405
|
+
["ATOM", "atom"],
|
|
1406
|
+
["MOLECULE", "molecule"],
|
|
1407
|
+
["ORGANISM", "organism"],
|
|
1408
|
+
["TEMPLATE", "template"],
|
|
1409
|
+
];
|
|
1410
|
+
for (let i = idx; i >= 0; i--) {
|
|
1411
|
+
const stripped = (pages[i]?.name || "").toUpperCase().replace(/[^A-Z]/g, "");
|
|
1412
|
+
for (const [marker, level] of LEVELS) {
|
|
1413
|
+
if (stripped.startsWith(marker))
|
|
1414
|
+
return level;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
return null;
|
|
1418
|
+
}
|
|
1419
|
+
catch {
|
|
1420
|
+
return null;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
function generateFrontmatter(componentName, description, node, componentMeta, fileUrl, codeInfo, canonicalSource, level) {
|
|
1384
1424
|
const status = codeInfo?.changelog?.[0]
|
|
1385
1425
|
? "stable"
|
|
1386
1426
|
: componentMeta?.description?.toLowerCase().includes("deprecated")
|
|
@@ -1388,6 +1428,8 @@ function generateFrontmatter(componentName, description, node, componentMeta, fi
|
|
|
1388
1428
|
: "stable";
|
|
1389
1429
|
const version = codeInfo?.changelog?.[0]?.version || "1.0.0";
|
|
1390
1430
|
const tags = [componentName.toLowerCase()];
|
|
1431
|
+
if (level)
|
|
1432
|
+
tags.push(level);
|
|
1391
1433
|
if (node.type === "COMPONENT_SET")
|
|
1392
1434
|
tags.push("variants");
|
|
1393
1435
|
if (node.componentPropertyDefinitions)
|
|
@@ -1395,10 +1437,11 @@ function generateFrontmatter(componentName, description, node, componentMeta, fi
|
|
|
1395
1437
|
const lines = [
|
|
1396
1438
|
"---",
|
|
1397
1439
|
`title: ${componentName}`,
|
|
1398
|
-
`description: ${(description.split(
|
|
1440
|
+
`description: ${((description.split(/\n\s*\n|\n#{1,6}\s|\n\*\*/)[0] || description).replace(/\n/g, " ").replace(/\s+/g, " ").trim().split(/(?<=[.!?])\s+/)[0] || `${componentName} component`)}`,
|
|
1399
1441
|
`status: ${status}`,
|
|
1400
1442
|
`version: ${version}`,
|
|
1401
1443
|
`category: components`,
|
|
1444
|
+
...(level ? [`level: ${level}`] : []),
|
|
1402
1445
|
`tags: [${tags.join(", ")}]`,
|
|
1403
1446
|
`figma: ${fileUrl}`,
|
|
1404
1447
|
];
|
|
@@ -2560,7 +2603,9 @@ export function registerDesignCodeTools(server, getFigmaAPI, getCurrentUrl, vari
|
|
|
2560
2603
|
logger.warn("Desktop Bridge fetch failed, proceeding without bridge-sourced data");
|
|
2561
2604
|
}
|
|
2562
2605
|
}
|
|
2563
|
-
|
|
2606
|
+
// Strip any existing query (e.g. the connected file's ?node-id=<page>) before
|
|
2607
|
+
// appending the target node, otherwise the URL ends up with a doubled ?node-id=.
|
|
2608
|
+
const fileUrl_ = `${url.split("?")[0]}?node-id=${nodeId.replace(":", "-")}`;
|
|
2564
2609
|
// Parse the component description for structured content
|
|
2565
2610
|
const parsedDesc = parseComponentDescription(description);
|
|
2566
2611
|
// Determine canonical source
|
|
@@ -2589,7 +2634,8 @@ export function registerDesignCodeTools(server, getFigmaAPI, getCurrentUrl, vari
|
|
|
2589
2634
|
const parts = [];
|
|
2590
2635
|
const includedSections = [];
|
|
2591
2636
|
if (includeFrontmatter) {
|
|
2592
|
-
|
|
2637
|
+
const atomicLevel = await detectAtomicLevel(api, fileKey, nodeId, setInfo.setNodeId, componentMeta, allComponentsMeta);
|
|
2638
|
+
parts.push(generateFrontmatter(componentName, description, node, componentMeta, fileUrl_, codeInfo, canonicalSource, atomicLevel));
|
|
2593
2639
|
parts.push("");
|
|
2594
2640
|
}
|
|
2595
2641
|
if (s.overview) {
|
|
@@ -30,7 +30,7 @@ const logger = createChildLogger({ component: "tokens-tools" });
|
|
|
30
30
|
* on every exported token document. Kept in sync with package.json by
|
|
31
31
|
* scripts/release.sh — see step 3 of the release flow.
|
|
32
32
|
*/
|
|
33
|
-
const MCP_VERSION = "1.
|
|
33
|
+
const MCP_VERSION = "1.30.0";
|
|
34
34
|
const EXPORT_TOOL_DESCRIPTION = `Export Figma variables to design token files in your codebase. Bidirectional with figma_import_tokens — together they replace Style Dictionary and Tokens Studio's export pipeline for the popular styling methods.
|
|
35
35
|
|
|
36
36
|
FULLY-IMPLEMENTED OUTPUT FORMATS:
|
|
@@ -230,6 +230,8 @@ export class WebSocketConnector {
|
|
|
230
230
|
params.fontWeight = options.fontWeight;
|
|
231
231
|
if (options.fontFamily)
|
|
232
232
|
params.fontFamily = options.fontFamily;
|
|
233
|
+
if (options.fontStyle)
|
|
234
|
+
params.fontStyle = options.fontStyle;
|
|
233
235
|
}
|
|
234
236
|
return this.wsServer.sendCommand('SET_TEXT_CONTENT', params);
|
|
235
237
|
}
|
|
@@ -872,8 +872,13 @@ After instantiating components, use figma_take_screenshot to verify the result l
|
|
|
872
872
|
type: "text",
|
|
873
873
|
text: JSON.stringify({
|
|
874
874
|
success: true,
|
|
875
|
-
message:
|
|
875
|
+
message: result.warnings && result.warnings.length
|
|
876
|
+
? "Component instantiated, but some overrides did not apply (see warnings)"
|
|
877
|
+
: "Component instantiated successfully",
|
|
876
878
|
instance: result.instance,
|
|
879
|
+
...(result.warnings && result.warnings.length
|
|
880
|
+
? { warnings: result.warnings }
|
|
881
|
+
: {}),
|
|
877
882
|
timestamp: Date.now(),
|
|
878
883
|
}),
|
|
879
884
|
},
|
|
@@ -1184,7 +1189,7 @@ After instantiating components, use figma_take_screenshot to verify the result l
|
|
|
1184
1189
|
}
|
|
1185
1190
|
});
|
|
1186
1191
|
// Tool: Set Node Fills
|
|
1187
|
-
server.tool("figma_set_fills", "Set the fill colors on a node. Accepts hex color strings (e.g., '#FF0000')
|
|
1192
|
+
server.tool("figma_set_fills", "Set the fill colors on a node. Accepts hex color strings (e.g., '#FF0000'). To bind a fill to a design token / color variable, pass that fill's variableId — the variable drives the color and this works on any Figma plan via the bridge (no raw figma_execute needed).", {
|
|
1188
1193
|
nodeId: z.string().describe("The node ID to modify"),
|
|
1189
1194
|
fills: z
|
|
1190
1195
|
.array(z.object({
|
|
@@ -1193,11 +1198,16 @@ After instantiating components, use figma_take_screenshot to verify the result l
|
|
|
1193
1198
|
.describe("Fill type (currently only SOLID supported)"),
|
|
1194
1199
|
color: z
|
|
1195
1200
|
.string()
|
|
1196
|
-
.
|
|
1201
|
+
.optional()
|
|
1202
|
+
.describe("Hex color string (e.g., '#FF0000', '#FF000080' for transparency). Optional when variableId is provided."),
|
|
1197
1203
|
opacity: z
|
|
1198
1204
|
.number()
|
|
1199
1205
|
.optional()
|
|
1200
1206
|
.describe("Opacity 0-1 (default: 1)"),
|
|
1207
|
+
variableId: z
|
|
1208
|
+
.string()
|
|
1209
|
+
.optional()
|
|
1210
|
+
.describe("Bind this fill's color to a Figma variable by id (e.g. 'VariableID:1:23' from figma_get_variables). When set, the variable drives the color. Import library variables first via figma_import_library_variable."),
|
|
1201
1211
|
}))
|
|
1202
1212
|
.describe("Array of fill objects"),
|
|
1203
1213
|
}, async ({ nodeId, fills }) => {
|
|
@@ -1279,13 +1289,20 @@ After instantiating components, use figma_take_screenshot to verify the result l
|
|
|
1279
1289
|
}
|
|
1280
1290
|
});
|
|
1281
1291
|
// Tool: Set Node Strokes
|
|
1282
|
-
server.tool("figma_set_strokes", "Set the stroke (border) on a node. Accepts hex color strings and optional stroke weight.", {
|
|
1292
|
+
server.tool("figma_set_strokes", "Set the stroke (border) on a node. Accepts hex color strings and optional stroke weight. To bind a stroke to a design token / color variable, pass that stroke's variableId — works on any Figma plan via the bridge.", {
|
|
1283
1293
|
nodeId: z.string().describe("The node ID to modify"),
|
|
1284
1294
|
strokes: z
|
|
1285
1295
|
.array(z.object({
|
|
1286
1296
|
type: z.literal("SOLID").describe("Stroke type"),
|
|
1287
|
-
color: z
|
|
1297
|
+
color: z
|
|
1298
|
+
.string()
|
|
1299
|
+
.optional()
|
|
1300
|
+
.describe("Hex color string. Optional when variableId is provided."),
|
|
1288
1301
|
opacity: z.number().optional().describe("Opacity 0-1"),
|
|
1302
|
+
variableId: z
|
|
1303
|
+
.string()
|
|
1304
|
+
.optional()
|
|
1305
|
+
.describe("Bind this stroke's color to a Figma variable by id (e.g. 'VariableID:1:23' from figma_get_variables). When set, the variable drives the color."),
|
|
1289
1306
|
}))
|
|
1290
1307
|
.describe("Array of stroke objects"),
|
|
1291
1308
|
strokeWeight: z
|
|
@@ -1443,14 +1460,24 @@ After instantiating components, use figma_take_screenshot to verify the result l
|
|
|
1443
1460
|
}
|
|
1444
1461
|
});
|
|
1445
1462
|
// Tool: Set Text Content
|
|
1446
|
-
server.tool("figma_set_text", "Set the text content of a text node. Optionally adjust font size.", {
|
|
1463
|
+
server.tool("figma_set_text", "Set the text content of a text node. Optionally adjust font size and the font family/style. Font style names are space-sensitive ('Semi Bold', not 'SemiBold'), but this tool auto-corrects common no-space variants and falls back gracefully — so you don't need raw figma_execute to change typography.", {
|
|
1447
1464
|
nodeId: z.string().describe("The text node ID"),
|
|
1448
1465
|
text: z.string().describe("The new text content"),
|
|
1449
1466
|
fontSize: z.number().optional().describe("Optional font size to set"),
|
|
1450
|
-
|
|
1467
|
+
fontFamily: z
|
|
1468
|
+
.string()
|
|
1469
|
+
.optional()
|
|
1470
|
+
.describe("Optional font family to apply (e.g., 'Inter')"),
|
|
1471
|
+
fontStyle: z
|
|
1472
|
+
.string()
|
|
1473
|
+
.optional()
|
|
1474
|
+
.describe("Optional font style/weight to apply (e.g., 'Bold', 'Semi Bold'). No-space variants like 'SemiBold' are auto-corrected."),
|
|
1475
|
+
}, async ({ nodeId, text, fontSize, fontFamily, fontStyle }) => {
|
|
1451
1476
|
try {
|
|
1452
1477
|
const connector = await getDesktopConnector();
|
|
1453
|
-
const result = await connector.setTextContent(nodeId, text, fontSize
|
|
1478
|
+
const result = await connector.setTextContent(nodeId, text, fontSize || fontFamily || fontStyle
|
|
1479
|
+
? { fontSize, fontFamily, fontStyle }
|
|
1480
|
+
: undefined);
|
|
1454
1481
|
if (!result.success) {
|
|
1455
1482
|
throw new Error(result.error || "Failed to set text");
|
|
1456
1483
|
}
|
package/dist/cloudflare/index.js
CHANGED
|
@@ -74,7 +74,7 @@ export class FigmaConsoleMCPv3 extends McpAgent {
|
|
|
74
74
|
this.server = (() => {
|
|
75
75
|
const s = new McpServer({
|
|
76
76
|
name: "Figma Console MCP",
|
|
77
|
-
version: "1.
|
|
77
|
+
version: "1.30.0",
|
|
78
78
|
});
|
|
79
79
|
// Identity wrap — every tool's response and thrown error gets stamped
|
|
80
80
|
// with our MCP name so cross-MCP attribution is unambiguous.
|
|
@@ -1083,7 +1083,7 @@ export default {
|
|
|
1083
1083
|
});
|
|
1084
1084
|
const statelessServer = new McpServer({
|
|
1085
1085
|
name: "Figma Console MCP",
|
|
1086
|
-
version: "1.
|
|
1086
|
+
version: "1.30.0",
|
|
1087
1087
|
});
|
|
1088
1088
|
wrapServerForIdentity(statelessServer);
|
|
1089
1089
|
// ================================================================
|
|
@@ -1720,7 +1720,7 @@ export default {
|
|
|
1720
1720
|
return new Response(JSON.stringify({
|
|
1721
1721
|
status: "healthy",
|
|
1722
1722
|
service: "Figma Console MCP",
|
|
1723
|
-
version: "1.
|
|
1723
|
+
version: "1.30.0",
|
|
1724
1724
|
endpoints: {
|
|
1725
1725
|
mcp: ["/sse", "/mcp"],
|
|
1726
1726
|
oauth_mcp_spec: ["/.well-known/oauth-authorization-server", "/authorize", "/token", "/oauth/register"],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"design-code-tools.d.ts","sourceRoot":"","sources":["../../src/core/design-code-tools.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAK/C,OAAO,KAAK,EAUX,uBAAuB,EACvB,MAAM,wBAAwB,CAAC;AAUhC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAG1F,qDAAqD;AACrD,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzG;AAiED,oEAAoE;AACpE,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAqBpG;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAUxD;AAMD,sEAAsE;AACtE,UAAU,iBAAiB;IAC1B,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,sCAAsC;IACtC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,uEAAuE;IACvE,iBAAiB,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAC/D,0BAA0B;IAC1B,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,yCAAyC;IACzC,eAAe,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,iBAAiB,CA0KhF;AAMD,mDAAmD;AACnD,UAAU,gBAAgB;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5F,OAAO,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9F,UAAU,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjG,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC7C;AAED,uCAAuC;AACvC,UAAU,aAAa;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,gBAAgB,EAAE,CAqBpG;AAoED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,GAAE,MAAU,EAAE,QAAQ,GAAE,MAAU,GAAG,aAAa,EAAE,CAiCzG;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,GAAE,MAAU,EAAE,QAAQ,GAAE,MAAU,GAAG,MAAM,CAqB3F;AAmHD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,GAAG,GAAG,GAAG,CAKhD;AAED,oGAAoG;AACpG,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED,uDAAuD;AACvD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1D;
|
|
1
|
+
{"version":3,"file":"design-code-tools.d.ts","sourceRoot":"","sources":["../../src/core/design-code-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -174,7 +174,7 @@ export function parseComponentDescription(description) {
|
|
|
174
174
|
for (const line of lines) {
|
|
175
175
|
const trimmed = line.trim();
|
|
176
176
|
// Detect section headers: bold text (**Header**), markdown headers (## Header), or plain text exact matches
|
|
177
|
-
const markdownHeaderMatch = trimmed.match(/^(
|
|
177
|
+
const markdownHeaderMatch = trimmed.match(/^(?:\*\*|#{1,6}\s*)(.+?)(?:\*\*)?$/);
|
|
178
178
|
const headerText = markdownHeaderMatch ? markdownHeaderMatch[1].trim().replace(/\*\*/g, "") : null;
|
|
179
179
|
// Check if this is a Figma per-property documentation block (e.g., "Show Left Icon: True – Purpose")
|
|
180
180
|
// These should be routed to "other" to avoid polluting content guidelines and accessibility sections
|
|
@@ -1380,7 +1380,47 @@ function buildParityInstruction(componentName, parityScore, counts, canonicalSou
|
|
|
1380
1380
|
// ============================================================================
|
|
1381
1381
|
// Documentation Section Generators
|
|
1382
1382
|
// ============================================================================
|
|
1383
|
-
|
|
1383
|
+
/**
|
|
1384
|
+
* Detect the atomic-design level (atom | molecule | organism | template) of a component
|
|
1385
|
+
* by finding its Figma page and walking the ordered page list back to the nearest
|
|
1386
|
+
* section-divider page (e.g. "ATOMS", "MOLECULES", "ORGANISMS"). Returns null when the
|
|
1387
|
+
* file doesn't use atomic-design page sections or the page can't be resolved — callers
|
|
1388
|
+
* then simply omit the `level` frontmatter. Best-effort and never throws.
|
|
1389
|
+
*/
|
|
1390
|
+
async function detectAtomicLevel(api, fileKey, nodeId, setNodeId, _componentMeta, _allComponentsMeta) {
|
|
1391
|
+
try {
|
|
1392
|
+
const targetId = setNodeId || nodeId;
|
|
1393
|
+
// Resolve the page the component lives on — independent of library-publish
|
|
1394
|
+
// status (published `containing_frame` metadata is empty for many files).
|
|
1395
|
+
// Requesting the file with `ids` returns every page in document order, but
|
|
1396
|
+
// prunes each page's children to only the path reaching the requested node,
|
|
1397
|
+
// so the single page whose subtree still contains the node is its home page.
|
|
1398
|
+
const pages = (await api.getFile(fileKey, { ids: [targetId] }))?.document?.children || [];
|
|
1399
|
+
const contains = (n) => n?.id === targetId || (Array.isArray(n?.children) && n.children.some(contains));
|
|
1400
|
+
const idx = pages.findIndex((p) => contains(p));
|
|
1401
|
+
if (idx < 0)
|
|
1402
|
+
return null;
|
|
1403
|
+
// Walk back to the nearest atomic-design divider page.
|
|
1404
|
+
const LEVELS = [
|
|
1405
|
+
["ATOM", "atom"],
|
|
1406
|
+
["MOLECULE", "molecule"],
|
|
1407
|
+
["ORGANISM", "organism"],
|
|
1408
|
+
["TEMPLATE", "template"],
|
|
1409
|
+
];
|
|
1410
|
+
for (let i = idx; i >= 0; i--) {
|
|
1411
|
+
const stripped = (pages[i]?.name || "").toUpperCase().replace(/[^A-Z]/g, "");
|
|
1412
|
+
for (const [marker, level] of LEVELS) {
|
|
1413
|
+
if (stripped.startsWith(marker))
|
|
1414
|
+
return level;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
return null;
|
|
1418
|
+
}
|
|
1419
|
+
catch {
|
|
1420
|
+
return null;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
function generateFrontmatter(componentName, description, node, componentMeta, fileUrl, codeInfo, canonicalSource, level) {
|
|
1384
1424
|
const status = codeInfo?.changelog?.[0]
|
|
1385
1425
|
? "stable"
|
|
1386
1426
|
: componentMeta?.description?.toLowerCase().includes("deprecated")
|
|
@@ -1388,6 +1428,8 @@ function generateFrontmatter(componentName, description, node, componentMeta, fi
|
|
|
1388
1428
|
: "stable";
|
|
1389
1429
|
const version = codeInfo?.changelog?.[0]?.version || "1.0.0";
|
|
1390
1430
|
const tags = [componentName.toLowerCase()];
|
|
1431
|
+
if (level)
|
|
1432
|
+
tags.push(level);
|
|
1391
1433
|
if (node.type === "COMPONENT_SET")
|
|
1392
1434
|
tags.push("variants");
|
|
1393
1435
|
if (node.componentPropertyDefinitions)
|
|
@@ -1395,10 +1437,11 @@ function generateFrontmatter(componentName, description, node, componentMeta, fi
|
|
|
1395
1437
|
const lines = [
|
|
1396
1438
|
"---",
|
|
1397
1439
|
`title: ${componentName}`,
|
|
1398
|
-
`description: ${(description.split(
|
|
1440
|
+
`description: ${((description.split(/\n\s*\n|\n#{1,6}\s|\n\*\*/)[0] || description).replace(/\n/g, " ").replace(/\s+/g, " ").trim().split(/(?<=[.!?])\s+/)[0] || `${componentName} component`)}`,
|
|
1399
1441
|
`status: ${status}`,
|
|
1400
1442
|
`version: ${version}`,
|
|
1401
1443
|
`category: components`,
|
|
1444
|
+
...(level ? [`level: ${level}`] : []),
|
|
1402
1445
|
`tags: [${tags.join(", ")}]`,
|
|
1403
1446
|
`figma: ${fileUrl}`,
|
|
1404
1447
|
];
|
|
@@ -2560,7 +2603,9 @@ export function registerDesignCodeTools(server, getFigmaAPI, getCurrentUrl, vari
|
|
|
2560
2603
|
logger.warn("Desktop Bridge fetch failed, proceeding without bridge-sourced data");
|
|
2561
2604
|
}
|
|
2562
2605
|
}
|
|
2563
|
-
|
|
2606
|
+
// Strip any existing query (e.g. the connected file's ?node-id=<page>) before
|
|
2607
|
+
// appending the target node, otherwise the URL ends up with a doubled ?node-id=.
|
|
2608
|
+
const fileUrl_ = `${url.split("?")[0]}?node-id=${nodeId.replace(":", "-")}`;
|
|
2564
2609
|
// Parse the component description for structured content
|
|
2565
2610
|
const parsedDesc = parseComponentDescription(description);
|
|
2566
2611
|
// Determine canonical source
|
|
@@ -2589,7 +2634,8 @@ export function registerDesignCodeTools(server, getFigmaAPI, getCurrentUrl, vari
|
|
|
2589
2634
|
const parts = [];
|
|
2590
2635
|
const includedSections = [];
|
|
2591
2636
|
if (includeFrontmatter) {
|
|
2592
|
-
|
|
2637
|
+
const atomicLevel = await detectAtomicLevel(api, fileKey, nodeId, setInfo.setNodeId, componentMeta, allComponentsMeta);
|
|
2638
|
+
parts.push(generateFrontmatter(componentName, description, node, componentMeta, fileUrl_, codeInfo, canonicalSource, atomicLevel));
|
|
2593
2639
|
parts.push("");
|
|
2594
2640
|
}
|
|
2595
2641
|
if (s.overview) {
|