@bridge-ds/mcp 0.8.4 → 0.9.0

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.
@@ -11,7 +11,7 @@ import {
11
11
  referenceImagePath,
12
12
  selectTokenFiles,
13
13
  showFile
14
- } from "./chunk-PMZGEZGX.js";
14
+ } from "./chunk-ZVORAO5H.js";
15
15
 
16
16
  // ../visual/src/run.ts
17
17
  import { existsSync as existsSync3 } from "node:fs";
@@ -317,6 +317,17 @@ async function switchStory(page, storyId2) {
317
317
  );
318
318
  if (!ok) throw new Error("Storybook channel not found");
319
319
  }
320
+ async function openMeasurePage(o = {}) {
321
+ const browser = await launch(o);
322
+ try {
323
+ const ctx = await browser.newContext({ viewport: { width: 1200, height: 800 }, deviceScaleFactor: o.scale ?? 1, reducedMotion: "reduce", colorScheme: "light", locale: "en-US", serviceWorkers: "block" });
324
+ const page = await ctx.newPage();
325
+ return { page, close: () => browser.close().catch(() => void 0) };
326
+ } catch (e) {
327
+ await browser.close().catch(() => void 0);
328
+ throw e;
329
+ }
330
+ }
320
331
 
321
332
  // ../visual/src/composite.ts
322
333
  import { readFile, writeFile as writeFile2 } from "node:fs/promises";
@@ -368,6 +379,15 @@ import pixelmatch from "pixelmatch";
368
379
  import { PNG } from "pngjs";
369
380
  var decodePng = (buf) => PNG.sync.read(buf);
370
381
  var encodePng = (png) => PNG.sync.write(png, { colorType: 6 });
