@bridge-ds/visual 0.3.12 → 0.3.14

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/LICENSE ADDED
@@ -0,0 +1,110 @@
1
+ # Functional Source License, Version 1.1, MIT Future License
2
+
3
+ ## Abbreviation
4
+
5
+ FSL-1.1-MIT
6
+
7
+ ## Notice
8
+
9
+ Copyright 2026 Florian Gouloubi
10
+
11
+ ## Terms and Conditions
12
+
13
+ ### Licensor ("We")
14
+
15
+ The party offering the Software under these Terms and Conditions.
16
+
17
+ ### The Software
18
+
19
+ The "Software" is each version of the software that we make available under
20
+ these Terms and Conditions, as indicated by our inclusion of these Terms and
21
+ Conditions with the Software.
22
+
23
+ ### License Grant
24
+
25
+ Subject to your compliance with this License Grant and the Patents,
26
+ Redistribution and Trademark clauses below, we hereby grant you the right to
27
+ use, copy, modify, create derivative works, publicly perform, publicly display
28
+ and redistribute the Software for any Permitted Purpose identified below.
29
+
30
+ ### Permitted Purpose
31
+
32
+ A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
33
+ means making the Software available to others in a commercial product or
34
+ service that:
35
+
36
+ 1. substitutes for the Software;
37
+
38
+ 2. substitutes for any other product or service we offer using the Software
39
+ that exists as of the date we make the Software available; or
40
+
41
+ 3. offers the same or substantially similar functionality as the Software.
42
+
43
+ Permitted Purposes specifically include using the Software:
44
+
45
+ 1. for your internal use and access;
46
+
47
+ 2. for non-commercial education;
48
+
49
+ 3. for non-commercial research; and
50
+
51
+ 4. in connection with professional services that you provide to a licensee
52
+ using the Software in accordance with these Terms and Conditions.
53
+
54
+ ### Patents
55
+
56
+ To the extent your use for a Permitted Purpose would necessarily infringe our
57
+ patents, the license grant above includes a license under our patents. If you
58
+ make a claim against any party that the Software infringes or contributes to
59
+ the infringement of any patent, then your patent license to the Software ends
60
+ immediately.
61
+
62
+ ### Redistribution
63
+
64
+ The Terms and Conditions apply to all copies, modifications and derivatives of
65
+ the Software.
66
+
67
+ If you redistribute any copies, modifications or derivatives of the Software,
68
+ you must include a copy of or a link to these Terms and Conditions and not
69
+ remove any copyright notices provided in or with the Software.
70
+
71
+ ### Disclaimer
72
+
73
+ THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
74
+ IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
75
+ PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
76
+
77
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
78
+ SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
79
+ EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
80
+
81
+ ### Trademarks
82
+
83
+ Except for displaying the License Details and identifying us as the origin of
84
+ the Software, you have no right under these Terms and Conditions to use our
85
+ trademarks, trade names, service marks or product names.
86
+
87
+ ## Grant of Future License
88
+
89
+ We hereby irrevocably grant you an additional license to use the Software under
90
+ the MIT license that is effective on the second anniversary of the date we make
91
+ the Software available. On or after that date, you may use the Software under
92
+ the MIT license, in which case the following will apply:
93
+
94
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
95
+ this software and associated documentation files (the "Software"), to deal in
96
+ the Software without restriction, including without limitation the rights to
97
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
98
+ of the Software, and to permit persons to whom the Software is furnished to do
99
+ so, subject to the following conditions:
100
+
101
+ The above copyright notice and this permission notice shall be included in all
102
+ copies or substantial portions of the Software.
103
+
104
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
105
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
106
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
107
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
108
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
109
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
110
+ SOFTWARE.
package/dist/capture.d.ts CHANGED
@@ -38,3 +38,13 @@ export declare function shoot(page: Page, r: ShotRequest): Promise<string | null
38
38
  * click a story), much faster than a page load per story. Resolves once the story rendered or failed.
39
39
  */
40
40
  export declare function switchStory(page: Page, storyId: string): Promise<void>;
