@mp3wizard/figma-console-mcp 1.32.2 → 1.34.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 +26 -17
- package/dist/cloudflare/core/cloud-websocket-connector.js +18 -0
- package/dist/cloudflare/core/design-code-tools.js +60 -17
- package/dist/cloudflare/core/design-system-manifest.js +19 -14
- package/dist/cloudflare/core/design-system-tools.js +43 -34
- package/dist/cloudflare/core/diagnose-tool.js +4 -0
- package/dist/cloudflare/core/enrichment/enrichment-service.js +11 -5
- package/dist/cloudflare/core/enrichment/style-resolver.js +38 -18
- package/dist/cloudflare/core/figma-api.js +118 -54
- package/dist/cloudflare/core/figma-tools.js +179 -63
- package/dist/cloudflare/core/port-discovery.js +404 -31
- package/dist/cloudflare/core/tokens/alias-resolver.js +75 -5
- package/dist/cloudflare/core/tokens/config.js +10 -0
- package/dist/cloudflare/core/tokens/dialect.js +232 -0
- package/dist/cloudflare/core/tokens/figma-converter.js +144 -16
- package/dist/cloudflare/core/tokens/formatters/css-vars.js +21 -12
- package/dist/cloudflare/core/tokens/formatters/dtcg.js +106 -30
- package/dist/cloudflare/core/tokens/formatters/json.js +28 -10
- package/dist/cloudflare/core/tokens/formatters/scss.js +19 -13
- package/dist/cloudflare/core/tokens/formatters/style-dictionary-v3.js +15 -9
- package/dist/cloudflare/core/tokens/formatters/tailwind-v4.js +14 -9
- package/dist/cloudflare/core/tokens/formatters/tokens-studio.js +11 -5
- package/dist/cloudflare/core/tokens/index.js +2 -1
- package/dist/cloudflare/core/tokens/parsers/dtcg.js +32 -5
- package/dist/cloudflare/core/tokens/schemas.js +4 -0
- package/dist/cloudflare/core/tokens-tools.js +1017 -88
- package/dist/cloudflare/core/version-tools.js +44 -3
- package/dist/cloudflare/core/websocket-connector.js +42 -0
- package/dist/cloudflare/core/websocket-server.js +99 -8
- package/dist/cloudflare/core/write-tools.js +355 -86
- package/dist/cloudflare/index.js +7 -7
- package/dist/core/design-code-tools.d.ts.map +1 -1
- package/dist/core/design-code-tools.js +60 -17
- package/dist/core/design-code-tools.js.map +1 -1
- package/dist/core/design-system-manifest.d.ts +1 -0
- package/dist/core/design-system-manifest.d.ts.map +1 -1
- package/dist/core/design-system-manifest.js +19 -14
- package/dist/core/design-system-manifest.js.map +1 -1
- package/dist/core/design-system-tools.d.ts.map +1 -1
- package/dist/core/design-system-tools.js +43 -34
- package/dist/core/design-system-tools.js.map +1 -1
- package/dist/core/diagnose-tool.d.ts +8 -0
- package/dist/core/diagnose-tool.d.ts.map +1 -1
- package/dist/core/diagnose-tool.js +4 -0
- package/dist/core/diagnose-tool.js.map +1 -1
- package/dist/core/enrichment/enrichment-service.d.ts.map +1 -1
- package/dist/core/enrichment/enrichment-service.js +11 -5
- package/dist/core/enrichment/enrichment-service.js.map +1 -1
- package/dist/core/enrichment/style-resolver.d.ts +7 -2
- package/dist/core/enrichment/style-resolver.d.ts.map +1 -1
- package/dist/core/enrichment/style-resolver.js +38 -18
- package/dist/core/enrichment/style-resolver.js.map +1 -1
- package/dist/core/figma-api.d.ts +18 -9
- package/dist/core/figma-api.d.ts.map +1 -1
- package/dist/core/figma-api.js +118 -54
- package/dist/core/figma-api.js.map +1 -1
- package/dist/core/figma-connector.d.ts +12 -0
- 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 +179 -63
- package/dist/core/figma-tools.js.map +1 -1
- package/dist/core/port-discovery.d.ts +40 -0
- package/dist/core/port-discovery.d.ts.map +1 -1
- package/dist/core/port-discovery.js +404 -31
- package/dist/core/port-discovery.js.map +1 -1
- package/dist/core/tokens/alias-resolver.d.ts +45 -3
- package/dist/core/tokens/alias-resolver.d.ts.map +1 -1
- package/dist/core/tokens/alias-resolver.js +75 -5
- package/dist/core/tokens/alias-resolver.js.map +1 -1
- package/dist/core/tokens/config.d.ts +28 -0
- package/dist/core/tokens/config.d.ts.map +1 -1
- package/dist/core/tokens/config.js +10 -0
- package/dist/core/tokens/config.js.map +1 -1
- package/dist/core/tokens/dialect.d.ts +107 -0
- package/dist/core/tokens/dialect.d.ts.map +1 -0
- package/dist/core/tokens/dialect.js +233 -0
- package/dist/core/tokens/dialect.js.map +1 -0
- package/dist/core/tokens/figma-converter.d.ts +23 -2
- package/dist/core/tokens/figma-converter.d.ts.map +1 -1
- package/dist/core/tokens/figma-converter.js +144 -16
- package/dist/core/tokens/figma-converter.js.map +1 -1
- package/dist/core/tokens/formatters/css-vars.d.ts.map +1 -1
- package/dist/core/tokens/formatters/css-vars.js +21 -12
- package/dist/core/tokens/formatters/css-vars.js.map +1 -1
- package/dist/core/tokens/formatters/dtcg.d.ts +2 -2
- package/dist/core/tokens/formatters/dtcg.d.ts.map +1 -1
- package/dist/core/tokens/formatters/dtcg.js +106 -30
- package/dist/core/tokens/formatters/dtcg.js.map +1 -1
- package/dist/core/tokens/formatters/json.d.ts.map +1 -1
- package/dist/core/tokens/formatters/json.js +28 -10
- package/dist/core/tokens/formatters/json.js.map +1 -1
- package/dist/core/tokens/formatters/scss.d.ts.map +1 -1
- package/dist/core/tokens/formatters/scss.js +19 -13
- package/dist/core/tokens/formatters/scss.js.map +1 -1
- package/dist/core/tokens/formatters/style-dictionary-v3.d.ts.map +1 -1
- package/dist/core/tokens/formatters/style-dictionary-v3.js +15 -9
- package/dist/core/tokens/formatters/style-dictionary-v3.js.map +1 -1
- package/dist/core/tokens/formatters/tailwind-v4.d.ts.map +1 -1
- package/dist/core/tokens/formatters/tailwind-v4.js +14 -9
- package/dist/core/tokens/formatters/tailwind-v4.js.map +1 -1
- package/dist/core/tokens/formatters/tokens-studio.d.ts.map +1 -1
- package/dist/core/tokens/formatters/tokens-studio.js +11 -5
- package/dist/core/tokens/formatters/tokens-studio.js.map +1 -1
- package/dist/core/tokens/index.d.ts +2 -1
- package/dist/core/tokens/index.d.ts.map +1 -1
- package/dist/core/tokens/index.js +2 -1
- package/dist/core/tokens/index.js.map +1 -1
- package/dist/core/tokens/parsers/dtcg.js +32 -5
- package/dist/core/tokens/parsers/dtcg.js.map +1 -1
- package/dist/core/tokens/schemas.d.ts +3 -0
- package/dist/core/tokens/schemas.d.ts.map +1 -1
- package/dist/core/tokens/schemas.js +4 -0
- package/dist/core/tokens/schemas.js.map +1 -1
- package/dist/core/tokens/types.d.ts +57 -1
- package/dist/core/tokens/types.d.ts.map +1 -1
- package/dist/core/tokens/types.js.map +1 -1
- package/dist/core/tokens-tools.d.ts +250 -7
- package/dist/core/tokens-tools.d.ts.map +1 -1
- package/dist/core/tokens-tools.js +1017 -88
- package/dist/core/tokens-tools.js.map +1 -1
- package/dist/core/version-tools.d.ts.map +1 -1
- package/dist/core/version-tools.js +44 -3
- package/dist/core/version-tools.js.map +1 -1
- package/dist/core/websocket-connector.d.ts +38 -0
- package/dist/core/websocket-connector.d.ts.map +1 -1
- package/dist/core/websocket-connector.js +42 -0
- package/dist/core/websocket-connector.js.map +1 -1
- package/dist/core/websocket-server.d.ts +23 -0
- package/dist/core/websocket-server.d.ts.map +1 -1
- package/dist/core/websocket-server.js +99 -8
- package/dist/core/websocket-server.js.map +1 -1
- package/dist/core/write-tools.d.ts.map +1 -1
- package/dist/core/write-tools.js +355 -86
- package/dist/core/write-tools.js.map +1 -1
- package/dist/local.d.ts +0 -1
- package/dist/local.d.ts.map +1 -1
- package/dist/local.js +253 -63
- package/dist/local.js.map +1 -1
- package/figma-desktop-bridge/code.js +382 -28
- package/figma-desktop-bridge/ui.html +578 -292
- package/package.json +2 -2
package/dist/local.js
CHANGED
|
@@ -36,7 +36,7 @@ import { registerWriteTools } from "./core/write-tools.js";
|
|
|
36
36
|
import { registerTokensTools } from "./core/tokens-tools.js";
|
|
37
37
|
import { wrapServerForIdentity } from "./core/identity.js";
|
|
38
38
|
import { PACKAGE_ROOT } from "./core/resolve-package-root.js";
|
|
39
|
-
import { FigmaWebSocketServer } from "./core/websocket-server.js";
|
|
39
|
+
import { FigmaWebSocketServer, getBundledPluginVersion } from "./core/websocket-server.js";
|
|
40
40
|
import { WebSocketConnector } from "./core/websocket-connector.js";
|
|
41
41
|
import { DEFAULT_WS_PORT, getPortRange, advertisePort, unadvertisePort, registerPortCleanup, startPeriodicReaper, discoverActiveInstances, cleanupStalePortFiles, cleanupOrphanedProcesses, evictOldestInstance, refreshPortAdvertisement, HEARTBEAT_INTERVAL_MS, } from "./core/port-discovery.js";
|
|
42
42
|
import { registerTokenBrowserApp } from "./apps/token-browser/server.js";
|
|
@@ -97,7 +97,6 @@ class LocalFigmaConsoleMCP {
|
|
|
97
97
|
}
|
|
98
98
|
constructor() {
|
|
99
99
|
this.figmaAPI = null;
|
|
100
|
-
this.desktopConnector = null;
|
|
101
100
|
this.wsServer = null;
|
|
102
101
|
this.wsStartupError = null;
|
|
103
102
|
/** The port the WebSocket server actually bound to (may differ from preferred if fallback occurred) */
|
|
@@ -211,9 +210,8 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
211
210
|
try {
|
|
212
211
|
const wsConnector = new WebSocketConnector(this.wsServer);
|
|
213
212
|
await wsConnector.initialize();
|
|
214
|
-
this.desktopConnector = wsConnector;
|
|
215
213
|
logger.debug("Desktop connector initialized via WebSocket bridge");
|
|
216
|
-
return
|
|
214
|
+
return wsConnector;
|
|
217
215
|
}
|
|
218
216
|
catch (wsError) {
|
|
219
217
|
const errorMsg = wsError instanceof Error ? wsError.message : String(wsError);
|
|
@@ -448,9 +446,11 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
448
446
|
};
|
|
449
447
|
}
|
|
450
448
|
});
|
|
451
|
-
// Tool 2: Take Screenshot (
|
|
449
|
+
// Tool 2: Take Screenshot (Desktop Bridge first, REST API fallback)
|
|
450
|
+
// Bridge-first: the plugin's exportAsync works on any Figma plan with no REST
|
|
451
|
+
// token, and reflects the current runtime state (no cloud-sync lag).
|
|
452
452
|
// Note: For screenshots of specific components, use figma_get_component_image instead
|
|
453
|
-
this.server.tool("figma_take_screenshot", `Export an image of the current Figma page or specific node
|
|
453
|
+
this.server.tool("figma_take_screenshot", `Export an image of the current Figma page or specific node. Uses the Desktop Bridge plugin (exportAsync) when connected — works on any plan, no REST token needed, reflects current runtime state. Falls back to the Figma REST API when the bridge is unavailable or for PDF format. Use for visual validation after design changes — check alignment, spacing, proportions. Pass nodeId to target specific elements. For components, prefer figma_get_component_image.`, {
|
|
454
454
|
nodeId: z
|
|
455
455
|
.string()
|
|
456
456
|
.optional()
|
|
@@ -468,6 +468,73 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
468
468
|
.default("png")
|
|
469
469
|
.describe("Image format (default: png)"),
|
|
470
470
|
}, async ({ nodeId, scale, format }) => {
|
|
471
|
+
// Callers routinely paste URL-format ids (123-456); both the Plugin
|
|
472
|
+
// API and REST response maps use colon format (123:456).
|
|
473
|
+
nodeId = nodeId?.replace(/-/g, ":");
|
|
474
|
+
// Bridge-first: exportAsync via the Desktop Bridge works on any plan
|
|
475
|
+
// (no REST token) and reflects the current runtime state. PDF is the
|
|
476
|
+
// only format exportAsync can't produce, so it goes straight to REST.
|
|
477
|
+
if (format !== "pdf" && this.wsServer?.isClientConnected()) {
|
|
478
|
+
try {
|
|
479
|
+
const connector = await this.getDesktopConnector();
|
|
480
|
+
// Resolve the target node the same way the REST path does;
|
|
481
|
+
// empty string means the plugin captures the current page.
|
|
482
|
+
let bridgeNodeId = nodeId || "";
|
|
483
|
+
if (!bridgeNodeId) {
|
|
484
|
+
const currentUrl = this.getCurrentFileUrl();
|
|
485
|
+
const nodeIdParam = currentUrl
|
|
486
|
+
? new URL(currentUrl).searchParams.get("node-id")
|
|
487
|
+
: null;
|
|
488
|
+
if (nodeIdParam) {
|
|
489
|
+
bridgeNodeId = nodeIdParam.replace(/-/g, ":");
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
const bridgeFormat = format === "jpg" ? "JPG" : format === "svg" ? "SVG" : "PNG";
|
|
493
|
+
// exportAsync scale floor is 0.5 (REST allows 0.01)
|
|
494
|
+
const bridgeScale = Math.min(Math.max(scale, 0.5), 4);
|
|
495
|
+
logger.info({ nodeId: bridgeNodeId, format: bridgeFormat, scale: bridgeScale }, "Capturing screenshot via Desktop Bridge (bridge-first)");
|
|
496
|
+
let result = await connector.captureScreenshot(bridgeNodeId, {
|
|
497
|
+
format: bridgeFormat,
|
|
498
|
+
scale: bridgeScale,
|
|
499
|
+
});
|
|
500
|
+
if (result &&
|
|
501
|
+
typeof result.success === "undefined" &&
|
|
502
|
+
result.image) {
|
|
503
|
+
result = { success: true, image: result };
|
|
504
|
+
}
|
|
505
|
+
if (!result?.success || !result.image?.base64) {
|
|
506
|
+
throw new Error(result?.error || "Bridge screenshot returned no image");
|
|
507
|
+
}
|
|
508
|
+
const bridgeMimeType = bridgeFormat === "JPG"
|
|
509
|
+
? "image/jpeg"
|
|
510
|
+
: bridgeFormat === "SVG"
|
|
511
|
+
? "image/svg+xml"
|
|
512
|
+
: "image/png";
|
|
513
|
+
return {
|
|
514
|
+
content: [
|
|
515
|
+
{
|
|
516
|
+
type: "text",
|
|
517
|
+
text: JSON.stringify({
|
|
518
|
+
nodeId: result.image.node?.id || bridgeNodeId || null,
|
|
519
|
+
scale: result.image.scale,
|
|
520
|
+
format,
|
|
521
|
+
byteLength: result.image.byteLength,
|
|
522
|
+
source: "desktop_bridge",
|
|
523
|
+
note: "Screenshot captured via the Desktop Bridge plugin (current runtime state, no REST token required). The image is included below for visual analysis.",
|
|
524
|
+
}),
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
type: "image",
|
|
528
|
+
data: result.image.base64,
|
|
529
|
+
mimeType: bridgeMimeType,
|
|
530
|
+
},
|
|
531
|
+
],
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
catch (bridgeError) {
|
|
535
|
+
logger.warn({ error: bridgeError }, "Desktop Bridge screenshot failed, falling back to REST API");
|
|
536
|
+
}
|
|
537
|
+
}
|
|
471
538
|
try {
|
|
472
539
|
const api = await this.getFigmaAPI();
|
|
473
540
|
// Get current URL to extract file key and node ID if not provided
|
|
@@ -544,6 +611,12 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
544
611
|
catch (error) {
|
|
545
612
|
logger.error({ error }, "Failed to capture screenshot");
|
|
546
613
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
614
|
+
// FigmaAPI.request() tags real token failures with isAuthError —
|
|
615
|
+
// a bare "403" substring also matches node IDs like "403:12"
|
|
616
|
+
// and permission-denied responses, which are not token problems.
|
|
617
|
+
const isAuthError = error?.isAuthError === true ||
|
|
618
|
+
errorMessage.toLowerCase().includes("token expired") ||
|
|
619
|
+
errorMessage.toLowerCase().includes("invalid token");
|
|
547
620
|
return {
|
|
548
621
|
content: [
|
|
549
622
|
{
|
|
@@ -551,7 +624,9 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
551
624
|
text: JSON.stringify({
|
|
552
625
|
error: errorMessage,
|
|
553
626
|
message: "Failed to capture screenshot via Figma API",
|
|
554
|
-
hint:
|
|
627
|
+
hint: isAuthError
|
|
628
|
+
? "Your FIGMA_ACCESS_TOKEN is expired or invalid. Generate a new personal access token at figma.com → Settings → Security → Personal access tokens, then update FIGMA_ACCESS_TOKEN in your MCP config. Alternatively, open the Desktop Bridge plugin in Figma Desktop — screenshots work through the bridge without any REST token."
|
|
629
|
+
: "Make sure you've called figma_navigate to open a file, or provide a valid nodeId parameter. Tip: with the Desktop Bridge plugin open, screenshots don't need a REST token at all.",
|
|
555
630
|
}),
|
|
556
631
|
},
|
|
557
632
|
],
|
|
@@ -952,6 +1027,11 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
952
1027
|
websocket: {
|
|
953
1028
|
available: wsConnected,
|
|
954
1029
|
serverRunning: this.wsServer?.isStarted() ?? false,
|
|
1030
|
+
// Version of the plugin files this server ships — what a
|
|
1031
|
+
// manifest re-import installs. pluginUpdateAvailable on
|
|
1032
|
+
// connected files compares against THIS, not the server
|
|
1033
|
+
// version (which can be newer on server-only releases).
|
|
1034
|
+
bundledPluginVersion: getBundledPluginVersion(),
|
|
955
1035
|
port: this.wsActualPort ? String(this.wsActualPort) : null,
|
|
956
1036
|
preferredPort: String(this.wsPreferredPort),
|
|
957
1037
|
portFallbackUsed: this.wsActualPort !== null && this.wsActualPort !== this.wsPreferredPort,
|
|
@@ -981,6 +1061,8 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
981
1061
|
fileKey: wsFileInfo.fileKey,
|
|
982
1062
|
currentPage: wsFileInfo.currentPage,
|
|
983
1063
|
connectedAt: new Date(wsFileInfo.connectedAt).toISOString(),
|
|
1064
|
+
pluginVersion: wsFileInfo.pluginVersion ?? undefined,
|
|
1065
|
+
pluginUpdateAvailable: wsFileInfo.pluginUpdateAvailable || undefined,
|
|
984
1066
|
} : undefined,
|
|
985
1067
|
connectedFiles: (() => {
|
|
986
1068
|
const files = this.wsServer?.getConnectedFiles();
|
|
@@ -993,6 +1075,8 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
993
1075
|
editorType: f.editorType || 'figma',
|
|
994
1076
|
isActive: f.isActive,
|
|
995
1077
|
connectedAt: new Date(f.connectedAt).toISOString(),
|
|
1078
|
+
pluginVersion: f.pluginVersion ?? undefined,
|
|
1079
|
+
pluginUpdateAvailable: f.pluginUpdateAvailable || undefined,
|
|
996
1080
|
}));
|
|
997
1081
|
})(),
|
|
998
1082
|
currentSelection: (() => {
|
|
@@ -1073,46 +1157,60 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
1073
1157
|
// Tool: Force reconnect to Figma Desktop
|
|
1074
1158
|
this.server.tool("figma_reconnect", "Force a complete reconnection to Figma Desktop. Use when connection seems stale or after switching files.", {}, async () => {
|
|
1075
1159
|
try {
|
|
1076
|
-
// Clear cached desktop connector to force fresh detection
|
|
1077
|
-
this.desktopConnector = null;
|
|
1078
|
-
let transport = "none";
|
|
1079
|
-
let currentUrl = null;
|
|
1080
|
-
let fileName = null;
|
|
1081
1160
|
// figma_reconnect is informational in WebSocket-only mode — the
|
|
1082
|
-
// plugin handles its own reconnect logic.
|
|
1083
|
-
// the
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
if (transport === "none") {
|
|
1161
|
+
// plugin handles its own reconnect logic. A TCP-open socket is not
|
|
1162
|
+
// proof of health (the plugin sandbox can be dead while ui.html
|
|
1163
|
+
// still pongs), so a live roundtrip through the sandbox is the
|
|
1164
|
+
// success criterion here.
|
|
1165
|
+
if (!this.wsServer?.isClientConnected()) {
|
|
1088
1166
|
throw new Error("Cannot connect to Figma Desktop.\n\n" +
|
|
1089
1167
|
"Open the Desktop Bridge plugin in Figma (Plugins → Development → Figma Desktop Bridge).");
|
|
1090
1168
|
}
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1169
|
+
const connector = await this.getDesktopConnector();
|
|
1170
|
+
const probe = async () => connector.executeCodeViaUI("return { fileName: figma.root.name, fileKey: figma.fileKey }", 5000);
|
|
1171
|
+
let fileInfo = await probe().catch(() => ({ success: false, result: null }));
|
|
1172
|
+
let selfHealed = false;
|
|
1173
|
+
if (!fileInfo.success) {
|
|
1174
|
+
// Sandbox-dead / wedged-relay state: attempt self-healing by
|
|
1175
|
+
// reloading the plugin iframe (RELOAD_UI re-runs figma.showUI,
|
|
1176
|
+
// which triggers a fresh port scan and reconnection). This only
|
|
1177
|
+
// works when the message relay is still partially functional —
|
|
1178
|
+
// if code.js itself is dead, the command times out and we fall
|
|
1179
|
+
// through to the honest error below.
|
|
1180
|
+
try {
|
|
1181
|
+
logger.info("Reconnect probe failed — attempting RELOAD_UI self-heal");
|
|
1182
|
+
await this.wsServer.sendCommand("RELOAD_UI", {}, 5000);
|
|
1183
|
+
// Give the fresh iframe time to rescan and re-identify
|
|
1184
|
+
await new Promise((resolve) => setTimeout(resolve, 4000));
|
|
1185
|
+
fileInfo = await probe().catch(() => ({ success: false, result: null }));
|
|
1186
|
+
selfHealed = fileInfo.success;
|
|
1187
|
+
}
|
|
1188
|
+
catch {
|
|
1189
|
+
// RELOAD_UI itself failed — the sandbox is truly unreachable
|
|
1097
1190
|
}
|
|
1098
1191
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1192
|
+
if (!fileInfo.success) {
|
|
1193
|
+
const probeErr = new Error("Desktop Bridge socket is open but the plugin is not responding to commands " +
|
|
1194
|
+
"(automatic plugin reload was attempted and did not help). " +
|
|
1195
|
+
"Close and reopen the Figma Console MCP plugin in Figma Desktop (Plugins → Development → Figma Console MCP).");
|
|
1196
|
+
probeErr.connectionError = this.buildConnectionError(probeErr);
|
|
1197
|
+
throw probeErr;
|
|
1101
1198
|
}
|
|
1199
|
+
const fileName = fileInfo.result?.fileName || null;
|
|
1102
1200
|
return {
|
|
1103
1201
|
content: [
|
|
1104
1202
|
{
|
|
1105
1203
|
type: "text",
|
|
1106
1204
|
text: JSON.stringify({
|
|
1107
|
-
status: "
|
|
1108
|
-
transport,
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1205
|
+
status: "connected",
|
|
1206
|
+
transport: "websocket",
|
|
1207
|
+
probeVerified: true,
|
|
1208
|
+
selfHealed: selfHealed || undefined,
|
|
1209
|
+
fileName,
|
|
1112
1210
|
timestamp: Date.now(),
|
|
1113
|
-
message:
|
|
1114
|
-
? `
|
|
1115
|
-
: `
|
|
1211
|
+
message: selfHealed
|
|
1212
|
+
? `Plugin was unresponsive; automatically reloaded it and verified the connection. Connected to: "${fileName || "(unnamed file)"}"`
|
|
1213
|
+
: `Connection verified via live roundtrip. Connected to: "${fileName || "(unnamed file)"}"`,
|
|
1116
1214
|
}),
|
|
1117
1215
|
},
|
|
1118
1216
|
],
|
|
@@ -1375,8 +1473,11 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
1375
1473
|
const { DesignSystemManifestCache, createEmptyManifest, figmaColorToHex, } = await import("./core/design-system-manifest.js");
|
|
1376
1474
|
const cache = DesignSystemManifestCache.getInstance();
|
|
1377
1475
|
const currentUrl = this.getCurrentFileUrl();
|
|
1378
|
-
|
|
1379
|
-
|
|
1476
|
+
// extractFileKey is branch-aware: on /design/KEY/branch/BRANCH_KEY/…
|
|
1477
|
+
// URLs the branch key is the effective file key — an ad-hoc regex
|
|
1478
|
+
// here returned the main key and cached the wrong manifest for
|
|
1479
|
+
// branch files.
|
|
1480
|
+
const fileKey = (currentUrl ? extractFileKey(currentUrl) : null) ?? "unknown";
|
|
1380
1481
|
// Check cache first
|
|
1381
1482
|
let cacheEntry = cache.get(fileKey);
|
|
1382
1483
|
if (cacheEntry) {
|
|
@@ -1431,7 +1532,10 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
1431
1532
|
logger.warn({ error }, "Could not fetch variables during auto-load");
|
|
1432
1533
|
}
|
|
1433
1534
|
// Get components
|
|
1535
|
+
// Maps are keyed by component key (unique) rather than name — same-named
|
|
1536
|
+
// components on different pages would silently overwrite each other.
|
|
1434
1537
|
let rawComponents;
|
|
1538
|
+
let componentsFetchError = null;
|
|
1435
1539
|
try {
|
|
1436
1540
|
const componentsResult = await connector.getLocalComponents();
|
|
1437
1541
|
if (componentsResult.success && componentsResult.data) {
|
|
@@ -1440,7 +1544,7 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
1440
1544
|
componentSets: componentsResult.data.componentSets || [],
|
|
1441
1545
|
};
|
|
1442
1546
|
for (const comp of rawComponents.components) {
|
|
1443
|
-
manifest.components[comp.
|
|
1547
|
+
manifest.components[comp.key || comp.nodeId] = {
|
|
1444
1548
|
key: comp.key,
|
|
1445
1549
|
nodeId: comp.nodeId,
|
|
1446
1550
|
name: comp.name,
|
|
@@ -1449,7 +1553,7 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
1449
1553
|
};
|
|
1450
1554
|
}
|
|
1451
1555
|
for (const compSet of rawComponents.componentSets) {
|
|
1452
|
-
manifest.componentSets[compSet.
|
|
1556
|
+
manifest.componentSets[compSet.key || compSet.nodeId] = {
|
|
1453
1557
|
key: compSet.key,
|
|
1454
1558
|
nodeId: compSet.nodeId,
|
|
1455
1559
|
name: compSet.name,
|
|
@@ -1466,8 +1570,14 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
1466
1570
|
};
|
|
1467
1571
|
}
|
|
1468
1572
|
}
|
|
1573
|
+
else {
|
|
1574
|
+
componentsFetchError =
|
|
1575
|
+
componentsResult?.error || "getLocalComponents returned no data";
|
|
1576
|
+
}
|
|
1469
1577
|
}
|
|
1470
1578
|
catch (error) {
|
|
1579
|
+
componentsFetchError =
|
|
1580
|
+
error instanceof Error ? error.message : String(error);
|
|
1471
1581
|
logger.warn({ error }, "Could not fetch components during auto-load");
|
|
1472
1582
|
}
|
|
1473
1583
|
// Update summary
|
|
@@ -1484,10 +1594,29 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
1484
1594
|
typographyScale: [],
|
|
1485
1595
|
componentCategories: [],
|
|
1486
1596
|
};
|
|
1487
|
-
// Cache the result
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1597
|
+
// Cache the result — but never cache a manifest built from a FAILED
|
|
1598
|
+
// components fetch: serving an empty manifest as "cached" for the full
|
|
1599
|
+
// TTL is exactly the "search returns 0 components" poisoning bug.
|
|
1600
|
+
if (!componentsFetchError) {
|
|
1601
|
+
cache.set(fileKey, manifest, rawComponents);
|
|
1602
|
+
cacheEntry = cache.get(fileKey);
|
|
1603
|
+
}
|
|
1604
|
+
else {
|
|
1605
|
+
cacheEntry = {
|
|
1606
|
+
manifest,
|
|
1607
|
+
timestamp: Date.now(),
|
|
1608
|
+
fileKey,
|
|
1609
|
+
rawComponents,
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
return {
|
|
1613
|
+
cacheEntry,
|
|
1614
|
+
fileKey,
|
|
1615
|
+
wasLoaded: true,
|
|
1616
|
+
warning: componentsFetchError
|
|
1617
|
+
? `Components fetch failed (${componentsFetchError}) — results may be incomplete and were NOT cached; retry after checking the Desktop Bridge plugin.`
|
|
1618
|
+
: undefined,
|
|
1619
|
+
};
|
|
1491
1620
|
};
|
|
1492
1621
|
// ============================================================================
|
|
1493
1622
|
// READ-SIDE LIBRARY / DESIGN-SYSTEM TOOLS
|
|
@@ -1505,7 +1634,7 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
1505
1634
|
const { DesignSystemManifestCache, createEmptyManifest, figmaColorToHex, getCategories, getTokenSummary, } = await import("./core/design-system-manifest.js");
|
|
1506
1635
|
const cache = DesignSystemManifestCache.getInstance();
|
|
1507
1636
|
const currentUrl = this.getCurrentFileUrl();
|
|
1508
|
-
const fileKeyMatch = currentUrl?.match(/\/(file|design)\/([a-zA-Z0-9]+)/);
|
|
1637
|
+
const fileKeyMatch = currentUrl?.match(/\/(file|design|board|slides)\/([a-zA-Z0-9]+)/);
|
|
1509
1638
|
const fileKey = fileKeyMatch ? fileKeyMatch[2] : "unknown";
|
|
1510
1639
|
// Check cache first
|
|
1511
1640
|
let cacheEntry = cache.get(fileKey);
|
|
@@ -1582,7 +1711,10 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
1582
1711
|
logger.warn({ error }, "Could not fetch variables");
|
|
1583
1712
|
}
|
|
1584
1713
|
// Get components (can be slow for large files)
|
|
1714
|
+
// Keyed by component key (unique) — name keys drop same-named
|
|
1715
|
+
// components on other pages.
|
|
1585
1716
|
let rawComponents;
|
|
1717
|
+
let componentsFetchError = null;
|
|
1586
1718
|
try {
|
|
1587
1719
|
const componentsResult = await connector.getLocalComponents();
|
|
1588
1720
|
if (componentsResult.success && componentsResult.data) {
|
|
@@ -1591,7 +1723,7 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
1591
1723
|
componentSets: componentsResult.data.componentSets || [],
|
|
1592
1724
|
};
|
|
1593
1725
|
for (const comp of rawComponents.components) {
|
|
1594
|
-
manifest.components[comp.
|
|
1726
|
+
manifest.components[comp.key || comp.nodeId] = {
|
|
1595
1727
|
key: comp.key,
|
|
1596
1728
|
nodeId: comp.nodeId,
|
|
1597
1729
|
name: comp.name,
|
|
@@ -1600,7 +1732,7 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
1600
1732
|
};
|
|
1601
1733
|
}
|
|
1602
1734
|
for (const compSet of rawComponents.componentSets) {
|
|
1603
|
-
manifest.componentSets[compSet.
|
|
1735
|
+
manifest.componentSets[compSet.key || compSet.nodeId] = {
|
|
1604
1736
|
key: compSet.key,
|
|
1605
1737
|
nodeId: compSet.nodeId,
|
|
1606
1738
|
name: compSet.name,
|
|
@@ -1617,8 +1749,14 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
1617
1749
|
};
|
|
1618
1750
|
}
|
|
1619
1751
|
}
|
|
1752
|
+
else {
|
|
1753
|
+
componentsFetchError =
|
|
1754
|
+
componentsResult?.error || "getLocalComponents returned no data";
|
|
1755
|
+
}
|
|
1620
1756
|
}
|
|
1621
1757
|
catch (error) {
|
|
1758
|
+
componentsFetchError =
|
|
1759
|
+
error instanceof Error ? error.message : String(error);
|
|
1622
1760
|
logger.warn({ error }, "Could not fetch components");
|
|
1623
1761
|
}
|
|
1624
1762
|
// Update summary
|
|
@@ -1635,8 +1773,11 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
1635
1773
|
typographyScale: [],
|
|
1636
1774
|
componentCategories: [],
|
|
1637
1775
|
};
|
|
1638
|
-
// Cache the result
|
|
1639
|
-
|
|
1776
|
+
// Cache the result — never cache a manifest built from a failed
|
|
1777
|
+
// components fetch (would serve empty results for the full TTL).
|
|
1778
|
+
if (!componentsFetchError) {
|
|
1779
|
+
cache.set(fileKey, manifest, rawComponents);
|
|
1780
|
+
}
|
|
1640
1781
|
const categories = getCategories(manifest);
|
|
1641
1782
|
const tokenSummary = getTokenSummary(manifest);
|
|
1642
1783
|
return {
|
|
@@ -1654,6 +1795,11 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
1654
1795
|
componentSets: manifest.summary.totalComponentSets,
|
|
1655
1796
|
tokens: manifest.summary.totalTokens,
|
|
1656
1797
|
},
|
|
1798
|
+
warnings: componentsFetchError
|
|
1799
|
+
? [
|
|
1800
|
+
`Components fetch failed (${componentsFetchError}) — component data is incomplete and was NOT cached. Check the Desktop Bridge plugin and retry.`,
|
|
1801
|
+
]
|
|
1802
|
+
: undefined,
|
|
1657
1803
|
hint: "Use figma_search_components to find specific components by name or category.",
|
|
1658
1804
|
}),
|
|
1659
1805
|
},
|
|
@@ -1800,7 +1946,18 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
|
|
|
1800
1946
|
const effectiveLimit = Math.min(limit || 10, 25);
|
|
1801
1947
|
const effectiveOffset = offset || 0;
|
|
1802
1948
|
const total = results.length;
|
|
1803
|
-
const paginatedResults = results
|
|
1949
|
+
const paginatedResults = results
|
|
1950
|
+
.slice(effectiveOffset, effectiveOffset + effectiveLimit)
|
|
1951
|
+
// Search hits only need a teaser — some design systems carry
|
|
1952
|
+
// multi-KB doc blocks per component, which multiplies across
|
|
1953
|
+
// a result page. figma_get_component_details returns full text.
|
|
1954
|
+
.map((item) => item.description && item.description.length > 200
|
|
1955
|
+
? {
|
|
1956
|
+
...item,
|
|
1957
|
+
description: `${item.description.slice(0, 200)}…`,
|
|
1958
|
+
descriptionTruncated: true,
|
|
1959
|
+
}
|
|
1960
|
+
: item);
|
|
1804
1961
|
return {
|
|
1805
1962
|
content: [
|
|
1806
1963
|
{
|
|
@@ -1827,7 +1984,7 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
|
|
|
1827
1984
|
// LOCAL SEARCH PATH: Use cached design system manifest (existing behavior)
|
|
1828
1985
|
const { searchComponents } = await import("./core/design-system-manifest.js");
|
|
1829
1986
|
// Auto-load design system cache if needed (no error returned to user)
|
|
1830
|
-
const { cacheEntry } = await ensureDesignSystemCache();
|
|
1987
|
+
const { cacheEntry, warning: cacheWarning } = await ensureDesignSystemCache();
|
|
1831
1988
|
if (!cacheEntry) {
|
|
1832
1989
|
return {
|
|
1833
1990
|
content: [
|
|
@@ -1858,6 +2015,7 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
|
|
|
1858
2015
|
query: query || "(all)",
|
|
1859
2016
|
category: category || "(all)",
|
|
1860
2017
|
results: results.results,
|
|
2018
|
+
warnings: cacheWarning ? [cacheWarning] : undefined,
|
|
1861
2019
|
pagination: {
|
|
1862
2020
|
offset: offset || 0,
|
|
1863
2021
|
limit: effectiveLimit,
|
|
@@ -1930,10 +2088,12 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
|
|
|
1930
2088
|
// Search for the component
|
|
1931
2089
|
let component = null;
|
|
1932
2090
|
let isComponentSet = false;
|
|
1933
|
-
// Check component sets first (they have variants)
|
|
1934
|
-
|
|
2091
|
+
// Check component sets first (they have variants).
|
|
2092
|
+
// Maps are keyed by component key; match names via the entry's
|
|
2093
|
+
// own name field, not the map key.
|
|
2094
|
+
for (const compSet of Object.values(cacheEntry.manifest.componentSets)) {
|
|
1935
2095
|
if ((componentKey && compSet.key === componentKey) ||
|
|
1936
|
-
(componentName && name === componentName)) {
|
|
2096
|
+
(componentName && compSet.name === componentName)) {
|
|
1937
2097
|
component = compSet;
|
|
1938
2098
|
isComponentSet = true;
|
|
1939
2099
|
break;
|
|
@@ -1941,9 +2101,9 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
|
|
|
1941
2101
|
}
|
|
1942
2102
|
// Check standalone components
|
|
1943
2103
|
if (!component) {
|
|
1944
|
-
for (const
|
|
2104
|
+
for (const comp of Object.values(cacheEntry.manifest.components)) {
|
|
1945
2105
|
if ((componentKey && comp.key === componentKey) ||
|
|
1946
|
-
(componentName && name === componentName)) {
|
|
2106
|
+
(componentName && comp.name === componentName)) {
|
|
1947
2107
|
component = comp;
|
|
1948
2108
|
break;
|
|
1949
2109
|
}
|
|
@@ -2363,8 +2523,9 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
|
|
|
2363
2523
|
registerWriteTools(this.server, () => this.getDesktopConnector());
|
|
2364
2524
|
// Register token sync tools — figma_export_tokens and figma_import_tokens.
|
|
2365
2525
|
// Replace Style Dictionary and Tokens Studio's export pipeline for the
|
|
2366
|
-
// popular styling methods (DTCG canonical
|
|
2367
|
-
//
|
|
2526
|
+
// popular styling methods (DTCG canonical — legacy + 2025.10 dialects —
|
|
2527
|
+
// plus CSS/Tailwind/SCSS/TS/JSON/Style Dictionary/Tokens Studio, all
|
|
2528
|
+
// derived from a single internal token model).
|
|
2368
2529
|
registerTokensTools(this.server, () => this.getDesktopConnector());
|
|
2369
2530
|
// Register Figma API tools (Tools 8-11)
|
|
2370
2531
|
registerFigmaAPITools(this.server, () => this.getFigmaAPI(), () => this.getCurrentFileUrl(), this.variablesCache, // Pass cache for efficient variable queries
|
|
@@ -2414,6 +2575,7 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
|
|
|
2414
2575
|
return "0.0.0";
|
|
2415
2576
|
}
|
|
2416
2577
|
},
|
|
2578
|
+
getBundledPluginVersion,
|
|
2417
2579
|
getPluginState: () => {
|
|
2418
2580
|
if (!this.wsServer)
|
|
2419
2581
|
return null;
|
|
@@ -2427,6 +2589,8 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
|
|
|
2427
2589
|
editorType: fileInfo?.editorType,
|
|
2428
2590
|
port: this.wsActualPort ?? undefined,
|
|
2429
2591
|
portFallbackFrom: this.wsPreferredPort,
|
|
2592
|
+
pluginVersion: fileInfo?.pluginVersion,
|
|
2593
|
+
pluginUpdateAvailable: fileInfo?.pluginUpdateAvailable,
|
|
2430
2594
|
};
|
|
2431
2595
|
},
|
|
2432
2596
|
getTokenState: () => {
|
|
@@ -2765,13 +2929,17 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
|
|
|
2765
2929
|
const wsHost = process.env.FIGMA_WS_HOST || 'localhost';
|
|
2766
2930
|
this.wsPreferredPort = parseInt(process.env.FIGMA_WS_PORT || String(DEFAULT_WS_PORT), 10);
|
|
2767
2931
|
// Clean up stale/orphaned MCP server instances before trying to bind.
|
|
2768
|
-
//
|
|
2932
|
+
// Step 1: Remove stale port files and terminate zombie processes that have port files
|
|
2769
2933
|
cleanupStalePortFiles();
|
|
2770
|
-
//
|
|
2934
|
+
// Step 2: Deep scan for orphaned processes holding ports WITHOUT port files
|
|
2771
2935
|
// (e.g., old instances from before port file tracking, or files already cleaned up)
|
|
2772
2936
|
cleanupOrphanedProcesses(this.wsPreferredPort);
|
|
2773
2937
|
const portsToTry = getPortRange(this.wsPreferredPort);
|
|
2774
2938
|
let boundPort = null;
|
|
2939
|
+
// Distinguish "every port was in use" from "bind failed for another
|
|
2940
|
+
// reason" — eviction must only fire for genuine port exhaustion, and
|
|
2941
|
+
// figma_get_status must report the real error code.
|
|
2942
|
+
let lastNonPortError = null;
|
|
2775
2943
|
for (const port of portsToTry) {
|
|
2776
2944
|
try {
|
|
2777
2945
|
this.wsServer = new FigmaWebSocketServer({ port, host: wsHost });
|
|
@@ -2806,12 +2974,18 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
|
|
|
2806
2974
|
}
|
|
2807
2975
|
// Non-port-conflict error — don't try more ports
|
|
2808
2976
|
logger.warn({ error: errorMsg, port }, "Failed to start WebSocket bridge server");
|
|
2977
|
+
lastNonPortError = {
|
|
2978
|
+
code: errorCode || "UNKNOWN",
|
|
2979
|
+
message: errorMsg,
|
|
2980
|
+
};
|
|
2809
2981
|
this.wsServer = null;
|
|
2810
2982
|
break;
|
|
2811
2983
|
}
|
|
2812
2984
|
}
|
|
2813
|
-
// Phase 3: If all ports exhausted, try evicting the oldest instance and
|
|
2814
|
-
|
|
2985
|
+
// Phase 3: If all ports exhausted, try evicting the oldest instance and
|
|
2986
|
+
// retry ONCE. Only for genuine EADDRINUSE exhaustion — killing a healthy
|
|
2987
|
+
// sibling can't fix an EACCES/EADDRNOTAVAIL bind failure.
|
|
2988
|
+
if (!boundPort && !lastNonPortError && evictOldestInstance(this.wsPreferredPort)) {
|
|
2815
2989
|
for (const port of portsToTry) {
|
|
2816
2990
|
try {
|
|
2817
2991
|
this.wsServer = new FigmaWebSocketServer({ port, host: wsHost });
|
|
@@ -2840,11 +3014,15 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
|
|
|
2840
3014
|
}
|
|
2841
3015
|
if (!boundPort) {
|
|
2842
3016
|
this.wsStartupError = {
|
|
2843
|
-
code: "EADDRINUSE",
|
|
3017
|
+
code: lastNonPortError?.code ?? "EADDRINUSE",
|
|
2844
3018
|
port: this.wsPreferredPort,
|
|
2845
3019
|
};
|
|
2846
3020
|
const rangeEnd = this.wsPreferredPort + portsToTry.length - 1;
|
|
2847
|
-
logger.warn(
|
|
3021
|
+
logger.warn(lastNonPortError
|
|
3022
|
+
? { error: lastNonPortError }
|
|
3023
|
+
: { portRange: `${this.wsPreferredPort}-${rangeEnd}` }, lastNonPortError
|
|
3024
|
+
? "WebSocket bridge failed to start (non-port error) — running without WebSocket transport"
|
|
3025
|
+
: "All WebSocket ports in range are in use — running without WebSocket transport");
|
|
2848
3026
|
}
|
|
2849
3027
|
if (this.wsServer) {
|
|
2850
3028
|
// Log when plugin files connect/disconnect (with file identity)
|
|
@@ -2859,6 +3037,11 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
|
|
|
2859
3037
|
logger.info({ fileKey: data.fileKey, fileName: data.fileName }, "Desktop Bridge plugin disconnected from WebSocket");
|
|
2860
3038
|
if (data.fileKey) {
|
|
2861
3039
|
this.variablesCache.delete(data.fileKey);
|
|
3040
|
+
// design-system-tools.ts stores token data under a prefixed key
|
|
3041
|
+
this.variablesCache.delete(`vars:${data.fileKey}`);
|
|
3042
|
+
void import("./core/design-system-manifest.js").then(({ DesignSystemManifestCache }) => {
|
|
3043
|
+
DesignSystemManifestCache.getInstance().invalidate(data.fileKey);
|
|
3044
|
+
}).catch(() => { });
|
|
2862
3045
|
}
|
|
2863
3046
|
});
|
|
2864
3047
|
// Invalidate variable cache when document changes are reported.
|
|
@@ -2868,8 +3051,15 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
|
|
|
2868
3051
|
this.wsServer.on("documentChange", (data) => {
|
|
2869
3052
|
if (data.hasStyleChanges || data.hasNodeChanges) {
|
|
2870
3053
|
if (data.fileKey) {
|
|
2871
|
-
// Per-file cache invalidation — only clear the affected file's cache
|
|
3054
|
+
// Per-file cache invalidation — only clear the affected file's cache.
|
|
3055
|
+
// Also clear the design-system-tools token entry (prefixed key)
|
|
3056
|
+
// and the component manifest, so searches see new components
|
|
3057
|
+
// and the design-system kit sees edited variables.
|
|
2872
3058
|
this.variablesCache.delete(data.fileKey);
|
|
3059
|
+
this.variablesCache.delete(`vars:${data.fileKey}`);
|
|
3060
|
+
void import("./core/design-system-manifest.js").then(({ DesignSystemManifestCache }) => {
|
|
3061
|
+
DesignSystemManifestCache.getInstance().invalidate(data.fileKey);
|
|
3062
|
+
}).catch(() => { });
|
|
2873
3063
|
logger.debug({ fileKey: data.fileKey, changeCount: data.changeCount, hasStyleChanges: data.hasStyleChanges, hasNodeChanges: data.hasNodeChanges }, "Variable cache invalidated due to document changes");
|
|
2874
3064
|
}
|
|
2875
3065
|
else {
|