@mp3wizard/figma-console-mcp 1.22.6 → 1.25.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 +15 -38
- package/dist/cloudflare/core/design-code-tools.js +3 -29
- package/dist/cloudflare/core/diff/changelog-formatter.js +275 -0
- package/dist/cloudflare/core/diff/diff-engine.js +334 -0
- package/dist/cloudflare/core/diff/property-compare.js +36 -0
- package/dist/cloudflare/core/diff/version-cache.js +74 -0
- package/dist/cloudflare/core/figma-api.js +19 -0
- package/dist/cloudflare/core/version-tools.js +1158 -0
- package/dist/cloudflare/core/websocket-server.js +72 -0
- package/dist/cloudflare/index.js +17 -13
- package/dist/core/design-code-tools.d.ts +1 -12
- package/dist/core/design-code-tools.d.ts.map +1 -1
- package/dist/core/design-code-tools.js +3 -29
- package/dist/core/design-code-tools.js.map +1 -1
- package/dist/core/diff/changelog-formatter.d.ts +35 -0
- package/dist/core/diff/changelog-formatter.d.ts.map +1 -0
- package/dist/core/diff/changelog-formatter.js +276 -0
- package/dist/core/diff/changelog-formatter.js.map +1 -0
- package/dist/core/diff/diff-engine.d.ts +127 -0
- package/dist/core/diff/diff-engine.d.ts.map +1 -0
- package/dist/core/diff/diff-engine.js +335 -0
- package/dist/core/diff/diff-engine.js.map +1 -0
- package/dist/core/diff/property-compare.d.ts +19 -0
- package/dist/core/diff/property-compare.d.ts.map +1 -0
- package/dist/core/diff/property-compare.js +37 -0
- package/dist/core/diff/property-compare.js.map +1 -0
- package/dist/core/diff/version-cache.d.ts +40 -0
- package/dist/core/diff/version-cache.d.ts.map +1 -0
- package/dist/core/diff/version-cache.js +75 -0
- package/dist/core/diff/version-cache.js.map +1 -0
- package/dist/core/figma-api.d.ts +29 -0
- package/dist/core/figma-api.d.ts.map +1 -1
- package/dist/core/figma-api.js +19 -0
- package/dist/core/figma-api.js.map +1 -1
- package/dist/core/version-tools.d.ts +59 -0
- package/dist/core/version-tools.d.ts.map +1 -0
- package/dist/core/version-tools.js +1159 -0
- package/dist/core/version-tools.js.map +1 -0
- package/dist/core/websocket-server.d.ts +43 -0
- package/dist/core/websocket-server.d.ts.map +1 -1
- package/dist/core/websocket-server.js +72 -0
- package/dist/core/websocket-server.js.map +1 -1
- package/dist/local.d.ts.map +1 -1
- package/dist/local.js +16 -0
- package/dist/local.js.map +1 -1
- package/figma-desktop-bridge/code.js +49 -1
- package/figma-desktop-bridge/ui.html +13 -0
- package/package.json +89 -1
|
@@ -335,6 +335,34 @@ export class FigmaWebSocketServer extends EventEmitter {
|
|
|
335
335
|
}
|
|
336
336
|
this.emit('documentChange', { fileKey: found?.fileKey ?? null, ...message.data });
|
|
337
337
|
}
|
|
338
|
+
// v1.25.0: buffer description/annotation changes for the specific file.
|
|
339
|
+
// These are the diff-engine blind spots that Figma REST doesn't expose;
|
|
340
|
+
// the diff engine consults this buffer when correlating to a version
|
|
341
|
+
// range, so changes made while the plugin was connected become visible.
|
|
342
|
+
if (message.type === 'METADATA_CHANGE' && message.data) {
|
|
343
|
+
const found = this.findClientByWs(ws);
|
|
344
|
+
if (found) {
|
|
345
|
+
const changes = Array.isArray(message.data.changes) ? message.data.changes : [];
|
|
346
|
+
for (const c of changes) {
|
|
347
|
+
if (!c || typeof c.node_id !== 'string' || !c.field)
|
|
348
|
+
continue;
|
|
349
|
+
const entry = {
|
|
350
|
+
node_id: c.node_id,
|
|
351
|
+
node_name: c.node_name ?? null,
|
|
352
|
+
node_type: c.node_type ?? null,
|
|
353
|
+
field: c.field === 'annotations' ? 'annotations' : 'description',
|
|
354
|
+
new_value: c.new_value,
|
|
355
|
+
timestamp: typeof c.timestamp === 'number' ? c.timestamp : Date.now(),
|
|
356
|
+
};
|
|
357
|
+
found.client.metadataChanges.push(entry);
|
|
358
|
+
if (found.client.metadataChanges.length > this.documentChangeBufferSize) {
|
|
359
|
+
found.client.metadataChanges.shift();
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
found.client.lastActivity = Date.now();
|
|
363
|
+
}
|
|
364
|
+
this.emit('metadataChange', { fileKey: found?.fileKey ?? null, changes: message.data.changes });
|
|
365
|
+
}
|
|
338
366
|
// Track selection changes — user interaction makes this the active file
|
|
339
367
|
if (message.type === 'SELECTION_CHANGE' && message.data) {
|
|
340
368
|
const found = this.findClientByWs(ws);
|
|
@@ -436,6 +464,7 @@ export class FigmaWebSocketServer extends EventEmitter {
|
|
|
436
464
|
},
|
|
437
465
|
selection: existing?.selection || null,
|
|
438
466
|
documentChanges: existing?.documentChanges || [],
|
|
467
|
+
metadataChanges: existing?.metadataChanges || [],
|
|
439
468
|
consoleLogs: existing?.consoleLogs || [],
|
|
440
469
|
lastActivity: Date.now(),
|
|
441
470
|
lastPongAt: Date.now(),
|
|
@@ -661,6 +690,49 @@ export class FigmaWebSocketServer extends EventEmitter {
|
|
|
661
690
|
client.documentChanges = [];
|
|
662
691
|
return count;
|
|
663
692
|
}
|
|
693
|
+
/**
|
|
694
|
+
* v1.25.0: Get buffered description/annotation change events for a file.
|
|
695
|
+
*
|
|
696
|
+
* Used by `figma_diff_versions` to surface changes that Figma REST doesn't
|
|
697
|
+
* expose. The diff engine passes a time window (from-version → to-version
|
|
698
|
+
* `last_modified` timestamps converted to Unix ms) and optional scoping by
|
|
699
|
+
* node IDs.
|
|
700
|
+
*
|
|
701
|
+
* Defaults to the active file if `fileKey` is omitted.
|
|
702
|
+
*/
|
|
703
|
+
getMetadataChanges(options) {
|
|
704
|
+
const fileKey = options?.fileKey ?? this._activeFileKey;
|
|
705
|
+
if (!fileKey)
|
|
706
|
+
return [];
|
|
707
|
+
const client = this.clients.get(fileKey);
|
|
708
|
+
if (!client)
|
|
709
|
+
return [];
|
|
710
|
+
let filtered = [...client.metadataChanges];
|
|
711
|
+
if (options?.since !== undefined) {
|
|
712
|
+
filtered = filtered.filter((e) => e.timestamp >= options.since);
|
|
713
|
+
}
|
|
714
|
+
if (options?.until !== undefined) {
|
|
715
|
+
filtered = filtered.filter((e) => e.timestamp <= options.until);
|
|
716
|
+
}
|
|
717
|
+
if (options?.nodeIds && options.nodeIds.length > 0) {
|
|
718
|
+
const set = new Set(options.nodeIds);
|
|
719
|
+
filtered = filtered.filter((e) => set.has(e.node_id));
|
|
720
|
+
}
|
|
721
|
+
return filtered;
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* v1.25.0: Clear metadata-change buffer for the active file.
|
|
725
|
+
*/
|
|
726
|
+
clearMetadataChanges() {
|
|
727
|
+
if (!this._activeFileKey)
|
|
728
|
+
return 0;
|
|
729
|
+
const client = this.clients.get(this._activeFileKey);
|
|
730
|
+
if (!client)
|
|
731
|
+
return 0;
|
|
732
|
+
const count = client.metadataChanges.length;
|
|
733
|
+
client.metadataChanges = [];
|
|
734
|
+
return count;
|
|
735
|
+
}
|
|
664
736
|
/**
|
|
665
737
|
* Get console logs from the active file with optional filtering
|
|
666
738
|
*/
|
package/dist/cloudflare/index.js
CHANGED
|
@@ -20,6 +20,7 @@ import { FigmaAPI, extractFileKey } from "./core/figma-api.js";
|
|
|
20
20
|
import { registerFigmaAPITools } from "./core/figma-tools.js";
|
|
21
21
|
import { registerDesignCodeTools } from "./core/design-code-tools.js";
|
|
22
22
|
import { registerCommentTools } from "./core/comment-tools.js";
|
|
23
|
+
import { registerVersionTools } from "./core/version-tools.js";
|
|
23
24
|
import { registerAnnotationTools } from "./core/annotation-tools.js";
|
|
24
25
|
import { registerDeepComponentTools } from "./core/deep-component-tools.js";
|
|
25
26
|
import { registerDesignSystemTools } from "./core/design-system-tools.js";
|
|
@@ -69,7 +70,7 @@ export class FigmaConsoleMCPv3 extends McpAgent {
|
|
|
69
70
|
super(...arguments);
|
|
70
71
|
this.server = new McpServer({
|
|
71
72
|
name: "Figma Console MCP",
|
|
72
|
-
version: "1.
|
|
73
|
+
version: "1.25.0",
|
|
73
74
|
});
|
|
74
75
|
this.browserManager = null;
|
|
75
76
|
this.consoleMonitor = null;
|
|
@@ -793,6 +794,8 @@ export class FigmaConsoleMCPv3 extends McpAgent {
|
|
|
793
794
|
{ isRemoteMode: true }, getCloudDesktopConnector);
|
|
794
795
|
// Register Comment tools
|
|
795
796
|
registerCommentTools(this.server, async () => await this.getFigmaAPI(), () => this.browserManager?.getCurrentUrl() || null, { isRemoteMode: true });
|
|
797
|
+
// Register Version History tools
|
|
798
|
+
registerVersionTools(this.server, async () => await this.getFigmaAPI(), () => this.browserManager?.getCurrentUrl() || null, { isRemoteMode: true });
|
|
796
799
|
// Register Design System Kit tool
|
|
797
800
|
registerDesignSystemTools(this.server, async () => await this.getFigmaAPI(), () => this.browserManager?.getCurrentUrl() || null, undefined, // variablesCache
|
|
798
801
|
{ isRemoteMode: true });
|
|
@@ -1055,7 +1058,7 @@ export default {
|
|
|
1055
1058
|
});
|
|
1056
1059
|
const statelessServer = new McpServer({
|
|
1057
1060
|
name: "Figma Console MCP",
|
|
1058
|
-
version: "1.
|
|
1061
|
+
version: "1.25.0",
|
|
1059
1062
|
});
|
|
1060
1063
|
// ================================================================
|
|
1061
1064
|
// Cloud Write Relay — Pairing Tool (stateless /mcp path)
|
|
@@ -1151,6 +1154,7 @@ export default {
|
|
|
1151
1154
|
registerDesignCodeTools(statelessServer, async () => statelessApi, getCloudFileUrl, new Map(), // Fresh variables cache per request
|
|
1152
1155
|
{ isRemoteMode: true }, getCloudDesktopConnector);
|
|
1153
1156
|
registerCommentTools(statelessServer, async () => statelessApi, getCloudFileUrl);
|
|
1157
|
+
registerVersionTools(statelessServer, async () => statelessApi, getCloudFileUrl);
|
|
1154
1158
|
registerDesignSystemTools(statelessServer, async () => statelessApi, getCloudFileUrl, new Map(), // Fresh variables cache per request
|
|
1155
1159
|
{ isRemoteMode: true });
|
|
1156
1160
|
await statelessServer.connect(transport);
|
|
@@ -1172,7 +1176,7 @@ export default {
|
|
|
1172
1176
|
const metadata = {
|
|
1173
1177
|
resource: url.origin,
|
|
1174
1178
|
authorization_servers: [`${url.origin}/`],
|
|
1175
|
-
scopes_supported: ["file_content:read", "file_variables:read", "library_content:read"],
|
|
1179
|
+
scopes_supported: ["file_content:read", "file_versions:read", "file_variables:read", "file_comments:read", "file_comments:write", "library_content:read"],
|
|
1176
1180
|
bearer_methods_supported: ["header"],
|
|
1177
1181
|
resource_signing_alg_values_supported: ["RS256"]
|
|
1178
1182
|
};
|
|
@@ -1191,7 +1195,7 @@ export default {
|
|
|
1191
1195
|
authorization_endpoint: `${url.origin}/authorize`,
|
|
1192
1196
|
token_endpoint: `${url.origin}/token`,
|
|
1193
1197
|
registration_endpoint: `${url.origin}/oauth/register`,
|
|
1194
|
-
scopes_supported: ["file_content:read", "file_variables:read", "library_content:read"],
|
|
1198
|
+
scopes_supported: ["file_content:read", "file_versions:read", "file_variables:read", "file_comments:read", "file_comments:write", "library_content:read"],
|
|
1195
1199
|
response_types_supported: ["code"],
|
|
1196
1200
|
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
1197
1201
|
token_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
|
|
@@ -1254,7 +1258,7 @@ export default {
|
|
|
1254
1258
|
const figmaAuthUrl = new URL("https://www.figma.com/oauth");
|
|
1255
1259
|
figmaAuthUrl.searchParams.set("client_id", env.FIGMA_OAUTH_CLIENT_ID);
|
|
1256
1260
|
figmaAuthUrl.searchParams.set("redirect_uri", `${oauthOrigin}/oauth/callback`);
|
|
1257
|
-
figmaAuthUrl.searchParams.set("scope", "file_content:read,file_variables:read,library_content:read");
|
|
1261
|
+
figmaAuthUrl.searchParams.set("scope", "file_content:read,file_versions:read,file_variables:read,file_comments:read,file_comments:write,library_content:read");
|
|
1258
1262
|
figmaAuthUrl.searchParams.set("state", stateToken);
|
|
1259
1263
|
figmaAuthUrl.searchParams.set("response_type", "code");
|
|
1260
1264
|
return Response.redirect(figmaAuthUrl.toString(), 302);
|
|
@@ -1295,7 +1299,7 @@ export default {
|
|
|
1295
1299
|
token_type: "Bearer",
|
|
1296
1300
|
expires_in: Math.max(0, Math.floor((tokenData.expiresAt - Date.now()) / 1000)),
|
|
1297
1301
|
refresh_token: tokenData.refreshToken,
|
|
1298
|
-
scope: "file_content:read file_variables:read library_content:read"
|
|
1302
|
+
scope: "file_content:read file_versions:read file_variables:read file_comments:read file_comments:write library_content:read"
|
|
1299
1303
|
}), {
|
|
1300
1304
|
headers: {
|
|
1301
1305
|
"Content-Type": "application/json",
|
|
@@ -1372,7 +1376,7 @@ export default {
|
|
|
1372
1376
|
token_type: "Bearer",
|
|
1373
1377
|
expires_in: tokenData.expires_in,
|
|
1374
1378
|
refresh_token: tokenData.refresh_token || refreshToken,
|
|
1375
|
-
scope: "file_content:read file_variables:read library_content:read"
|
|
1379
|
+
scope: "file_content:read file_versions:read file_variables:read file_comments:read file_comments:write library_content:read"
|
|
1376
1380
|
}), {
|
|
1377
1381
|
headers: {
|
|
1378
1382
|
"Content-Type": "application/json",
|
|
@@ -1444,7 +1448,7 @@ export default {
|
|
|
1444
1448
|
const figmaAuthUrl = new URL("https://www.figma.com/oauth");
|
|
1445
1449
|
figmaAuthUrl.searchParams.set("client_id", env.FIGMA_OAUTH_CLIENT_ID);
|
|
1446
1450
|
figmaAuthUrl.searchParams.set("redirect_uri", redirectUri);
|
|
1447
|
-
figmaAuthUrl.searchParams.set("scope", "file_content:read,file_variables:read,library_content:read");
|
|
1451
|
+
figmaAuthUrl.searchParams.set("scope", "file_content:read,file_versions:read,file_variables:read,file_comments:read,file_comments:write,library_content:read");
|
|
1448
1452
|
figmaAuthUrl.searchParams.set("state", stateToken);
|
|
1449
1453
|
figmaAuthUrl.searchParams.set("response_type", "code");
|
|
1450
1454
|
return Response.redirect(figmaAuthUrl.toString(), 302);
|
|
@@ -1690,7 +1694,7 @@ export default {
|
|
|
1690
1694
|
return new Response(JSON.stringify({
|
|
1691
1695
|
status: "healthy",
|
|
1692
1696
|
service: "Figma Console MCP",
|
|
1693
|
-
version: "1.
|
|
1697
|
+
version: "1.25.0",
|
|
1694
1698
|
endpoints: {
|
|
1695
1699
|
mcp: ["/sse", "/mcp"],
|
|
1696
1700
|
oauth_mcp_spec: ["/.well-known/oauth-authorization-server", "/authorize", "/token", "/oauth/register"],
|
|
@@ -1736,13 +1740,13 @@ export default {
|
|
|
1736
1740
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1737
1741
|
<title>Figma Console MCP - The Most Comprehensive MCP Server for Figma</title>
|
|
1738
1742
|
<link rel="icon" type="image/svg+xml" href="https://docs.figma-console-mcp.southleft.com/favicon.svg">
|
|
1739
|
-
<meta name="description" content="Turn your Figma design system into a living API.
|
|
1743
|
+
<meta name="description" content="Turn your Figma design system into a living API. 100+ tools give AI assistants deep access to design tokens, component specs, variables, and programmatic design creation.">
|
|
1740
1744
|
|
|
1741
1745
|
<!-- Open Graph -->
|
|
1742
1746
|
<meta property="og:type" content="website">
|
|
1743
1747
|
<meta property="og:url" content="https://figma-console-mcp.southleft.com">
|
|
1744
1748
|
<meta property="og:title" content="Figma Console MCP - Turn Your Design System Into a Living API">
|
|
1745
|
-
<meta property="og:description" content="The most comprehensive MCP server for Figma.
|
|
1749
|
+
<meta property="og:description" content="The most comprehensive MCP server for Figma. 100+ tools give AI assistants deep access to design tokens, components, variables, and programmatic design creation.">
|
|
1746
1750
|
<meta property="og:image" content="https://docs.figma-console-mcp.southleft.com/images/og-image.jpg">
|
|
1747
1751
|
<meta property="og:image:width" content="1200">
|
|
1748
1752
|
<meta property="og:image:height" content="630">
|
|
@@ -1750,7 +1754,7 @@ export default {
|
|
|
1750
1754
|
<!-- Twitter -->
|
|
1751
1755
|
<meta name="twitter:card" content="summary_large_image">
|
|
1752
1756
|
<meta name="twitter:title" content="Figma Console MCP - Turn Your Design System Into a Living API">
|
|
1753
|
-
<meta name="twitter:description" content="The most comprehensive MCP server for Figma.
|
|
1757
|
+
<meta name="twitter:description" content="The most comprehensive MCP server for Figma. 100+ tools give AI assistants deep access to design tokens, components, variables, and programmatic design creation.">
|
|
1754
1758
|
<meta name="twitter:image" content="https://docs.figma-console-mcp.southleft.com/images/og-image.jpg">
|
|
1755
1759
|
|
|
1756
1760
|
<meta name="theme-color" content="#0D9488">
|
|
@@ -2637,7 +2641,7 @@ export default {
|
|
|
2637
2641
|
<div class="grid-cell showcase-cell rule-left">
|
|
2638
2642
|
<div class="showcase-label">What AI Can Access</div>
|
|
2639
2643
|
<div class="showcase-stat">
|
|
2640
|
-
<span class="number">
|
|
2644
|
+
<span class="number">100+</span>
|
|
2641
2645
|
<span class="label">MCP tools for Figma</span>
|
|
2642
2646
|
</div>
|
|
2643
2647
|
<div class="capability-list">
|
|
@@ -6,17 +6,7 @@
|
|
|
6
6
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
7
|
import type { FigmaAPI } from "./figma-api.js";
|
|
8
8
|
import type { CompanyDocsContentEntry } from "./types/design-code.js";
|
|
9
|
-
|
|
10
|
-
export declare function figmaRGBAToHex(color: {
|
|
11
|
-
r: number;
|
|
12
|
-
g: number;
|
|
13
|
-
b: number;
|
|
14
|
-
a?: number;
|
|
15
|
-
}): string;
|
|
16
|
-
/** Normalize a color string for comparison (uppercase hex without alpha if fully opaque) */
|
|
17
|
-
export declare function normalizeColor(color: string): string;
|
|
18
|
-
/** Compare numeric values with a tolerance */
|
|
19
|
-
export declare function numericClose(a: number, b: number, tolerance?: number): boolean;
|
|
9
|
+
export { figmaRGBAToHex, normalizeColor, numericClose } from "./diff/property-compare.js";
|
|
20
10
|
/** Calculate parity score from discrepancy counts */
|
|
21
11
|
export declare function calculateParityScore(critical: number, major: number, minor: number, info: number): number;
|
|
22
12
|
/** Split markdown by H2 headers for platforms that need chunking */
|
|
@@ -123,5 +113,4 @@ export declare function registerDesignCodeTools(server: McpServer, getFigmaAPI:
|
|
|
123
113
|
}>, options?: {
|
|
124
114
|
isRemoteMode?: boolean;
|
|
125
115
|
}, getDesktopConnector?: () => Promise<any>): void;
|
|
126
|
-
export {};
|
|
127
116
|
//# sourceMappingURL=design-code-tools.d.ts.map
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"design-code-tools.d.ts","sourceRoot":"","sources":["../../src/core/design-code-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,35 +12,9 @@ const enrichmentService = new EnrichmentService(logger);
|
|
|
12
12
|
// ============================================================================
|
|
13
13
|
// Shared Helpers
|
|
14
14
|
// ============================================================================
|
|
15
|
-
|
|
16
|
-
export
|
|
17
|
-
|
|
18
|
-
const g = Math.round(color.g * 255);
|
|
19
|
-
const b = Math.round(color.b * 255);
|
|
20
|
-
const hex = `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`.toUpperCase();
|
|
21
|
-
if (color.a !== undefined && color.a < 1) {
|
|
22
|
-
const a = Math.round(color.a * 255);
|
|
23
|
-
return `${hex}${a.toString(16).padStart(2, "0")}`;
|
|
24
|
-
}
|
|
25
|
-
return hex;
|
|
26
|
-
}
|
|
27
|
-
/** Normalize a color string for comparison (uppercase hex without alpha if fully opaque) */
|
|
28
|
-
export function normalizeColor(color) {
|
|
29
|
-
let c = color.trim().toUpperCase();
|
|
30
|
-
// Strip alpha if fully opaque (FF)
|
|
31
|
-
if (c.length === 9 && c.endsWith("FF")) {
|
|
32
|
-
c = c.slice(0, 7);
|
|
33
|
-
}
|
|
34
|
-
// Expand shorthand (#RGB -> #RRGGBB)
|
|
35
|
-
if (/^#[0-9A-F]{3}$/.test(c)) {
|
|
36
|
-
c = `#${c[1]}${c[1]}${c[2]}${c[2]}${c[3]}${c[3]}`;
|
|
37
|
-
}
|
|
38
|
-
return c;
|
|
39
|
-
}
|
|
40
|
-
/** Compare numeric values with a tolerance */
|
|
41
|
-
export function numericClose(a, b, tolerance = 1) {
|
|
42
|
-
return Math.abs(a - b) <= tolerance;
|
|
43
|
-
}
|
|
15
|
+
// Re-exported from the shared diff module so existing imports continue to work.
|
|
16
|
+
export { figmaRGBAToHex, normalizeColor, numericClose } from "./diff/property-compare.js";
|
|
17
|
+
import { figmaRGBAToHex, normalizeColor, numericClose } from "./diff/property-compare.js";
|
|
44
18
|
/** Calculate parity score from discrepancy counts */
|
|
45
19
|
export function calculateParityScore(critical, major, minor, info) {
|
|
46
20
|
return Math.max(0, 100 - (critical * 15 + major * 8 + minor * 3 + info * 1));
|