41
+ /**
42
+ * A page for `bridge specs --measure`: an installed Chrome, one page at 1200x800, light scheme, reduced motion. Unlike
43
+ * the capture context it reaches remote hosts (a team's deployed Storybook). Close it when done.
44
+ */
45
+ export declare function openMeasurePage(o?: LaunchOptions & {
46
+ scale?: number;
47
+ }): Promise<{
48
+ page: Page;
49
+ close: () => Promise<void>;
50
+ }>;
package/dist/diff.d.ts CHANGED
@@ -12,6 +12,8 @@ export interface DiffResult {
12
12
  }
13
13
  export declare const decodePng: (buf: Buffer) => PNG;
14
14
  export declare const encodePng: (png: PNG) => Buffer;
15
+ /** The `x, y, width, height` region of `src` (px of the image), clipped to it. */
16
+ export declare function cropPng(src: PNG, x: number, y: number, width: number, height: number): PNG;
15
17
  /** Copies `src` onto a `width` x `height` canvas filled with an opaque neutral, so sizes can differ. */
16
18
  export declare function padTo(src: PNG, width: number, height: number): PNG;
17
19
  /**
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  export { runImpact, planImpact, changedTokens, type ImpactOptions, type ImpactResult, type Plan } from './run.js';
2
2
  export { selectImpact, storiesFromSource, storyId, storyComponent, type SelectInput } from './select.js';
3
- export { diffPngs, boxesFromDiff, boxesFromCells, padTo, decodePng, encodePng, type DiffResult } from './diff.js';
3
+ export { diffPngs, boxesFromDiff, boxesFromCells, padTo, cropPng, decodePng, encodePng, type DiffResult } from './diff.js';
4
4
  export { COMMENT_MARKER, hasVisualChange, rankShots, renderComment, summarize, summaryLine, type CommentOptions } from './manifest.js';
5
5
  export { DEFAULT_ROUTES, DEFAULT_TENANTS, routeUrl, storyUrl, shotKey } from './routes.js';
6
6
  export { captureFigmaReferences, type ReferencesOptions, type ReferencesResult } from './references.js';
7
7
  export { linkNodeModules } from './workspace.js';
8
+ export { openMeasurePage } from './capture.js';
8
9
  export type * from './types.js';
package/dist/index.js CHANGED
@@ -121,6 +121,12 @@ var CONFIG_FILE = "bridge.config.json";
121
121
  var CHANGES_MODES = ["suggest", "direct"];
122
122
  var DEFAULT_CHANGES_MODE = "suggest";
123
123
  var DEFAULT_OVERRIDE = { allowed: [], requireReason: true, minReasonLength: 20 };
124
+ var TEAM_NAME_MAX = 60;
125
+ function teamName(value) {
126
+ if (typeof value !== "string") return void 0;
127
+ const v = value.trim().replace(/\s+/g, " ");
128
+ return v && v.length <= TEAM_NAME_MAX ? v : void 0;
129
+ }
124
130
  var DEFAULT_TOKEN_PATHS = {
125
131
  root: "tokens",
126
132
  primitives: "primitives",
@@ -226,6 +232,7 @@ function defineConfig(input) {
226
232
  const g = input.governance ?? {};
227
233
  const contrastFile = contrastPath ?? [root === "." ? "" : root, paths.contrastChecks].filter(Boolean).join("/");
228
234
  return {
235
+ ...teamName(input.name) ? { name: teamName(input.name) } : {},
229
236
  repo: parseRepoInput(input.repo, input.branch),
230
237
  tokens: { ...paths, include: input.tokens?.include ?? ["**/*.tokens.json"], exclude: input.tokens?.exclude ?? [], tiers, layers, ...t.usage ? { usage: trimSlashes(t.usage) } : {} },
231
238
  axes,
@@ -258,12 +265,14 @@ function defineConfig(input) {
258
265
  }
259
266
  } : {},
260
267
  ...g.rights ? { rights: normalizeRights(g.rights) } : {},
261
- ...g.soloOwnerCanApproveBoth === true ? { soloOwnerCanApproveBoth: true } : {}
268
+ ...g.soloOwnerCanApproveBoth === true ? { soloOwnerCanApproveBoth: true } : {},
269
+ ...g.leadsPublishTier1 === false ? { leadsPublishTier1: false } : {}
262
270
  },