382
+ function cropPng(src, x, y, width, height) {
383
+ const left = Math.max(0, Math.round(x));
384
+ const top = Math.max(0, Math.round(y));
385
+ const w = Math.max(1, Math.min(src.width - left, Math.round(width)));
386
+ const h = Math.max(1, Math.min(src.height - top, Math.round(height)));
387
+ const out = new PNG({ width: w, height: h });
388
+ PNG.bitblt(src, out, left, top, w, h, 0, 0);
389
+ return out;
390
+ }
371
391
  function padTo(src, width, height) {
372
392
  if (src.width === width && src.height === height) return src;
373
393
  const out = new PNG({ width, height });
@@ -1208,9 +1228,12 @@ async function captureFigmaReferences(o) {
1208
1228
  try {
1209
1229
  const ctx = await newContext(browser, { width: 1200, height: 800 }, scale);
1210
1230
  const page = await newPage(ctx);
1211
- await page.goto(storyUrl(served.url, FIGMA_REFERENCE_STORY, brand, theme), { waitUntil: "load", timeout: 3e4 });
1212
- await page.waitForSelector("[data-fidelity-root], [data-fidelity-error]", { state: "attached", timeout: 2e4 });
1213
- await settle(page, { busy: false });
1231
+ const openStory = async () => {
1232
+ await page.goto(storyUrl(served.url, FIGMA_REFERENCE_STORY, brand, theme), { waitUntil: "load", timeout: 3e4 });
1233
+ await page.waitForSelector("[data-fidelity-root], [data-fidelity-error]", { state: "attached", timeout: 2e4 });
1234
+ await settle(page, { busy: false });
1235
+ };
1236
+ await openStory();
1214
1237
  log(`Capturing ${jobs.length} variants at ${scale}x (${brand} ${theme})`);
1215
1238
  for (const j of jobs) {
1216
1239
  const variants = manifest.components[j.component].variants;
@@ -1219,25 +1242,17 @@ async function captureFigmaReferences(o) {
1219
1242
  skipped++;
1220
1243
  continue;
1221
1244
  }
1222
- const plan = JSON.stringify(j.plan.render);
1223
- await page.evaluate(
1224
- ({ id, plan: plan2 }) => new Promise((resolve2) => {
1225
- const ch = window.__STORYBOOK_ADDONS_CHANNEL__;
1226
- const done = () => {
1227
- clearTimeout(timer);
1228
- ch.off("storyRendered", done);
1229
- resolve2();
1230
- };
1231
- const timer = setTimeout(done, 1e4);
1232
- ch.on("storyRendered", done);
1233
- ch.emit("updateStoryArgs", { storyId: id, updatedArgs: { plan: plan2 } });
1234
- }),
1235
- { id: FIGMA_REFERENCE_STORY, plan }
1236
- );
1245
+ const shown = await renderPlan(page, JSON.stringify(j.plan.render));
1246
+ if (shown.broken) {
1247
+ variants[j.key] = { skipped: `the code example threw: ${shown.error ?? "the story did not finish rendering"}` };
1248
+ skipped++;
1249
+ await openStory();
1250
+ continue;
1251
+ }
1237
1252
  await settle(page, { network: false, quiet: 2, interval: 40, busy: false });
1238
1253
  const error = await page.locator("[data-fidelity-error]").first().textContent({ timeout: 200 }).catch(() => null);
1239
1254
  let target = page.locator("[data-fidelity-root] > *").first();
1240
- if (!error && !await target.count()) {
1255
+ if (!error && (j.plan.render.within || j.plan.render.popup || !await target.count())) {
1241
1256
  const marked = page.locator("[data-fidelity-target]").first();
1242
1257
  await marked.waitFor({ state: "visible", timeout: 3e3 }).catch(() => void 0);
1243
1258
  await settle(page, { network: false, quiet: 2, interval: 40, busy: false });
@@ -1248,6 +1263,15 @@ async function captureFigmaReferences(o) {
1248
1263
  skipped++;
1249
1264
  continue;
1250
1265
  }
1266
+ const transparent = await target.evaluate((el) => {
1267
+ for (let e = el; e; e = e.parentElement) if (getComputedStyle(e).opacity === "0") return true;
1268
+ return false;
1269
+ });
1270
+ if (transparent) {
1271
+ variants[j.key] = { skipped: "the code example rendered invisible (opacity 0)" };
1272
+ skipped++;
1273
+ continue;
1274
+ }
1251
1275
  const file = referenceImagePath(j.component, j.key);
1252
1276
  const path = join4(o.out, file);
1253
1277
  await mkdir4(dirname3(path), { recursive: true });
@@ -1268,6 +1292,35 @@ async function captureFigmaReferences(o) {
1268
1292
  log(`${captured} images, ${skipped} variants skipped. ${manifestPath}`);
1269
1293
  return { manifest, manifestPath, captured, skipped };
1270
1294
  }
1295
+ async function renderPlan(page, plan) {
1296
+ const r = await page.evaluate(
1297
+ ({ id, plan: plan2 }) => new Promise((resolve2) => {
1298
+ const ch = window.__STORYBOOK_ADDONS_CHANNEL__;
1299
+ let error;
1300
+ const failed = (p) => {
1301
+ const e = p;
1302
+ error ??= e?.message ?? e?.title ?? e?.description;
1303
+ };
1304
+ const finish = (rendered) => {
1305
+ clearTimeout(timer);
1306
+ ch.off("storyRendered", done);
1307
+ ch.off("storyThrewException", failed);
1308
+ ch.off("storyErrored", failed);
1309
+ resolve2({ rendered, ...error ? { error } : {} });
1310
+ };
1311
+ const done = () => finish(true);
1312
+ document.querySelectorAll("[data-fidelity-target]").forEach((el) => el.removeAttribute("data-fidelity-target"));
1313
+ const timer = setTimeout(() => finish(false), 1e4);
1314
+ ch.on("storyThrewException", failed);
1315
+ ch.on("storyErrored", failed);
1316
+ ch.on("storyRendered", done);
1317
+ ch.emit("updateStoryArgs", { storyId: id, updatedArgs: { plan: plan2 } });
1318
+ }),
1319
+ { id: FIGMA_REFERENCE_STORY, plan }
1320
+ );
1321
+ const errorScreen = await page.evaluate(() => document.body.classList.contains("sb-show-errordisplay"));
1322
+ return { broken: !r.rendered || !!r.error || errorScreen, ...r.error ? { error: r.error } : {} };
1323
+ }
1271
1324
  export {
1272
1325
  COMMENT_MARKER,
1273
1326
  DEFAULT_ROUTES,
@@ -1276,11 +1329,13 @@ export {
1276
1329
  boxesFromDiff,
1277
1330
  captureFigmaReferences,
1278
1331
  changedTokens,
1332
+ cropPng,
1279
1333
  decodePng,
1280
1334
  diffPngs,
1281
1335
  encodePng,
1282
1336
  hasVisualChange,
1283
1337
  linkNodeModules,
1338
+ openMeasurePage,
1284
1339
  padTo,
1285
1340
  planImpact,
1286
1341
  rankShots,
package/dist/studio.d.ts CHANGED
@@ -114,6 +114,16 @@ export declare function getSettings(ctx: BridgeContext): Promise<{
114
114
  visualImpact: import("@bridge-ds/core").VisualImpactMode;
115
115
  note: string;
116
116
  }>;
117
+ /**
118
+ * One count of components everywhere: the component folders (snapshot.json and llms.txt count the same), then what the
119
+ * other numbers are: layout helpers have no Figma component, and in Figma popups and items are sets of their own.
120
+ */
121
+ export declare function coverageLine(s: {
122
+ inFigma: number;
123
+ drafted: number;
124
+ layout: number;
125
+ total: number;
126
+ }, figmaSets: number): string;
117
127
  /** Which components are in Figma (Studio's "In Figma" column), and whether code images exist for the fidelity check. */
118
128
  export declare function figmaCoverage(ctx: BridgeContext, a?: {
119
129
  component?: string | undefined;
@@ -9,7 +9,9 @@ export declare const SUGGEST_RIGHTS = "Issues: read and write (Contents: read is
9
9
  */
10
10
  export declare function changesFor(ctx: BridgeContext, explicit?: ChangesMode | undefined): Promise<ChangesMode>;
11
11
  /** What to do next once a suggestion is filed. The wording matches the issue's "How to apply". */
12
- export declare const appliedNext: (n: number) => string;
12
+ export declare const appliedNext: (n: number, ctx?: {
13
+ surface: "mcp" | "cli";
14
+ }) => string;
13
15
  /** Why nothing was filed: no GitHub repo, or no token that can write issues. */
14
16
  export declare function noSuggestionAccess(ctx: BridgeContext, gh: GitHubClient | null): string;
15
17
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bridge-ds/mcp",
3
- "version": "0.8.4",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "description": "Bridge MCP server: lets Claude (Claude Code, Claude Desktop or any MCP client) read, check and propose changes to a DTCG design system repo through pull requests.",
6
6
  "license": "MIT",
@@ -51,11 +51,12 @@
51
51
  "typecheck": "tsc -p tsconfig.json --noEmit",
52
52
  "test": "npm run build && vitest run --config vitest.config.ts",
53
53
  "start": "node dist/index.js",
54
+ "readme:tools": "BRIDGE_UPDATE_README=1 vitest run --config vitest.config.ts packages/mcp/test/readme.test.ts",
54
55
  "build:package": "node ../../scripts/build-packages.mjs mcp"
55
56
  },
56
57
  "dependencies": {
57
- "@bridge-ds/core": "^0.17.0",
58
- "@bridge-ds/visual": "^0.3.11",
58
+ "@bridge-ds/core": "^0.19.0",
59
+ "@bridge-ds/visual": "^0.3.13",
59
60
  "@modelcontextprotocol/sdk": "1.31.0",
60
61
  "typescript": "5.9.3",
61
62
  "zod": "3.25.76"