263
271
  products: input.products?.length ? input.products.map((p) => ({ ...p })) : DEFAULT_PRODUCTS.map((p) => ({ ...p })),
264
272
  changes: input.changes === "direct" ? "direct" : DEFAULT_CHANGES_MODE,
265
273
  ...input.storybook?.url ? { storybook: { url: input.storybook.url.trim().replace(/\/+$/, "") } } : {},
266
274
  ...input.library?.public === true ? { library: { public: true } } : {},
275
+ ...input.demo?.viewer?.trim() ? { demo: { viewer: input.demo.viewer.trim(), ...input.demo.role?.trim() ? { role: input.demo.role.trim() } : {} } } : {},
267
276
  ...input.audit ? { audit: { ...input.audit } } : {},
268
277
  ...input.visualImpact ? { visualImpact: { mode: input.visualImpact.mode === "on-demand" ? "on-demand" : "auto" } } : {},
269
278
  ...input.integrations ? { integrations: { ...input.integrations } } : {},
@@ -385,9 +394,11 @@ function loadConfig(source) {
385
394
  }
386
395
  problems.push(...rightsProblems(gov.rights));
387
396
  if (gov.soloOwnerCanApproveBoth !== void 0 && typeof gov.soloOwnerCanApproveBoth !== "boolean") problems.push('"governance.soloOwnerCanApproveBoth" must be true or false');
397
+ if (gov.leadsPublishTier1 !== void 0 && typeof gov.leadsPublishTier1 !== "boolean") problems.push('"governance.leadsPublishTier1" must be true or false');
388
398
  }
389
399
  }
390
400
  problems.push(...visualImpactProblems(d.visualImpact));
401
+ if (d.name !== void 0 && teamName(d.name) === void 0) problems.push(`"name" must be the team's name, 1 to ${TEAM_NAME_MAX} characters`);
391
402
  if (d.changes !== void 0 && !CHANGES_MODES.includes(d.changes)) problems.push(`"changes" must be "suggest" or "direct" (got ${JSON.stringify(d.changes)})`);
392
403
  const sb = d.storybook;
393
404
  if (sb !== void 0) {
@@ -403,6 +414,14 @@ function loadConfig(source) {
403
414
  for (const k of Object.keys(lib)) if (k !== "public") problems.push(`"library.${k}" is not a known key`);
404
415
  }
405
416
  }
417
+ const demo = d.demo;
418
+ if (demo !== void 0) {
419
+ if (!isObj(demo) || !isStr(demo.viewer) || !demo.viewer.trim()) problems.push('"demo" must be { "viewer" }, for example { "viewer": "Dana", "role": "DS lead" }');
420
+ else {
421
+ if (demo.role !== void 0 && !isStr(demo.role)) problems.push('"demo.role" must be a string, for example "DS lead"');
422
+ for (const k of Object.keys(demo)) if (k !== "viewer" && k !== "role") problems.push(`"demo.${k}" is not a known key`);
423
+ }
424
+ }
406
425
  problems.push(...productProblems(d.products));
407
426
  const audit = d.audit;
408
427
  if (audit !== void 0) {
@@ -897,7 +916,7 @@ var STATE_PSEUDO = {
897
916
  var iconName = (component) => component.replace(/^Icon\//, "");
898
917
  var exportName = (name) => name.replace(/[^A-Za-z0-9]+/g, "");
899
918
  function planReference(spec, variant, file) {
900
- if (spec.view === "popup") return { ok: false, reason: "drawn open in Figma; the code image shows it closed" };
919
+ if (spec.view === "popup") return planPopup(spec, variant);
901
920
  const props = { ...spec.fixed ?? {} };
902
921
  let pseudo;
903
922
  for (const axis of spec.axes) {
@@ -926,6 +945,7 @@ function planReference(spec, variant, file) {
926
945
  if (part.swap && swapTo) {
927
946
  const slot = file?.slots?.find((s) => s.name === swapTo);
928
947
  if (!slot) return { reason: `${swapTo} is not in the specs` };
948
+ if (slot.kind === "media") return { reason: `Figma shows a media placeholder (${swapTo}); code shows the image it is given, nothing to compare` };
929
949
  if (slot.kind !== "text" || !slot.text) return { reason: `Figma shows a placeholder (${swapTo}) that code has no example of` };
930
950
  return slot.text.default;
931
951
  }
@@ -977,10 +997,13 @@ function planReference(spec, variant, file) {
977
997
  }
978
998
  const example = spec.example;
979
999
  const exampleChildren = example?.children?.length ? example.children : void 0;
1000
+ const component = exportName(example?.c ?? (spec.name.includes("/") ? spec.component ?? spec.name : spec.name));
1001
+ const parent = spec.component ? exportName(spec.component) : void 0;
980
1002
  return {
981
1003
  ok: true,
982
1004
  render: {
983
- component: exportName(spec.view || spec.fixed || example ? example?.c ?? spec.component ?? spec.name : spec.name),
1005
+ component,
1006
+ ...parent && parent !== component ? { within: parent } : {},
984
1007
  props: { ...example?.p ?? {}, ...props },
985
1008
  ...exampleChildren ? { children: exampleChildren } : {},
986
1009
  ...text !== void 0 && !exampleChildren ? { text } : {},
@@ -991,6 +1014,28 @@ function planReference(spec, variant, file) {
991
1014
  }
992
1015
  };
993
1016
  }
1017
+ function planPopup(spec, variant) {
1018
+ const root = spec.parts.find((p) => !p.parent);
1019
+ const own = {};
1020
+ for (const axis of spec.axes) {
1021
+ const value = variant.props[axis.name];
1022
+ if (!axis.prop || value === void 0) return { ok: false, reason: `${axis.name} has no matching option in code` };
1023
+ own[axis.prop] = value === "true" ? true : value === "false" ? false : value;
1024
+ }
1025
+ const className = /\.module\.css \.([\w-]+)/.exec(root?.source ?? "")?.[1];
1026
+ const component = spec.component ? exportName(spec.component) : void 0;
1027
+ const example = spec.example;
1028
+ if (!component || !example) return { ok: false, reason: "drawn open from no code example: nothing to open in code" };
1029
+ if (!className) return { ok: false, reason: "the popup has no class to find it by in code" };
1030
+ const popup = { className };
1031
+ if (exportName(example.c) === component) {
1032
+ return { ok: true, render: { component, props: { ...example.p ?? {}, ...own, defaultOpen: true }, ...example.children?.length ? { children: example.children } : {}, popup } };
1033
+ }
1034
+ return {
1035
+ ok: true,
1036
+ render: { component, props: { defaultOpen: true }, children: [{ c: `${component}Trigger`, children: ["Open"] }, { ...example, p: { ...example.p ?? {}, ...own } }], popup }
1037
+ };
1038
+ }
994
1039
  function referenceImagePath(component, key) {
995
1040
  const slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
996
1041
  const file = key.split(",").map((kv) => slug(kv)).filter(Boolean).join("__");
@@ -1214,6 +1259,17 @@ async function switchStory(page, storyId2) {
1214
1259
  );
1215
1260
  if (!ok) throw new Error("Storybook channel not found");
1216
1261
  }
1262
+ async function openMeasurePage(o = {}) {
1263
+ const browser = await launch(o);
1264
+ try {
1265
+ const ctx = await browser.newContext({ viewport: { width: 1200, height: 800 }, deviceScaleFactor: o.scale ?? 1, reducedMotion: "reduce", colorScheme: "light", locale: "en-US", serviceWorkers: "block" });
1266
+ const page = await ctx.newPage();
1267
+ return { page, close: () => browser.close().catch(() => void 0) };
1268
+ } catch (e) {
1269
+ await browser.close().catch(() => void 0);
1270
+ throw e;
1271
+ }
1272
+ }
1217
1273
 
1218
1274
  // src/composite.ts
1219
1275
  import { readFile, writeFile as writeFile2 } from "node:fs/promises";
@@ -1265,6 +1321,15 @@ import pixelmatch from "pixelmatch";
1265
1321
  import { PNG } from "pngjs";
1266
1322
  var decodePng = (buf) => PNG.sync.read(buf);
1267
1323
  var encodePng = (png) => PNG.sync.write(png, { colorType: 6 });
1324
+ function cropPng(src, x, y, width, height) {
1325
+ const left = Math.max(0, Math.round(x));
1326
+ const top = Math.max(0, Math.round(y));
1327
+ const w = Math.max(1, Math.min(src.width - left, Math.round(width)));
1328
+ const h = Math.max(1, Math.min(src.height - top, Math.round(height)));
1329
+ const out = new PNG({ width: w, height: h });
1330
+ PNG.bitblt(src, out, left, top, w, h, 0, 0);
1331
+ return out;
1332
+ }
1268
1333
  function padTo(src, width, height) {
1269
1334
  if (src.width === width && src.height === height) return src;
1270
1335
  const out = new PNG({ width, height });
@@ -2105,9 +2170,12 @@ async function captureFigmaReferences(o) {
2105
2170
  try {
2106
2171
  const ctx = await newContext(browser, { width: 1200, height: 800 }, scale);
2107
2172
  const page = await newPage(ctx);
2108
- await page.goto(storyUrl(served.url, FIGMA_REFERENCE_STORY, brand, theme), { waitUntil: "load", timeout: 3e4 });
2109
- await page.waitForSelector("[data-fidelity-root], [data-fidelity-error]", { state: "attached", timeout: 2e4 });
2110
- await settle(page, { busy: false });
2173
+ const openStory = async () => {
2174
+ await page.goto(storyUrl(served.url, FIGMA_REFERENCE_STORY, brand, theme), { waitUntil: "load", timeout: 3e4 });
2175
+ await page.waitForSelector("[data-fidelity-root], [data-fidelity-error]", { state: "attached", timeout: 2e4 });
2176
+ await settle(page, { busy: false });
2177
+ };
2178
+ await openStory();
2111
2179
  log(`Capturing ${jobs.length} variants at ${scale}x (${brand} ${theme})`);
2112
2180
  for (const j of jobs) {
2113
2181
  const variants = manifest.components[j.component].variants;
@@ -2116,25 +2184,17 @@ async function captureFigmaReferences(o) {
2116
2184
  skipped++;
2117
2185
  continue;
2118
2186
  }
2119
- const plan = JSON.stringify(j.plan.render);
2120
- await page.evaluate(
2121
- ({ id, plan: plan2 }) => new Promise((resolve2) => {
2122
- const ch = window.__STORYBOOK_ADDONS_CHANNEL__;
2123
- const done = () => {
2124
- clearTimeout(timer);
2125
- ch.off("storyRendered", done);
2126
- resolve2();
2127
- };
2128
- const timer = setTimeout(done, 1e4);
2129
- ch.on("storyRendered", done);
2130
- ch.emit("updateStoryArgs", { storyId: id, updatedArgs: { plan: plan2 } });
2131
- }),
2132
- { id: FIGMA_REFERENCE_STORY, plan }
2133
- );
2187
+ const shown = await renderPlan(page, JSON.stringify(j.plan.render));
2188
+ if (shown.broken) {
2189
+ variants[j.key] = { skipped: `the code example threw: ${shown.error ?? "the story did not finish rendering"}` };
2190
+ skipped++;
2191
+ await openStory();
2192
+ continue;
2193
+ }
2134
2194
  await settle(page, { network: false, quiet: 2, interval: 40, busy: false });
2135
2195
  const error = await page.locator("[data-fidelity-error]").first().textContent({ timeout: 200 }).catch(() => null);
2136
2196
  let target = page.locator("[data-fidelity-root] > *").first();
2137
- if (!error && !await target.count()) {
2197
+ if (!error && (j.plan.render.within || j.plan.render.popup || !await target.count())) {
2138
2198
  const marked = page.locator("[data-fidelity-target]").first();
2139
2199
  await marked.waitFor({ state: "visible", timeout: 3e3 }).catch(() => void 0);
2140
2200
  await settle(page, { network: false, quiet: 2, interval: 40, busy: false });
@@ -2145,6 +2205,15 @@ async function captureFigmaReferences(o) {
2145
2205
  skipped++;
2146
2206
  continue;
2147
2207
  }
2208
+ const transparent = await target.evaluate((el) => {
2209
+ for (let e = el; e; e = e.parentElement) if (getComputedStyle(e).opacity === "0") return true;
2210
+ return false;
2211
+ });
2212
+ if (transparent) {
2213
+ variants[j.key] = { skipped: "the code example rendered invisible (opacity 0)" };
2214
+ skipped++;
2215
+ continue;
2216
+ }
2148
2217
  const file = referenceImagePath(j.component, j.key);
2149
2218
  const path = join4(o.out, file);
2150
2219
  await mkdir4(dirname3(path), { recursive: true });
@@ -2165,6 +2234,35 @@ async function captureFigmaReferences(o) {
2165
2234
  log(`${captured} images, ${skipped} variants skipped. ${manifestPath}`);
2166
2235
  return { manifest, manifestPath, captured, skipped };
2167
2236
  }
2237
+ async function renderPlan(page, plan) {
2238
+ const r = await page.evaluate(
2239
+ ({ id, plan: plan2 }) => new Promise((resolve2) => {
2240
+ const ch = window.__STORYBOOK_ADDONS_CHANNEL__;
2241
+ let error;
2242
+ const failed = (p) => {
2243
+ const e = p;
2244
+ error ??= e?.message ?? e?.title ?? e?.description;
2245
+ };
2246
+ const finish = (rendered) => {
2247
+ clearTimeout(timer);
2248
+ ch.off("storyRendered", done);
2249
+ ch.off("storyThrewException", failed);
2250
+ ch.off("storyErrored", failed);
2251
+ resolve2({ rendered, ...error ? { error } : {} });
2252
+ };
2253
+ const done = () => finish(true);
2254
+ document.querySelectorAll("[data-fidelity-target]").forEach((el) => el.removeAttribute("data-fidelity-target"));
2255
+ const timer = setTimeout(() => finish(false), 1e4);
2256
+ ch.on("storyThrewException", failed);
2257
+ ch.on("storyErrored", failed);
2258
+ ch.on("storyRendered", done);
2259
+ ch.emit("updateStoryArgs", { storyId: id, updatedArgs: { plan: plan2 } });
2260
+ }),
2261
+ { id: FIGMA_REFERENCE_STORY, plan }
2262
+ );
2263
+ const errorScreen = await page.evaluate(() => document.body.classList.contains("sb-show-errordisplay"));
2264
+ return { broken: !r.rendered || !!r.error || errorScreen, ...r.error ? { error: r.error } : {} };
2265
+ }
2168
2266
  export {
2169
2267
  COMMENT_MARKER,
2170
2268
  DEFAULT_ROUTES,
@@ -2173,11 +2271,13 @@ export {
2173
2271
  boxesFromDiff,
2174
2272
  captureFigmaReferences,
2175
2273
  changedTokens,
2274
+ cropPng,
2176
2275
  decodePng,
2177
2276
  diffPngs,
2178
2277
  encodePng,
2179
2278
  hasVisualChange,
2180
2279
  linkNodeModules,
2280
+ openMeasurePage,
2181
2281
  padTo,
2182
2282
  planImpact,
2183
2283
  rankShots,
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@bridge-ds/visual",
3
- "version": "0.3.12",
3
+ "version": "0.3.14",
4
4
  "type": "module",
5
5
  "description": "Bridge visual impact analysis: screenshots the stories and app screens a design system change touches, at the base and head refs, and pixel-diffs them into before/after/diff images and a JSON manifest.",
6
- "license": "MIT",
6
+ "license": "FSL-1.1-MIT",
7
7
  "author": "Florian Gouloubi",
8
8
  "homepage": "https://github.com/peterbono/ai-design-system-demo/tree/main/packages/visual#readme",
9
9
  "repository": {
@@ -47,7 +47,7 @@
47
47
  "build:package": "node ../../scripts/build-packages.mjs visual"
48
48
  },
49
49
  "dependencies": {
50
- "@bridge-ds/core": "^0.18.0",
50
+ "@bridge-ds/core": "^0.20.0",
51
51
  "pixelmatch": "7.1.0",
52
52
  "playwright-core": "1.63.0",
53
53
  "pngjs": "7.0.0"