@bridge-ds/core 0.19.0 → 0.21.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.
Files changed (45) hide show
  1. package/LICENSE +17 -0
  2. package/bridge.config.schema.json +5 -0
  3. package/dist/audit/index.js +275 -52
  4. package/dist/audit/index.js.map +1 -1
  5. package/dist/audit/missing-pieces.d.ts +4 -0
  6. package/dist/component-spec.d.ts +13 -0
  7. package/dist/config.d.ts +5 -0
  8. package/dist/exploration-done-github.d.ts +50 -0
  9. package/dist/exploration-resume.d.ts +136 -0
  10. package/dist/explorations-github.d.ts +75 -3
  11. package/dist/explorations.d.ts +137 -8
  12. package/dist/fidelity.d.ts +12 -1
  13. package/dist/figma-fix-loop.d.ts +1 -1
  14. package/dist/figma-fix-run.d.ts +103 -0
  15. package/dist/figma-fixes.d.ts +14 -2
  16. package/dist/gap-case.d.ts +1 -2
  17. package/dist/github-client.d.ts +17 -0
  18. package/dist/index.d.ts +6 -0
  19. package/dist/index.js +2139 -1290
  20. package/dist/index.js.map +1 -1
  21. package/dist/measure-location.d.ts +42 -0
  22. package/dist/measure-workflow.d.ts +48 -0
  23. package/dist/measured-only.d.ts +15 -1
  24. package/dist/node/index.js +15 -12
  25. package/dist/node/index.js.map +1 -1
  26. package/dist/previews-github.d.ts +13 -0
  27. package/dist/previews.d.ts +8 -1
  28. package/dist/{prod-branch-Bdx_2bRQ.js → prod-branch-C7tphFAA.js} +257 -192
  29. package/dist/prod-branch-C7tphFAA.js.map +1 -0
  30. package/dist/prod-branch-github.d.ts +54 -4
  31. package/dist/prod-branch.d.ts +42 -1
  32. package/dist/products.d.ts +17 -0
  33. package/dist/repo-model.d.ts +2 -0
  34. package/dist/{repo-source-m0Tc603o.js → repo-source-CEBBBtm1.js} +2183 -1937
  35. package/dist/repo-source-CEBBBtm1.js.map +1 -0
  36. package/dist/{report-DFPKygfk.js → report-B7wNAnSu.js} +38 -38
  37. package/dist/{report-DFPKygfk.js.map → report-B7wNAnSu.js.map} +1 -1
  38. package/dist/review-gate.d.ts +7 -1
  39. package/dist/suggestion-review.d.ts +43 -2
  40. package/dist/suggestions-github.d.ts +12 -1
  41. package/dist/tier1-publish.d.ts +48 -0
  42. package/dist/try-in-product.d.ts +4 -1
  43. package/package.json +1 -1
  44. package/dist/prod-branch-Bdx_2bRQ.js.map +0 -1
  45. package/dist/repo-source-m0Tc603o.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { m as modeDisplayName, b as modesOf$1, i as isMultiMode, T as TIER_NAMES, c as TIER_AXIS, d as collectRefs, f as findReferenceProblems, e as evaluatePairs, g as contrastScope, h as contextLabel, S as SINGLE_MODE, j as isAlias$1, k as aliasTarget, l as pathId, v as valuesEqual, n as flattenTokens, s as serializeTokenFile, r as removeToken, u as upsertToken, o as threeWayDiff, q as emptyTokenFile, w as parseTokenFile, E as EXT_ID, x as EXT_SCOPES, y as parseColor, z as rgbaToHex, A as formatValue, B as resolve, C as moveToken, D as effectiveScopes, F as sameScopes, G as defaultScopes, H as readTokenAt, I as modeFile, J as defaultFile, K as selectTokenFiles, L as LayeredTokenSet, M as resolveBranchPreviews, N as loadConfig, O as tokensPath, P as toConfigJson, Q as validateLayered, R as parseContrastChecks, U as classifyRisk, V as CONFIG_FILE, W as RISK_LABELS, X as previewLinks, Y as findPreviewComment, Z as previewsSettled, _ as DEFAULT_TOKEN_PATHS, $ as layoutOptions, a0 as buildTokenSet, a1 as readPreviewComment, a2 as linkStatus, a3 as libraryPreviewState, a4 as parsePiecesRecord, a5 as libraryBranchPlan, a6 as NEEDED_BY_LABEL, a7 as withNeededBy, a8 as withLink, a9 as withPiecesRecord, aa as productBranchSentence, ab as mergePiecesRecord, ac as productOfBranch, ad as defaultProduct, ae as slugify, af as defineConfig, ag as globToRegExp, ah as ConfigError, ai as DEFAULT_CHANGES_MODE, aj as DEFAULT_OVERRIDE, ak as DEFAULT_PRODUCTS, al as isGuardrail } from "./prod-branch-Bdx_2bRQ.js";
2
- import { am, an, ao, ap, aq, ar, as, at, au, av, aw, ax, ay, az, aA, aB, aC, aD, aE, aF, aG, aH, aI, aJ, aK, aL, aM, aN, aO, aP, aQ, aR, aS, aT, aU, aV, aW, aX, aY, aZ, a_, a$, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, ba, bb, bc, bd, be, bf, bg, bh, bi, bj, bk, bl, bm, bn, bo, bp, bq, br, bs, bt, bu, bv, bw, bx, by, bz, bA, bB, bC, bD, bE, bF, bG, bH, bI, bJ, bK, bL, bM, bN, bO, bP, bQ, bR, bS, p, bT, bU, bV, bW, bX, bY, bZ, b_, b$, c0, c1, a, c2, c3, c4, c5, c6, c7, c8, c9, ca, cb, cc, cd, ce, cf, cg, ch, ci, cj, ck, cl, cm, cn, co, cp, cq, cr, t, cs, ct, cu, cv, cw, cx, cy, cz, cA } from "./prod-branch-Bdx_2bRQ.js";
3
- import { G as GitHubError, c as collectHistory, p as packageDirCandidates, a as parseChangelog, r as releasesFromTags, m as mergeReleases, b as buildReport, d as applyTokenEdits, e as buildHistoryEntry, f as coerceValue, g as checkLayeredPair, h as formatChangeset, P as PROSE_KEYS, i as markClaudeDraft, j as cleanDraftText, k as docsPrompt, D as DRAFT_LINE_MAX, R as REQUESTS_BRANCH, l as REQUEST_KINDS, s as sanitizeDraftLine, n as parseExplorationBody, E as EXPLORATION_PREFIX, o as explorationSlug, q as explorationStatus, t as explorationTryIt, u as isExplorationPull, v as promotionPlan, w as EXPLORATION_LABEL, x as explorationProblems, y as explorationNote, z as explorationNotePath, A as explorationPrTitle, B as explorationPrBody, C as compareSpecToMeasured, F as variantKeyOf, H as withoutAddedWrappers, I as pairParts, S as SPEC_FIELDS, J as COLOR_FIELDS, K as hashSpec, L as overrideComment, O as OVERRIDE_HEADING, M as overrideRefusedComment, N as suggestionStateFromLabels, Q as suggestionStatus, T as composeRequestIssue, U as EXPLORATION_NOTES_DIR, V as isExplorationBranch, W as diffLayered, X as contrastDelta, Y as runLayeredContrast, Z as layeredImpact, _ as classifyLayered, $ as composeProposalBody, a0 as createSuggestionOnGitHub, a1 as gitBlobSha, a2 as SUGGESTION_SCHEMA, a3 as branchWithJiraKey, a4 as titleWithJiraKey, a5 as figmaSpecPath, a6 as formatFigmaSpec, a7 as figmaDraftBrief, a8 as parseFigmaOrigin, a9 as figmaPushStatus, aa as parseFigmaSpec, ab as FIGMA_SPEC_DIR, ac as FROM_FIGMA_LABEL, ad as withFigmaOrigin, ae as WAITING_FOR_CLAUDE_LABEL, af as DRAFTED_BY_CLAUDE_LABEL, ag as planComponentPush, ah as figmaOriginSection, ai as figmaBranchTitle, aj as figmaSpecSlug, ak as figmaDraftBody, al as REQUEST_LABEL, am as figmaNodeUrl, an as parentDir, ao as parseComponentSpecs, ap as readRepoTexts, aq as normalizeRepoPath, ar as isUnder, as as componentMotion, at as GitHubClient, au as effectiveTier, av as parseRequestIssue, aw as requestStatus, ax as listSuggestionsOnGitHub, ay as getSuggestionOnGitHub, az as closingIssueNumbers, aA as requestIssueTitle, aB as pullState, aC as summarizeCi, aD as summarizeApprovals } from "./repo-source-m0Tc603o.js";
4
- import { aE as aE2, aF as aF2, aG as aG2, aH as aH2, aI as aI2, aJ as aJ2, aK as aK2, aL as aL2, aM as aM2, aN as aN2, aO as aO2, aP as aP2, aQ as aQ2, aR as aR2, aS as aS2, aT as aT2, aU as aU2, aV as aV2, aW as aW2, aX as aX2, aY as aY2, aZ as aZ2, a_ as a_2, a$ as a$2, b0 as b02, b1 as b12, b2 as b22, b3 as b32, b4 as b42, b5 as b52, b6 as b62, b7 as b72, b8 as b82, b9 as b92, ba as ba2, bb as bb2, bc as bc2, bd as bd2, be as be2, bf as bf2, bg as bg2, bh as bh2, bi as bi2, bj as bj2, bk as bk2, bl as bl2, bm as bm2, bn as bn2, bo as bo2, bp as bp2, bq as bq2, br as br2, bs as bs2, bt as bt2, bu as bu2, bv as bv2, bw as bw2, bx as bx2, by as by2, bz as bz2, bA as bA2, bB as bB2, bC as bC2, bD as bD2, bE as bE2, bF as bF2, bG as bG2, bH as bH2, bI as bI2, bJ as bJ2, bK as bK2, bL as bL2, bM as bM2, bN as bN2, bO as bO2, bP as bP2, bQ as bQ2, bR as bR2, bS as bS2, bT as bT2, bU as bU2, bV as bV2, bW as bW2, bX as bX2, bY as bY2, bZ as bZ2, b_ as b_2, b$ as b$2, c0 as c02, c1 as c12, c2 as c22, c3 as c32, c4 as c42, c5 as c52, c6 as c62, c7 as c72, c8 as c82, c9 as c92, ca as ca2, cb as cb2, cc as cc2, cd as cd2, ce as ce2, cf as cf2, cg as cg2, ch as ch2, ci as ci2, cj as cj2, ck as ck2, cl as cl2, cm as cm2, cn as cn2, co as co2, cp as cp2, cq as cq2, cr as cr2, cs as cs2, ct as ct2, cu as cu2, cv as cv2, cw as cw2, cx as cx2, cy as cy2, cz as cz2, cA as cA2, cB, cC, cD, cE, cF, cG, cH, cI, cJ, cK, cL, cM, cN, cO, cP, cQ, cR, cS, cT, cU, cV, cW, cX, cY, cZ, c_, c$, d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, da, db, dc, dd, de, df, dg, dh, di, dj, dk, dl, dm, dn, dp, dq, dr, ds, dt, du, dv, dw, dx, dy, dz, dA, dB, dC, dD, dE, dF, dG, dH, dI, dJ, dK, dL, dM, dN, dO, dP, dQ, dR, dS, dT, dU, dV, dW, dX, dY, dZ, d_, d$, e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, ea, eb, ec, ed, ee, ef, eg, eh, ei, ej, ek, el, em } from "./repo-source-m0Tc603o.js";
1
+ import { m as modeDisplayName, b as modesOf$1, i as isMultiMode, T as TIER_NAMES, c as TIER_AXIS, d as collectRefs, f as findReferenceProblems, e as evaluatePairs, g as contrastScope, h as contextLabel, S as SINGLE_MODE, j as isAlias$1, k as aliasTarget, l as pathId, v as valuesEqual, n as flattenTokens, s as serializeTokenFile, r as removeToken, u as upsertToken, o as threeWayDiff, q as emptyTokenFile, w as parseTokenFile, E as EXT_ID, x as EXT_SCOPES, y as parseColor, z as rgbaToHex, A as formatValue, B as resolve, C as moveToken, D as effectiveScopes, F as sameScopes, G as defaultScopes, H as readTokenAt, I as modeFile, J as defaultFile, K as selectTokenFiles, L as LayeredTokenSet, M as resolveBranchPreviews, N as loadConfig, O as tokensPath, P as toConfigJson, Q as validateLayered, R as parseContrastChecks, U as classifyRisk, V as CONFIG_FILE, W as RISK_LABELS, X as previewLinks, Y as findPreviewComment, Z as previewsSettled, _ as previewUrl, $ as productRepos, a0 as linkStatus, a1 as libraryPreviewState, a2 as parsePiecesRecord, a3 as libraryBranchPlan, a4 as NEEDED_BY_LABEL, a5 as withNeededBy, a6 as withLink, a7 as withPiecesRecord, a8 as productBranchSentence, a9 as productOfBranch, aa as pieceRequest, ab as mergePiecesRecord, ac as defaultProduct, ad as productOfRepo, ae as baseBranchOf, af as DEFAULT_TOKEN_PATHS, ag as layoutOptions, ah as buildTokenSet, ai as readPreviewComment, aj as slugify, ak as defineConfig, al as globToRegExp, am as ConfigError, an as DEFAULT_CHANGES_MODE, ao as DEFAULT_OVERRIDE, ap as DEFAULT_PRODUCTS, aq as isGuardrail } from "./prod-branch-C7tphFAA.js";
2
+ import { ar, as, at, au, av, aw, ax, ay, az, aA, aB, aC, aD, aE, aF, aG, aH, aI, aJ, aK, aL, aM, aN, aO, aP, aQ, aR, aS, aT, aU, aV, aW, aX, aY, aZ, a_, a$, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, ba, bb, bc, bd, be, bf, bg, bh, bi, bj, bk, bl, bm, bn, bo, bp, bq, br, bs, bt, bu, bv, bw, bx, by, bz, bA, bB, bC, bD, bE, bF, bG, bH, bI, bJ, bK, bL, bM, bN, bO, bP, bQ, bR, bS, bT, bU, bV, bW, p, bX, bY, bZ, b_, b$, c0, c1, c2, c3, c4, c5, c6, a, c7, c8, c9, ca, cb, cc, cd, ce, cf, cg, ch, ci, cj, ck, cl, cm, cn, co, cp, cq, cr, cs, ct, cu, cv, cw, t, cx, cy, cz, cA, cB, cC, cD, cE, cF } from "./prod-branch-C7tphFAA.js";
3
+ import { G as GitHubError, c as collectHistory, p as packageDirCandidates, a as parseChangelog, r as releasesFromTags, m as mergeReleases, b as buildReport, d as applyTokenEdits, e as buildHistoryEntry, f as coerceValue, g as checkLayeredPair, h as formatChangeset, P as PROSE_KEYS, i as markClaudeDraft, j as cleanDraftText, k as docsPrompt, D as DRAFT_LINE_MAX, R as REQUESTS_BRANCH, l as REQUEST_KINDS, s as sanitizeDraftLine, E as EXPLORATION_ARCHIVED_LABEL, n as parseExplorationBody, o as EXPLORATION_PREFIX, q as explorationSlug, t as isArchivedExploration, u as explorationStatus, v as explorationTryIt, w as isExplorationPull, x as promotionPlan, y as EXPLORATION_LABEL, z as explorationProblems, A as explorationNote, B as explorationNotePath, C as explorationPrTitle, F as explorationPrBody, H as suggestionStateFromLabels, I as suggestionStatus, J as composeRequestIssue, K as EXPLORATION_NOTES_DIR, L as isExplorationBranch, M as doneExploringPlan, N as withDoneExploring, O as explorationStartingPoint, Q as explorationRefLabel, S as compareSpecToMeasured, T as variantKeyOf, U as matchVariant, V as withoutAddedWrappers, W as pairParts, X as hashSpec, Y as SPEC_FIELDS, Z as COLOR_FIELDS, _ as DRAFTED_BY_CLAUDE_LABEL, $ as overrideComment, a0 as OVERRIDE_HEADING, a1 as overrideRefusedComment, a2 as diffLayered, a3 as contrastDelta, a4 as runLayeredContrast, a5 as layeredImpact, a6 as classifyLayered, a7 as composeProposalBody, a8 as createSuggestionOnGitHub, a9 as gitBlobSha, aa as SUGGESTION_SCHEMA, ab as branchWithJiraKey, ac as titleWithJiraKey, ad as figmaSpecPath, ae as formatFigmaSpec, af as figmaDraftBrief, ag as parseFigmaOrigin, ah as figmaPushStatus, ai as parseFigmaSpec, aj as FIGMA_SPEC_DIR, ak as FROM_FIGMA_LABEL, al as withFigmaOrigin, am as WAITING_FOR_CLAUDE_LABEL, an as planComponentPush, ao as figmaOriginSection, ap as figmaBranchTitle, aq as figmaSpecSlug, ar as figmaDraftBody, as as REQUEST_LABEL, at as figmaNodeUrl, au as parentDir, av as parseComponentSpecs, aw as readRepoTexts, ax as normalizeRepoPath, ay as isUnder, az as componentMotion, aA as GitHubClient, aB as effectiveTier, aC as parseRequestIssue, aD as requestStatus, aE as listSuggestionsOnGitHub, aF as getSuggestionOnGitHub, aG as closingIssueNumbers, aH as requestIssueTitle, aI as pullState, aJ as summarizeCi, aK as summarizeApprovals } from "./repo-source-CEBBBtm1.js";
4
+ import { aL as aL2, aM as aM2, aN as aN2, aO as aO2, aP as aP2, aQ as aQ2, aR as aR2, aS as aS2, aT as aT2, aU as aU2, aV as aV2, aW as aW2, aX as aX2, aY as aY2, aZ as aZ2, a_ as a_2, a$ as a$2, b0 as b02, b1 as b12, b2 as b22, b3 as b32, b4 as b42, b5 as b52, b6 as b62, b7 as b72, b8 as b82, b9 as b92, ba as ba2, bb as bb2, bc as bc2, bd as bd2, be as be2, bf as bf2, bg as bg2, bh as bh2, bi as bi2, bj as bj2, bk as bk2, bl as bl2, bm as bm2, bn as bn2, bo as bo2, bp as bp2, bq as bq2, br as br2, bs as bs2, bt as bt2, bu as bu2, bv as bv2, bw as bw2, bx as bx2, by as by2, bz as bz2, bA as bA2, bB as bB2, bC as bC2, bD as bD2, bE as bE2, bF as bF2, bG as bG2, bH as bH2, bI as bI2, bJ as bJ2, bK as bK2, bL as bL2, bM as bM2, bN as bN2, bO as bO2, bP as bP2, bQ as bQ2, bR as bR2, bS as bS2, bT as bT2, bU as bU2, bV as bV2, bW as bW2, bX as bX2, bY as bY2, bZ as bZ2, b_ as b_2, b$ as b$2, c0 as c02, c1 as c12, c2 as c22, c3 as c32, c4 as c42, c5 as c52, c6 as c62, c7 as c72, c8 as c82, c9 as c92, ca as ca2, cb as cb2, cc as cc2, cd as cd2, ce as ce2, cf as cf2, cg as cg2, ch as ch2, ci as ci2, cj as cj2, ck as ck2, cl as cl2, cm as cm2, cn as cn2, co as co2, cp as cp2, cq as cq2, cr as cr2, cs as cs2, ct as ct2, cu as cu2, cv as cv2, cw as cw2, cx as cx2, cy as cy2, cz as cz2, cA as cA2, cB as cB2, cC as cC2, cD as cD2, cE as cE2, cF as cF2, cG, cH, cI, cJ, cK, cL, cM, cN, cO, cP, cQ, cR, cS, cT, cU, cV, cW, cX, cY, cZ, c_, c$, d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, da, db, dc, dd, de, df, dg, dh, di, dj, dk, dl, dm, dn, dp, dq, dr, ds, dt, du, dv, dw, dx, dy, dz, dA, dB, dC, dD, dE, dF, dG, dH, dI, dJ, dK, dL, dM, dN, dO, dP, dQ, dR, dS, dT, dU, dV, dW, dX, dY, dZ, d_, d$, e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, ea, eb, ec, ed, ee, ef, eg, eh, ei, ej, ek, el, em, en, eo, ep, eq, er, es, et, eu, ev, ew, ex, ey, ez, eA, eB, eC, eD, eE, eF, eG } from "./repo-source-CEBBBtm1.js";
5
5
  const issueKey$1 = (i) => `${i.code}|${i.path ?? ""}|${i.mode ?? ""}|${i.code === "file-problem" ? i.message : ""}`;
6
6
  const modeName = (mode) => mode === SINGLE_MODE ? "all modes" : modeDisplayName(mode);
7
7
  const isPalette = (t2) => t2.tier === "brand" && t2.path.startsWith("palette.");
@@ -162,7 +162,7 @@ function validateProposal(proposed, head, pairs, typeChanges = []) {
162
162
  const warnings = [];
163
163
  for (const i of after.issues) {
164
164
  if (i.severity === "warning") warnings.push(i);
165
- else if (existing.has(issueKey$1(i))) warnings.push({ ...i, severity: "warning", message: `Already on main: ${i.message}` });
165
+ else if (existing.has(issueKey$1(i))) warnings.push({ ...i, severity: "warning", message: `Already in the design system: ${i.message}` });
166
166
  else blocking.push(i);
167
167
  }
168
168
  for (const c of typeChanges) {
@@ -726,7 +726,7 @@ function importsIn(text) {
726
726
  for (const m of text.matchAll(IMPORT_RE)) out.add(m[1] ?? m[2]);
727
727
  return [...out];
728
728
  }
729
- const norm = (p2) => p2.replace(/\\/g, "/").replace(/\/+$/, "");
729
+ const norm$1 = (p2) => p2.replace(/\\/g, "/").replace(/\/+$/, "");
730
730
  const isStory = (p2) => /\.stories\.[cm]?[jt]sx?$/.test(p2);
731
731
  const isTest = (p2) => /\.(test|spec)\.[cm]?[jt]sx?$/.test(p2);
732
732
  const isCss = (p2) => /\.(css|scss|less)$/.test(p2);
@@ -744,14 +744,14 @@ function componentOf(from, spec, componentsDir) {
744
744
  return abs.slice(componentsDir.length + 1).split("/")[0] || null;
745
745
  }
746
746
  function componentForPath(path, componentsDir) {
747
- const dir = norm(componentsDir);
748
- const p2 = norm(path);
747
+ const dir = norm$1(componentsDir);
748
+ const p2 = norm$1(path);
749
749
  if (!p2.startsWith(`${dir}/`)) return null;
750
750
  const rest = p2.slice(dir.length + 1).split("/");
751
751
  return rest.length >= 2 ? rest[0] : null;
752
752
  }
753
753
  function buildUsageIndex(files, componentsDir) {
754
- const dir = norm(componentsDir);
754
+ const dir = norm$1(componentsDir);
755
755
  const modules = {};
756
756
  const vars = /* @__PURE__ */ new Map();
757
757
  const imports = /* @__PURE__ */ new Map();
@@ -759,7 +759,7 @@ function buildUsageIndex(files, componentsDir) {
759
759
  const globalVars = {};
760
760
  const get = (m, k) => m.get(k) ?? m.set(k, /* @__PURE__ */ new Set()).get(k);
761
761
  for (const f of files) {
762
- const path = norm(f.path);
762
+ const path = norm$1(f.path);
763
763
  const id = componentForPath(path, dir);
764
764
  if (!id) {
765
765
  if (isCss(path)) {
@@ -803,7 +803,7 @@ function impactedModules(index, input) {
803
803
  for (const v of changedVars) for (const id of index.byVar[v] ?? []) add(id, `uses ${v}`, false);
804
804
  for (const f of input.files ?? []) {
805
805
  const id = componentForPath(f, index.componentsDir);
806
- if (id && index.modules[id]) add(id, `file changed: ${norm(f)}`, false);
806
+ if (id && index.modules[id]) add(id, `file changed: ${norm$1(f)}`, false);
807
807
  }
808
808
  const renderedBy = /* @__PURE__ */ new Map();
809
809
  for (const m of Object.values(index.modules)) for (const d of m.imports) (renderedBy.get(d) ?? renderedBy.set(d, []).get(d)).push(m.id);
@@ -1023,6 +1023,16 @@ async function branchPreviewUrls(gh, ref, config, opts = {}) {
1023
1023
  ]);
1024
1024
  return resolveBranchPreviews(apps, { branch: branch ?? "", ...pr !== void 0 ? { pr } : {}, ...sha ? { sha } : {} }, { comments, statuses, deployments });
1025
1025
  }
1026
+ async function productPreviewOnGitHub(gh, product, e) {
1027
+ if (!product.preview) return null;
1028
+ const [comments, statuses, deployments] = await Promise.all([
1029
+ gh.listIssueComments(e.number).catch(() => []),
1030
+ e.sha ? gh.listCommitStatuses(e.sha).catch(() => []) : Promise.resolve([]),
1031
+ e.sha ? gh.listDeployments(e.sha).catch(() => []) : Promise.resolve([])
1032
+ ]);
1033
+ const p2 = resolveBranchPreviews({ admin: product.preview }, { branch: e.branch, pr: e.number, ...e.sha ? { sha: e.sha } : {} }, { comments, statuses, deployments })[0];
1034
+ return p2 ? { ...p2, app: product.key, label: product.name } : null;
1035
+ }
1026
1036
  const STEPS = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
1027
1037
  const LIGHTNESS = { 50: 0.975, 100: 0.952, 200: 0.912, 300: 0.85, 400: 0.735, 500: 0.63, 600: 0.53, 700: 0.445, 800: 0.36, 900: 0.27, 950: 0.205 };
1028
1038
  const BELL = { 50: 0.12, 100: 0.22, 200: 0.4, 300: 0.62, 400: 0.85, 500: 1, 600: 1, 700: 0.92, 800: 0.8, 900: 0.62, 950: 0.5 };
@@ -1130,7 +1140,7 @@ const BRAND_SLUG_RE = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
1130
1140
  const DEFAULT_PRIMARY_CHROMA = 0.14;
1131
1141
  const isObj$4 = (x) => !!x && typeof x === "object" && !Array.isArray(x);
1132
1142
  const fileMap = (f) => f instanceof Map ? new Map(f) : new Map(Object.entries(f));
1133
- const brandDisplayName = (slug) => slug.split("-").filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
1143
+ const brandDisplayName = (slug2) => slug2.split("-").filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
1134
1144
  function brandLayers(config) {
1135
1145
  const out = {};
1136
1146
  for (const l of config.tokens.layers) {
@@ -1200,7 +1210,7 @@ function aliasRamp(doc, role, ramp, description) {
1200
1210
  }
1201
1211
  group.$description = description;
1202
1212
  }
1203
- function addBrandToConfigText(text, slug) {
1213
+ function addBrandToConfigText(text, slug2) {
1204
1214
  const canonical = /("brand"\s*:\s*\{\s*"values"\s*:\s*\[)([^\]]*)(\])/;
1205
1215
  const flat = /("brands"\s*:\s*\[)([^\]]*)(\])/;
1206
1216
  for (const re of [canonical, flat]) {
@@ -1208,7 +1218,7 @@ function addBrandToConfigText(text, slug) {
1208
1218
  if (!m) continue;
1209
1219
  const items = m[2].trim();
1210
1220
  const sep = items.includes("\n") ? `,${/\n(\s*)"/.exec(m[2])?.[0]?.replace(/"$/, "") ?? " "}` : ", ";
1211
- return text.slice(0, m.index) + m[1] + (items ? `${m[2].replace(/\s*$/, "")}${sep}"${slug}"` : `"${slug}"`) + (m[2].match(/\s*$/)?.[0] ?? "") + m[3] + text.slice(m.index + m[0].length);
1221
+ return text.slice(0, m.index) + m[1] + (items ? `${m[2].replace(/\s*$/, "")}${sep}"${slug2}"` : `"${slug2}"`) + (m[2].match(/\s*$/)?.[0] ?? "") + m[3] + text.slice(m.index + m[0].length);
1212
1222
  }
1213
1223
  throw new Error("bridge.config.json has no modes.brand.values list to add the brand to.");
1214
1224
  }
@@ -1219,21 +1229,21 @@ function listBrands(source) {
1219
1229
  const layers = brandLayers(config);
1220
1230
  const brandAxis = config.axes.brand;
1221
1231
  if (!brandAxis || !layers.brand) return [];
1222
- return brandAxis.values.map((slug) => {
1223
- const file = fill(layers.brand, { brand: slug });
1232
+ return brandAxis.values.map((slug2) => {
1233
+ const file = fill(layers.brand, { brand: slug2 });
1224
1234
  let doc = {};
1225
1235
  try {
1226
1236
  doc = JSON.parse(files.get(file) ?? "{}");
1227
1237
  } catch {
1228
1238
  }
1229
1239
  const meta = isObj$4(doc.$extensions) && isObj$4(doc.$extensions["dev.bridge.brand"]) ? doc.$extensions["dev.bridge.brand"] : {};
1230
- const combo = set.normalizeCombo({ brand: slug });
1240
+ const combo = set.normalizeCombo({ brand: slug2 });
1231
1241
  const primary = set.resolve("color.action.primary.bg", combo);
1232
- const overrides = layers.override ? (config.axes[layers.overrideAxis]?.values ?? []).map((v) => fill(layers.override, { brand: slug, [layers.overrideAxis]: v })).filter((f) => files.has(f)) : [];
1242
+ const overrides = layers.override ? (config.axes[layers.overrideAxis]?.values ?? []).map((v) => fill(layers.override, { brand: slug2, [layers.overrideAxis]: v })).filter((f) => files.has(f)) : [];
1233
1243
  return {
1234
- slug,
1235
- name: typeof meta.name === "string" ? meta.name : brandDisplayName(slug),
1236
- default: slug === brandAxis.default,
1244
+ slug: slug2,
1245
+ name: typeof meta.name === "string" ? meta.name : brandDisplayName(slug2),
1246
+ default: slug2 === brandAxis.default,
1237
1247
  file: tokensPath(config, file),
1238
1248
  ...typeof doc.$description === "string" ? { description: doc.$description } : {},
1239
1249
  ...primary.css && !primary.error ? { primary: primary.css } : {},
@@ -1241,9 +1251,9 @@ function listBrands(source) {
1241
1251
  };
1242
1252
  });
1243
1253
  }
1244
- function checkBrandSlug(config, files, slug) {
1254
+ function checkBrandSlug(config, files, slug2) {
1245
1255
  const out = [];
1246
- const s = slug.trim();
1256
+ const s = slug2.trim();
1247
1257
  if (!s) return [{ level: "error", code: "slug-empty", message: "Give the brand a name." }];
1248
1258
  if (s.length < 2 || s.length > 24 || !BRAND_SLUG_RE.test(s)) out.push({ level: "error", code: "slug-format", message: 'Use 2 to 24 lowercase letters, digits and single dashes, starting with a letter: "acme" or "north-star".' });
1249
1259
  const taken = Object.values(config.axes).flatMap((a2) => a2.values);
@@ -1258,12 +1268,12 @@ function planNewBrand(source, input) {
1258
1268
  const { config } = source;
1259
1269
  const files = fileMap(source.files);
1260
1270
  const issues = [];
1261
- const slug = input.slug.trim();
1262
- const name = input.name?.trim() || brandDisplayName(slug);
1271
+ const slug2 = input.slug.trim();
1272
+ const name = input.name?.trim() || brandDisplayName(slug2);
1263
1273
  const layers = brandLayers(config);
1264
1274
  const brandAxis = config.axes.brand;
1265
1275
  if (!brandAxis || !layers.brand) throw new Error("This repo has no brand mode: bridge.config.json needs modes.brand and a tokens layer with {brand}.");
1266
- issues.push(...checkBrandSlug(config, files, slug));
1276
+ issues.push(...checkBrandSlug(config, files, slug2));
1267
1277
  const from = input.from?.trim() || brandAxis.default;
1268
1278
  const blank = from === "blank";
1269
1279
  if (!blank && !brandAxis.values.includes(from)) throw new Error(`Unknown brand "${from}" to start from. Brands: ${brandAxis.values.join(", ")} (or "blank").`);
@@ -1290,8 +1300,8 @@ function planNewBrand(source, input) {
1290
1300
  const prims = /* @__PURE__ */ new Map();
1291
1301
  const addRamp = (role, ramp, description) => {
1292
1302
  const steps = new Map(STEPS.map((st) => [st, dtcgColor(ramp[st])]));
1293
- prims.set(`${slug}-${role}`, { description, steps });
1294
- return `${slug}-${role}`;
1303
+ prims.set(`${slug2}-${role}`, { description, steps });
1304
+ return `${slug2}-${role}`;
1295
1305
  };
1296
1306
  if (input.primaryHue !== void 0 && input.primaryHue !== "") {
1297
1307
  const ramp = safeRamp("primary", input.primaryHue, "color", actionStep);
@@ -1358,7 +1368,7 @@ function planNewBrand(source, input) {
1358
1368
  if (role.startsWith("$") || !isObj$4(group)) continue;
1359
1369
  for (const [step, node] of Object.entries(group)) {
1360
1370
  if (step.startsWith("$") || !isObj$4(node) || !("$value" in node) || typeof node.$value === "string") continue;
1361
- const ramp = `${slug}-${role}`;
1371
+ const ramp = `${slug2}-${role}`;
1362
1372
  const entry = prims.get(ramp) ?? { description: `${name} ${role}. Exact values set when the brand was created.`, steps: /* @__PURE__ */ new Map() };
1363
1373
  entry.steps.set(Number(step), node.$value);
1364
1374
  prims.set(ramp, entry);
@@ -1366,13 +1376,13 @@ function planNewBrand(source, input) {
1366
1376
  }
1367
1377
  }
1368
1378
  const primDir = config.tokens.tiers.primitive?.[0]?.replace(/\/$/, "") ?? config.tokens.primitives;
1369
- const primFile = `${primDir}/${slug}.tokens.json`;
1379
+ const primFile = `${primDir}/${slug2}.tokens.json`;
1370
1380
  if (prims.size && files.has(primFile)) issues.push({ level: "error", code: "slug-file-exists", message: `${tokensPath(config, primFile)} already exists. Pick another name.` });
1371
1381
  for (const r of prims.keys()) if (before.allPaths().some((p2) => p2.startsWith(`color.${r}.`))) issues.push({ level: "error", code: "ramp-exists", message: `A primitive ramp color.${r} already exists. Pick another name.` });
1372
1382
  doc.$description = `${name}: created with Bridge, started from ${blank ? "a blank brand" : brandDisplayName(from)}.`;
1373
1383
  doc.$extensions = { ...isObj$4(doc.$extensions) ? doc.$extensions : {}, "dev.bridge.brand": { name, from, ...Object.keys(anchors).length ? { anchors } : {} } };
1374
1384
  const { $description, $extensions, ...rest } = doc;
1375
- const brandFile = fill(layers.brand, { brand: slug });
1385
+ const brandFile = fill(layers.brand, { brand: slug2 });
1376
1386
  const nextFiles = new Map(files);
1377
1387
  const created = [];
1378
1388
  if (prims.size) {
@@ -1382,7 +1392,7 @@ function planNewBrand(source, input) {
1382
1392
  for (const st of [...steps.keys()].sort((a2, b) => a2 - b)) ramp[String(st)] = { $value: steps.get(st) };
1383
1393
  color[r] = ramp;
1384
1394
  }
1385
- const prim = { $description: `${name} brand ramps, generated by Bridge (@bridge-ds/core ramp engine). Never used by components directly: brands/${slug}.tokens.json maps them to palette roles.`, color };
1395
+ const prim = { $description: `${name} brand ramps, generated by Bridge (@bridge-ds/core ramp engine). Never used by components directly: brands/${slug2}.tokens.json maps them to palette roles.`, color };
1386
1396
  nextFiles.set(primFile, `${JSON.stringify(prim, null, 2)}
1387
1397
  `);
1388
1398
  created.push(primFile);
@@ -1397,7 +1407,7 @@ function planNewBrand(source, input) {
1397
1407
  if (!text2) continue;
1398
1408
  const o = JSON.parse(text2);
1399
1409
  const out = { ...o, $description: `${name} overrides on top of semantic/${v}, copied from ${brandDisplayName(from)}.` };
1400
- const dst = fill(layers.override, { brand: slug, [layers.overrideAxis]: v });
1410
+ const dst = fill(layers.override, { brand: slug2, [layers.overrideAxis]: v });
1401
1411
  nextFiles.set(dst, `${JSON.stringify(out, null, 2)}
1402
1412
  `);
1403
1413
  created.push(dst);
@@ -1408,7 +1418,7 @@ function planNewBrand(source, input) {
1408
1418
  let configText = baseConfigText;
1409
1419
  let nextConfig = config;
1410
1420
  try {
1411
- configText = addBrandToConfigText(baseConfigText, slug);
1421
+ configText = addBrandToConfigText(baseConfigText, slug2);
1412
1422
  nextConfig = issues.some((i) => i.code.startsWith("slug-")) ? config : loadConfig(configText);
1413
1423
  } catch (e) {
1414
1424
  issues.push({ level: "error", code: "config", message: e.message });
@@ -1416,21 +1426,21 @@ function planNewBrand(source, input) {
1416
1426
  const after = new LayeredTokenSet(nextConfig, nextFiles);
1417
1427
  const key2 = (i) => `${i.code}|${i.path ?? ""}|${i.message}`;
1418
1428
  const known = new Set(validateLayered(before).map(key2));
1419
- const brandKey = `brand=${slug}`;
1429
+ const brandKey = `brand=${slug2}`;
1420
1430
  for (const i of validateLayered(after)) {
1421
1431
  if (known.has(key2(i))) continue;
1422
1432
  if (i.modes && !i.modes.some((m) => m.split(",").includes(brandKey)) && !created.includes(i.file ?? "")) continue;
1423
1433
  issues.push({ level: i.level, code: i.code, message: i.message, ...i.path ? { path: i.path } : {}, ...i.file ? { file: tokensPath(config, i.file) } : {} });
1424
1434
  }
1425
1435
  const pairs = parseContrastChecks(source.contrastText);
1426
- const inGate = nextConfig.axes.brand?.values.includes(slug);
1427
- const combos = inGate ? after.combos(nextConfig.contrast?.modes).filter((c) => c.brand === slug) : [];
1436
+ const inGate = nextConfig.axes.brand?.values.includes(slug2);
1437
+ const combos = inGate ? after.combos(nextConfig.contrast?.modes).filter((c) => c.brand === slug2) : [];
1428
1438
  const results = combos.flatMap((c) => pairs.map((p2) => checkLayeredPair(after, p2, c)));
1429
1439
  const failing = results.filter((r) => !r.pass);
1430
1440
  const themes = [...new Set(combos.map((c) => c.theme).filter((t2) => !!t2))];
1431
1441
  const ramps = {};
1432
1442
  if (inGate) {
1433
- const combo = after.normalizeCombo({ brand: slug });
1443
+ const combo = after.normalizeCombo({ brand: slug2 });
1434
1444
  const read = (role) => STEPS.map((s) => ({ step: s, hex: after.resolve(`palette.${role}.${s}`, combo).css })).filter((x) => /^#[0-9a-f]{6}$/i.test(x.hex));
1435
1445
  ramps.primary = read("primary");
1436
1446
  ramps.neutral = read("neutral");
@@ -1447,19 +1457,19 @@ function planNewBrand(source, input) {
1447
1457
  ];
1448
1458
  const cs3 = config.changesets ?? (config.changeset ? { dir: ".changeset", package: config.changeset.package } : void 0);
1449
1459
  const changeset = cs3 ? {
1450
- path: `${cs3.dir}/bridge-brand-${slug}.md`,
1451
- content: formatChangeset(cs3.package, "minor", `Add the ${name} brand (\`${slug}\`), started from ${blank ? "a blank brand" : brandDisplayName(from)}.`, `- ${created.map((f) => `\`${tokensPath(config, f)}\``).join("\n- ")}
1452
- - \`${CONFIG_FILE}\`: modes.brand gains "${slug}", so the token build and the contrast gate cover it.`)
1460
+ path: `${cs3.dir}/bridge-brand-${slug2}.md`,
1461
+ content: formatChangeset(cs3.package, "minor", `Add the ${name} brand (\`${slug2}\`), started from ${blank ? "a blank brand" : brandDisplayName(from)}.`, `- ${created.map((f) => `\`${tokensPath(config, f)}\``).join("\n- ")}
1462
+ - \`${CONFIG_FILE}\`: modes.brand gains "${slug2}", so the token build and the contrast gate cover it.`)
1453
1463
  } : void 0;
1454
1464
  if (changeset) planned.push({ path: changeset.path, action: "create", content: changeset.content });
1455
1465
  const errors = issues.filter((i) => i.level === "error");
1456
1466
  return {
1457
1467
  ok: errors.length === 0 && failing.length === 0 && results.length > 0,
1458
- slug,
1468
+ slug: slug2,
1459
1469
  name,
1460
1470
  from,
1461
1471
  title: `Add the ${name} brand`,
1462
- branch: `${config.governance.branchPrefix}brand-${slug || "new"}`,
1472
+ branch: `${config.governance.branchPrefix}brand-${slug2 || "new"}`,
1463
1473
  files: planned,
1464
1474
  tokenFiles: Object.fromEntries(nextFiles),
1465
1475
  configText,
@@ -2112,7 +2122,7 @@ class DraftPoller {
2112
2122
  function productPreview(apps, ctx, sources) {
2113
2123
  if (!apps.admin || ctx.fromFork) return null;
2114
2124
  const admin = resolveBranchPreviews(apps, { branch: ctx.branch, pr: ctx.pr, sha: ctx.sha }, sources).find((p2) => p2.app === "admin");
2115
- if (!admin?.url || admin.source === "template" || admin.state === "failed") return null;
2125
+ if (!admin?.url || admin.source === "template" || admin.state === "failed" || admin.skipped) return null;
2116
2126
  return admin.url;
2117
2127
  }
2118
2128
  function tryInProductUrl(preview, pr, screen) {
@@ -2135,18 +2145,20 @@ function localTryCommand(branch) {
2135
2145
  const name = /^[\w./-]+$/.test(branch) ? branch : `'${branch.replace(/'/g, `'\\''`)}'`;
2136
2146
  return `git fetch origin && git switch ${name}`;
2137
2147
  }
2148
+ const explorationStartMessage = (e) => [`chore(explore): start ${e.name.trim()}`, ...e.purpose?.trim() ? [e.purpose.trim()] : [], "Exploration: never merged as is. Promote it to review or close it."].join("\n\n");
2138
2149
  function explorationSummary(p2, _owner, _repo, s) {
2139
2150
  const e = parseExplorationBody(p2.body);
2140
- const slug = p2.headRef?.startsWith(EXPLORATION_PREFIX) ? p2.headRef.slice(EXPLORATION_PREFIX.length) : e?.slug ?? explorationSlug(p2.title);
2151
+ const slug2 = p2.headRef?.startsWith(EXPLORATION_PREFIX) ? p2.headRef.slice(EXPLORATION_PREFIX.length) : e?.slug ?? explorationSlug(p2.title);
2141
2152
  return {
2142
2153
  number: p2.number,
2143
2154
  url: p2.url,
2144
2155
  branch: p2.headRef,
2145
- slug,
2156
+ slug: slug2,
2146
2157
  name: e?.name ?? p2.title.replace(/^[A-Z][A-Z0-9_]*-\d+\s+/, "").replace(/^Exploration:\s*/i, ""),
2147
2158
  purpose: e?.purpose ?? "",
2148
2159
  ...e?.useCase ? { useCase: e.useCase } : {},
2149
- status: explorationStatus({ state: p2.state, labels: p2.labels, headRef: p2.headRef, body: p2.body }),
2160
+ status: explorationStatus({ state: p2.state, labels: p2.labels, headRef: p2.headRef, body: p2.body, headSha: p2.headSha, draft: p2.draft }),
2161
+ archived: isArchivedExploration(p2.labels),
2150
2162
  draft: p2.draft,
2151
2163
  author: e?.author ?? p2.author,
2152
2164
  ...e?.source ? { source: e.source } : {},
@@ -2158,35 +2170,52 @@ function explorationSummary(p2, _owner, _repo, s) {
2158
2170
  }
2159
2171
  async function listExplorationsOnGitHub(gh, s, opts = {}) {
2160
2172
  const pulls = await gh.listPullRequests(opts.state ?? "open");
2161
- return pulls.filter((p2) => isExplorationPull({ state: p2.state, labels: p2.labels, headRef: p2.headRef, body: p2.body })).map((p2) => explorationSummary(p2, gh.owner, gh.name, s));
2173
+ const archived = opts.archived ?? "exclude";
2174
+ return pulls.filter((p2) => isExplorationPull({ state: p2.state, labels: p2.labels, headRef: p2.headRef, body: p2.body })).filter((p2) => archived === "include" || archived === "only" === isArchivedExploration(p2.labels)).map((p2) => explorationSummary(p2, gh.owner, gh.name, s));
2175
+ }
2176
+ const isHiddenRepoError = (err) => err instanceof GitHubError && (err.status === 404 || err.status === 409 || err.status === 403 && err.auth !== "rejected" && !err.resetsAt);
2177
+ async function listProductExplorationsOnGitHub(clientFor, products, designSystemRepo, opts = {}) {
2178
+ const unreachable = [];
2179
+ const lists = await Promise.all(
2180
+ productRepos(products, designSystemRepo).map(async ({ product, repo }) => {
2181
+ try {
2182
+ const list = await listExplorationsOnGitHub(clientFor(repo), { packages: [], apps: {} }, opts);
2183
+ return list.map((x) => inProductRepo(x, product, repo));
2184
+ } catch (err) {
2185
+ unreachable.push({ product: product.key, name: product.name, repo, reason: isHiddenRepoError(err) ? "not-connected" : "unavailable" });
2186
+ return [];
2187
+ }
2188
+ })
2189
+ );
2190
+ return { explorations: lists.flat(), unreachable };
2191
+ }
2192
+ function inProductRepo(x, product, repo) {
2193
+ const url = previewUrl(product.preview, { branch: x.branch, pr: x.number, sha: x.headSha });
2194
+ return { ...x, repo, product: product.key, previews: url ? [{ app: product.key, label: product.name, url }] : [] };
2162
2195
  }
2163
2196
  async function startExplorationOnGitHub(gh, e, s) {
2164
2197
  const problems = explorationProblems(e);
2165
2198
  if (problems.length) throw new GitHubError(422, problems.join(" "));
2166
2199
  if (!gh.canWrite) throw new GitHubError(401, "Starting an exploration needs a GitHub token (contents and pull requests, read and write, on this repository only).");
2167
- const slug = e.slug || explorationSlug(e.name);
2168
- const branch = `${EXPLORATION_PREFIX}${slug}`;
2200
+ const slug2 = e.slug || explorationSlug(e.name);
2201
+ const branch = `${EXPLORATION_PREFIX}${slug2}`;
2169
2202
  const x = gh.withBranchPrefix(EXPLORATION_PREFIX);
2170
- if (await x.getBranchSha(branch)) throw new GitHubError(422, `An exploration named "${slug}" already exists (${branch}). Pick another name, or continue that one.`);
2171
- const full = { ...e, slug, startedAt: e.startedAt ?? (/* @__PURE__ */ new Date()).toISOString() };
2203
+ if (await x.getBranchSha(branch)) throw new GitHubError(422, `An exploration named "${slug2}" already exists (${branch}). Pick another name, or continue that one.`);
2204
+ const full = { ...e, slug: slug2, startedAt: e.startedAt ?? (/* @__PURE__ */ new Date()).toISOString() };
2172
2205
  const commit = await x.commitToNewBranch({
2173
2206
  base: s.base,
2174
2207
  branch,
2175
- message: `chore(explore): start ${full.name.trim()}
2176
-
2177
- ${full.purpose.trim()}
2178
-
2179
- Exploration: never merged as is. Promote it to review or close it.`,
2180
- files: [{ path: explorationNotePath(slug), content: explorationNote(full) }]
2208
+ message: explorationStartMessage(full),
2209
+ files: [{ path: explorationNotePath(slug2), content: explorationNote(full) }]
2181
2210
  });
2182
- const ctx = { owner: gh.owner, repo: gh.name, packages: s.packages, apps: s.apps, ...s.requests?.length ? { requests: s.requests } : {} };
2211
+ const ctx = { owner: gh.owner, repo: gh.name, packages: s.packages, apps: s.apps, ...s.requests?.length ? { requests: s.requests } : {}, ...s.inProduct ? { inProduct: s.inProduct } : {} };
2183
2212
  const title = full.jira ? `${full.jira.trim().toUpperCase()} ${explorationPrTitle(full)}` : explorationPrTitle(full);
2184
2213
  const pr = await x.createPullRequest({ title, head: branch, base: s.base, body: explorationPrBody(full, ctx), draft: true });
2185
2214
  await x.addLabels(pr.number, [EXPLORATION_LABEL, ...s.labels ?? []]);
2186
2215
  await x.updatePullRequest(pr.number, { body: explorationPrBody(full, { ...ctx, pr: pr.number }) }).catch(() => void 0);
2187
2216
  return {
2188
2217
  branch,
2189
- slug,
2218
+ slug: slug2,
2190
2219
  pr: pr.number,
2191
2220
  url: pr.url,
2192
2221
  commit: commit.sha,
@@ -2194,6 +2223,16 @@ Exploration: never merged as is. Promote it to review or close it.`,
2194
2223
  previews: previewLinks(s.apps, { branch, pr: pr.number, sha: commit.sha })
2195
2224
  };
2196
2225
  }
2226
+ async function productRepoAccess(gh) {
2227
+ try {
2228
+ const r = await gh.getRepo();
2229
+ if (r.permissions && r.permissions.push === false) return { ok: false, reason: "read-only" };
2230
+ return { ok: true, base: r.defaultBranch };
2231
+ } catch (err) {
2232
+ if (err instanceof GitHubError && (err.status === 404 || err.status === 403 && err.auth !== "rejected" && !err.resetsAt)) return { ok: false, reason: "not-connected" };
2233
+ throw err;
2234
+ }
2235
+ }
2197
2236
  async function explorationTryItOnGitHub(gh, e) {
2198
2237
  const [comments, head] = await Promise.all([gh.listIssueComments(e.number), gh.getBranchSha(e.branch).catch(() => void 0)]);
2199
2238
  return explorationTryIt({ comment: findPreviewComment(comments)?.body, headSha: head });
@@ -2241,12 +2280,470 @@ async function promoteExplorationOnGitHub(gh, ref, opts = {}) {
2241
2280
  });
2242
2281
  return out;
2243
2282
  }
2283
+ async function archiveExplorationOnGitHub(gh, ref, opts = {}) {
2284
+ if (!gh.canWrite) throw new GitHubError(401, "Archiving an exploration needs a GitHub token (pull requests or issues: write).");
2285
+ const p2 = await findExplorationOnGitHub(gh, ref);
2286
+ if (p2.state !== "open") throw new GitHubError(422, `#${p2.number} is ${p2.state === "merged" ? "shipped" : "closed"}: only an open exploration is archived. Its link already stays on GitHub.`);
2287
+ const archived = opts.archived ?? true;
2288
+ if (archived) await gh.addLabels(p2.number, [EXPLORATION_ARCHIVED_LABEL]);
2289
+ else
2290
+ await gh.removeLabel(p2.number, EXPLORATION_ARCHIVED_LABEL).catch((e) => {
2291
+ if (!(e instanceof GitHubError && e.status === 404)) throw e;
2292
+ });
2293
+ return { number: p2.number, url: p2.url, branch: p2.headRef, archived };
2294
+ }
2244
2295
  async function closeExplorationOnGitHub(gh, ref) {
2245
2296
  if (!gh.canWrite) throw new GitHubError(401, "Closing an exploration needs a GitHub token (pull requests: write).");
2246
2297
  const p2 = await findExplorationOnGitHub(gh, ref);
2247
2298
  await gh.updatePullRequest(p2.number, { state: "closed" });
2248
2299
  return { number: p2.number, url: p2.url, branch: p2.headRef };
2249
2300
  }
2301
+ const PRODUCT_FILE = /\.(css|scss|sass|less|tsx|jsx|ts|js|mjs|cjs|mts|cts|astro|vue|svelte)$/;
2302
+ const SKIP_FILE = /\.d\.[cm]?ts$|\.(test|spec|stories|story)\.[cm]?[jt]sx?$|\.min\.(css|js)$/;
2303
+ const PIECES_FILE_LIMIT = 40;
2304
+ const cleanPath = (p2) => p2.trim().replace(/^\.?\/+|\/+$/g, "");
2305
+ function ownProduct(products, repo) {
2306
+ const p2 = repo ? productOfRepo(products, repo) : void 0;
2307
+ return p2 && p2.key !== defaultProduct(products).key ? p2 : void 0;
2308
+ }
2309
+ const repoOfProductBranch = (gh, lib) => lib && lib.fullName.toLowerCase() !== gh.fullName.toLowerCase() ? gh.fullName : void 0;
2310
+ function productOf(products, pull, files, repo) {
2311
+ const own = ownProduct(products, repo);
2312
+ if (own) return own;
2313
+ const product = productOfBranch(products, { labels: pull.labels, files });
2314
+ if (product.key === defaultProduct(products).key) throw new GitHubError(422, `This branch is a library branch (${product.name}), not a product branch: missing pieces are checked on a branch started for a product.`);
2315
+ return product;
2316
+ }
2317
+ async function readProductBranchOnGitHub(gh, pr, opts) {
2318
+ const pull = await gh.getPullRequest(pr);
2319
+ const cmp = await gh.compareDetailed(pull.baseRef, pull.headSha);
2320
+ const product = productOf(opts.products, pull, cmp.files.map((f) => f.filename), gh.fullName);
2321
+ const dir = product.path && !ownProduct(opts.products, gh.fullName) ? `${cleanPath(product.path)}/` : "";
2322
+ const changed = cmp.files.filter((f) => f.status !== "removed" && (!dir || f.filename.startsWith(dir)) && PRODUCT_FILE.test(f.filename) && !SKIP_FILE.test(f.filename) && !f.filename.split("/").includes("node_modules"));
2323
+ const read = changed.slice(0, PIECES_FILE_LIMIT);
2324
+ const start = cmp.mergeBase ?? pull.baseRef;
2325
+ const files = await Promise.all(
2326
+ read.map(async (f) => {
2327
+ const [before, after] = await Promise.all([f.status === "added" ? Promise.resolve(void 0) : gh.getFileText(f.previous_filename ?? f.filename, start), gh.getFileText(f.filename, pull.headSha)]);
2328
+ return { path: f.filename, before: before ?? null, after: after ?? null };
2329
+ })
2330
+ );
2331
+ return { pull, product, files, skipped: changed.length - read.length, record: parsePiecesRecord(pull.body) };
2332
+ }
2333
+ async function recordPiecesOnGitHub(gh, pr, check) {
2334
+ const pull = await gh.getPullRequest(pr);
2335
+ const record = mergePiecesRecord(parsePiecesRecord(pull.body), { checkedAt: check.checkedAt ?? (/* @__PURE__ */ new Date()).toISOString(), headSha: check.headSha ?? pull.headSha, pieces: check.pieces, fixes: check.fixes });
2336
+ await gh.updatePullRequest(pr, { body: withPiecesRecord(pull.body, record) });
2337
+ return record;
2338
+ }
2339
+ const productWriter = (gh, branch) => isExplorationBranch(branch) ? gh.withBranchPrefix(EXPLORATION_PREFIX) : gh;
2340
+ async function useLibraryPreviewOnGitHub(gh, input) {
2341
+ const message = `chore(bridge): use the preview of ${input.libraryBranch} (#${input.libraryNumber})
2342
+
2343
+ The product branch takes the library branch before it ships. Switch to the published version once it does.`;
2344
+ try {
2345
+ const r = await productWriter(gh, input.productBranch).mergeIntoBranch(input.productBranch, input.libraryBranch, message);
2346
+ return { ...r, said: r.status === "merged" ? "Your branch now uses the preview of the library branch." : "Your branch already has the latest preview." };
2347
+ } catch (err) {
2348
+ if (err instanceof GitHubError && err.status === 409) return { status: "conflict", sha: null, said: `Your branch and the library branch change the same lines. A developer merges ${input.libraryBranch} into ${input.productBranch} by hand.` };
2349
+ throw err;
2350
+ }
2351
+ }
2352
+ function productBranchRef(pull, product, repo) {
2353
+ const name = /<!-- bridge-exploration (\{[\s\S]*?\}) -->/.exec(pull.body ?? "");
2354
+ let title = pull.title.replace(/^[A-Z][A-Z0-9_]*-\d+\s+/, "").replace(/^Exploration:\s*/i, "");
2355
+ let author = pull.author;
2356
+ if (name) {
2357
+ try {
2358
+ const e = JSON.parse(name[1]);
2359
+ if (e.name) title = e.name;
2360
+ if (e.author) author = e.author;
2361
+ } catch {
2362
+ }
2363
+ }
2364
+ return { pr: pull.number, title: title.charAt(0).toUpperCase() + title.slice(1), branch: pull.headRef, url: pull.url, productKey: product.key, productName: product.name, ...author ? { author } : {}, ...repo ? { repo } : {} };
2365
+ }
2366
+ async function openLibraryBranchOnGitHub(gh, input) {
2367
+ const pull = await gh.getPullRequest(input.pr);
2368
+ const record = parsePiecesRecord(pull.body);
2369
+ const piece = record?.pieces.find((p2) => p2.id === input.piece);
2370
+ if (!record || !piece) throw new GitHubError(404, record ? `No piece "${input.piece}" on #${input.pr}. Pieces: ${record.pieces.map((p2) => p2.id).join(", ") || "none"}.` : `#${input.pr} was not checked yet: find its missing pieces first (bridge_missing_pieces with record, or \`bridge pieces ${input.pr} --record\`).`);
2371
+ const product = productOf(input.products, pull, await gh.listPullRequestFiles(input.pr).then((f) => f.map((x) => x.filename), () => []), gh.fullName);
2372
+ const mode = input.mode ?? "direct";
2373
+ const lib = input.library ?? gh;
2374
+ const repo = repoOfProductBranch(gh, input.library);
2375
+ const plan = libraryBranchPlan(piece, productBranchRef(pull, product, repo), { source: input.source, requestedBy: input.openedBy, mode });
2376
+ const existing = record.links.find((l) => l.piece === piece.id);
2377
+ if (existing) return { status: "already-linked", plan, link: existing, preview: null, warnings: [] };
2378
+ if (input.dryRun) return { status: "dry-run", plan, link: null, preview: null, warnings: [] };
2379
+ if (!gh.canWrite || !lib.canWrite) throw new GitHubError(401, mode === "suggest" ? "Suggesting a library change needs a GitHub token (issues: write)." : "Opening a library branch needs a GitHub token (contents, pull requests and issues: write).");
2380
+ const warnings = [];
2381
+ const at2 = (/* @__PURE__ */ new Date()).toISOString();
2382
+ const who = { ...input.openedBy ? { openedBy: input.openedBy } : {}, ...input.agent ? { agent: input.agent } : {}, ...repo ? { repo: lib.fullName } : {} };
2383
+ let link;
2384
+ if (plan.kind === "token-change") {
2385
+ if (!input.writeTokens) throw new GitHubError(422, "This piece is a token: open it where the token files are read (Bridge Studio, bridge_open_library_branch or `bridge pieces open`).");
2386
+ const r = await input.writeTokens(plan, { mode });
2387
+ link = r.status === "suggested" ? { piece: piece.id, kind: "suggestion", number: r.number, url: r.url, title: plan.title, at: at2, ...who } : { piece: piece.id, kind: "token-change", number: r.number, url: r.url, title: plan.title, branch: r.branch, at: at2, ...who };
2388
+ } else if (plan.kind === "exploration") {
2389
+ const r = await startExplorationOnGitHub(lib, { ...plan.exploration, ...input.openedBy ? { author: input.openedBy } : {}, ...input.agent ? { agent: input.agent } : {} }, { base: input.base, ...input.exploration, labels: [NEEDED_BY_LABEL] });
2390
+ link = { piece: piece.id, kind: "exploration", number: r.pr, url: r.url, title: plan.title, branch: r.branch, at: at2, ...who };
2391
+ } else {
2392
+ const issue = composeRequestIssue(plan.request, { labels: [NEEDED_BY_LABEL] });
2393
+ const r = await lib.createIssue(issue);
2394
+ link = { piece: piece.id, kind: "request", number: r.number, url: r.url, title: plan.title, at: at2, ...who };
2395
+ }
2396
+ try {
2397
+ if (link.kind === "request" || link.kind === "suggestion") {
2398
+ const issue = await lib.getIssue(link.number);
2399
+ await lib.updateIssue(link.number, { body: withNeededBy(issue.body, plan.neededBy) });
2400
+ if (link.kind === "suggestion" && !issue.labels.includes(NEEDED_BY_LABEL)) await lib.addLabels(link.number, [NEEDED_BY_LABEL]);
2401
+ } else {
2402
+ const libPull = await lib.getPullRequest(link.number);
2403
+ await lib.updatePullRequest(link.number, { body: withNeededBy(libPull.body, plan.neededBy) });
2404
+ await lib.addLabels(link.number, [NEEDED_BY_LABEL]);
2405
+ }
2406
+ } catch (err) {
2407
+ if (err instanceof GitHubError && err.auth === "rejected") throw err;
2408
+ warnings.push(`The library side does not say who needs it yet (${err.message}). Add "Needed by ${repo ?? ""}#${input.pr}" to #${link.number} on GitHub.`);
2409
+ }
2410
+ try {
2411
+ const fresh = await gh.getPullRequest(input.pr);
2412
+ const next = withLink(parsePiecesRecord(fresh.body) ?? record, link);
2413
+ await gh.updatePullRequest(input.pr, { body: withPiecesRecord(fresh.body, next) });
2414
+ } catch (err) {
2415
+ if (mode !== "suggest") throw err;
2416
+ warnings.push(`#${input.pr} does not list #${link.number} yet (${err.message}). Add "Library: ${repo ? lib.fullName : ""}#${link.number}" to #${input.pr} on GitHub, so this piece is not sent twice.`);
2417
+ }
2418
+ let preview = null;
2419
+ if (!repo && (input.usePreview ?? true) && link.kind === "token-change" && link.branch) {
2420
+ preview = await useLibraryPreviewOnGitHub(gh, { productBranch: pull.headRef, libraryBranch: link.branch, libraryNumber: link.number }).catch((err) => {
2421
+ warnings.push(`Your branch does not use the preview yet: ${err.message}`);
2422
+ return null;
2423
+ });
2424
+ }
2425
+ return { status: link.kind === "suggestion" ? "suggested" : "opened", plan, link, preview, warnings };
2426
+ }
2427
+ function acrossRepos(p2, lifecycle, across) {
2428
+ if (!across || !p2.action) return p2;
2429
+ if (lifecycle === "merged") return { ...p2, sentence: "The library published it. Update @bridge-ds/react in your product to use it.", action: null };
2430
+ return { ...p2, action: null };
2431
+ }
2432
+ const isNote = (path) => path.startsWith(`${EXPLORATION_NOTES_DIR}/`);
2433
+ async function linkedLibraryBranchOnGitHub(gh, link, product, piece, opts = {}) {
2434
+ const nearest = piece?.nearest?.label;
2435
+ const behind = () => opts.across ? Promise.resolve(null) : gh.compare(product.baseRef, product.headSha).then((c) => c.behindBy, () => null);
2436
+ if (link.kind === "suggestion") {
2437
+ const issue = await gh.getIssue(link.number);
2438
+ const cached = suggestionStateFromLabels(issue.labels);
2439
+ const s = suggestionStatus({ state: issue.state, stateReason: issue.stateReason, applied: cached === "applying", review: cached === "applying" ? void 0 : cached });
2440
+ const status2 = linkStatus({ kind: "suggestion", status: s });
2441
+ const lifecycle2 = s === "applied" ? "merged" : issue.state === "closed" ? "closed" : "open";
2442
+ const behindMain2 = lifecycle2 === "merged" ? await behind() : null;
2443
+ const preview2 = libraryPreviewState({ kind: "suggestion", lifecycle: lifecycle2, hasChanges: false, missing: null, total: null, behindMain: behindMain2, nearest });
2444
+ return { link, piece, status: status2, preview: acrossRepos(preview2, lifecycle2, opts.across), people: status2.people };
2445
+ }
2446
+ if (link.kind === "request") {
2447
+ const issue = await gh.getIssue(link.number);
2448
+ const status2 = linkStatus({ kind: "request", state: issue.state, stateReason: issue.stateReason });
2449
+ const lifecycle2 = issue.state === "closed" ? issue.stateReason === "not_planned" ? "closed" : "merged" : "open";
2450
+ const behindMain2 = lifecycle2 === "merged" ? await behind() : null;
2451
+ const preview2 = libraryPreviewState({ kind: "request", lifecycle: lifecycle2, hasChanges: false, missing: null, total: null, behindMain: behindMain2, nearest });
2452
+ return { link, piece, status: status2, preview: acrossRepos(preview2, lifecycle2, opts.across), people: status2.people };
2453
+ }
2454
+ const lib = await gh.getPullRequest(link.number);
2455
+ const reviews = await gh.listReviews(link.number).catch(() => []);
2456
+ const status = linkStatus({ kind: link.kind, state: lib.state, draft: lib.draft, labels: lib.labels, requestedReviewers: lib.requestedReviewers, reviews: reviews.map((r) => ({ user: r.user, state: r.state })) });
2457
+ const lifecycle = lib.state === "merged" ? "merged" : lib.state === "open" ? "open" : "closed";
2458
+ let missing = null;
2459
+ let total = null;
2460
+ let behindMain = null;
2461
+ let hasChanges = true;
2462
+ if (lifecycle === "open") {
2463
+ const [files, own, inProduct] = await Promise.all([
2464
+ gh.listPullRequestFiles(link.number).catch(() => null),
2465
+ gh.compare(lib.baseRef, lib.headSha).catch(() => null),
2466
+ opts.across ? Promise.resolve(null) : gh.compare(product.headSha, lib.headSha).catch(() => null)
2467
+ ]);
2468
+ hasChanges = files ? files.some((f) => !isNote(f.filename)) : true;
2469
+ total = own?.aheadBy ?? null;
2470
+ missing = inProduct?.aheadBy ?? null;
2471
+ } else if (lifecycle === "merged") {
2472
+ behindMain = await behind();
2473
+ }
2474
+ const preview = libraryPreviewState({ kind: link.kind, lifecycle, hasChanges, missing, total, behindMain, nearest });
2475
+ return { link, piece, status, preview: acrossRepos(preview, lifecycle, opts.across), people: status.people };
2476
+ }
2477
+ async function productBranchViewOnGitHub(gh, pr, opts) {
2478
+ const pull = await gh.getPullRequest(pr);
2479
+ const files = await gh.listPullRequestFiles(pr).then((f) => f.map((x) => x.filename), () => []);
2480
+ const product = productOf(opts.products, pull, files, gh.fullName);
2481
+ const record = parsePiecesRecord(pull.body);
2482
+ const across = Boolean(repoOfProductBranch(gh, opts.library));
2483
+ const lib = opts.library ?? gh;
2484
+ const linked = await Promise.all(
2485
+ (record?.links ?? []).map((l) => linkedLibraryBranchOnGitHub(lib, l, { headSha: pull.headSha, baseRef: pull.baseRef }, record?.pieces.find((p2) => p2.id === l.piece), { across }))
2486
+ );
2487
+ const open = (record?.pieces ?? []).filter((p2) => !record.links.some((l) => l.piece === p2.id));
2488
+ return { pull, product, record, sentence: productBranchSentence(record, { viewer: opts.viewer, agentName: opts.agentName }), linked, open };
2489
+ }
2490
+ async function switchToPublishedOnGitHub(gh, pr) {
2491
+ if (!gh.canWrite) throw new GitHubError(401, "Switching needs a GitHub token (contents and pull requests: write).");
2492
+ const pull = await gh.getPullRequest(pr);
2493
+ if (pull.state !== "open") throw new GitHubError(422, `#${pr} is ${pull.state}: only an open branch switches.`);
2494
+ const cmp = await gh.compare(pull.baseRef, pull.headSha);
2495
+ if (cmp.behindBy === 0) return { status: "up-to-date", said: "Your branch already uses the published version." };
2496
+ try {
2497
+ await gh.updatePullRequestBranch(pr, pull.headSha);
2498
+ } catch (err) {
2499
+ if (err instanceof GitHubError && err.status === 422) throw new GitHubError(422, `Your branch and ${pull.baseRef} change the same lines, so Bridge cannot switch it alone. Use "Update from main" to pick the token values, or ask a developer.`, err.detail);
2500
+ throw err;
2501
+ }
2502
+ return { status: "switched", said: `Your branch now uses the published version from ${pull.baseRef}.` };
2503
+ }
2504
+ async function proposePiecesOnGitHub(gh, pr, input) {
2505
+ const out = { proposed: [], already: [], unknown: [], warnings: [] };
2506
+ const ids = [...new Set(input.pieces.map((p2) => p2.trim()).filter(Boolean))];
2507
+ if (!ids.length) return out;
2508
+ const pull = await gh.getPullRequest(pr);
2509
+ const record = parsePiecesRecord(pull.body);
2510
+ if (!record) throw new GitHubError(404, `#${pr} was not checked against the library yet: find its missing pieces first (bridge_missing_pieces with record, or \`bridge pieces ${pr} --record\`).`);
2511
+ const files = await gh.listPullRequestFiles(pr).then((f) => f.map((x) => x.filename), () => []);
2512
+ const lib = input.library ?? gh;
2513
+ const repo = repoOfProductBranch(gh, input.library);
2514
+ const ref = productBranchRef(pull, ownProduct(input.products, repo) ?? productOfBranch(input.products, { labels: pull.labels, files }), repo);
2515
+ const todo = [];
2516
+ for (const id of ids) {
2517
+ const piece = record.pieces.find((p2) => p2.id === id);
2518
+ const link = record.links.find((l) => l.piece === id);
2519
+ if (!piece) out.unknown.push(id);
2520
+ else if (link) out.already.push({ piece: id, number: link.number });
2521
+ else todo.push({ piece, ask: pieceRequest(piece, ref, { previewUrl: input.previewUrl, source: input.source, requestedBy: input.openedBy }) });
2522
+ }
2523
+ if (input.dryRun) return { ...out, proposed: todo.map((t2) => ({ piece: t2.piece.id, title: t2.ask.request.title, number: 0, url: "" })) };
2524
+ if (!todo.length) return out;
2525
+ if (!lib.canWrite) throw new GitHubError(401, "Sending pieces to the library needs a GitHub token (issues: write).");
2526
+ const at2 = (/* @__PURE__ */ new Date()).toISOString();
2527
+ const who = { ...input.openedBy ? { openedBy: input.openedBy } : {}, ...input.agent ? { agent: input.agent } : {}, ...repo ? { repo: lib.fullName } : {} };
2528
+ const links = [];
2529
+ for (const t2 of todo) {
2530
+ const issue = composeRequestIssue(t2.ask.request, { labels: [NEEDED_BY_LABEL] });
2531
+ const r = await lib.createIssue({ ...issue, body: withNeededBy(issue.body, t2.ask.neededBy) });
2532
+ links.push({ piece: t2.piece.id, kind: "request", number: r.number, url: r.url, title: t2.ask.request.title, at: at2, ...who });
2533
+ out.proposed.push({ piece: t2.piece.id, title: t2.ask.request.title, number: r.number, url: r.url });
2534
+ }
2535
+ try {
2536
+ const fresh = await gh.getPullRequest(pr);
2537
+ const next = links.reduce((rec, l) => withLink(rec, l), parsePiecesRecord(fresh.body) ?? record);
2538
+ await gh.updatePullRequest(pr, { body: withPiecesRecord(fresh.body, next) });
2539
+ } catch (err) {
2540
+ if (err instanceof GitHubError && err.auth === "rejected") throw err;
2541
+ out.warnings.push(`#${pr} does not list the requests yet (${err.message}). Add "Library: ${links.map((l) => `${repo ? lib.fullName : ""}#${l.number}`).join(", ")}" to #${pr} on GitHub, so they are not sent twice.`);
2542
+ }
2543
+ return out;
2544
+ }
2545
+ async function doneExploringOnGitHub(gh, ref, input = {}) {
2546
+ const p2 = await findExplorationOnGitHub(gh, ref);
2547
+ const status = explorationStatus({ state: p2.state, labels: p2.labels, headRef: p2.headRef, body: p2.body, headSha: p2.headSha, draft: p2.draft });
2548
+ if (status === "closed") throw new GitHubError(422, `Exploration #${p2.number} is closed. Reopen it on GitHub first: its preview comes back with it.`);
2549
+ if (status === "shipped") throw new GitHubError(422, `#${p2.number} is already shipped.`);
2550
+ if (status === "promoted") throw new GitHubError(422, `#${p2.number} was already sent to review.`);
2551
+ const fresh = status === "active";
2552
+ const files = fresh ? await gh.listPullRequestFiles(p2.number) : [];
2553
+ const plan = fresh ? doneExploringPlan({ body: p2.body, branch: p2.headRef, files: files.map((f) => ({ path: f.filename, status: f.status })), date: input.date }) : null;
2554
+ const take = plan?.take ?? ["git fetch origin", `git checkout ${p2.headRef}`];
2555
+ const checks = p2.headSha ? await Promise.resolve(p2.headSha).then((sha) => gh.getChecks(sha)).then((c) => c.state, () => null) : null;
2556
+ const out = { number: p2.number, url: p2.url, branch: p2.headRef, status: fresh ? "ready" : status, handedOff: fresh, plan, pieces: { proposed: [], already: [], unknown: [], warnings: [] }, take, checks, note: explorationStartingPoint(checks), warnings: [] };
2557
+ const products = input.products ?? [];
2558
+ const ask = { pieces: input.pieces ?? [], products, previewUrl: input.previewUrl, openedBy: input.openedBy, agent: input.agent, source: input.source, library: input.library };
2559
+ if (input.dryRun) {
2560
+ out.pieces = await proposePiecesOnGitHub(gh, p2.number, { ...ask, dryRun: true }).catch((e) => ({ proposed: [], already: [], unknown: [], warnings: [e.message] }));
2561
+ return out;
2562
+ }
2563
+ if (!gh.canWrite) throw new GitHubError(401, 'Saying "Done exploring" needs a GitHub token (contents, pull requests and issues: write).');
2564
+ out.pieces = await proposePiecesOnGitHub(gh, p2.number, ask);
2565
+ out.warnings.push(...out.pieces.warnings);
2566
+ if (!plan) return out;
2567
+ const x = gh.withBranchPrefix(EXPLORATION_PREFIX);
2568
+ let doneSha = p2.headSha;
2569
+ if (plan.removeFiles.length) {
2570
+ const c = await x.commitToBranch({ branch: p2.headRef, message: "chore(explore): done exploring, remove the exploration note", files: plan.removeFiles.map((path) => ({ path, content: null })) }).catch((e) => {
2571
+ if (e instanceof GitHubError && e.auth === "rejected") throw e;
2572
+ out.warnings.push(`The exploration note stays on the branch (${e.message}). Delete ${plan.removeFiles.join(", ")} before merging.`);
2573
+ return null;
2574
+ });
2575
+ if (c) doneSha = c.sha;
2576
+ }
2577
+ await x.addLabels(p2.number, plan.addLabels);
2578
+ await x.removeLabel(p2.number, plan.removeLabels[0]).catch((e) => {
2579
+ if (!(e instanceof GitHubError && e.status === 404)) out.warnings.push(`The exploration label is still on (${e.message}). Remove it on GitHub so developers can ship it later.`);
2580
+ });
2581
+ const latest = await gh.getPullRequest(p2.number).catch(() => p2);
2582
+ const doneAt = parseExplorationBody(plan.body)?.doneAt ?? (input.date ?? /* @__PURE__ */ new Date()).toISOString();
2583
+ await x.updatePullRequest(p2.number, { body: withDoneExploring(latest.body, { branch: p2.headRef, doneAt, doneSha, checks }) });
2584
+ return out;
2585
+ }
2586
+ const CONTINUE_PHRASE = "Continue Bridge exploration";
2587
+ const onlyNotes = (files) => Boolean(files?.length) && files.every((f) => f.startsWith(".bridge/"));
2588
+ function explorationStage(x) {
2589
+ if (x.status !== "active" || x.archived) return "handed";
2590
+ if (x.files && (x.files.length === 0 || onlyNotes(x.files))) return "not-started";
2591
+ return "exploring";
2592
+ }
2593
+ const continueRef = (x) => explorationRefLabel(x);
2594
+ function continueLines(x) {
2595
+ const ref = continueRef(x);
2596
+ const name = x.name.replace(/"/g, "'").trim();
2597
+ return {
2598
+ app: `${CONTINUE_PHRASE} ${ref} "${name}" in ${x.productName} and use the Bridge library.`,
2599
+ terminal: `claude "${CONTINUE_PHRASE} ${ref}"`
2600
+ };
2601
+ }
2602
+ function parseContinueLine(text) {
2603
+ const m = /continue\s+bridge\s+exploration\s+((?:[a-z0-9][\w.-]*)?#\d+)/i.exec(text);
2604
+ return m ? m[1].toLowerCase() : null;
2605
+ }
2606
+ function wordsOf(name) {
2607
+ const spaced = name.replace(/[-_]+/g, " ").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/([a-z0-9])([A-Z])/g, "$1 $2").trim();
2608
+ const words = spaced.split(/\s+/).filter(Boolean);
2609
+ return words.map((w, i) => i === 0 ? w.charAt(0).toUpperCase() + w.slice(1) : /^[A-Z]{2,}$/.test(w) ? w : w.toLowerCase()).join(" ");
2610
+ }
2611
+ const SKIP$1 = [/^\.bridge\//, /^\.changeset\//, /^\.github\//, /\.(test|spec|stories)\.[jt]sx?$/, /\.d\.ts$/, /(^|\/)package(-lock)?\.json$/, /\.md$/, /(^|\/)__snapshots__\//, /\.docs\.json$/];
2612
+ const CODE = /\.(tsx|jsx|ts|js|vue|svelte|astro|css|scss)$/;
2613
+ const SCREEN_DIRS = /(^|\/)(pages|routes|screens|views|app)\//;
2614
+ function thingOf(path) {
2615
+ if (SKIP$1.some((r) => r.test(path))) return null;
2616
+ if (/(^|\/)tokens\/src\//.test(path) || /\.tokens\.json$/.test(path)) return "Tokens";
2617
+ if (!CODE.test(path)) return null;
2618
+ const parts = path.split("/");
2619
+ const file = parts[parts.length - 1];
2620
+ const base = file.replace(/\.module\.(css|scss)$/, "").replace(/\.[^.]+$/, "");
2621
+ const ci3 = parts.lastIndexOf("components");
2622
+ if (ci3 >= 0 && ci3 < parts.length - 2 && /^[A-Z]/.test(parts[ci3 + 1])) return wordsOf(parts[ci3 + 1]);
2623
+ if (/^[A-Z][A-Za-z0-9]+$/.test(base)) return wordsOf(base);
2624
+ if (SCREEN_DIRS.test(path)) {
2625
+ const name = base === "index" || base === "page" ? parts[parts.length - 2] : base;
2626
+ if (name && !/^\[/.test(name) && !["pages", "routes", "screens", "views", "app", "src"].includes(name)) return `${wordsOf(name)} screen`;
2627
+ }
2628
+ return null;
2629
+ }
2630
+ function changedThings(files) {
2631
+ const out = [];
2632
+ for (const f of files) {
2633
+ const t2 = thingOf(f);
2634
+ if (t2 && !out.includes(t2)) out.push(t2);
2635
+ }
2636
+ return out;
2637
+ }
2638
+ function listThings(things, max = 2) {
2639
+ if (things.length <= max) return things.length === 2 ? `${things[0]} and ${things[1]}` : things[0] ?? "";
2640
+ return `${things.slice(0, max).join(", ")} and ${things.length - max} more`;
2641
+ }
2642
+ const PAST = {
2643
+ add: "added",
2644
+ build: "built",
2645
+ change: "changed",
2646
+ drop: "dropped",
2647
+ fix: "fixed",
2648
+ make: "made",
2649
+ move: "moved",
2650
+ polish: "polished",
2651
+ remove: "removed",
2652
+ rename: "renamed",
2653
+ replace: "replaced",
2654
+ rework: "reworked",
2655
+ set: "set",
2656
+ show: "showed",
2657
+ split: "split",
2658
+ swap: "swapped",
2659
+ try: "tried",
2660
+ tweak: "tweaked",
2661
+ update: "updated",
2662
+ use: "used",
2663
+ wire: "wired"
2664
+ };
2665
+ function isClaudeAssisted(c) {
2666
+ const m = c.commit.message;
2667
+ return /co-authored-by:\s*claude/i.test(m) || /generated with \[?claude/i.test(m);
2668
+ }
2669
+ const isClaudeAccount = (c) => /claude/i.test(c.author?.login ?? "") || /^claude\b/i.test(c.commit.author?.name ?? "");
2670
+ const isBot = (name) => /\[bot\]$|^github-actions\b|-bot$/i.test(name);
2671
+ function humanAuthorOf(c) {
2672
+ if (isClaudeAccount(c)) return null;
2673
+ const login = c.author?.login?.trim();
2674
+ if (login) return isBot(login) ? null : login;
2675
+ const name = c.commit.author?.name?.trim();
2676
+ return name && !isBot(name) ? name : null;
2677
+ }
2678
+ function isClaudeCommit(c) {
2679
+ return isClaudeAccount(c) || isClaudeAssisted(c) && !humanAuthorOf(c);
2680
+ }
2681
+ const isStartCommit = (c) => /^chore\(explore\): start /.test(c.commit.message);
2682
+ function subjectWords(subject) {
2683
+ const s = subject.replace(/^[a-z]+(\([^)]*\))?!?:\s*/i, "").replace(/\s*—\s*/g, ", ").replace(/\s*\(#\d+\)\s*$/, "").trim();
2684
+ if (!s) return "made a change";
2685
+ const [first2, ...rest] = s.split(/\s+/);
2686
+ const past = PAST[first2.toLowerCase()];
2687
+ if (past) return [past, ...rest].join(" ");
2688
+ if (/^(added|built|changed|fixed|made|moved|removed|renamed|replaced|updated|used|tried|swapped|tweaked)$/i.test(first2)) return [first2.toLowerCase(), ...rest].join(" ");
2689
+ return `changed ${/^[A-Z][a-z]/.test(s) ? s.charAt(0).toLowerCase() + s.slice(1) : s}`;
2690
+ }
2691
+ const dateOf = (c) => c.commit.author?.date ?? c.commit.committer?.date ?? "";
2692
+ function explorationActivity(input) {
2693
+ const commits = input.commits.filter((c) => !isStartCommit(c));
2694
+ const events = commits.map((c) => {
2695
+ const claude = isClaudeCommit(c);
2696
+ const withClaude = !claude && isClaudeAssisted(c);
2697
+ const who = claude ? "claude" : humanAuthorOf(c) ?? input.author;
2698
+ return { who, claude, ...withClaude ? { withClaude } : {}, text: subjectWords(c.commit.message.split("\n")[0]), at: dateOf(c), sha: c.sha };
2699
+ }).sort((a2, b) => b.at.localeCompare(a2.at));
2700
+ const start = input.commits.find(isStartCommit);
2701
+ const startedAt = input.startedAt ?? (start ? dateOf(start) : null);
2702
+ if (startedAt) events.push({ who: input.author, claude: false, text: "started the exploration", at: startedAt });
2703
+ return events.slice(0, input.limit ?? events.length);
2704
+ }
2705
+ function lastActivity(input) {
2706
+ const latest = explorationActivity({ commits: input.commits, author: input.author }).find((e) => e.text !== "started the exploration");
2707
+ if (!latest) return null;
2708
+ const things = changedThings(input.files ?? []);
2709
+ return { who: latest.who, claude: latest.claude, ...latest.withClaude ? { withClaude: true } : {}, text: things.length ? `changed ${listThings(things)}` : latest.text, at: latest.at };
2710
+ }
2711
+ function shortAgo(iso, now = /* @__PURE__ */ new Date()) {
2712
+ const t2 = new Date(iso).getTime();
2713
+ if (!Number.isFinite(t2)) return "";
2714
+ const s = Math.max(0, (now.getTime() - t2) / 1e3);
2715
+ if (s < 60) return "just now";
2716
+ if (s < 3600) return `${Math.round(s / 60)} min ago`;
2717
+ if (s < 86400) return `${Math.round(s / 3600)} h ago`;
2718
+ const days = Math.floor(s / 86400);
2719
+ if (days === 1) return "Yesterday";
2720
+ if (days < 7) return `${days} days ago`;
2721
+ return new Date(t2).toLocaleDateString("en", { month: "short", day: "numeric", ...new Date(t2).getFullYear() !== now.getFullYear() ? { year: "numeric" } : {} });
2722
+ }
2723
+ function startedWhen(iso, now = /* @__PURE__ */ new Date()) {
2724
+ const t2 = new Date(iso);
2725
+ if (!Number.isFinite(t2.getTime())) return "";
2726
+ const day = (d) => Math.floor(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()) / 864e5);
2727
+ const diff = day(now) - day(t2);
2728
+ if (diff <= 0) return "today";
2729
+ if (diff === 1) return "yesterday";
2730
+ if (diff < 7) return `${diff} days ago`;
2731
+ return t2.toLocaleDateString("en", { month: "short", day: "numeric", ...t2.getFullYear() !== now.getFullYear() ? { year: "numeric" } : {} });
2732
+ }
2733
+ function actorName(e, names2) {
2734
+ if (e.claude) return "Claude";
2735
+ const n = names2?.[e.who] ?? e.who;
2736
+ return e.withClaude ? `${n} with Claude` : n;
2737
+ }
2738
+ function whereItStands(input) {
2739
+ if (input.stage === "not-started") return `"${input.name}" is started but nothing is built yet.`;
2740
+ if (input.stage === "handed") return `"${input.name}" is ${input.statusLabel ? input.statusLabel.toLowerCase() : "with the developers"}.`;
2741
+ const things = changedThings(input.files ?? []);
2742
+ const latest = input.events.find((e) => e.text !== "started the exploration");
2743
+ const who = (e) => actorName(e, input.names);
2744
+ const so = things.length ? `So far it changes ${listThings(things, 3)}.` : "Something changed already.";
2745
+ return latest ? `${so} Last: ${who(latest)} ${latest.text}, ${shortAgo(latest.at, input.now).toLowerCase()}.` : so;
2746
+ }
2250
2747
  const hex2 = (x) => Math.round(Math.max(0, Math.min(255, x))).toString(16).padStart(2, "0");
2251
2748
  function rgbOf(v) {
2252
2749
  const m = /^rgba?\(([^)]*)\)$/i.exec(v.trim());
@@ -2664,8 +3161,8 @@ function colorDelta(a2, b) {
2664
3161
  const db2 = a2[2] - b[2];
2665
3162
  const y = dr2 * 0.29889531 + dg2 * 0.58662247 + db2 * 0.11448223;
2666
3163
  const i = dr2 * 0.59597799 - dg2 * 0.2741761 - db2 * 0.32180189;
2667
- const q = dr2 * 0.21147017 - dg2 * 0.52261711 + db2 * 0.31114694;
2668
- return 0.5053 * y * y + 0.299 * i * i + 0.1957 * q * q;
3164
+ const q2 = dr2 * 0.21147017 - dg2 * 0.52261711 + db2 * 0.31114694;
3165
+ return 0.5053 * y * y + 0.299 * i * i + 0.1957 * q2 * q2;
2669
3166
  }
2670
3167
  function foundNear(img, x, y, c, radius, max) {
2671
3168
  for (let dy2 = -radius; dy2 <= radius; dy2++) for (let dx2 = -radius; dx2 <= radius; dx2++) if (colorDelta(c, rgbAt(img, x + dx2, y + dy2)) <= max) return true;
@@ -2764,10 +3261,20 @@ function buildFidelityReport(input) {
2764
3261
  }
2765
3262
  const fmtPercent = (p2) => p2 >= 10 ? `${Math.round(p2)}%` : p2 >= 1 ? `${Math.round(p2 * 10) / 10}%` : p2 > 0 ? "under 1%" : "0%";
2766
3263
  const fmtSize = (s) => `${s.width}x${s.height}`;
3264
+ function sizeGap(s, tolerance = FIDELITY_DEFAULTS.sizeTolerance) {
3265
+ const r = (n) => Math.round(n);
3266
+ const parts = [];
3267
+ if (Math.abs(s.figma.width - s.code.width) > tolerance) parts.push(`width ${r(s.figma.width)} in Figma vs ${r(s.code.width)} in code`);
3268
+ if (Math.abs(s.figma.height - s.code.height) > tolerance) parts.push(`height ${r(s.figma.height)} in Figma vs ${r(s.code.height)} in code`);
3269
+ return parts.length ? parts.join(", ") : void 0;
3270
+ }
2767
3271
  function fidelityVerdict(v) {
2768
3272
  if (v.status === "skipped") return `Not compared: ${v.reason ?? "no image of the code"}`;
2769
3273
  if (v.status === "matches") return "Matches the code";
2770
- const parts = [`${fmtPercent(v.percent ?? 0)} of pixels`];
3274
+ const pixels = `${fmtPercent(v.percent ?? 0)} of pixels`;
3275
+ const gap = v.size ? sizeGap(v.size) : void 0;
3276
+ if (gap) return `Differs from the code: ${gap}, ${pixels}`;
3277
+ const parts = [pixels];
2771
3278
  if (v.size && !sameSize(v.size)) parts.push(`size ${fmtSize(v.size.figma)} vs ${fmtSize(v.size.code)}`);
2772
3279
  return `Differs from the code: ${parts.join(", ")}`;
2773
3280
  }
@@ -2920,9 +3427,9 @@ function planPopup(spec, variant) {
2920
3427
  };
2921
3428
  }
2922
3429
  function referenceImagePath(component, key2) {
2923
- const slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
2924
- const file = key2.split(",").map((kv) => slug(kv)).filter(Boolean).join("__");
2925
- return `${slug(component)}/${file || "default"}.png`;
3430
+ const slug2 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
3431
+ const file = key2.split(",").map((kv) => slug2(kv)).filter(Boolean).join("__");
3432
+ return `${slug2(component)}/${file || "default"}.png`;
2926
3433
  }
2927
3434
  function planReferences(file) {
2928
3435
  return file.components.flatMap(
@@ -2961,8 +3468,10 @@ function storyForVariant(spec, props, stories, shot) {
2961
3468
  }
2962
3469
  return order(ok).find(fits);
2963
3470
  }
3471
+ const ICON_LIBRARY = /^(lucide|heroicons?|phosphor|tabler|feather|material-?icons?|fa|fontawesome|svg)(?:$|[-_])/i;
2964
3472
  function partWords(part, id = part?.id ?? "") {
2965
3473
  if (id === "root") return "component";
3474
+ if (ICON_LIBRARY.test(part?.id ?? id)) return "icon";
2966
3475
  const base = (part?.id ?? id).replace(/^[a-z]{1,6}--/i, "").replace(/[-_]+/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").trim().toLowerCase();
2967
3476
  return base || (part?.name ?? id).toLowerCase();
2968
3477
  }
@@ -3031,7 +3540,7 @@ function locate(parentBuilt, parentMeasured, axis, built, measured, builtParts,
3031
3540
  if (!stacked) {
3032
3541
  const tallest = [...kids].sort((a2, b) => b.m[len] - a2.m[len])[0];
3033
3542
  const change = tallest.b[len] - tallest.m[len];
3034
- if (Math.abs(change) >= 1) out.push(...ownSize(tallest, change));
3543
+ if (Math.abs(change) >= 1) out.push(...ownSize2(tallest, change));
3035
3544
  return out;
3036
3545
  }
3037
3546
  let prevB = pb[axis];
@@ -3040,13 +3549,13 @@ function locate(parentBuilt, parentMeasured, axis, built, measured, builtParts,
3040
3549
  const gap = k.b[axis] - prevB - (k.m[axis] - prevM);
3041
3550
  say(gap, i === 0 ? `${before} the ${partWords(k.p)}` : `${after} the ${partWords(kids[i - 1].p)}`);
3042
3551
  const change = k.b[len] - k.m[len];
3043
- if (Math.abs(change) >= 1) out.push(...ownSize(k, change));
3552
+ if (Math.abs(change) >= 1) out.push(...ownSize2(k, change));
3044
3553
  prevB = k.b[axis] + k.b[len];
3045
3554
  prevM = k.m[axis] + k.m[len];
3046
3555
  });
3047
3556
  say(pb[axis] + pb[len] - prevB - (pm[axis] + pm[len] - prevM), `${after} the ${partWords(kids.at(-1).p)}`);
3048
3557
  return out;
3049
- function ownSize(k, change) {
3558
+ function ownSize2(k, change) {
3050
3559
  const inner = depth < 3 ? locate(k.p.id, pairs.get(k.p.id), axis, built, measured, builtParts, pairs, depth + 1) : [];
3051
3560
  if (inner.length) return inner;
3052
3561
  return [{ amount: change, text: `the ${partWords(k.p)} is ${round$1(Math.abs(change))} px ${change < 0 ? axis === "y" ? "shorter" : "narrower" : axis === "y" ? "taller" : "wider"} in Figma` }];
@@ -3151,12 +3660,16 @@ function buildGapCase(input) {
3151
3660
  const checkedVariants = [];
3152
3661
  const specVariant = new Map((spec?.variants ?? []).map((v) => [variantKeyOf(v.props), v]));
3153
3662
  if (input.fidelity) {
3154
- for (const r of input.fidelity.variants) if (r.status !== "skipped") checkedVariants.push({ key: r.key, props: specVariant.get(r.key)?.props ?? propsOfKey(r.key), result: r, variant: specVariant.get(r.key) });
3663
+ for (const r of input.fidelity.variants) {
3664
+ if (r.status === "skipped") continue;
3665
+ const variant = matchVariant(spec, propsOfKey(r.key));
3666
+ checkedVariants.push({ key: r.key, props: variant?.props ?? propsOfKey(r.key), result: r, variant });
3667
+ }
3155
3668
  } else for (const [key2, v] of specVariant) checkedVariants.push({ key: key2, props: v.props, variant: v });
3156
3669
  const variants = [];
3157
3670
  for (const cv3 of checkedVariants) {
3158
3671
  if (cv3.result && cv3.result.status !== "differs") continue;
3159
- const shot = input.shots?.[cv3.key];
3672
+ const shot = input.shots?.[cv3.key] ?? (cv3.variant ? input.shots?.[variantKeyOf(cv3.variant.props)] : void 0);
3160
3673
  const story = spec ? storyForVariant(spec, cv3.props, stories, shot) : shot ? stories.find((s) => s.shot?.file === shot.file) : void 0;
3161
3674
  const gv = {
3162
3675
  key: cv3.key,
@@ -3226,6 +3739,8 @@ function gapSummary(component, variants, checked, fidelity) {
3226
3739
  if (!variants.length) return checked ? `${component} matches the code on ${checked} variant${checked === 1 ? "" : "s"}.` : `${component} has no variant to compare.`;
3227
3740
  const first2 = variants.flatMap((v) => v.differences)[0];
3228
3741
  const head = `${component} differs from the code on ${variants.length} of ${checked} variant${checked === 1 ? "" : "s"}`;
3742
+ const sized = variants.map((v) => v.size ? sizeGap(v.size) : void 0).find(Boolean);
3743
+ if (!first2 && sized) return `${head}: ${sized}.`;
3229
3744
  if (!first2) return variants.some((v) => (v.percent ?? 0) > UNMEASURED_PERCENT) ? `${head}: Bridge doesn't measure what differs here yet (the measured values agree, the images do not).` : `${head}: the measure agrees, compare the images.`;
3230
3745
  const more = variants.reduce((n, v) => n + v.differences.length, 0) - 1;
3231
3746
  return `${head}. ${first2.text}${more > 0 ? `, and ${more} more` : ""}.`;
@@ -3243,622 +3758,255 @@ function gapDetail(component, variants, checked, referee) {
3243
3758
  }
3244
3759
  return lines.join("\n");
3245
3760
  }
3246
- const FIGMA_FIXES_VERSION = 1;
3247
- const DEFAULT_FIGMA_FIXES_FILE = "figma-fixes.json";
3248
- class FigmaFixesError extends Error {
3249
- constructor(problems) {
3250
- super(`figma-fixes.json: ${problems.join("; ")}`);
3251
- this.problems = problems;
3252
- }
3253
- problems;
3254
- }
3255
- const isObj$3 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
3256
- const STATES$1 = ["rest", "hover", "active", "focus-visible", "disabled"];
3257
- const KINDS = ["part", "axis", "story", "status"];
3258
- const BASE_KEYS = ["id", "component", "reason", "by", "where", "kind"];
3259
- const KEYS = {
3260
- part: ["part", "set", "width", "height", "direction", "align", "overlay", "hide"],
3261
- axis: ["action", "arg", "property", "values"],
3262
- story: ["use"],
3263
- status: ["status"]
3264
- };
3265
- const PRIMARY = ["MIN", "CENTER", "MAX", "SPACE_BETWEEN"];
3266
- const COUNTER = ["MIN", "CENTER", "MAX", "STRETCH"];
3267
- const plain = (v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean";
3268
- function whereProblems(w, at2) {
3269
- if (!isObj$3(w)) return at2("where must be an object with args and/or state");
3270
- for (const k of Object.keys(w)) if (k !== "args" && k !== "state") at2(`where.${k} is not known (args, state)`);
3271
- if (w.args !== void 0 && (!isObj$3(w.args) || !Object.values(w.args).every(plain))) at2("where.args maps arg names to a text, number or true/false");
3272
- if (w.state !== void 0 && !STATES$1.includes(w.state)) at2(`where.state is one of ${STATES$1.join(", ")}`);
3761
+ const NOT_AN_AXIS = /^(as|asChild|render|component|className|class|style|id|key|ref|role|type|name|form|dir|lang|tabIndex|slot|children|htmlFor|value|defaultValue|placeholder|href|target|rel|src|alt)$/i;
3762
+ const STATE_PROPS = /* @__PURE__ */ new Set(["disabled", "checked", "indeterminate"]);
3763
+ const STATES$1 = [
3764
+ { value: "Default", of: (s) => !s.state && s.props?.disabled !== true },
3765
+ { value: "Hover", of: (s) => s.state === "hover" && s.props?.disabled !== true },
3766
+ { value: "Pressed", of: (s) => s.state === "active" && s.props?.disabled !== true },
3767
+ { value: "Focus", of: (s) => s.state === "focus-visible" && s.props?.disabled !== true },
3768
+ { value: "Disabled", of: (s) => s.props?.disabled === true }
3769
+ ];
3770
+ const setNameOf$2 = (title) => title.split("/").at(-1).trim();
3771
+ const axisName = (prop) => prop.charAt(0).toUpperCase() + prop.slice(1).replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase();
3772
+ const propsOf$1 = (s) => ({ ...s.props ?? {}, ...s.overrides ?? {} });
3773
+ function checkedOf(s) {
3774
+ const p2 = propsOf$1(s);
3775
+ return p2.indeterminate === true ? "mixed" : String(p2.checked === true);
3273
3776
  }
3274
- function validateFigmaFixes(data) {
3275
- const out = [];
3276
- if (!isObj$3(data)) return ["expected an object with version and fixes"];
3277
- if (data.version !== FIGMA_FIXES_VERSION) out.push(`version ${String(data.version)} is not supported (expected ${FIGMA_FIXES_VERSION})`);
3278
- for (const k of Object.keys(data)) if (!["$schema", "version", "fixes"].includes(k)) out.push(`${k} is not known (version, fixes)`);
3279
- if (!Array.isArray(data.fixes)) return [...out, "fixes must be a list"];
3280
- const ids = /* @__PURE__ */ new Set();
3281
- data.fixes.forEach((f, i) => {
3282
- const label = isObj$3(f) && typeof f.id === "string" ? `fix ${f.id}` : `fix ${i + 1}`;
3283
- const at2 = (why) => out.push(`${label}: ${why}`);
3284
- if (!isObj$3(f)) return at2("must be an object");
3285
- if (typeof f.id !== "string" || !/^[a-z0-9][a-z0-9-]{0,79}$/.test(f.id)) at2("id must be lower case letters, digits and dashes");
3286
- else if (ids.has(f.id)) at2("id is repeated");
3287
- else ids.add(f.id);
3288
- if (typeof f.component !== "string" || !f.component.trim()) at2('component is missing (the Figma set name, like "Toggle")');
3289
- if (typeof f.reason !== "string" || f.reason.trim().length < 8) at2("reason is missing: say why, in a sentence");
3290
- else if (/\u2014/.test(f.reason)) at2("reason uses an em dash: use a colon or a comma");
3291
- if (f.by !== void 0 && typeof f.by !== "string") at2("by must be a text");
3292
- if (f.where !== void 0) whereProblems(f.where, at2);
3293
- if (!KINDS.includes(f.kind)) return at2(`kind must be one of ${KINDS.join(", ")}`);
3294
- const kind = f.kind;
3295
- for (const k of Object.keys(f)) if (!BASE_KEYS.includes(k) && !KEYS[kind].includes(k)) at2(`${k} is not a key of a ${kind} fix`);
3296
- if (kind === "part") partProblems(f, at2);
3297
- if (kind === "axis") axisProblems(f, at2);
3298
- if (kind === "story") {
3299
- const u = f.use;
3300
- if (!isObj$3(u) || typeof u.story !== "string" || !u.story) at2("use.story is the story id to draw the variant from");
3301
- else {
3302
- for (const k of Object.keys(u)) if (!["story", "args", "state"].includes(k)) at2(`use.${k} is not known (story, args, state)`);
3303
- if (u.args !== void 0 && (!isObj$3(u.args) || !Object.values(u.args).every(plain))) at2("use.args maps arg names to a text, number or true/false");
3304
- if (u.state !== void 0 && !STATES$1.includes(u.state)) at2(`use.state is one of ${STATES$1.join(", ")}`);
3305
- }
3306
- if (!isObj$3(f.where) || !f.where.args && !f.where.state) at2("where names the variant (args and/or state) the story is for");
3307
- }
3308
- if (kind === "status") {
3309
- if (f.status === "matches") at2('status cannot be "matches": only the shot comparison says a component matches');
3310
- else if (f.status !== void 0 && f.status !== "differs" && f.status !== "cant-check") at2("status is differs or cant-check (absent: a note)");
3311
- }
3312
- });
3777
+ const LITERAL_FIELDS = ["fill", "textFill", "stroke", "fontFamily", "shadow"];
3778
+ function valuesOf(spec, partId, hasChildren, known, fills = false, own = { width: true, height: true }) {
3779
+ const sized = { ...hasChildren ? boxSized(spec, partId, own) : {}, ...spec.sized?.[partId] };
3780
+ const v = { ...spec.values[partId] ?? {} };
3781
+ const raw = spec.raw[partId] ?? {};
3782
+ for (const field of Object.keys(v)) {
3783
+ const value = v[field];
3784
+ if (typeof value !== "string" || value.startsWith("calc(") || known(value)) continue;
3785
+ const literal = raw[field];
3786
+ if (literal === void 0) delete v[field];
3787
+ else v[field] = literal;
3788
+ }
3789
+ for (const field of LITERAL_FIELDS) if (v[field] === void 0 && typeof raw[field] === "string") v[field] = raw[field];
3790
+ if (hasChildren) {
3791
+ if (typeof v.width === "number" && !fills) delete v.width;
3792
+ if (typeof v.height === "number") delete v.height;
3793
+ }
3794
+ if (sized?.width !== void 0) v.width = sized.width;
3795
+ if (sized?.height !== void 0) v.height = sized.height;
3796
+ return v;
3797
+ }
3798
+ function boxSized(spec, partId, own) {
3799
+ const box = spec.boxes?.[partId];
3800
+ const kids = spec.parts.filter((c) => c.parent === partId && !c.pin && !c.cover && !c.overlay).map((c) => spec.boxes?.[c.id]).filter((b) => !!b);
3801
+ if (!box || !kids.length) return {};
3802
+ const raw = spec.raw[partId] ?? {};
3803
+ const num = (f) => typeof raw[f] === "number" ? raw[f] : 0;
3804
+ const left = Math.min(...kids.map((k) => k.x));
3805
+ const top = Math.min(...kids.map((k) => k.y));
3806
+ const side = (f) => typeof raw[f] === "number" ? raw[f] : num("strokeWeight");
3807
+ const contentW = Math.max(...kids.map((k) => k.x + k.width)) - left + num("paddingLeft") + num("paddingRight") + side("strokeLeftWeight") + side("strokeRightWeight");
3808
+ const contentH = Math.max(...kids.map((k) => k.y + k.height)) - top + num("paddingTop") + num("paddingBottom") + side("strokeTopWeight") + side("strokeBottomWeight");
3809
+ const out = {};
3810
+ if (own.width && box.width - contentW > 1) out.width = box.width;
3811
+ if (own.height && box.height - contentH > 1) out.height = box.height;
3313
3812
  return out;
3314
3813
  }
3315
- function partProblems(f, at2) {
3316
- if (typeof f.part !== "string" || !f.part.trim()) at2("part is a part id or a .class");
3317
- const changes = ["set", "width", "height", "direction", "align", "overlay", "hide"].filter((k) => f[k] !== void 0);
3318
- if (!changes.length) at2("changes nothing: give set, width, height, direction, align, overlay or hide");
3319
- if (f.hide !== void 0 && typeof f.hide !== "boolean") at2("hide is true or false");
3320
- if (f.hide === true && changes.length > 1) at2("hide goes alone: a hidden part has nothing else to change");
3321
- if (f.set !== void 0) {
3322
- if (!isObj$3(f.set) || !Object.keys(f.set).length) at2("set maps fields to values");
3323
- else
3324
- for (const [field, value] of Object.entries(f.set)) {
3325
- if (!SPEC_FIELDS.includes(field)) {
3326
- at2(`set.${field} is not a field (${SPEC_FIELDS.join(", ")})`);
3327
- continue;
3328
- }
3329
- const color = COLOR_FIELDS.has(field);
3330
- if (value === "full") {
3331
- if (field !== "cornerRadius") at2(`set.${field}: "full" is for cornerRadius only`);
3332
- } else if (value === null) {
3333
- if (!color) at2(`set.${field} cannot be none (only colors can)`);
3334
- } else if (typeof value === "number") {
3335
- if (color) at2(`set.${field} is a color: a token path or null, not a number`);
3336
- else if (!Number.isFinite(value)) at2(`set.${field} must be a finite number`);
3337
- } else if (typeof value !== "string" || !value.trim()) at2(`set.${field} must be a token path, a number or null`);
3338
- else if (field === "textDecoration" && !/^(UNDERLINE|STRIKETHROUGH|NONE)$/.test(value)) at2(`set.textDecoration is UNDERLINE, STRIKETHROUGH or NONE`);
3339
- }
3340
- }
3341
- for (const side of ["width", "height"]) {
3342
- const v = f[side];
3343
- if (v !== void 0 && !["hug", "fill", "fixed"].includes(v) && !(typeof v === "number" && v > 0)) at2(`${side} is hug, fill, fixed or a size in px`);
3814
+ function setOf(name, stories, argTypes, source, known, hints = {}) {
3815
+ const propName = (prop) => hints.properties?.[prop] ?? axisName(prop);
3816
+ const figmaValue = (prop, value) => hints.values?.[prop]?.[value] ?? value;
3817
+ const lead = stories.find((s) => s.name === "Default" && !s.state) ?? stories[0];
3818
+ const baseline = (k, a2) => (lead ? propsOf$1(lead)[k] : void 0) ?? a2.default ?? false;
3819
+ const shown = stories.filter((s) => !/skeleton/i.test(s.name));
3820
+ const ordered = [...shown.length ? shown : stories].sort((a2, b) => Number(!!a2.gallery) - Number(!!b.gallery));
3821
+ const valueOf = (s, k, a2) => (propsOf$1(s)[k] ?? a2.default ?? false) === true;
3822
+ const flags = Object.entries(argTypes).filter(
3823
+ ([k, a2]) => a2.kind === "boolean" && !STATE_PROPS.has(k) && !NOT_AN_AXIS.test(k) && ordered.some((s) => valueOf(s, k, a2)) && ordered.some((s) => !valueOf(s, k, a2))
3824
+ );
3825
+ const plain2 = ordered.filter(
3826
+ (s) => Object.entries(argTypes).every(
3827
+ ([k, a2]) => a2.kind !== "boolean" || STATE_PROPS.has(k) || NOT_AN_AXIS.test(k) || flags.some(([f]) => f === k) || (propsOf$1(s)[k] ?? a2.default ?? false) === baseline(k, a2)
3828
+ )
3829
+ );
3830
+ if (!plain2.length) return void 0;
3831
+ const axes = [];
3832
+ for (const [prop, a2] of flags) {
3833
+ const def = String(baseline(prop, a2) === true);
3834
+ axes.push({ name: propName(prop), prop, values: [figmaValue(prop, def), figmaValue(prop, def === "true" ? "false" : "true")], default: figmaValue(prop, def) });
3344
3835
  }
3345
- if (f.direction !== void 0 && f.direction !== "HORIZONTAL" && f.direction !== "VERTICAL") at2("direction is HORIZONTAL or VERTICAL");
3346
- if (f.align !== void 0) {
3347
- const a2 = f.align;
3348
- if (!isObj$3(a2) || a2.primary === void 0 && a2.counter === void 0) at2("align gives primary and/or counter");
3349
- else {
3350
- if (a2.primary !== void 0 && !PRIMARY.includes(a2.primary)) at2(`align.primary is ${PRIMARY.join(", ")}`);
3351
- if (a2.counter !== void 0 && !COUNTER.includes(a2.counter)) at2(`align.counter is ${COUNTER.join(", ")}`);
3352
- }
3836
+ for (const [prop, a2] of Object.entries(argTypes)) {
3837
+ if (a2.kind !== "enum" || NOT_AN_AXIS.test(prop) || !a2.options || a2.options.length < 2 || a2.options.length > 12) continue;
3838
+ const seen = new Set(plain2.map((s) => String(propsOf$1(s)[prop] ?? a2.default ?? a2.options[0])));
3839
+ const values = a2.options.filter((o) => seen.has(o));
3840
+ if (values.length < 2) continue;
3841
+ const def = a2.default !== void 0 && values.includes(String(a2.default)) ? String(a2.default) : values[0];
3842
+ axes.push({ name: propName(prop), prop, values: [...new Set(values.map((v) => figmaValue(prop, v)))], default: figmaValue(prop, def) });
3353
3843
  }
3354
- if (f.overlay !== void 0 && typeof f.overlay !== "boolean") {
3355
- const o = f.overlay;
3356
- if (!isObj$3(o) || !Object.keys(o).length || !Object.entries(o).every(([k, v]) => ["top", "right", "bottom", "left"].includes(k) && typeof v === "number")) at2('overlay is true, false, or offsets in px ({ "top": 3, "left": 3 })');
3844
+ if ("checked" in argTypes || plain2.some((s) => "checked" in propsOf$1(s))) {
3845
+ const values = ["false", "true", "mixed"].filter((v) => plain2.some((s) => checkedOf(s) === v));
3846
+ if (values.length > 1) axes.push({ name: "Checked", values, default: values[0] });
3357
3847
  }
3358
- if (f.part === "root" && (f.hide === true || f.overlay !== void 0)) at2("the root cannot be hidden or overlaid");
3359
- }
3360
- function axisProblems(f, at2) {
3361
- const action = f.action;
3362
- if (action !== "map" && action !== "ignore" && action !== "prune") return at2("action is map, ignore or prune");
3363
- if (action !== "prune" && (typeof f.arg !== "string" || !f.arg)) at2(`${action} needs arg (the story arg, like "kind")`);
3364
- if (action === "map") {
3365
- if (f.property === void 0 && f.values === void 0) at2("map gives property (the Figma name) and/or values");
3366
- if (f.property !== void 0 && (typeof f.property !== "string" || !f.property.trim())) at2("property is the Figma property name");
3367
- if (f.values !== void 0 && (!isObj$3(f.values) || !Object.values(f.values).every((v) => typeof v === "string" && v.trim()))) at2("values maps code values to Figma values (texts)");
3368
- else if (isObj$3(f.values)) {
3369
- const figma = Object.values(f.values);
3370
- if (new Set(figma).size !== figma.length) at2("values gives two code values the same Figma value: use prune for the one to drop");
3848
+ const states = STATES$1.filter((st) => plain2.some(st.of)).map((st) => st.value);
3849
+ if (states.length > 1) axes.push({ name: "State", values: states, default: states[0] });
3850
+ const keyOf = (s) => {
3851
+ const props = {};
3852
+ for (const axis of axes) {
3853
+ const flag = flags.find(([k]) => k === axis.prop);
3854
+ const value = axis.name === "State" ? STATES$1.find((st) => st.of(s))?.value : axis.name === "Checked" ? checkedOf(s) : flag ? figmaValue(flag[0], String(valueOf(s, flag[0], flag[1]))) : propsOf$1(s)[axis.prop] === void 0 ? axis.default : figmaValue(axis.prop, String(propsOf$1(s)[axis.prop]));
3855
+ if (value === void 0 || !axis.values.includes(value)) return void 0;
3856
+ props[axis.name] = value;
3371
3857
  }
3858
+ return props;
3859
+ };
3860
+ const candidates = /* @__PURE__ */ new Map();
3861
+ for (const s of plain2) {
3862
+ const props = keyOf(s);
3863
+ if (!props) continue;
3864
+ const k = JSON.stringify(props);
3865
+ if (!candidates.has(k)) candidates.set(k, { props, stories: [] });
3866
+ candidates.get(k).stories.push(s);
3372
3867
  }
3373
- if (action === "ignore" && (f.property !== void 0 || f.values !== void 0)) at2("ignore takes no property or values");
3374
- if (action === "prune") {
3375
- if (!isObj$3(f.where) || !f.where.args && !f.where.state) at2("prune needs where (the args and/or state of the variants to drop)");
3376
- if (f.property !== void 0 || f.values !== void 0) at2("prune takes no property or values");
3377
- }
3378
- }
3379
- function parseFigmaFixes(input) {
3380
- let data = input;
3381
- if (typeof input === "string") {
3382
- try {
3383
- data = JSON.parse(input);
3384
- } catch (e) {
3385
- throw new FigmaFixesError([`not JSON: ${e.message}`]);
3868
+ const storyFor = /* @__PURE__ */ new Map();
3869
+ if (axes.some((a2) => a2.name === "State")) {
3870
+ const rest = (p2) => JSON.stringify(Object.entries(p2).filter(([k]) => k !== "State"));
3871
+ const groups = /* @__PURE__ */ new Map();
3872
+ for (const c of candidates.values()) groups.set(rest(c.props), [...groups.get(rest(c.props)) ?? [], c]);
3873
+ for (const [key2, group] of groups) {
3874
+ const ids = [...new Set(group.flatMap((c) => c.stories.map((s) => s.id)))];
3875
+ const score = (id) => group.filter((c) => c.stories.some((s) => s.id === id)).length;
3876
+ storyFor.set(key2, ids.reduce((best, id) => score(id) > score(best) ? id : best, ids[0]));
3877
+ }
3878
+ for (const c of candidates.values()) {
3879
+ const same = c.stories.filter((s) => s.id === storyFor.get(rest(c.props)));
3880
+ if (same.length) c.stories = same;
3386
3881
  }
3387
3882
  }
3388
- const problems = validateFigmaFixes(data);
3389
- if (problems.length) throw new FigmaFixesError(problems);
3390
- return data;
3391
- }
3392
- const lc$1 = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
3393
- const setNameOf$2 = (title) => title.split("/").at(-1).trim();
3394
- const propsOf$1 = (s) => ({ ...s.props ?? {}, ...s.overrides ?? {} });
3395
- const sameArg = (a2, b) => a2 === b || a2 !== void 0 && b !== void 0 && String(a2).toLowerCase() === String(b).toLowerCase();
3396
- function storyMatchesWhere(s, where) {
3397
- if (!where) return true;
3398
- const p2 = propsOf$1(s);
3399
- if (where.args && !Object.entries(where.args).every(([k, v]) => sameArg(p2[k], v))) return false;
3400
- if (where.state && storyState(s) !== where.state) return false;
3401
- return true;
3402
- }
3403
- function partsNamed(spec, sel) {
3404
- if (sel.startsWith(".")) {
3405
- const cls = sel.slice(1);
3406
- return spec.parts.filter((p2) => (p2.source ?? "").split(/[\s,]+/).map((x) => x.replace(/^\./, "")).includes(cls));
3883
+ const byVariant = new Map([...candidates].map(([k, c]) => [k, { props: c.props, story: c.stories[0] }]));
3884
+ if (!byVariant.size) return void 0;
3885
+ const isDefault = (p2) => axes.every((a2) => p2[a2.name] === a2.default);
3886
+ const base = [...byVariant.values()].find((v) => isDefault(v.props)) ?? [...byVariant.values()][0];
3887
+ const baseSpec = base.story.spec;
3888
+ const parts = baseSpec.parts.map((p2) => baseSpec.vectors?.[p2.id] ? { ...p2, kind: "vector" } : { ...p2 });
3889
+ for (const p2 of parts) {
3890
+ if (!p2.overlay || p2.pin || p2.cover) continue;
3891
+ const pin = pinFromBoxes(baseSpec, p2);
3892
+ if (pin) p2.pin = pin;
3893
+ else p2.cover = true;
3407
3894
  }
3408
- return spec.parts.filter((p2) => p2.id === sel);
3409
- }
3410
- const clone = (v) => JSON.parse(JSON.stringify(v));
3411
- function patchSpec(spec, fix) {
3412
- const targets = partsNamed(spec, fix.part);
3413
- if (!targets.length) return false;
3414
- for (const part of targets) {
3415
- const id = part.id;
3416
- const box = spec.boxes[id];
3417
- const parent = part.parent ? spec.parts.find((p2) => p2.id === part.parent) : void 0;
3418
- if (fix.hide) {
3419
- const gone = /* @__PURE__ */ new Set([id]);
3420
- for (const p2 of spec.parts) if (p2.parent && gone.has(p2.parent)) gone.add(p2.id);
3421
- spec.parts = spec.parts.filter((p2) => !gone.has(p2.id));
3422
- for (const map of [spec.values, spec.raw, spec.tokens, spec.boxes, spec.sized ?? {}, spec.spacers ?? {}, spec.vectors ?? {}]) for (const g of gone) delete map[g];
3423
- continue;
3424
- }
3425
- const values = spec.values[id] ??= {};
3426
- for (const [field, value] of Object.entries(fix.set ?? {})) {
3427
- values[field] = value === "full" ? box ? Math.min(box.width, box.height) / 2 : 9999 : value;
3428
- }
3429
- for (const side of ["width", "height"]) {
3430
- const how = fix[side];
3431
- if (how === void 0) continue;
3432
- const flowsAlong = parent?.layout?.direction === (side === "width" ? "HORIZONTAL" : "VERTICAL");
3433
- if (how === "hug") {
3434
- delete values[side];
3435
- if (spec.sized?.[id]) delete spec.sized[id][side];
3436
- if (part.stretch) delete part.stretch[side];
3437
- if (flowsAlong) delete part.grow;
3438
- if (!part.parent && side === "width") delete part.fill;
3439
- } else if (how === "fill") {
3440
- if (spec.sized?.[id]) delete spec.sized[id][side];
3441
- if (!part.parent) {
3442
- if (side === "width") part.fill = true;
3443
- } else if (flowsAlong) part.grow = true;
3444
- else part.stretch = { ...part.stretch ?? {}, [side]: true };
3445
- } else {
3446
- const px = how === "fixed" ? typeof values[side] === "number" ? values[side] : box?.[side] : how;
3447
- if (px === void 0) continue;
3448
- values[side] = px;
3449
- spec.sized = { ...spec.sized ?? {}, [id]: { ...spec.sized?.[id] ?? {}, [side]: px } };
3450
- if (part.stretch) delete part.stretch[side];
3451
- if (flowsAlong) delete part.grow;
3452
- if (!part.parent && side === "width") delete part.fill;
3453
- }
3454
- }
3455
- if (fix.direction || fix.align) {
3456
- part.layout = { direction: "HORIZONTAL", primary: "MIN", counter: "MIN", ...part.layout ?? {} };
3457
- if (fix.direction) part.layout.direction = fix.direction;
3458
- if (fix.align?.primary) part.layout.primary = fix.align.primary;
3459
- if (fix.align?.counter) part.layout.counter = fix.align.counter;
3460
- }
3461
- if (fix.overlay !== void 0) {
3462
- delete part.cover;
3463
- delete part.pin;
3464
- if (fix.overlay === true) part.cover = true;
3465
- else if (fix.overlay !== false) part.pin = { ...fix.overlay };
3466
- }
3467
- }
3468
- return true;
3469
- }
3470
- function applyFixes(measured, fixes) {
3471
- const list = Array.isArray(fixes) ? fixes : fixes.fixes;
3472
- const file = clone(measured);
3473
- const before = new Set((file.figmaFixes?.applied ?? []).filter((a2) => a2.result === "applied").map((a2) => a2.id));
3474
- const applied = [];
3475
- const entryOf = (component) => (file.components ?? []).find((c) => lc$1(setNameOf$2(c.title)) === lc$1(component));
3476
- const hints = (component) => {
3477
- const c = entryOf(component);
3478
- return c ? c.figma ??= {} : void 0;
3479
- };
3480
- const mark = (s, id) => {
3481
- s.fixes = [.../* @__PURE__ */ new Set([...s.fixes ?? [], id])];
3895
+ const paints = (id) => {
3896
+ const v = baseSpec.values[id] ?? {};
3897
+ return [v.fill, v.stroke].some((x) => x !== void 0 && x !== null);
3482
3898
  };
3483
- for (const fix of list) {
3484
- const record = (result, stories, detail) => applied.push({ id: fix.id, kind: fix.kind, component: fix.component, result, stories, detail });
3485
- if (before.has(fix.id)) {
3486
- record("already", 0, "already applied to this file");
3487
- continue;
3488
- }
3489
- const mine = () => file.stories.filter((s) => lc$1(setNameOf$2(s.title)) === lc$1(fix.component));
3490
- if (!mine().length) {
3491
- record("not-found", 0, `no measured story of ${fix.component}`);
3492
- continue;
3493
- }
3494
- const scoped = () => mine().filter((s) => storyMatchesWhere(s, fix.where));
3495
- if (fix.kind === "part") {
3496
- let n = 0;
3497
- for (const s of scoped()) {
3498
- let hit = false;
3499
- for (const spec of [s.spec, ...s.instances ?? []]) if (spec && patchSpec(spec, fix)) hit = true;
3500
- if (hit) {
3501
- n++;
3502
- mark(s, fix.id);
3503
- }
3504
- }
3505
- if (n) record("applied", n, `${describePartFix(fix)} on ${n} stor${n === 1 ? "y" : "ies"}`);
3506
- else record("not-found", 0, `no part ${fix.part} in the ${scoped().length ? "stories it names" : "variants where names"}`);
3507
- } else if (fix.kind === "axis") {
3508
- const h2 = hints(fix.component);
3509
- if (fix.action === "prune") {
3510
- const drop = new Set(scoped());
3511
- if (!drop.size) {
3512
- record("not-found", 0, "no measured variant matches where");
3513
- continue;
3514
- }
3515
- file.stories = file.stories.filter((s) => !drop.has(s));
3516
- if (h2) h2.pruned = [...h2.pruned ?? [], fix.where];
3517
- record("applied", drop.size, `dropped ${drop.size} variant stor${drop.size === 1 ? "y" : "ies"}`);
3518
- } else {
3519
- const arg = fix.arg;
3520
- const entry = entryOf(fix.component);
3521
- const known = !!entry?.argTypes[arg] || mine().some((s) => arg in propsOf$1(s));
3522
- if (!known) {
3523
- record("not-found", 0, `${fix.component} has no arg ${arg}`);
3524
- continue;
3525
- }
3526
- if (fix.action === "map") {
3527
- if (h2) {
3528
- if (fix.property) h2.properties = { ...h2.properties ?? {}, [arg]: fix.property };
3529
- if (fix.values) h2.values = { ...h2.values ?? {}, [arg]: { ...h2.values?.[arg] ?? {}, ...fix.values } };
3530
- }
3531
- record("applied", 0, `${arg} drawn as ${fix.property ?? arg}${fix.values ? ` (${Object.entries(fix.values).map(([c, f]) => `${c} as ${f}`).join(", ")})` : ""}`);
3532
- } else {
3533
- const at2 = entry?.argTypes[arg];
3534
- const lead = mine().find((s) => !s.overrides || !(arg in s.overrides));
3535
- const baseline = at2?.default ?? (lead ? propsOf$1(lead)[arg] : void 0) ?? (at2?.kind === "boolean" ? false : void 0);
3536
- const drop = new Set(scoped().filter((s) => baseline !== void 0 && propsOf$1(s)[arg] !== void 0 && !sameArg(propsOf$1(s)[arg], baseline)));
3537
- file.stories = file.stories.filter((s) => !drop.has(s));
3538
- if (entry) {
3539
- delete entry.argTypes[arg];
3540
- entry.figma ??= {};
3541
- entry.figma.ignored = [.../* @__PURE__ */ new Set([...entry.figma.ignored ?? [], arg])];
3542
- }
3543
- record("applied", drop.size, `${arg} is not a Figma property${drop.size ? `: ${drop.size} stor${drop.size === 1 ? "y" : "ies"} at another value left out` : ""}`);
3544
- }
3545
- }
3546
- } else if (fix.kind === "story") {
3547
- const src = file.stories.find(
3548
- (s) => s.id === fix.use.story && s.spec && !s.error && (!fix.use.args || Object.entries(fix.use.args).every(([k, v]) => sameArg(propsOf$1(s)[k], v))) && (!fix.use.state || storyState(s) === fix.use.state)
3549
- );
3550
- if (!src) {
3551
- record("not-found", 0, `no measured render of story ${fix.use.story}${fix.use.args ? ` with ${JSON.stringify(fix.use.args)}` : ""}`);
3899
+ for (const p2 of [...parts].reverse()) {
3900
+ if (!p2.parent || p2.kind !== "frame" || p2.pin || p2.cover || p2.overlay || paints(p2.id)) continue;
3901
+ const kids = parts.filter((c) => c.parent === p2.id);
3902
+ if (kids.length && kids.every((c) => c.overlay || c.pin || c.cover)) p2.cover = true;
3903
+ }
3904
+ const aligners = new Set(
3905
+ parts.filter((p2) => {
3906
+ const flow = parts.filter((c) => c.parent === p2.id && !c.pin && !c.cover && !c.overlay);
3907
+ return flow.length === 1 && !!p2.layout?.align?.[flow[0].id];
3908
+ }).map((p2) => p2.id)
3909
+ );
3910
+ for (const p2 of parts) if (p2.layout) p2.layout = alignedLayout(p2, parts);
3911
+ const parents = new Set(parts.filter((p2) => !p2.pin && !p2.cover).map((p2) => p2.parent).filter(Boolean));
3912
+ const variants = [];
3913
+ const axesMemo = /* @__PURE__ */ new Map();
3914
+ const shotOf = /* @__PURE__ */ new Map();
3915
+ const spacersOf = /* @__PURE__ */ new Map();
3916
+ const wrapsOf = /* @__PURE__ */ new Map();
3917
+ for (const { props, story: story2 } of byVariant.values()) {
3918
+ const m = story2.spec;
3919
+ const pairs = story2 === base.story ? new Map(parts.map((p2) => [p2.id, p2.id])) : pairParts(baseSpec.parts, m);
3920
+ const values = {};
3921
+ const vectors = {};
3922
+ const pins = {};
3923
+ const hidden = [];
3924
+ const spacers = {};
3925
+ const wraps = {};
3926
+ const layouts = {};
3927
+ for (const p2 of parts) {
3928
+ const mid = pairs.get(p2.id);
3929
+ if (mid === void 0) {
3930
+ hidden.push(p2.id);
3552
3931
  continue;
3553
3932
  }
3554
- const target = fix.where ?? {};
3555
- const others = new Set(scoped().filter((s) => s !== src));
3556
- const copy = clone(src);
3557
- const overrides = { ...copy.overrides ?? {}, ...target.args ?? {} };
3558
- if (target.state === "disabled") overrides.disabled = true;
3559
- copy.overrides = overrides;
3560
- if (target.state && target.state !== "rest" && target.state !== "disabled") copy.state = target.state;
3561
- else if (target.state) delete copy.state;
3562
- if (lc$1(setNameOf$2(copy.title)) !== lc$1(fix.component)) copy.title = copy.title.split("/").slice(0, -1).concat(fix.component).join("/");
3563
- mark(copy, fix.id);
3564
- let placed = false;
3565
- file.stories = file.stories.flatMap((s) => others.has(s) ? placed ? [] : (placed = true, [copy]) : [s]);
3566
- if (!placed) file.stories.push(copy);
3567
- record("applied", others.size + 1, `variant ${whereWords(target)} drawn from ${src.id}${others.size ? `, in place of ${others.size} other stor${others.size === 1 ? "y" : "ies"}` : ""}`);
3568
- } else {
3569
- const h2 = hints(fix.component);
3570
- if (!h2) {
3571
- record("not-found", 0, `no component entry for ${fix.component} in the measure`);
3572
- continue;
3933
+ values[p2.id] = valuesOf(m, mid, parents.has(p2.id) && !aligners.has(p2.id), known, !p2.parent && !!p2.fill, ownAxes(p2, parts, baseSpec, axesMemo));
3934
+ if (m.spacers?.[mid]) spacers[p2.id] = m.spacers[mid];
3935
+ if (p2.kind === "icon" || p2.kind === "vector") delete values[p2.id].fill;
3936
+ const mp = m.parts.find((x) => x.id === mid);
3937
+ const pin = mp?.pin ?? (mp?.overlay ? pinFromBoxes(m, mp) : void 0);
3938
+ if (p2.pin && pin && JSON.stringify(pin) !== JSON.stringify(p2.pin)) pins[p2.id] = pin;
3939
+ if (mp?.layout && p2.layout) {
3940
+ const layout = alignedLayout({ ...mp, id: p2.id }, m.parts.map((x) => ({ ...x, parent: x.parent === mid ? p2.id : x.parent })));
3941
+ if (layout && (layout.primary !== p2.layout.primary || layout.counter !== p2.layout.counter || layout.direction !== p2.layout.direction)) layouts[p2.id] = layout;
3573
3942
  }
3574
- if (fix.status) {
3575
- h2.status = [...h2.status ?? [], { status: fix.status, reason: fix.reason, fix: fix.id, ...fix.where ? { where: fix.where } : {} }];
3576
- if (!fix.where) {
3577
- const st = (file.statuses ?? []).find((x) => lc$1(x.name) === lc$1(fix.component));
3578
- if (st && st.status !== fix.status) {
3579
- st.status = fix.status;
3580
- delete st.reason;
3581
- st.summary = fix.reason;
3582
- }
3583
- }
3584
- } else h2.notes = [...h2.notes ?? [], { text: fix.reason, fix: fix.id, ...fix.where ? { where: fix.where } : {} }];
3585
- record("applied", 0, fix.status ? `${fix.status === "differs" ? "Differs" : "Can't check"}: ${fix.reason}` : `note: ${fix.reason}`);
3943
+ if (mp?.wrap) wraps[p2.id] = mp.wrap.padding;
3944
+ const vec = p2.kind === "vector" ? m.vectors?.[mid] : void 0;
3945
+ if (vec) vectors[p2.id] = { svg: vec.svg, strokes: vec.strokes.every((v) => typeof v !== "string" || known(v)) ? [...vec.strokes] : [] };
3586
3946
  }
3587
- const h = hints(fix.component);
3588
- if (h && applied.at(-1).result === "applied") h.fixes = [.../* @__PURE__ */ new Set([...h.fixes ?? [], fix.id])];
3947
+ const variant = { props, values, ...hidden.length ? { hidden } : {}, ...Object.keys(vectors).length ? { vectors } : {}, ...Object.keys(pins).length ? { pins } : {}, ...Object.keys(layouts).length ? { layout: layouts } : {} };
3948
+ variants.push(variant);
3949
+ spacersOf.set(variant, spacers);
3950
+ wrapsOf.set(variant, wraps);
3951
+ if (story2.shot) shotOf.set(variant, story2.shot);
3589
3952
  }
3590
- file.figmaFixes = { applied: [...(file.figmaFixes?.applied ?? []).filter((a2) => a2.result === "applied"), ...applied.filter((a2) => a2.result !== "already")] };
3591
- return { file, applied };
3953
+ textRootInFrame(parts, variants);
3954
+ spacerWrappers(parts, variants, spacersOf, wrapsOf);
3955
+ const properties = textProperties(parts, base.story, argTypes, new Set(axes.map((a2) => a2.name)));
3956
+ const kept = drawnAxes(axes, variants);
3957
+ const story = `${source.replace(/\/+$/, "")}/?path=/story/${base.story.id}`;
3958
+ const spec = { name, hash: "", source: [story], axes: kept.axes, properties, parts, variants: withoutIdleStates(kept.variants), unmapped: [] };
3959
+ const shots = {};
3960
+ kept.variants.forEach((v) => {
3961
+ const from = variants.find((x) => JSON.stringify(x.values) === JSON.stringify(v.values) && Object.entries(v.props).every(([k, val]) => x.props[k] === val));
3962
+ const shot = from && shotOf.get(from);
3963
+ if (shot) shots[variantKeyOf(v.props)] = shot;
3964
+ });
3965
+ return { spec: { ...spec, hash: hashSpec(spec) }, shots };
3592
3966
  }
3593
- function whereWords(w) {
3594
- const args = Object.entries(w.args ?? {}).map(([k, v]) => `${k}=${String(v)}`);
3595
- if (w.state) args.push(`state=${w.state}`);
3596
- return args.join(", ") || "every variant";
3967
+ const TEXT_FIELDS = /* @__PURE__ */ new Set(["textFill", "fontFamily", "fontSize", "fontWeight", "lineHeight", "letterSpacing", "textDecoration"]);
3968
+ function textRootInFrame(parts, variants) {
3969
+ const root = parts.find((p2) => !p2.parent);
3970
+ if (!root || root.kind !== "text") return;
3971
+ const id = `${root.id}-text`;
3972
+ const text = { ...root, id, name: `${root.name} text`, parent: root.id };
3973
+ parts.splice(parts.indexOf(root), 1, { id: root.id, name: root.name, kind: "frame", ...root.source ? { source: root.source } : {}, layout: { direction: "HORIZONTAL", primary: "MIN", counter: "MIN" } }, text);
3974
+ for (const v of variants) {
3975
+ const all = v.values[root.id] ?? {};
3976
+ v.values[root.id] = Object.fromEntries(Object.entries(all).filter(([f, value]) => !TEXT_FIELDS.has(f) && !((f === "width" || f === "height") && typeof value === "number")));
3977
+ v.values[id] = Object.fromEntries(Object.entries(all).filter(([f]) => TEXT_FIELDS.has(f)));
3978
+ if (v.hidden?.includes(root.id)) v.hidden.push(id);
3979
+ }
3597
3980
  }
3598
- function describePartFix(f) {
3599
- if (f.hide) return `${f.part} hidden`;
3600
- const bits = [
3601
- ...Object.entries(f.set ?? {}).map(([k, v]) => `${k} ${v === null ? "none" : String(v)}`),
3602
- ...f.width !== void 0 ? [`width ${String(f.width)}`] : [],
3603
- ...f.height !== void 0 ? [`height ${String(f.height)}`] : [],
3604
- ...f.direction ? [`direction ${f.direction}`] : [],
3605
- ...f.align ? [`align ${[f.align.primary, f.align.counter].filter(Boolean).join(" ")}`] : [],
3606
- ...f.overlay !== void 0 ? [`overlay ${typeof f.overlay === "boolean" ? String(f.overlay) : JSON.stringify(f.overlay)}`] : []
3607
- ];
3608
- return `${f.part}: ${bits.join(", ")}`;
3981
+ function ownAxes(part, parts, spec, memo = /* @__PURE__ */ new Map()) {
3982
+ const parent = parts.find((x) => x.id === part.parent);
3983
+ const dir = parent?.layout?.direction;
3984
+ const stretch = parent?.layout?.counter === "STRETCH";
3985
+ const sizedBy = (axis) => !parent || !spec || definiteAxis(parent, axis, parts, spec, memo);
3986
+ const acrossW = dir === "VERTICAL" && stretch && sizedBy("width");
3987
+ const alongW = dir === "HORIZONTAL" && !!part.grow && sizedBy("width");
3988
+ const acrossH = dir === "HORIZONTAL" && stretch && sizedBy("height");
3989
+ const alongH = dir === "VERTICAL" && !!part.grow && sizedBy("height");
3990
+ return {
3991
+ width: !(!part.parent && part.fill) && !acrossW && !alongW && !part.stretch?.width,
3992
+ height: !acrossH && !alongH && !part.stretch?.height
3993
+ };
3609
3994
  }
3610
- function appliedFixLines(applied) {
3611
- return applied.map((a2) => `${a2.result === "applied" ? "applied" : a2.result === "already" ? "already applied" : "not found"} ${a2.id} (${a2.component}, ${a2.kind}): ${a2.detail}`);
3612
- }
3613
- const NOT_AN_AXIS = /^(as|asChild|render|component|className|class|style|id|key|ref|role|type|name|form|dir|lang|tabIndex|slot|children|htmlFor|value|defaultValue|placeholder|href|target|rel|src|alt)$/i;
3614
- const STATE_PROPS = /* @__PURE__ */ new Set(["disabled", "checked", "indeterminate"]);
3615
- const STATES = [
3616
- { value: "Default", of: (s) => !s.state && s.props?.disabled !== true },
3617
- { value: "Hover", of: (s) => s.state === "hover" && s.props?.disabled !== true },
3618
- { value: "Pressed", of: (s) => s.state === "active" && s.props?.disabled !== true },
3619
- { value: "Focus", of: (s) => s.state === "focus-visible" && s.props?.disabled !== true },
3620
- { value: "Disabled", of: (s) => s.props?.disabled === true }
3621
- ];
3622
- const setNameOf$1 = (title) => title.split("/").at(-1).trim();
3623
- const axisName = (prop) => prop.charAt(0).toUpperCase() + prop.slice(1).replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase();
3624
- const propsOf = (s) => ({ ...s.props ?? {}, ...s.overrides ?? {} });
3625
- function checkedOf(s) {
3626
- const p2 = propsOf(s);
3627
- return p2.indeterminate === true ? "mixed" : String(p2.checked === true);
3628
- }
3629
- const LITERAL_FIELDS = ["fill", "textFill", "stroke", "fontFamily", "shadow"];
3630
- function valuesOf(spec, partId, hasChildren, known, fills = false, own = { width: true, height: true }) {
3631
- const sized = { ...hasChildren ? boxSized(spec, partId, own) : {}, ...spec.sized?.[partId] };
3632
- const v = { ...spec.values[partId] ?? {} };
3633
- const raw = spec.raw[partId] ?? {};
3634
- for (const field of Object.keys(v)) {
3635
- const value = v[field];
3636
- if (typeof value !== "string" || value.startsWith("calc(") || known(value)) continue;
3637
- const literal = raw[field];
3638
- if (literal === void 0) delete v[field];
3639
- else v[field] = literal;
3640
- }
3641
- for (const field of LITERAL_FIELDS) if (v[field] === void 0 && typeof raw[field] === "string") v[field] = raw[field];
3642
- if (hasChildren) {
3643
- if (typeof v.width === "number" && !fills) delete v.width;
3644
- if (typeof v.height === "number") delete v.height;
3645
- }
3646
- if (sized?.width !== void 0) v.width = sized.width;
3647
- if (sized?.height !== void 0) v.height = sized.height;
3648
- return v;
3649
- }
3650
- function boxSized(spec, partId, own) {
3651
- const box = spec.boxes?.[partId];
3652
- const kids = spec.parts.filter((c) => c.parent === partId && !c.pin && !c.cover && !c.overlay).map((c) => spec.boxes?.[c.id]).filter((b) => !!b);
3653
- if (!box || !kids.length) return {};
3654
- const raw = spec.raw[partId] ?? {};
3655
- const num = (f) => typeof raw[f] === "number" ? raw[f] : 0;
3656
- const left = Math.min(...kids.map((k) => k.x));
3657
- const top = Math.min(...kids.map((k) => k.y));
3658
- const side = (f) => typeof raw[f] === "number" ? raw[f] : num("strokeWeight");
3659
- const contentW = Math.max(...kids.map((k) => k.x + k.width)) - left + num("paddingLeft") + num("paddingRight") + side("strokeLeftWeight") + side("strokeRightWeight");
3660
- const contentH = Math.max(...kids.map((k) => k.y + k.height)) - top + num("paddingTop") + num("paddingBottom") + side("strokeTopWeight") + side("strokeBottomWeight");
3661
- const out = {};
3662
- if (own.width && box.width - contentW > 1) out.width = box.width;
3663
- if (own.height && box.height - contentH > 1) out.height = box.height;
3664
- return out;
3665
- }
3666
- function setOf(name, stories, argTypes, source, known, hints = {}) {
3667
- const propName = (prop) => hints.properties?.[prop] ?? axisName(prop);
3668
- const figmaValue = (prop, value) => hints.values?.[prop]?.[value] ?? value;
3669
- const lead = stories.find((s) => s.name === "Default" && !s.state) ?? stories[0];
3670
- const baseline = (k, a2) => (lead ? propsOf(lead)[k] : void 0) ?? a2.default ?? false;
3671
- const shown = stories.filter((s) => !/skeleton/i.test(s.name));
3672
- const ordered = [...shown.length ? shown : stories].sort((a2, b) => Number(!!a2.gallery) - Number(!!b.gallery));
3673
- const valueOf = (s, k, a2) => (propsOf(s)[k] ?? a2.default ?? false) === true;
3674
- const flags = Object.entries(argTypes).filter(
3675
- ([k, a2]) => a2.kind === "boolean" && !STATE_PROPS.has(k) && !NOT_AN_AXIS.test(k) && ordered.some((s) => valueOf(s, k, a2)) && ordered.some((s) => !valueOf(s, k, a2))
3676
- );
3677
- const plain2 = ordered.filter(
3678
- (s) => Object.entries(argTypes).every(
3679
- ([k, a2]) => a2.kind !== "boolean" || STATE_PROPS.has(k) || NOT_AN_AXIS.test(k) || flags.some(([f]) => f === k) || (propsOf(s)[k] ?? a2.default ?? false) === baseline(k, a2)
3680
- )
3681
- );
3682
- if (!plain2.length) return void 0;
3683
- const axes = [];
3684
- for (const [prop, a2] of flags) {
3685
- const def = String(baseline(prop, a2) === true);
3686
- axes.push({ name: propName(prop), prop, values: [figmaValue(prop, def), figmaValue(prop, def === "true" ? "false" : "true")], default: figmaValue(prop, def) });
3687
- }
3688
- for (const [prop, a2] of Object.entries(argTypes)) {
3689
- if (a2.kind !== "enum" || NOT_AN_AXIS.test(prop) || !a2.options || a2.options.length < 2 || a2.options.length > 12) continue;
3690
- const seen = new Set(plain2.map((s) => String(propsOf(s)[prop] ?? a2.default ?? a2.options[0])));
3691
- const values = a2.options.filter((o) => seen.has(o));
3692
- if (values.length < 2) continue;
3693
- const def = a2.default !== void 0 && values.includes(String(a2.default)) ? String(a2.default) : values[0];
3694
- axes.push({ name: propName(prop), prop, values: [...new Set(values.map((v) => figmaValue(prop, v)))], default: figmaValue(prop, def) });
3695
- }
3696
- if ("checked" in argTypes || plain2.some((s) => "checked" in propsOf(s))) {
3697
- const values = ["false", "true", "mixed"].filter((v) => plain2.some((s) => checkedOf(s) === v));
3698
- if (values.length > 1) axes.push({ name: "Checked", values, default: values[0] });
3699
- }
3700
- const states = STATES.filter((st) => plain2.some(st.of)).map((st) => st.value);
3701
- if (states.length > 1) axes.push({ name: "State", values: states, default: states[0] });
3702
- const keyOf = (s) => {
3703
- const props = {};
3704
- for (const axis of axes) {
3705
- const flag = flags.find(([k]) => k === axis.prop);
3706
- const value = axis.name === "State" ? STATES.find((st) => st.of(s))?.value : axis.name === "Checked" ? checkedOf(s) : flag ? figmaValue(flag[0], String(valueOf(s, flag[0], flag[1]))) : propsOf(s)[axis.prop] === void 0 ? axis.default : figmaValue(axis.prop, String(propsOf(s)[axis.prop]));
3707
- if (value === void 0 || !axis.values.includes(value)) return void 0;
3708
- props[axis.name] = value;
3709
- }
3710
- return props;
3711
- };
3712
- const candidates = /* @__PURE__ */ new Map();
3713
- for (const s of plain2) {
3714
- const props = keyOf(s);
3715
- if (!props) continue;
3716
- const k = JSON.stringify(props);
3717
- if (!candidates.has(k)) candidates.set(k, { props, stories: [] });
3718
- candidates.get(k).stories.push(s);
3719
- }
3720
- const storyFor = /* @__PURE__ */ new Map();
3721
- if (axes.some((a2) => a2.name === "State")) {
3722
- const rest = (p2) => JSON.stringify(Object.entries(p2).filter(([k]) => k !== "State"));
3723
- const groups = /* @__PURE__ */ new Map();
3724
- for (const c of candidates.values()) groups.set(rest(c.props), [...groups.get(rest(c.props)) ?? [], c]);
3725
- for (const [key2, group] of groups) {
3726
- const ids = [...new Set(group.flatMap((c) => c.stories.map((s) => s.id)))];
3727
- const score = (id) => group.filter((c) => c.stories.some((s) => s.id === id)).length;
3728
- storyFor.set(key2, ids.reduce((best, id) => score(id) > score(best) ? id : best, ids[0]));
3729
- }
3730
- for (const c of candidates.values()) {
3731
- const same = c.stories.filter((s) => s.id === storyFor.get(rest(c.props)));
3732
- if (same.length) c.stories = same;
3733
- }
3734
- }
3735
- const byVariant = new Map([...candidates].map(([k, c]) => [k, { props: c.props, story: c.stories[0] }]));
3736
- if (!byVariant.size) return void 0;
3737
- const isDefault = (p2) => axes.every((a2) => p2[a2.name] === a2.default);
3738
- const base = [...byVariant.values()].find((v) => isDefault(v.props)) ?? [...byVariant.values()][0];
3739
- const baseSpec = base.story.spec;
3740
- const parts = baseSpec.parts.map((p2) => baseSpec.vectors?.[p2.id] ? { ...p2, kind: "vector" } : { ...p2 });
3741
- for (const p2 of parts) {
3742
- if (!p2.overlay || p2.pin || p2.cover) continue;
3743
- const pin = pinFromBoxes(baseSpec, p2);
3744
- if (pin) p2.pin = pin;
3745
- else p2.cover = true;
3746
- }
3747
- const paints = (id) => {
3748
- const v = baseSpec.values[id] ?? {};
3749
- return [v.fill, v.stroke].some((x) => x !== void 0 && x !== null);
3750
- };
3751
- for (const p2 of [...parts].reverse()) {
3752
- if (!p2.parent || p2.kind !== "frame" || p2.pin || p2.cover || p2.overlay || paints(p2.id)) continue;
3753
- const kids = parts.filter((c) => c.parent === p2.id);
3754
- if (kids.length && kids.every((c) => c.overlay || c.pin || c.cover)) p2.cover = true;
3755
- }
3756
- const aligners = new Set(
3757
- parts.filter((p2) => {
3758
- const flow = parts.filter((c) => c.parent === p2.id && !c.pin && !c.cover && !c.overlay);
3759
- return flow.length === 1 && !!p2.layout?.align?.[flow[0].id];
3760
- }).map((p2) => p2.id)
3761
- );
3762
- for (const p2 of parts) if (p2.layout) p2.layout = alignedLayout(p2, parts);
3763
- const parents = new Set(parts.filter((p2) => !p2.pin && !p2.cover).map((p2) => p2.parent).filter(Boolean));
3764
- const variants = [];
3765
- const axesMemo = /* @__PURE__ */ new Map();
3766
- const shotOf = /* @__PURE__ */ new Map();
3767
- const spacersOf = /* @__PURE__ */ new Map();
3768
- const wrapsOf = /* @__PURE__ */ new Map();
3769
- for (const { props, story: story2 } of byVariant.values()) {
3770
- const m = story2.spec;
3771
- const pairs = story2 === base.story ? new Map(parts.map((p2) => [p2.id, p2.id])) : pairParts(baseSpec.parts, m);
3772
- const values = {};
3773
- const vectors = {};
3774
- const pins = {};
3775
- const hidden = [];
3776
- const spacers = {};
3777
- const wraps = {};
3778
- const layouts = {};
3779
- for (const p2 of parts) {
3780
- const mid = pairs.get(p2.id);
3781
- if (mid === void 0) {
3782
- hidden.push(p2.id);
3783
- continue;
3784
- }
3785
- values[p2.id] = valuesOf(m, mid, parents.has(p2.id) && !aligners.has(p2.id), known, !p2.parent && !!p2.fill, ownAxes(p2, parts, baseSpec, axesMemo));
3786
- if (m.spacers?.[mid]) spacers[p2.id] = m.spacers[mid];
3787
- if (p2.kind === "icon" || p2.kind === "vector") delete values[p2.id].fill;
3788
- const mp = m.parts.find((x) => x.id === mid);
3789
- const pin = mp?.pin ?? (mp?.overlay ? pinFromBoxes(m, mp) : void 0);
3790
- if (p2.pin && pin && JSON.stringify(pin) !== JSON.stringify(p2.pin)) pins[p2.id] = pin;
3791
- if (mp?.layout && p2.layout) {
3792
- const layout = alignedLayout({ ...mp, id: p2.id }, m.parts.map((x) => ({ ...x, parent: x.parent === mid ? p2.id : x.parent })));
3793
- if (layout && (layout.primary !== p2.layout.primary || layout.counter !== p2.layout.counter || layout.direction !== p2.layout.direction)) layouts[p2.id] = layout;
3794
- }
3795
- if (mp?.wrap) wraps[p2.id] = mp.wrap.padding;
3796
- const vec = p2.kind === "vector" ? m.vectors?.[mid] : void 0;
3797
- if (vec) vectors[p2.id] = { svg: vec.svg, strokes: vec.strokes.every((v) => typeof v !== "string" || known(v)) ? [...vec.strokes] : [] };
3798
- }
3799
- const variant = { props, values, ...hidden.length ? { hidden } : {}, ...Object.keys(vectors).length ? { vectors } : {}, ...Object.keys(pins).length ? { pins } : {}, ...Object.keys(layouts).length ? { layout: layouts } : {} };
3800
- variants.push(variant);
3801
- spacersOf.set(variant, spacers);
3802
- wrapsOf.set(variant, wraps);
3803
- if (story2.shot) shotOf.set(variant, story2.shot);
3804
- }
3805
- textRootInFrame(parts, variants);
3806
- spacerWrappers(parts, variants, spacersOf, wrapsOf);
3807
- const properties = textProperties(parts, base.story, argTypes, new Set(axes.map((a2) => a2.name)));
3808
- const kept = drawnAxes(axes, variants);
3809
- const story = `${source.replace(/\/+$/, "")}/?path=/story/${base.story.id}`;
3810
- const spec = { name, hash: "", source: [story], axes: kept.axes, properties, parts, variants: withoutIdleStates(kept.variants), unmapped: [] };
3811
- const shots = {};
3812
- kept.variants.forEach((v) => {
3813
- const from = variants.find((x) => JSON.stringify(x.values) === JSON.stringify(v.values) && Object.entries(v.props).every(([k, val]) => x.props[k] === val));
3814
- const shot = from && shotOf.get(from);
3815
- if (shot) shots[variantKeyOf(v.props)] = shot;
3816
- });
3817
- return { spec: { ...spec, hash: hashSpec(spec) }, shots };
3818
- }
3819
- const TEXT_FIELDS = /* @__PURE__ */ new Set(["textFill", "fontFamily", "fontSize", "fontWeight", "lineHeight", "letterSpacing", "textDecoration"]);
3820
- function textRootInFrame(parts, variants) {
3821
- const root = parts.find((p2) => !p2.parent);
3822
- if (!root || root.kind !== "text") return;
3823
- const id = `${root.id}-text`;
3824
- const text = { ...root, id, name: `${root.name} text`, parent: root.id };
3825
- parts.splice(parts.indexOf(root), 1, { id: root.id, name: root.name, kind: "frame", ...root.source ? { source: root.source } : {}, layout: { direction: "HORIZONTAL", primary: "MIN", counter: "MIN" } }, text);
3826
- for (const v of variants) {
3827
- const all = v.values[root.id] ?? {};
3828
- v.values[root.id] = Object.fromEntries(Object.entries(all).filter(([f, value]) => !TEXT_FIELDS.has(f) && !((f === "width" || f === "height") && typeof value === "number")));
3829
- v.values[id] = Object.fromEntries(Object.entries(all).filter(([f]) => TEXT_FIELDS.has(f)));
3830
- if (v.hidden?.includes(root.id)) v.hidden.push(id);
3831
- }
3832
- }
3833
- function ownAxes(part, parts, spec, memo = /* @__PURE__ */ new Map()) {
3834
- const parent = parts.find((x) => x.id === part.parent);
3835
- const dir = parent?.layout?.direction;
3836
- const stretch = parent?.layout?.counter === "STRETCH";
3837
- const sizedBy = (axis) => !parent || !spec || definiteAxis(parent, axis, parts, spec, memo);
3838
- const acrossW = dir === "VERTICAL" && stretch && sizedBy("width");
3839
- const alongW = dir === "HORIZONTAL" && !!part.grow && sizedBy("width");
3840
- const acrossH = dir === "HORIZONTAL" && stretch && sizedBy("height");
3841
- const alongH = dir === "VERTICAL" && !!part.grow && sizedBy("height");
3842
- return {
3843
- width: !(!part.parent && part.fill) && !acrossW && !alongW && !part.stretch?.width,
3844
- height: !acrossH && !alongH && !part.stretch?.height
3845
- };
3846
- }
3847
- function definiteAxis(part, axis, parts, spec, memo) {
3848
- const key2 = `${part.id}:${axis}`;
3849
- const known = memo.get(key2);
3850
- if (known !== void 0) return known;
3851
- memo.set(key2, false);
3852
- let result;
3853
- if (!part.parent) result = axis === "width" && !!part.fill;
3854
- else if (spec.sized?.[part.id]?.[axis] !== void 0) result = true;
3855
- else {
3856
- const own = ownAxes(part, parts, spec, memo);
3857
- const hasKids = parts.some((c) => c.parent === part.id && !c.pin && !c.cover && !c.overlay);
3858
- result = !own[axis] || (hasKids ? boxSized(spec, part.id, own)[axis] !== void 0 : typeof spec.values[part.id]?.[axis] === "number");
3859
- }
3860
- memo.set(key2, result);
3861
- return result;
3995
+ function definiteAxis(part, axis, parts, spec, memo) {
3996
+ const key2 = `${part.id}:${axis}`;
3997
+ const known = memo.get(key2);
3998
+ if (known !== void 0) return known;
3999
+ memo.set(key2, false);
4000
+ let result;
4001
+ if (!part.parent) result = axis === "width" && !!part.fill;
4002
+ else if (spec.sized?.[part.id]?.[axis] !== void 0) result = true;
4003
+ else {
4004
+ const own = ownAxes(part, parts, spec, memo);
4005
+ const hasKids = parts.some((c) => c.parent === part.id && !c.pin && !c.cover && !c.overlay);
4006
+ result = !own[axis] || (hasKids ? boxSized(spec, part.id, own)[axis] !== void 0 : typeof spec.values[part.id]?.[axis] === "number");
4007
+ }
4008
+ memo.set(key2, result);
4009
+ return result;
3862
4010
  }
3863
4011
  function pinFromBoxes(spec, part) {
3864
4012
  const box = spec.boxes?.[part.id];
@@ -3899,7 +4047,7 @@ function spacerWrappers(parts, variants, spacersOf, wrapsOf = /* @__PURE__ */ ne
3899
4047
  }
3900
4048
  function textProperties(parts, story, argTypes, axes) {
3901
4049
  const properties = [];
3902
- const props = propsOf(story);
4050
+ const props = propsOf$1(story);
3903
4051
  for (const [k, a2] of Object.entries(argTypes)) {
3904
4052
  const value = props[k];
3905
4053
  if (a2.kind !== "text" || NOT_AN_AXIS.test(k) || typeof value !== "string" || !value.trim()) continue;
@@ -3948,36 +4096,465 @@ function drawnAxes(axes, variants) {
3948
4096
  variants: variants2.map((v) => ({ ...v, props: Object.fromEntries(Object.entries(v.props).filter(([k]) => k !== axis.name)) }))
3949
4097
  };
3950
4098
  }
3951
- return current;
4099
+ return current;
4100
+ }
4101
+ function specsFromMeasure(file, opts = {}) {
4102
+ const known = opts.known ?? (() => true);
4103
+ const key2 = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
4104
+ const only = opts.only === void 0 ? void 0 : key2(opts.only);
4105
+ const groups = /* @__PURE__ */ new Map();
4106
+ for (const s of file.stories) {
4107
+ if (s.error || !s.spec) continue;
4108
+ const name = setNameOf$2(s.title);
4109
+ if (only !== void 0 && key2(name) !== only) continue;
4110
+ groups.set(name, [...groups.get(name) ?? [], s]);
4111
+ }
4112
+ const argTypesOf = (name) => Object.assign({}, ...(file.components ?? []).filter((c) => setNameOf$2(c.title) === name).map((c) => c.argTypes));
4113
+ const hints = {};
4114
+ for (const c of file.components ?? []) if (c.figma) hints[setNameOf$2(c.title)] = { ...hints[setNameOf$2(c.title)], ...c.figma };
4115
+ const components = [];
4116
+ const shots = {};
4117
+ const report = { sets: 0, variants: 0, stories: 0, skipped: [] };
4118
+ for (const [name, stories] of [...groups].sort(([a2], [b]) => a2.localeCompare(b))) {
4119
+ const built = setOf(name, stories, argTypesOf(name), file.storybook, known, hints[name]);
4120
+ if (!built) {
4121
+ report.skipped.push(name);
4122
+ continue;
4123
+ }
4124
+ const spec = built.spec;
4125
+ components.push(spec);
4126
+ if (Object.keys(built.shots).length) shots[name] = built.shots;
4127
+ report.sets++;
4128
+ report.variants += spec.variants.length;
4129
+ report.stories += stories.length;
4130
+ }
4131
+ return { specs: { version: 1, generated: `Measured from ${file.storybook} on ${file.measuredAt} (bridge specs --measure).`, components }, report, shots, hints };
4132
+ }
4133
+ const FIGMA_FIXES_VERSION = 1;
4134
+ const DEFAULT_FIGMA_FIXES_FILE = "figma-fixes.json";
4135
+ class FigmaFixesError extends Error {
4136
+ constructor(problems) {
4137
+ super(`figma-fixes.json: ${problems.join("; ")}`);
4138
+ this.problems = problems;
4139
+ }
4140
+ problems;
4141
+ }
4142
+ const isObj$3 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
4143
+ const STATES = ["rest", "hover", "active", "focus-visible", "disabled"];
4144
+ const KINDS = ["part", "axis", "story", "status"];
4145
+ const BASE_KEYS = ["id", "component", "reason", "by", "where", "kind"];
4146
+ const KEYS = {
4147
+ part: ["part", "set", "width", "height", "direction", "align", "overlay", "hide"],
4148
+ axis: ["action", "arg", "property", "values"],
4149
+ story: ["use"],
4150
+ status: ["status"]
4151
+ };
4152
+ const PRIMARY = ["MIN", "CENTER", "MAX", "SPACE_BETWEEN"];
4153
+ const COUNTER = ["MIN", "CENTER", "MAX", "STRETCH"];
4154
+ const plain = (v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean";
4155
+ function whereProblems(w, at2) {
4156
+ if (!isObj$3(w)) return at2("where must be an object with args and/or state");
4157
+ for (const k of Object.keys(w)) if (k !== "args" && k !== "state") at2(`where.${k} is not known (args, state)`);
4158
+ if (w.args !== void 0 && (!isObj$3(w.args) || !Object.values(w.args).every(plain))) at2("where.args maps arg names to a text, number or true/false");
4159
+ if (w.state !== void 0 && !STATES.includes(w.state)) at2(`where.state is one of ${STATES.join(", ")}`);
4160
+ }
4161
+ function validateFigmaFixes(data) {
4162
+ const out = [];
4163
+ if (!isObj$3(data)) return ["expected an object with version and fixes"];
4164
+ if (data.version !== FIGMA_FIXES_VERSION) out.push(`version ${String(data.version)} is not supported (expected ${FIGMA_FIXES_VERSION})`);
4165
+ for (const k of Object.keys(data)) if (!["$schema", "version", "fixes"].includes(k)) out.push(`${k} is not known (version, fixes)`);
4166
+ if (!Array.isArray(data.fixes)) return [...out, "fixes must be a list"];
4167
+ const ids = /* @__PURE__ */ new Set();
4168
+ data.fixes.forEach((f, i) => {
4169
+ const label = isObj$3(f) && typeof f.id === "string" ? `fix ${f.id}` : `fix ${i + 1}`;
4170
+ const at2 = (why) => out.push(`${label}: ${why}`);
4171
+ if (!isObj$3(f)) return at2("must be an object");
4172
+ if (typeof f.id !== "string" || !/^[a-z0-9][a-z0-9-]{0,79}$/.test(f.id)) at2("id must be lower case letters, digits and dashes");
4173
+ else if (ids.has(f.id)) at2("id is repeated");
4174
+ else ids.add(f.id);
4175
+ if (typeof f.component !== "string" || !f.component.trim()) at2('component is missing (the Figma set name, like "Toggle")');
4176
+ if (typeof f.reason !== "string" || f.reason.trim().length < 8) at2("reason is missing: say why, in a sentence");
4177
+ else if (/\u2014/.test(f.reason)) at2("reason uses an em dash: use a colon or a comma");
4178
+ if (f.by !== void 0 && typeof f.by !== "string") at2("by must be a text");
4179
+ if (f.where !== void 0) whereProblems(f.where, at2);
4180
+ if (!KINDS.includes(f.kind)) return at2(`kind must be one of ${KINDS.join(", ")}`);
4181
+ const kind = f.kind;
4182
+ for (const k of Object.keys(f)) if (!BASE_KEYS.includes(k) && !KEYS[kind].includes(k)) at2(`${k} is not a key of a ${kind} fix`);
4183
+ if (kind === "part") partProblems(f, at2);
4184
+ if (kind === "axis") axisProblems(f, at2);
4185
+ if (kind === "story") {
4186
+ const u = f.use;
4187
+ if (!isObj$3(u) || typeof u.story !== "string" || !u.story) at2("use.story is the story id to draw the variant from");
4188
+ else {
4189
+ for (const k of Object.keys(u)) if (!["story", "args", "state"].includes(k)) at2(`use.${k} is not known (story, args, state)`);
4190
+ if (u.args !== void 0 && (!isObj$3(u.args) || !Object.values(u.args).every(plain))) at2("use.args maps arg names to a text, number or true/false");
4191
+ if (u.state !== void 0 && !STATES.includes(u.state)) at2(`use.state is one of ${STATES.join(", ")}`);
4192
+ }
4193
+ if (!isObj$3(f.where) || !f.where.args && !f.where.state) at2("where names the variant (args and/or state) the story is for");
4194
+ }
4195
+ if (kind === "status") {
4196
+ if (f.status === "matches") at2('status cannot be "matches": only the shot comparison says a component matches');
4197
+ else if (f.status !== void 0 && f.status !== "differs" && f.status !== "cant-check") at2("status is differs or cant-check (absent: a note)");
4198
+ }
4199
+ });
4200
+ return out;
4201
+ }
4202
+ function partProblems(f, at2) {
4203
+ if (typeof f.part !== "string" || !f.part.trim()) at2("part is a part id or a .class");
4204
+ const changes = ["set", "width", "height", "direction", "align", "overlay", "hide"].filter((k) => f[k] !== void 0);
4205
+ if (!changes.length) at2("changes nothing: give set, width, height, direction, align, overlay or hide");
4206
+ if (f.hide !== void 0 && typeof f.hide !== "boolean") at2("hide is true or false");
4207
+ if (f.hide === true && changes.length > 1) at2("hide goes alone: a hidden part has nothing else to change");
4208
+ if (f.set !== void 0) {
4209
+ if (!isObj$3(f.set) || !Object.keys(f.set).length) at2("set maps fields to values");
4210
+ else
4211
+ for (const [field, value] of Object.entries(f.set)) {
4212
+ if (!SPEC_FIELDS.includes(field)) {
4213
+ at2(`set.${field} is not a field (${SPEC_FIELDS.join(", ")})`);
4214
+ continue;
4215
+ }
4216
+ const color = COLOR_FIELDS.has(field);
4217
+ if (value === "full") {
4218
+ if (field !== "cornerRadius") at2(`set.${field}: "full" is for cornerRadius only`);
4219
+ } else if (value === null) {
4220
+ if (!color) at2(`set.${field} cannot be none (only colors can)`);
4221
+ } else if (typeof value === "number") {
4222
+ if (color) at2(`set.${field} is a color: a token path or null, not a number`);
4223
+ else if (!Number.isFinite(value)) at2(`set.${field} must be a finite number`);
4224
+ } else if (typeof value !== "string" || !value.trim()) at2(`set.${field} must be a token path, a number or null`);
4225
+ else if (field === "textDecoration" && !/^(UNDERLINE|STRIKETHROUGH|NONE)$/.test(value)) at2(`set.textDecoration is UNDERLINE, STRIKETHROUGH or NONE`);
4226
+ }
4227
+ }
4228
+ for (const side of ["width", "height"]) {
4229
+ const v = f[side];
4230
+ if (v !== void 0 && !["hug", "fill", "fixed"].includes(v) && !(typeof v === "number" && v > 0)) at2(`${side} is hug, fill, fixed or a size in px`);
4231
+ }
4232
+ if (f.direction !== void 0 && f.direction !== "HORIZONTAL" && f.direction !== "VERTICAL") at2("direction is HORIZONTAL or VERTICAL");
4233
+ if (f.align !== void 0) {
4234
+ const a2 = f.align;
4235
+ if (!isObj$3(a2) || a2.primary === void 0 && a2.counter === void 0) at2("align gives primary and/or counter");
4236
+ else {
4237
+ if (a2.primary !== void 0 && !PRIMARY.includes(a2.primary)) at2(`align.primary is ${PRIMARY.join(", ")}`);
4238
+ if (a2.counter !== void 0 && !COUNTER.includes(a2.counter)) at2(`align.counter is ${COUNTER.join(", ")}`);
4239
+ }
4240
+ }
4241
+ if (f.overlay !== void 0 && typeof f.overlay !== "boolean") {
4242
+ const o = f.overlay;
4243
+ if (!isObj$3(o) || !Object.keys(o).length || !Object.entries(o).every(([k, v]) => ["top", "right", "bottom", "left"].includes(k) && typeof v === "number")) at2('overlay is true, false, or offsets in px ({ "top": 3, "left": 3 })');
4244
+ }
4245
+ if (f.part === "root" && (f.hide === true || f.overlay !== void 0)) at2("the root cannot be hidden or overlaid");
4246
+ }
4247
+ function axisProblems(f, at2) {
4248
+ const action = f.action;
4249
+ if (action !== "map" && action !== "ignore" && action !== "prune") return at2("action is map, ignore or prune");
4250
+ if (action !== "prune" && (typeof f.arg !== "string" || !f.arg)) at2(`${action} needs arg (the story arg, like "kind")`);
4251
+ if (action === "map") {
4252
+ if (f.property === void 0 && f.values === void 0) at2("map gives property (the Figma name) and/or values");
4253
+ if (f.property !== void 0 && (typeof f.property !== "string" || !f.property.trim())) at2("property is the Figma property name");
4254
+ if (f.values !== void 0 && (!isObj$3(f.values) || !Object.values(f.values).every((v) => typeof v === "string" && v.trim()))) at2("values maps code values to Figma values (texts)");
4255
+ else if (isObj$3(f.values)) {
4256
+ const figma = Object.values(f.values);
4257
+ if (new Set(figma).size !== figma.length) at2("values gives two code values the same Figma value: use prune for the one to drop");
4258
+ }
4259
+ }
4260
+ if (action === "ignore" && (f.property !== void 0 || f.values !== void 0)) at2("ignore takes no property or values");
4261
+ if (action === "prune") {
4262
+ if (!isObj$3(f.where) || !f.where.args && !f.where.state) at2("prune needs where (the args and/or state of the variants to drop)");
4263
+ if (f.property !== void 0 || f.values !== void 0) at2("prune takes no property or values");
4264
+ }
4265
+ }
4266
+ function parseFigmaFixes(input) {
4267
+ let data = input;
4268
+ if (typeof input === "string") {
4269
+ try {
4270
+ data = JSON.parse(input);
4271
+ } catch (e) {
4272
+ throw new FigmaFixesError([`not JSON: ${e.message}`]);
4273
+ }
4274
+ }
4275
+ const problems = validateFigmaFixes(data);
4276
+ if (problems.length) throw new FigmaFixesError(problems);
4277
+ return data;
4278
+ }
4279
+ const lc$1 = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
4280
+ const setNameOf$1 = (title) => title.split("/").at(-1).trim();
4281
+ const propsOf = (s) => ({ ...s.props ?? {}, ...s.overrides ?? {} });
4282
+ const sameArg = (a2, b) => a2 === b || a2 !== void 0 && b !== void 0 && String(a2).toLowerCase() === String(b).toLowerCase();
4283
+ function storyMatchesWhere(s, where) {
4284
+ if (!where) return true;
4285
+ const p2 = propsOf(s);
4286
+ if (where.args && !Object.entries(where.args).every(([k, v]) => sameArg(p2[k], v))) return false;
4287
+ if (where.state && storyState(s) !== where.state) return false;
4288
+ return true;
4289
+ }
4290
+ function partsNamed(spec, sel) {
4291
+ if (sel.startsWith(".")) {
4292
+ const cls = sel.slice(1);
4293
+ return spec.parts.filter((p2) => (p2.source ?? "").split(/[\s,]+/).map((x) => x.replace(/^\./, "")).includes(cls));
4294
+ }
4295
+ return spec.parts.filter((p2) => p2.id === sel);
4296
+ }
4297
+ const clone = (v) => JSON.parse(JSON.stringify(v));
4298
+ function wordsIn(spec, id) {
4299
+ const kids = spec.parts.filter((p2) => p2.parent === id);
4300
+ return kids.some((k) => k.kind === "text" && !!k.characters?.trim() || wordsIn(spec, k.id));
4301
+ }
4302
+ function ownSize(spec, part, side) {
4303
+ const id = part.id;
4304
+ const box = spec.boxes[id];
4305
+ const value = spec.values[id]?.[side];
4306
+ const measured = typeof value === "number" ? value : box?.[side];
4307
+ const skip = (why) => ({ part: id, side, why, ...measured !== void 0 && why !== "unmeasured" ? { measured } : {} });
4308
+ if (!part.parent && side === "width" && part.fill) return skip("fills");
4309
+ const css = spec.sized?.[id]?.[side];
4310
+ if (typeof css === "number") return css;
4311
+ if (side === "width" && box && wordsIn(spec, id) && boxSized(spec, id, { width: true, height: true }).width === void 0) return skip("words");
4312
+ return measured ?? skip("unmeasured");
4313
+ }
4314
+ function patchSpec(spec, fix) {
4315
+ const skipped = [];
4316
+ const targets = partsNamed(spec, fix.part);
4317
+ if (!targets.length) return { hit: false, skipped };
4318
+ let laid = false;
4319
+ for (const part of targets) {
4320
+ const id = part.id;
4321
+ const box = spec.boxes[id];
4322
+ const parent = part.parent ? spec.parts.find((p2) => p2.id === part.parent) : void 0;
4323
+ if (fix.hide) {
4324
+ const gone = /* @__PURE__ */ new Set([id]);
4325
+ for (const p2 of spec.parts) if (p2.parent && gone.has(p2.parent)) gone.add(p2.id);
4326
+ spec.parts = spec.parts.filter((p2) => !gone.has(p2.id));
4327
+ for (const map of [spec.values, spec.raw, spec.tokens, spec.boxes, spec.sized ?? {}, spec.spacers ?? {}, spec.vectors ?? {}]) for (const g of gone) delete map[g];
4328
+ laid = true;
4329
+ continue;
4330
+ }
4331
+ const values = spec.values[id] ??= {};
4332
+ for (const [field, value] of Object.entries(fix.set ?? {})) {
4333
+ values[field] = value === "full" ? box ? Math.min(box.width, box.height) / 2 : 9999 : value;
4334
+ laid = true;
4335
+ }
4336
+ for (const side of ["width", "height"]) {
4337
+ const how = fix[side];
4338
+ if (how === void 0) continue;
4339
+ const flowsAlong = parent?.layout?.direction === (side === "width" ? "HORIZONTAL" : "VERTICAL");
4340
+ const px = how === "fixed" ? ownSize(spec, part, side) : how;
4341
+ if (typeof px === "object") {
4342
+ skipped.push(px);
4343
+ continue;
4344
+ }
4345
+ laid = true;
4346
+ if (px === "hug") {
4347
+ delete values[side];
4348
+ if (spec.sized?.[id]) delete spec.sized[id][side];
4349
+ if (part.stretch) delete part.stretch[side];
4350
+ if (flowsAlong) delete part.grow;
4351
+ if (!part.parent && side === "width") delete part.fill;
4352
+ } else if (px === "fill") {
4353
+ if (spec.sized?.[id]) delete spec.sized[id][side];
4354
+ if (!part.parent) {
4355
+ if (side === "width") part.fill = true;
4356
+ } else if (flowsAlong) part.grow = true;
4357
+ else part.stretch = { ...part.stretch ?? {}, [side]: true };
4358
+ } else {
4359
+ values[side] = px;
4360
+ spec.sized = { ...spec.sized ?? {}, [id]: { ...spec.sized?.[id] ?? {}, [side]: px } };
4361
+ if (part.stretch) delete part.stretch[side];
4362
+ if (flowsAlong) delete part.grow;
4363
+ if (!part.parent && side === "width") delete part.fill;
4364
+ }
4365
+ }
4366
+ if (fix.direction || fix.align) {
4367
+ laid = true;
4368
+ part.layout = { direction: "HORIZONTAL", primary: "MIN", counter: "MIN", ...part.layout ?? {} };
4369
+ if (fix.direction) part.layout.direction = fix.direction;
4370
+ if (fix.align?.primary) part.layout.primary = fix.align.primary;
4371
+ if (fix.align?.counter) part.layout.counter = fix.align.counter;
4372
+ }
4373
+ if (fix.overlay !== void 0) {
4374
+ laid = true;
4375
+ delete part.cover;
4376
+ delete part.pin;
4377
+ if (fix.overlay === true) part.cover = true;
4378
+ else if (fix.overlay !== false) part.pin = { ...fix.overlay };
4379
+ }
4380
+ }
4381
+ return { hit: laid, skipped };
4382
+ }
4383
+ function applyFixes(measured, fixes) {
4384
+ const list = Array.isArray(fixes) ? fixes : fixes.fixes;
4385
+ const file = clone(measured);
4386
+ const before = new Set((file.figmaFixes?.applied ?? []).filter((a2) => a2.result === "applied").map((a2) => a2.id));
4387
+ const applied = [];
4388
+ const entryOf = (component) => (file.components ?? []).find((c) => lc$1(setNameOf$1(c.title)) === lc$1(component));
4389
+ const hints = (component) => {
4390
+ const c = entryOf(component);
4391
+ return c ? c.figma ??= {} : void 0;
4392
+ };
4393
+ const mark = (s, id) => {
4394
+ s.fixes = [.../* @__PURE__ */ new Set([...s.fixes ?? [], id])];
4395
+ };
4396
+ for (const fix of list) {
4397
+ const record = (result, stories, detail) => applied.push({ id: fix.id, kind: fix.kind, component: fix.component, result, stories, detail });
4398
+ if (before.has(fix.id)) {
4399
+ record("already", 0, "already applied to this file");
4400
+ continue;
4401
+ }
4402
+ const mine = () => file.stories.filter((s) => lc$1(setNameOf$1(s.title)) === lc$1(fix.component));
4403
+ if (!mine().length) {
4404
+ record("not-found", 0, `no measured story of ${fix.component}`);
4405
+ continue;
4406
+ }
4407
+ const scoped = () => mine().filter((s) => storyMatchesWhere(s, fix.where));
4408
+ if (fix.kind === "part") {
4409
+ let n = 0;
4410
+ let matched = false;
4411
+ const unpinned = [];
4412
+ for (const s of scoped()) {
4413
+ let hit = false;
4414
+ for (const spec of [s.spec, ...s.instances ?? []]) {
4415
+ if (!spec) continue;
4416
+ const r = patchSpec(spec, fix);
4417
+ if (r.hit) hit = true;
4418
+ if (r.hit || r.skipped.length) matched = true;
4419
+ for (const skip of r.skipped) unpinned.push({ story: s, skip });
4420
+ }
4421
+ if (hit) {
4422
+ n++;
4423
+ mark(s, fix.id);
4424
+ }
4425
+ }
4426
+ const skippedWords = unpinned.length ? skippedSizeWords(unpinned) : "";
4427
+ if (n) record("applied", n, `${describePartFix(fix)} on ${n} stor${n === 1 ? "y" : "ies"}${skippedWords ? `; ${skippedWords}` : ""}`);
4428
+ else if (matched) record("skipped", 0, `nothing laid on: ${skippedWords}`);
4429
+ else record("not-found", 0, `no part ${fix.part} in the ${scoped().length ? "stories it names" : "variants where names"}`);
4430
+ } else if (fix.kind === "axis") {
4431
+ const h2 = hints(fix.component);
4432
+ if (fix.action === "prune") {
4433
+ const drop = new Set(scoped());
4434
+ if (!drop.size) {
4435
+ record("not-found", 0, "no measured variant matches where");
4436
+ continue;
4437
+ }
4438
+ file.stories = file.stories.filter((s) => !drop.has(s));
4439
+ if (h2) h2.pruned = [...h2.pruned ?? [], fix.where];
4440
+ record("applied", drop.size, `dropped ${drop.size} variant stor${drop.size === 1 ? "y" : "ies"}`);
4441
+ } else {
4442
+ const arg = fix.arg;
4443
+ const entry = entryOf(fix.component);
4444
+ const known = !!entry?.argTypes[arg] || mine().some((s) => arg in propsOf(s));
4445
+ if (!known) {
4446
+ record("not-found", 0, `${fix.component} has no arg ${arg}`);
4447
+ continue;
4448
+ }
4449
+ if (fix.action === "map") {
4450
+ if (h2) {
4451
+ if (fix.property) h2.properties = { ...h2.properties ?? {}, [arg]: fix.property };
4452
+ if (fix.values) h2.values = { ...h2.values ?? {}, [arg]: { ...h2.values?.[arg] ?? {}, ...fix.values } };
4453
+ }
4454
+ record("applied", 0, `${arg} drawn as ${fix.property ?? arg}${fix.values ? ` (${Object.entries(fix.values).map(([c, f]) => `${c} as ${f}`).join(", ")})` : ""}`);
4455
+ } else {
4456
+ const at2 = entry?.argTypes[arg];
4457
+ const lead = mine().find((s) => !s.overrides || !(arg in s.overrides));
4458
+ const baseline = at2?.default ?? (lead ? propsOf(lead)[arg] : void 0) ?? (at2?.kind === "boolean" ? false : void 0);
4459
+ const drop = new Set(scoped().filter((s) => baseline !== void 0 && propsOf(s)[arg] !== void 0 && !sameArg(propsOf(s)[arg], baseline)));
4460
+ file.stories = file.stories.filter((s) => !drop.has(s));
4461
+ if (entry) {
4462
+ delete entry.argTypes[arg];
4463
+ entry.figma ??= {};
4464
+ entry.figma.ignored = [.../* @__PURE__ */ new Set([...entry.figma.ignored ?? [], arg])];
4465
+ }
4466
+ record("applied", drop.size, `${arg} is not a Figma property${drop.size ? `: ${drop.size} stor${drop.size === 1 ? "y" : "ies"} at another value left out` : ""}`);
4467
+ }
4468
+ }
4469
+ } else if (fix.kind === "story") {
4470
+ const src = file.stories.find(
4471
+ (s) => s.id === fix.use.story && s.spec && !s.error && (!fix.use.args || Object.entries(fix.use.args).every(([k, v]) => sameArg(propsOf(s)[k], v))) && (!fix.use.state || storyState(s) === fix.use.state)
4472
+ );
4473
+ if (!src) {
4474
+ record("not-found", 0, `no measured render of story ${fix.use.story}${fix.use.args ? ` with ${JSON.stringify(fix.use.args)}` : ""}`);
4475
+ continue;
4476
+ }
4477
+ const target = fix.where ?? {};
4478
+ const others = new Set(scoped().filter((s) => s !== src));
4479
+ const copy = clone(src);
4480
+ const overrides = { ...copy.overrides ?? {}, ...target.args ?? {} };
4481
+ if (target.state === "disabled") overrides.disabled = true;
4482
+ copy.overrides = overrides;
4483
+ if (target.state && target.state !== "rest" && target.state !== "disabled") copy.state = target.state;
4484
+ else if (target.state) delete copy.state;
4485
+ if (lc$1(setNameOf$1(copy.title)) !== lc$1(fix.component)) copy.title = copy.title.split("/").slice(0, -1).concat(fix.component).join("/");
4486
+ mark(copy, fix.id);
4487
+ let placed = false;
4488
+ file.stories = file.stories.flatMap((s) => others.has(s) ? placed ? [] : (placed = true, [copy]) : [s]);
4489
+ if (!placed) file.stories.push(copy);
4490
+ record("applied", others.size + 1, `variant ${whereWords(target)} drawn from ${src.id}${others.size ? `, in place of ${others.size} other stor${others.size === 1 ? "y" : "ies"}` : ""}`);
4491
+ } else {
4492
+ const h2 = hints(fix.component);
4493
+ if (!h2) {
4494
+ record("not-found", 0, `no component entry for ${fix.component} in the measure`);
4495
+ continue;
4496
+ }
4497
+ if (fix.status) {
4498
+ h2.status = [...h2.status ?? [], { status: fix.status, reason: fix.reason, fix: fix.id, ...fix.where ? { where: fix.where } : {} }];
4499
+ if (!fix.where) {
4500
+ const st = (file.statuses ?? []).find((x) => lc$1(x.name) === lc$1(fix.component));
4501
+ if (st && st.status !== fix.status) {
4502
+ st.status = fix.status;
4503
+ delete st.reason;
4504
+ st.summary = fix.reason;
4505
+ }
4506
+ }
4507
+ } else h2.notes = [...h2.notes ?? [], { text: fix.reason, fix: fix.id, ...fix.where ? { where: fix.where } : {} }];
4508
+ record("applied", 0, fix.status ? `${fix.status === "differs" ? "Differs" : "Can't check"}: ${fix.reason}` : `note: ${fix.reason}`);
4509
+ }
4510
+ const h = hints(fix.component);
4511
+ if (h && applied.at(-1).result === "applied") h.fixes = [.../* @__PURE__ */ new Set([...h.fixes ?? [], fix.id])];
4512
+ }
4513
+ file.figmaFixes = { applied: [...(file.figmaFixes?.applied ?? []).filter((a2) => a2.result === "applied"), ...applied.filter((a2) => a2.result !== "already")] };
4514
+ return { file, applied };
3952
4515
  }
3953
- function specsFromMeasure(file, opts = {}) {
3954
- const known = opts.known ?? (() => true);
4516
+ const variantWords = (s) => {
4517
+ const p2 = propsOf(s);
4518
+ const args = Object.keys(s.overrides ?? {}).filter((k) => k !== "disabled").map((k) => `${k}=${String(p2[k])}`);
4519
+ const state = storyState(s);
4520
+ return `${s.id}${args.length || state !== "rest" ? ` (${[...args, ...state !== "rest" ? [state] : []].join(", ")})` : ""}`;
4521
+ };
4522
+ function skippedSizeWords(list) {
4523
+ const why = {
4524
+ words: "it hugs its text in code, so the measured px is that story's words, not a width of its own",
4525
+ fills: "it fills its container in code, so the measured px is the container's",
4526
+ unmeasured: "the measure has no size for it in that story"
4527
+ };
3955
4528
  const groups = /* @__PURE__ */ new Map();
3956
- for (const s of file.stories) {
3957
- if (s.error || !s.spec) continue;
3958
- const name = setNameOf$1(s.title);
3959
- groups.set(name, [...groups.get(name) ?? [], s]);
3960
- }
3961
- const argTypesOf = (name) => Object.assign({}, ...(file.components ?? []).filter((c) => setNameOf$1(c.title) === name).map((c) => c.argTypes));
3962
- const hints = {};
3963
- for (const c of file.components ?? []) if (c.figma) hints[setNameOf$1(c.title)] = { ...hints[setNameOf$1(c.title)], ...c.figma };
3964
- const components = [];
3965
- const shots = {};
3966
- const report = { sets: 0, variants: 0, stories: 0, skipped: [] };
3967
- for (const [name, stories] of [...groups].sort(([a2], [b]) => a2.localeCompare(b))) {
3968
- const built = setOf(name, stories, argTypesOf(name), file.storybook, known, hints[name]);
3969
- if (!built) {
3970
- report.skipped.push(name);
3971
- continue;
3972
- }
3973
- const spec = built.spec;
3974
- components.push(spec);
3975
- if (Object.keys(built.shots).length) shots[name] = built.shots;
3976
- report.sets++;
3977
- report.variants += spec.variants.length;
3978
- report.stories += stories.length;
4529
+ for (const x of list) {
4530
+ const k = `${x.skip.part}|${x.skip.side}|${x.skip.why}`;
4531
+ groups.set(k, [...groups.get(k) ?? [], x]);
3979
4532
  }
3980
- return { specs: { version: 1, generated: `Measured from ${file.storybook} on ${file.measuredAt} (bridge specs --measure).`, components }, report, shots, hints };
4533
+ return [...groups.values()].map((g) => {
4534
+ const { part, side, why: w } = g[0].skip;
4535
+ const stories = [...new Set(g.map((x) => `${variantWords(x.story)}${x.skip.measured !== void 0 ? ` ${Math.round(x.skip.measured * 100) / 100} px` : ""}`))];
4536
+ return `${part} ${side} not fixed on ${stories.length} stor${stories.length === 1 ? "y" : "ies"} (${why[w]}):${stories.join(", ")}`;
4537
+ }).join("; ");
4538
+ }
4539
+ function whereWords(w) {
4540
+ const args = Object.entries(w.args ?? {}).map(([k, v]) => `${k}=${String(v)}`);
4541
+ if (w.state) args.push(`state=${w.state}`);
4542
+ return args.join(", ") || "every variant";
4543
+ }
4544
+ function describePartFix(f) {
4545
+ if (f.hide) return `${f.part} hidden`;
4546
+ const bits = [
4547
+ ...Object.entries(f.set ?? {}).map(([k, v]) => `${k} ${v === null ? "none" : String(v)}`),
4548
+ ...f.width !== void 0 ? [`width ${String(f.width)}`] : [],
4549
+ ...f.height !== void 0 ? [`height ${String(f.height)}`] : [],
4550
+ ...f.direction ? [`direction ${f.direction}`] : [],
4551
+ ...f.align ? [`align ${[f.align.primary, f.align.counter].filter(Boolean).join(" ")}`] : [],
4552
+ ...f.overlay !== void 0 ? [`overlay ${typeof f.overlay === "boolean" ? String(f.overlay) : JSON.stringify(f.overlay)}`] : []
4553
+ ];
4554
+ return `${f.part}: ${bits.join(", ")}`;
4555
+ }
4556
+ function appliedFixLines(applied) {
4557
+ return applied.map((a2) => `${a2.result === "applied" ? "applied" : a2.result === "already" ? "already applied" : a2.result === "skipped" ? "skipped" : "not found"} ${a2.id} (${a2.component}, ${a2.kind}): ${a2.detail}`);
3981
4558
  }
3982
4559
  const DEFAULT_FIDELITY_RUN_FILE = "figma-fidelity.json";
3983
4560
  const FIX_LOOP_MAX_ROUNDS = 3;
@@ -4048,7 +4625,7 @@ function figmaGaps(measured, run) {
4048
4625
  }
4049
4626
  function gapCaseFor(input) {
4050
4627
  const fixed = input.fixes ? applyFixes(input.measured, input.fixes).file : input.measured;
4051
- const { specs, shots } = specsFromMeasure(fixed);
4628
+ const { specs, shots } = specsFromMeasure(fixed, { only: input.component });
4052
4629
  const spec = specs.components.find((c) => lc(c.name) === lc(input.component));
4053
4630
  const fidelity = input.run?.report.components.find((c) => lc(c.component) === lc(input.component));
4054
4631
  const setShots = byName(shots, input.component) ?? {};
@@ -4065,7 +4642,7 @@ function gapCaseFor(input) {
4065
4642
  }
4066
4643
  const FIX_SCHEMA_SUMMARY = `figma-fixes.json fixes (version ${FIGMA_FIXES_VERSION}). Each fix is one JSON object:
4067
4644
  - Every fix: "id" (lower case, digits, dashes, unique), "kind", "component" (the Figma set, e.g. "TextInput"), "reason" (one plain sentence, no em dash), optional "by": "claude", optional "where": { "args": { "<arg>": value }, "state": "rest" | "hover" | "active" | "focus-visible" | "disabled" } (absent: every variant).
4068
- - kind "part": "part" is a part id from the gap case ("root", "cds--text-input-label-wrapper") or ".class". At least one of: "set" { field: token path | px number | null (colors only) | "full" (cornerRadius only) }, "width" / "height": "fixed" (the measured size) | px number | "hug" | "fill", "direction": "HORIZONTAL" | "VERTICAL", "align": { "primary": MIN|CENTER|MAX|SPACE_BETWEEN, "counter": MIN|CENTER|MAX|STRETCH }, "overlay": true | false | { "top": 3, "left": 3 }, "hide": true (alone; never the root). Fields: fill, stroke, strokeWeight, strokeTopWeight, strokeRightWeight, strokeBottomWeight, strokeLeftWeight, cornerRadius, paddingTop, paddingRight, paddingBottom, paddingLeft, itemSpacing, counterAxisSpacing, width, height, minWidth, minHeight, opacity, textFill, fontFamily, fontSize, fontWeight, lineHeight, letterSpacing, textDecoration.
4645
+ - kind "part": "part" is a part id from the gap case ("root", "cds--text-input-label-wrapper") or ".class". At least one of: "set" { field: token path | px number | null (colors only) | "full" (cornerRadius only) }, "width" / "height": "fixed" (each story's own measured size; a width that only hugs text in code is skipped, since it is the story's words) | px number | "hug" | "fill", "direction": "HORIZONTAL" | "VERTICAL", "align": { "primary": MIN|CENTER|MAX|SPACE_BETWEEN, "counter": MIN|CENTER|MAX|STRETCH }, "overlay": true | false | { "top": 3, "left": 3 }, "hide": true (alone; never the root). Fields: fill, stroke, strokeWeight, strokeTopWeight, strokeRightWeight, strokeBottomWeight, strokeLeftWeight, cornerRadius, paddingTop, paddingRight, paddingBottom, paddingLeft, itemSpacing, counterAxisSpacing, width, height, minWidth, minHeight, opacity, textFill, fontFamily, fontSize, fontWeight, lineHeight, letterSpacing, textDecoration.
4069
4646
  - kind "axis": "action" "map" ("arg", "property" and/or "values" { code value: Figma value }), "ignore" ("arg"), or "prune" ("where" names the variants to drop).
4070
4647
  - kind "story": "where" names the variant, "use": { "story": "<story id>", "args"?, "state"? } is the measured render to draw it from.
4071
4648
  - kind "status": "status": "differs" | "cant-check" with the reason; never "matches" (only the shots say a component matches). Without "status": a note.
@@ -4110,25 +4687,25 @@ function tryFigmaFixes(input) {
4110
4687
  const before = gapCaseFor({ component, measured: input.measured, fixes: input.fixes, run: input.run });
4111
4688
  const afterRun = applyFixes(input.measured, file);
4112
4689
  const applied = afterRun.applied.filter((a2) => proposedIds.has(a2.id));
4113
- const afterBuilt = specsFromMeasure(afterRun.file);
4690
+ const afterBuilt = specsFromMeasure(afterRun.file, { only: component });
4114
4691
  const afterSpec = afterBuilt.specs.components.find((c) => lc(c.name) === lc(component));
4115
4692
  const hintsA = afterSpec ? afterBuilt.hints[afterSpec.name] : void 0;
4116
- const hintsB = before.spec ? specsFromMeasure(before.measured).hints[before.spec.name] : void 0;
4693
+ const hintsB = before.spec ? specsFromMeasure(before.measured, { only: before.spec.name }).hints[before.spec.name] : void 0;
4117
4694
  const resolve2 = measuredResolver(before.measured);
4118
4695
  const built = byName(input.run?.built, component);
4119
4696
  const stories = storiesOfComponent(before.measured, component);
4120
4697
  const variants = [];
4121
4698
  for (const gv of before.gap.variants) {
4122
- const vb = before.spec?.variants.find((v) => variantKeyOf(v.props) === gv.key);
4123
- const want = before.spec ? renamedProps(gv.props, before.spec, afterSpec, hintsB, hintsA) : gv.props;
4124
- const va = afterSpec?.variants.find((v) => variantKeyOf(v.props) === gv.key) ?? afterSpec?.variants.find((v) => sameProps(v.props, want));
4699
+ const vb = matchVariant(before.spec, gv.props);
4700
+ const want = before.spec ? renamedProps(vb?.props ?? gv.props, before.spec, afterSpec, hintsB, hintsA) : gv.props;
4701
+ const va = matchVariant(afterSpec, gv.props) ?? matchVariant(afterSpec, want);
4125
4702
  if (!va || !afterSpec) {
4126
4703
  variants.push({ key: gv.key, ...gv.story ? { story: gv.story.id } : {}, verdict: "gone", differences: [], regressions: [] });
4127
4704
  continue;
4128
4705
  }
4129
4706
  const differences = gv.differences.map((d) => tryDifference(d, before.spec, vb, afterSpec, va, built?.[gv.key], resolve2));
4130
4707
  let regressions2 = [];
4131
- const story = before.spec ? storyForVariant(before.spec, gv.props, stories, before.shots[gv.key]) : void 0;
4708
+ const story = before.spec ? storyForVariant(before.spec, gv.props, stories, before.shots[gv.key] ?? (vb ? before.shots[variantKeyOf(vb.props)] : void 0)) : void 0;
4132
4709
  const m = story ? story.instances?.[0] ?? story.spec : void 0;
4133
4710
  if (m && before.spec && vb) {
4134
4711
  const key2 = (d) => `${d.part}|${d.property}`;
@@ -4141,7 +4718,7 @@ function tryFigmaFixes(input) {
4141
4718
  const all = variants.flatMap((v) => v.differences);
4142
4719
  const regressions = variants.reduce((n, v) => n + v.regressions.length, 0);
4143
4720
  const verdict = verdictOf(all, variants.flatMap((v) => v.regressions));
4144
- const notFound = applied.filter((a2) => a2.result === "not-found");
4721
+ const notFound = applied.filter((a2) => a2.result === "not-found" || a2.result === "skipped");
4145
4722
  return {
4146
4723
  status: "checked",
4147
4724
  component,
@@ -4150,12 +4727,11 @@ function tryFigmaFixes(input) {
4150
4727
  verdict,
4151
4728
  referee: "values",
4152
4729
  variants,
4153
- summary: trySummary(component, before.gap, all, regressions, verdict, notFound, proposed),
4730
+ summary: trySummary(component, before.gap, all, regressions, verdict, notFound, proposed, variants.filter((v) => v.verdict === "gone").length),
4154
4731
  detail: tryDetail(variants, applied),
4155
4732
  file
4156
4733
  };
4157
4734
  }
4158
- const sameProps = (a2, b) => Object.keys(a2).length === Object.keys(b).length && Object.entries(a2).every(([k, v]) => b[k] === v);
4159
4735
  function renamedProps(props, specB, specA, hintsB, hintsA) {
4160
4736
  const out = {};
4161
4737
  for (const [name, value] of Object.entries(props)) {
@@ -4284,17 +4860,24 @@ function sizeShift(field, specB, vb, specA, va, built, resolve2) {
4284
4860
  walk(root);
4285
4861
  return unknown ? void 0 : round(shift);
4286
4862
  }
4287
- function trySummary(component, gap, diffs, regressions, verdict, notFound, proposed) {
4863
+ function trySummary(component, gap, diffs, regressions, verdict, notFound, proposed, gone) {
4288
4864
  if (!gap.variants.length) return `${component} has no differing variant in the gap case: nothing for these fixes to move (the values referee needs a Check fidelity report or a difference in the measure).`;
4865
+ const missing = notFound.filter((a2) => a2.result === "not-found");
4866
+ const skipped = notFound.filter((a2) => a2.result === "skipped");
4867
+ const absent = (missing.length ? ` ${missing.map((a2) => a2.id).join(", ")} found nothing in the measure.` : "") + (skipped.length ? ` ${skipped.map((a2) => a2.id).join(", ")} laid nothing on: the measure has no size of the part's own to fix it at (see the detail).` : "");
4868
+ if (!diffs.length && gone) {
4869
+ const n = gap.variants.length;
4870
+ const which = gone < n ? `${gone} of the ${n} differing variants are not` : n === 1 ? "the differing variant is not" : `none of the ${n} differing variants is`;
4871
+ return `The values referee compared nothing: ${which} in the rebuilt set.${absent} Pull this branch in the DS Bridge Figma plugin and run Check fidelity to see what the fixes do.`;
4872
+ }
4289
4873
  const resolved = diffs.filter((d) => d.result === "resolved").length;
4290
4874
  const closer = diffs.filter((d) => d.result === "closer").length;
4291
4875
  const worse = diffs.filter((d) => d.result === "worse").length;
4292
- const bits = [`${resolved} of ${diffs.length} difference${diffs.length === 1 ? "" : "s"} resolved`, ...closer ? [`${closer} closer`] : [], ...worse ? [`${worse} worse`] : [], ...regressions ? [`${regressions} new`] : []];
4876
+ const bits = [`${resolved} of ${diffs.length} difference${diffs.length === 1 ? "" : "s"} resolved`, ...closer ? [`${closer} closer`] : [], ...worse ? [`${worse} worse`] : [], ...regressions ? [`${regressions} new`] : [], ...gone ? [`${gone} not in the rebuilt set`] : []];
4293
4877
  const word = verdict === "closer" ? "Closer to the code" : verdict === "worse" ? "Further from the code" : "No closer to the code";
4294
4878
  const statusOnly = proposed.length > 0 && proposed.every((f) => f.kind === "status" || f.kind === "axis" && f.action === "map");
4295
- const missing = notFound.length ? ` ${notFound.map((a2) => a2.id).join(", ")} found nothing in the measure.` : "";
4296
4879
  const note = statusOnly && verdict === "same" ? " These fixes change how the set is named or labelled, not what it draws." : "";
4297
- return `${word} on the measured values: ${bits.join(", ")}.${missing}${note} The pixel check runs in the plugin after the next pull.`;
4880
+ return `${word} on the measured values: ${bits.join(", ")}.${absent}${note} The pixel check runs in the plugin after the next pull.`;
4298
4881
  }
4299
4882
  function tryDetail(variants, applied) {
4300
4883
  const fmt = (v) => v === null ? "none" : v === void 0 ? "unknown" : typeof v === "string" ? v : JSON.stringify(v);
@@ -4319,6 +4902,32 @@ function fixWords(f) {
4319
4902
  function touchesLayout(f) {
4320
4903
  return f.kind === "part" && (f.width !== void 0 || f.height !== void 0 || f.direction !== void 0 || f.align !== void 0 || f.overlay !== void 0);
4321
4904
  }
4905
+ const VARIANTS_INLINE = 8;
4906
+ const valueWords = (v) => v === null ? "none" : v === void 0 ? "unknown" : typeof v === "string" ? v : JSON.stringify(v);
4907
+ function triedHighlights(variants) {
4908
+ const groups = /* @__PURE__ */ new Map();
4909
+ for (const v of variants) {
4910
+ for (const d of v.differences) {
4911
+ const key2 = [d.part, d.property, valueWords(d.before), valueWords(d.expected), valueWords(d.after), d.result].join("|");
4912
+ const seen = groups.get(key2);
4913
+ if (seen) seen.n += 1;
4914
+ else groups.set(key2, { n: 1, d });
4915
+ }
4916
+ }
4917
+ return [...groups.values()].map(
4918
+ ({ n, d }) => `- ${d.part} ${d.property}, ${n === 1 ? "1 variant" : `${n} variants`}: ${valueWords(d.before)} in Figma against ${valueWords(d.expected)} in code, now ${valueWords(d.after)}: ${d.result}`
4919
+ );
4920
+ }
4921
+ function triedEvidence(variants) {
4922
+ const lines = variants.flatMap((v) => [
4923
+ `- ${v.key}: ${v.verdict === "gone" ? "not in the rebuilt set" : v.verdict}`,
4924
+ ...v.differences.map((d) => ` - ${d.text}: ${d.result}${d.deltaBefore !== void 0 ? ` (${d.deltaBefore} px, now ${d.deltaAfter ?? "?"} px${d.estimated ? ", estimated" : ""})` : ""}`),
4925
+ ...v.regressions.map((r) => ` - new difference: ${r.text}`)
4926
+ ]);
4927
+ if (variants.length <= VARIANTS_INLINE) return [...lines, ""];
4928
+ const highlights = triedHighlights(variants);
4929
+ return [...highlights.length ? [...highlights, ""] : [], "<details>", `<summary>Variant by variant (${variants.length})</summary>`, "", ...lines, "", "</details>", ""];
4930
+ }
4322
4931
  function figmaFixesPrBody(input) {
4323
4932
  const { tried } = input;
4324
4933
  const where = (f) => {
@@ -4345,13 +4954,9 @@ function figmaFixesPrBody(input) {
4345
4954
  "",
4346
4955
  tried.summary,
4347
4956
  "",
4348
- ...tried.variants.flatMap((v) => [
4349
- `- ${v.key}: ${v.verdict}`,
4350
- ...v.differences.map((d) => ` - ${d.text}: ${d.result}${d.deltaBefore !== void 0 ? ` (${d.deltaBefore} px, now ${d.deltaAfter ?? "?"} px${d.estimated ? ", estimated" : ""})` : ""}`),
4351
- ...v.regressions.map((r) => ` - new difference: ${r.text}`)
4352
- ]),
4353
- "",
4957
+ // The caveat is the reader's key to the section, so it comes before the evidence, not after it.
4354
4958
  ...tried.verdict === "same" && input.fixes.some(touchesLayout) ? ["These fixes size, place or lay out parts. The values referee can't judge layout, so it says same: Check fidelity decides.", ""] : [],
4959
+ ...triedEvidence(tried.variants),
4355
4960
  "The values referee compares the rebuilt set with what the code renders. The story shot is the final referee: pull this branch in the DS Bridge Figma plugin and run Check fidelity before approving. A fix stays only when the variant moved closer to its shot.",
4356
4961
  "",
4357
4962
  `Drafted by Claude through ${input.tool}. figma-fixes.json only: no component, token or plugin change.`
@@ -4402,6 +5007,345 @@ function statusFixFor(gap, existingIds = []) {
4402
5007
  const reason = `${gap.summary.replace(/\u2014/g, ",").replace(/\.$/, "")}. Claude found no fix that brings it closer in ${FIX_LOOP_MAX_ROUNDS} rounds.`;
4403
5008
  return { id, kind: "status", component: gap.component, status: "differs", reason, by: "claude" };
4404
5009
  }
5010
+ const DEFAULT_MEASURED_SPECS_FILE = "measured-specs.json";
5011
+ const FIGMA_FIXES_LABEL = "figma-fixes";
5012
+ const startFixLoop = (component) => ({ component, round: 0, attempts: [], done: false });
5013
+ async function fixLoopRound(input, state, ask, gap = gapCaseFor(input).gap) {
5014
+ if (state.done || state.round >= FIX_LOOP_MAX_ROUNDS) return { state: { ...state, done: true }, round: { round: state.round, fixes: [], summary: "No round left." } };
5015
+ const round2 = state.round + 1;
5016
+ const reply = await ask(fixPrompt(gap, state.attempts));
5017
+ const next = (attempt2, r2, extra = {}) => {
5018
+ const s = { ...state, ...extra, round: round2, attempts: [...state.attempts, attempt2] };
5019
+ if (s.round >= FIX_LOOP_MAX_ROUNDS) s.done = true;
5020
+ return { state: s, round: r2 };
5021
+ };
5022
+ let fixes;
5023
+ try {
5024
+ fixes = parseFixReply(reply);
5025
+ } catch (e) {
5026
+ const msg = e.message;
5027
+ return next({ fixes: [], feedback: `${msg} Answer with a JSON array of fix objects only.` }, { round: round2, fixes: [], summary: msg });
5028
+ }
5029
+ const tried = tryFigmaFixes({ ...input, proposed: fixes });
5030
+ if (tried.status === "invalid") return next({ fixes, feedback: `Invalid, fix these problems:
5031
+ ${tried.problems.join("\n")}` }, { round: round2, fixes, verdict: "invalid", summary: tried.summary });
5032
+ const r = { round: round2, fixes, verdict: tried.verdict, summary: tried.summary };
5033
+ const attempt = { fixes, feedback: `${tried.summary}
5034
+ ${tried.detail}`, verdict: tried.verdict };
5035
+ if (tried.verdict !== "closer") return next(attempt, r);
5036
+ const all = tried.variants.flatMap((v) => v.differences);
5037
+ const score = all.filter((d) => d.result === "resolved").length * 2 + all.filter((d) => d.result === "closer").length - tried.variants.reduce((n, v) => n + v.regressions.length, 0);
5038
+ const best = !state.best || score > state.best.score ? { fixes, score, summary: tried.summary } : state.best;
5039
+ const resolved = all.every((d) => d.result === "resolved");
5040
+ return next(attempt, r, { best, ...resolved ? { done: true } : {} });
5041
+ }
5042
+ function fixLoopOutcome(gap, state, existingIds = []) {
5043
+ if (state.best) return { kind: "fixes", fixes: state.best.fixes, summary: `Claude's fixes bring ${gap.component} closer to its story. ${state.best.summary}` };
5044
+ return {
5045
+ kind: "status",
5046
+ fixes: [statusFixFor(gap, existingIds)],
5047
+ summary: `No fix brought ${gap.component} closer to its story in ${FIX_LOOP_MAX_ROUNDS} rounds: it keeps an honest Differs status with the reason.`
5048
+ };
5049
+ }
5050
+ const slug = (s) => s.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "component";
5051
+ const figmaFixesBranchStem = (config, component) => `${config.governance.branchPrefix}figma-fixes-${slug(component)}`;
5052
+ function figmaFixesProposal(input) {
5053
+ const { config } = input;
5054
+ const tried = tryFigmaFixes(input);
5055
+ if (tried.status === "invalid") throw new Error(`Not proposed: ${tried.problems.join("; ")}.`);
5056
+ const summary = (input.summary ?? "").trim();
5057
+ if (summary.length < 8) throw new Error("Not proposed: summary is one or two sentences a reviewer reads first (what the fixes do for the designer).");
5058
+ if (/—/.test(summary)) throw new Error("Not proposed: the summary uses an em dash: use a colon or a comma.");
5059
+ const ids = new Set(input.proposed.map((f) => f.id));
5060
+ const proposed = tried.file.fixes.filter((f) => ids.has(f.id));
5061
+ const { gap } = gapCaseFor(input);
5062
+ const drawing = proposed.some((f) => f.kind === "part" || f.kind === "story" || f.kind === "axis" && f.action !== "map");
5063
+ const measurable = gap.variants.some((v) => v.differences.length);
5064
+ if (tried.verdict === "worse" || drawing && measurable && tried.verdict === "same" && !proposed.some(touchesLayout)) {
5065
+ return {
5066
+ status: "blocked",
5067
+ verdict: tried.verdict,
5068
+ why: `${tried.summary} Bridge does not open fixes that move ${gap.component} ${tried.verdict === "worse" ? "further from" : "no closer to"} the code.`,
5069
+ detail: tried.detail,
5070
+ component: gap.component
5071
+ };
5072
+ }
5073
+ const content = `${JSON.stringify(tried.file, null, 2)}
5074
+ `;
5075
+ const fileContents = [{ path: input.fixesPath, content }];
5076
+ const risk = classifyRisk({ files: [{ path: input.fixesPath, status: input.fixes ? "modified" : "added" }], config });
5077
+ const prBody = `${figmaFixesPrBody({ component: gap.component, summary, gap, tried, fixes: proposed, tool: input.tool })}
5078
+
5079
+ Risk: ${risk.label}.`;
5080
+ const title = `fix(figma): ${gap.component} drawn closer to its story`;
5081
+ const message = `${title}
5082
+
5083
+ ${summary}
5084
+
5085
+ Fixes: ${proposed.map((f) => f.id).join(", ")}. Drafted by Claude through ${input.tool}.`;
5086
+ const labels = [.../* @__PURE__ */ new Set([...config.governance.labels, `tier: ${risk.tier}`, `source: ${input.source}`, FIGMA_FIXES_LABEL, DRAFTED_BY_CLAUDE_LABEL])];
5087
+ const report = { component: gap.component, verdict: tried.verdict, summary: tried.summary, variants: tried.variants, fixes: proposed.map((f) => f.id), tier: risk.tier, prTitle: title };
5088
+ return { status: "ready", report, fixes: proposed, labels, prBody, fileContents, title, message, draft: risk.tier >= 2, base: baseBranchOf(config), branchStem: figmaFixesBranchStem(config, gap.component) };
5089
+ }
5090
+ const MEASURE_BRANCH = REQUESTS_BRANCH;
5091
+ const MEASURED_SHOTS_DIR = "measured-shots";
5092
+ const MEASURE_WORKFLOW_FILE = ".github/workflows/bridge-measure.yml";
5093
+ const MEASURE_SETUP_ACTION = "Set up automatic measure";
5094
+ const MEASURE_MISSING_TEXT = `Bridge hasn't measured your components yet. A developer turns it on once: ${MEASURE_SETUP_ACTION}.`;
5095
+ function measureSetupSteps(repo) {
5096
+ return [
5097
+ `${MEASURE_SETUP_ACTION}${repo ? ` for ${repo}` : ""}, once:`,
5098
+ `- In Bridge Studio: Settings, For developers, ${MEASURE_SETUP_ACTION}. It opens a pull request that adds ${MEASURE_WORKFLOW_FILE}.`,
5099
+ `- Or in the repository: npx @bridge-ds/cli measure setup, then commit ${MEASURE_WORKFLOW_FILE}.`,
5100
+ `Once it is merged, the workflow measures the Storybook each time it changes and publishes ${DEFAULT_MEASURED_SPECS_FILE} to the ${MEASURE_BRANCH} branch.`
5101
+ ];
5102
+ }
5103
+ async function findMeasure(files, path = DEFAULT_MEASURED_SPECS_FILE) {
5104
+ const local = await files.repo(path);
5105
+ if (local) return { text: local, path };
5106
+ if (!files.branch) return void 0;
5107
+ const remote = await files.branch(path, MEASURE_BRANCH).catch(() => void 0);
5108
+ return remote ? { text: remote, path, branch: MEASURE_BRANCH } : void 0;
5109
+ }
5110
+ const measureFromWords = (m) => m.branch ? `${m.path} on ${m.branch}` : m.path;
5111
+ const measureDir = (path) => path.includes("/") ? path.replace(/\/[^/]*$/, "") : "";
5112
+ class MeasureMissingError extends Error {
5113
+ code = "measure-missing";
5114
+ steps;
5115
+ constructor(repo, extra = []) {
5116
+ const steps = [...measureSetupSteps(repo), ...extra];
5117
+ super([MEASURE_MISSING_TEXT, "", ...steps].join("\n"));
5118
+ this.name = "MeasureMissingError";
5119
+ this.steps = steps;
5120
+ }
5121
+ }
5122
+ const MEASURE_CLI_PACKAGE = "@bridge-ds/cli@^0.13.0";
5123
+ const MEASURE_FLAGS = "--variants --states rest,hover,active,disabled --tokens --shots --check";
5124
+ const MEASURE_DEFAULT_PATHS = ["src/**", "packages/**", ".storybook/**", "package.json", "package-lock.json"];
5125
+ function measureWorkflowOptionsFromConfig(config) {
5126
+ const c = config && typeof config === "object" ? config : {};
5127
+ const str2 = (v) => typeof v === "string" && v.trim() ? v.trim() : void 0;
5128
+ const storybook = str2(c.previews?.production?.storybook);
5129
+ const dir = str2(c.components?.dir)?.replace(/^\.?\/+|\/+$/g, "");
5130
+ const tokens = str2(c.tokens?.root)?.replace(/^\.?\/+|\/+$/g, "");
5131
+ const source = dir ? dir.includes("/") ? dir.replace(/\/[^/]*$/, "") : dir : void 0;
5132
+ const paths = [.../* @__PURE__ */ new Set([...source ? [`${source}/**`] : [], ...tokens ? [`${tokens}/**`] : [], ".storybook/**", "package.json", "package-lock.json"])];
5133
+ return {
5134
+ ...str2(c.repo?.defaultBranch) ? { defaultBranch: str2(c.repo?.defaultBranch) } : {},
5135
+ ...storybook && /^https:\/\//.test(storybook) ? { storybookUrl: storybook } : {},
5136
+ ...source || tokens ? { paths } : {}
5137
+ };
5138
+ }
5139
+ const q = (s) => `'${s.replace(/'/g, "''")}'`;
5140
+ const MEASURE_WORKFLOW_HEADER = `# Bridge measure: keeps the measure of your components fresh, so Claude can fix what differs in Figma.
5141
+ # Copy it to ${MEASURE_WORKFLOW_FILE} in your design system repository, or let Bridge add it: Studio, Settings,
5142
+ # For developers, Set up automatic measure (a pull request), or \`npx @bridge-ds/cli measure setup\`.
5143
+ #
5144
+ # It measures what your Storybook really draws (\`bridge specs --measure\`, headless Chrome): every variant at rest, on
5145
+ # hover, pressed and disabled, the CSS variables behind each value, and a screenshot of each. It publishes
5146
+ # ${DEFAULT_MEASURED_SPECS_FILE} and ${MEASURED_SHOTS_DIR}/ to the \`${MEASURE_BRANCH}\` branch, next to figma-fidelity.json (the Figma plugin's
5147
+ # Compare with code). The Claude fix tools, the Figma plugin ("Ask Claude to fix it") and Studio read it there.
5148
+ # It commits only when the measure changed.
5149
+ #
5150
+ # When: after each deploy of your Storybook (deployed Storybook), or on each push to the default branch that changes it
5151
+ # (built here), every Monday as a fallback, and by hand (Actions, Bridge measure, Run workflow).
5152
+ #
5153
+ # Token: only the GITHUB_TOKEN of this run, with \`contents: write\` to push the \`${MEASURE_BRANCH}\` branch. Your
5154
+ # default branch is never written. No secret to add.
5155
+ # A Storybook behind a login: set it up without an address (\`npx @bridge-ds/cli measure setup --build\`), the run then
5156
+ # builds Storybook itself. If installing needs a private registry, add an NPM_TOKEN repository secret (optional); the
5157
+ # install step passes it as NODE_AUTH_TOKEN.
5158
+ # Docs: https://bridge.floriangouloubi.com/docs/automatic-measure/`;
5159
+ function measureWorkflowYaml(o = {}) {
5160
+ const branch = o.defaultBranch?.trim() || "main";
5161
+ const url = o.storybookUrl?.trim().replace(/\/+$/, "") ?? "";
5162
+ const deployed = Boolean(url);
5163
+ const deployName = (o.deployName?.trim() || "storybook").toLowerCase();
5164
+ const paths = o.paths?.length ? o.paths : MEASURE_DEFAULT_PATHS;
5165
+ const install = o.install?.trim() || "npm ci";
5166
+ const build = o.buildCommand?.trim() || "npm run build-storybook";
5167
+ const dir = o.storybookDir?.trim().replace(/\/+$/, "") || "storybook-static";
5168
+ const cli = o.cli?.trim() || `npx --yes ${MEASURE_CLI_PACKAGE}`;
5169
+ const node = o.node?.trim() || "20";
5170
+ const nodeLine = node.startsWith("file:") ? `node-version-file: ${node.slice(5)}` : `node-version: ${q(node)}`;
5171
+ const pathList = paths.map((p2) => ` - ${q(p2)}`).join("\n");
5172
+ const deployIf = `(github.event_name == 'status' && github.event.state == 'success' && contains(github.event.branches.*.name, github.event.repository.default_branch) && contains(github.event.context, ${q(deployName)})) || (github.event_name == 'deployment_status' && github.event.deployment_status.state == 'success' && contains(github.event.deployment.environment, ${q(deployName)}) && !contains(github.event.deployment.environment, 'preview'))`;
5173
+ const triggers = deployed ? `on:
5174
+ # After each successful deploy of the Storybook from the default branch (Vercel and Netlify report it as a commit
5175
+ # status, others as a GitHub deployment).
5176
+ status:
5177
+ deployment_status:
5178
+ schedule:
5179
+ - cron: '17 5 * * 1' # Mondays, a fallback
5180
+ workflow_dispatch:` : `on:
5181
+ push:
5182
+ branches: [${branch}]
5183
+ # What changes the Storybook: a push touching these measures again.
5184
+ paths:
5185
+ ${pathList}
5186
+ schedule:
5187
+ - cron: '17 5 * * 1' # Mondays, a fallback
5188
+ workflow_dispatch:`;
5189
+ const jobIf = deployed ? ` # A manual run from another branch would measure another branch. A deploy: the Storybook's, from the default branch.
5190
+ if: >-
5191
+ (github.event_name == 'schedule')
5192
+ || (github.event_name == 'workflow_dispatch' && github.ref_name == github.event.repository.default_branch)
5193
+ || ${deployIf}` : ` # A manual run from another branch would measure another branch.
5194
+ if: github.event_name != 'workflow_dispatch' || github.ref_name == github.event.repository.default_branch`;
5195
+ const changedGuard = deployed ? `
5196
+ # A deploy runs on every change to the default branch: measure only when this one can change the Storybook.
5197
+ - name: Did the Storybook change?
5198
+ id: changed
5199
+ run: |
5200
+ if [ "$GITHUB_EVENT_NAME" = "status" ] || [ "$GITHUB_EVENT_NAME" = "deployment_status" ]; then
5201
+ if git rev-parse -q --verify HEAD^ > /dev/null && git diff --quiet HEAD^ HEAD -- ${paths.map(q).join(" ")}; then
5202
+ echo "Nothing that changes the Storybook in this deploy: the measure stays as it is."
5203
+ echo "skip=true" >> "$GITHUB_OUTPUT"
5204
+ fi
5205
+ fi
5206
+ ` : "";
5207
+ const whenStep = (s) => deployed ? s.replace(/^( {6}- name: .*\n)/, `$1 if: steps.changed.outputs.skip != 'true'
5208
+ `) : s;
5209
+ const storybookSteps = deployed ? "" : whenStep(` - name: Build Storybook
5210
+ run: ${build}
5211
+
5212
+ - name: Serve the build
5213
+ run: |
5214
+ python3 -m http.server 6006 --bind 127.0.0.1 --directory ${q(dir)} > /dev/null 2>&1 &
5215
+ for i in $(seq 1 30); do curl -sf http://127.0.0.1:6006/index.json > /dev/null && exit 0; sleep 1; done
5216
+ echo "Storybook did not answer on http://127.0.0.1:6006 (is ${dir} the build folder?)"; exit 1
5217
+
5218
+ `);
5219
+ const storybook = deployed ? url : "http://127.0.0.1:6006";
5220
+ const steps = ` - uses: actions/checkout@v4
5221
+ with:
5222
+ # A deploy status names its commit; other events run on the default branch head.
5223
+ ref: \${{ github.event.sha || github.sha }}
5224
+ fetch-depth: 2
5225
+ ${changedGuard}
5226
+ ${whenStep(` - name: Set up Node
5227
+ uses: actions/setup-node@v4
5228
+ with:
5229
+ ${nodeLine}
5230
+ cache: npm
5231
+ `)}
5232
+ ${whenStep(` - name: Install
5233
+ env:
5234
+ NODE_AUTH_TOKEN: \${{ secrets.NPM_TOKEN }}
5235
+ run: ${install}
5236
+ `)}${o.prepare ? `
5237
+ ${whenStep(` - name: Prepare
5238
+ run: ${o.prepare}
5239
+ `)}` : ""}
5240
+ ${storybookSteps}${whenStep(` - name: Measure the Storybook
5241
+ env:
5242
+ # ubuntu-latest ships Google Chrome; the measure drives it headless, no browser download.
5243
+ BRIDGE_CHROME_CHANNEL: chrome
5244
+ run: |
5245
+ set -o pipefail
5246
+ ${cli} specs --measure --storybook "$STORYBOOK_URL" ${MEASURE_FLAGS} --out "$RUNNER_TEMP/bridge-measure/${DEFAULT_MEASURED_SPECS_FILE}" | tail -n 40 | tee -a "$GITHUB_STEP_SUMMARY"
5247
+ `)}
5248
+ ${whenStep(` - name: Publish to the ${MEASURE_BRANCH} branch, if the measure changed
5249
+ env:
5250
+ OUT: \${{ runner.temp }}/bridge-measure
5251
+ run: |
5252
+ set -euo pipefail
5253
+ git config --global user.name "github-actions[bot]"
5254
+ git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
5255
+ dir="$RUNNER_TEMP/measure-branch"
5256
+ if git ls-remote --exit-code --heads origin "$MEASURE_BRANCH" > /dev/null; then
5257
+ git fetch -q --depth 1 origin "$MEASURE_BRANCH"
5258
+ git worktree add -q --detach "$dir" FETCH_HEAD
5259
+ else
5260
+ # Like the Figma plugin: the requests branch starts from the default branch.
5261
+ git worktree add -q --detach "$dir" HEAD
5262
+ fi
5263
+ # The same measure at another time is not a change: compare without the timestamps.
5264
+ same() { node -e 'const f=require("fs");const r=(p)=>{try{const j=JSON.parse(f.readFileSync(p,"utf8"));delete j.measuredAt;delete j.createdAt;return JSON.stringify(j)}catch{return null}};const a=r(process.argv[1]);process.exit(a!==null&&a===r(process.argv[2])?0:1)' "$1" "$2"; }
5265
+ if same "$dir/${DEFAULT_MEASURED_SPECS_FILE}" "$OUT/${DEFAULT_MEASURED_SPECS_FILE}" && { [ ! -f "$OUT/${MEASURED_SHOTS_DIR}/manifest.json" ] || same "$dir/${MEASURED_SHOTS_DIR}/manifest.json" "$OUT/${MEASURED_SHOTS_DIR}/manifest.json"; }; then
5266
+ echo "The measure did not change: nothing to publish." | tee -a "$GITHUB_STEP_SUMMARY"
5267
+ exit 0
5268
+ fi
5269
+ rm -rf "$dir/${DEFAULT_MEASURED_SPECS_FILE}" "$dir/${MEASURED_SHOTS_DIR}"
5270
+ cp "$OUT/${DEFAULT_MEASURED_SPECS_FILE}" "$dir/"
5271
+ if [ -d "$OUT/${MEASURED_SHOTS_DIR}" ]; then cp -R "$OUT/${MEASURED_SHOTS_DIR}" "$dir/"; fi
5272
+ cd "$dir"
5273
+ git add -A
5274
+ git commit -qm "bridge measure: \${GITHUB_SHA:0:7}"
5275
+ # The Figma plugin writes to this branch too: on a race, rebase and retry.
5276
+ for i in 1 2 3 4; do
5277
+ if git push -q origin "HEAD:refs/heads/$MEASURE_BRANCH"; then echo "Published to $MEASURE_BRANCH." | tee -a "$GITHUB_STEP_SUMMARY"; exit 0; fi
5278
+ sleep $((i * 3))
5279
+ git fetch -q --depth 20 origin "$MEASURE_BRANCH" && git rebase -q FETCH_HEAD
5280
+ done
5281
+ exit 1
5282
+ `)}`;
5283
+ return `${o.header ?? MEASURE_WORKFLOW_HEADER}
5284
+
5285
+ name: Bridge measure
5286
+
5287
+ ${triggers}
5288
+
5289
+ concurrency:
5290
+ # One measure at a time; a newer one replaces a waiting one.
5291
+ group: bridge-measure
5292
+ cancel-in-progress: false
5293
+
5294
+ permissions:
5295
+ contents: write # push to the ${MEASURE_BRANCH} branch only
5296
+
5297
+ env:
5298
+ MEASURE_BRANCH: ${MEASURE_BRANCH}
5299
+ # ${deployed ? "The deployed Storybook the measure reads." : "Storybook as this run builds and serves it (below)."}
5300
+ STORYBOOK_URL: ${q(storybook)}
5301
+
5302
+ jobs:
5303
+ measure:
5304
+ name: Measure the Storybook
5305
+ ${jobIf}
5306
+ runs-on: ubuntu-latest
5307
+ timeout-minutes: 120
5308
+ steps:
5309
+ ${steps.replace(/\n{3,}/g, "\n\n").replace(/\n+$/, "")}
5310
+ `;
5311
+ }
5312
+ const leadsPublishTier1On = (gov) => gov?.leadsPublishTier1 !== false;
5313
+ const CODE_TIER1 = [/\.stories\.[cm]?[jt]sx?$/, /\.test\.[cm]?[jt]sx?$/];
5314
+ function tier1Domain(paths) {
5315
+ return paths.some((p2) => CODE_TIER1.some((r) => r.test(p2))) ? "tech" : "design";
5316
+ }
5317
+ const norm = (x) => x.trim().replace(/^@/, "").toLowerCase();
5318
+ function canPublishTier1(x) {
5319
+ if (!x.enabled || x.tier !== 1 || !x.viewer) return false;
5320
+ if (x.admin) return true;
5321
+ const list = x.domain === "design" ? x.approvers.design : x.approvers.tech;
5322
+ return list.some((l) => !l.includes("/") && norm(l) === norm(x.viewer));
5323
+ }
5324
+ const PUBLISH_LABEL = "Publish";
5325
+ const TIER1_PUBLISH_HEADING = "**Bridge: published without review (Tier 1)**";
5326
+ const publishedWithoutReview = (name) => `Published by ${name.trim() || "a lead"} without review (Tier 1).`;
5327
+ function tier1PublishReviewBody(name, login) {
5328
+ return `${TIER1_PUBLISH_HEADING}
5329
+
5330
+ ${publishedWithoutReview(name)} @${norm(login)} leads this part of the design system and the team lets leads publish Tier 1 changes (governance.leadsPublishTier1). It merges once the automatic checks pass; a failing check sends it to review instead.`;
5331
+ }
5332
+ function tier1PublishedBy(body) {
5333
+ if (!body || !body.trimStart().startsWith(TIER1_PUBLISH_HEADING)) return null;
5334
+ const m = /Published by (.+?) without review \(Tier 1\)\./.exec(body);
5335
+ return m ? m[1] : null;
5336
+ }
5337
+ function tier1PublishOf(reviews, headSha) {
5338
+ for (const r of [...reviews ?? []].reverse()) {
5339
+ if (r.state !== "APPROVED") continue;
5340
+ if (headSha && r.commit_id && r.commit_id !== headSha) continue;
5341
+ const by3 = tier1PublishedBy(r.body);
5342
+ if (by3) return by3;
5343
+ }
5344
+ return null;
5345
+ }
5346
+ function tier1PublishSentence(enabled) {
5347
+ return enabled ? "Tier 1 (docs, guidance, token descriptions, story and test fixes): team admins and the reviewers of that part publish it without review; Bridge merges it once the checks pass." : "Tier 1 needs one owner approval: leads publishing small changes without review is off.";
5348
+ }
4405
5349
  const plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
4406
5350
  const names$1 = (files) => files.flatMap((f) => f.previous_filename ? [f.filename, f.previous_filename] : [f.filename]);
4407
5351
  function overlap(changeFiles, libraryFiles, isTokenFile) {
@@ -4737,192 +5681,6 @@ async function readCombineBranch(gh, pull, opts) {
4737
5681
  );
4738
5682
  return { number: pull.number, title: pull.title, tokens: true, edits: changeEdits(source) };
4739
5683
  }
4740
- const PRODUCT_FILE = /\.(css|scss|sass|less|tsx|jsx|ts|js|mjs|cjs|mts|cts|astro|vue|svelte)$/;
4741
- const SKIP_FILE = /\.d\.[cm]?ts$|\.(test|spec|stories|story)\.[cm]?[jt]sx?$|\.min\.(css|js)$/;
4742
- const PIECES_FILE_LIMIT = 40;
4743
- const cleanPath = (p2) => p2.trim().replace(/^\.?\/+|\/+$/g, "");
4744
- function productOf(products, pull, files) {
4745
- const product = productOfBranch(products, { labels: pull.labels, files });
4746
- if (product.key === defaultProduct(products).key) throw new GitHubError(422, `This branch is a library branch (${product.name}), not a product branch: missing pieces are checked on a branch started for a product.`);
4747
- return product;
4748
- }
4749
- async function readProductBranchOnGitHub(gh, pr, opts) {
4750
- const pull = await gh.getPullRequest(pr);
4751
- const cmp = await gh.compareDetailed(pull.baseRef, pull.headSha);
4752
- const product = productOf(opts.products, pull, cmp.files.map((f) => f.filename));
4753
- const dir = product.path ? `${cleanPath(product.path)}/` : "";
4754
- const changed = cmp.files.filter((f) => f.status !== "removed" && (!dir || f.filename.startsWith(dir)) && PRODUCT_FILE.test(f.filename) && !SKIP_FILE.test(f.filename) && !f.filename.split("/").includes("node_modules"));
4755
- const read = changed.slice(0, PIECES_FILE_LIMIT);
4756
- const start = cmp.mergeBase ?? pull.baseRef;
4757
- const files = await Promise.all(
4758
- read.map(async (f) => {
4759
- const [before, after] = await Promise.all([f.status === "added" ? Promise.resolve(void 0) : gh.getFileText(f.previous_filename ?? f.filename, start), gh.getFileText(f.filename, pull.headSha)]);
4760
- return { path: f.filename, before: before ?? null, after: after ?? null };
4761
- })
4762
- );
4763
- return { pull, product, files, skipped: changed.length - read.length, record: parsePiecesRecord(pull.body) };
4764
- }
4765
- async function recordPiecesOnGitHub(gh, pr, check) {
4766
- const pull = await gh.getPullRequest(pr);
4767
- const record = mergePiecesRecord(parsePiecesRecord(pull.body), { checkedAt: check.checkedAt ?? (/* @__PURE__ */ new Date()).toISOString(), headSha: check.headSha ?? pull.headSha, pieces: check.pieces, fixes: check.fixes });
4768
- await gh.updatePullRequest(pr, { body: withPiecesRecord(pull.body, record) });
4769
- return record;
4770
- }
4771
- const productWriter = (gh, branch) => isExplorationBranch(branch) ? gh.withBranchPrefix(EXPLORATION_PREFIX) : gh;
4772
- async function useLibraryPreviewOnGitHub(gh, input) {
4773
- const message = `chore(bridge): use the preview of ${input.libraryBranch} (#${input.libraryNumber})
4774
-
4775
- The product branch takes the library branch before it ships. Switch to the published version once it does.`;
4776
- try {
4777
- const r = await productWriter(gh, input.productBranch).mergeIntoBranch(input.productBranch, input.libraryBranch, message);
4778
- return { ...r, said: r.status === "merged" ? "Your branch now uses the preview of the library branch." : "Your branch already has the latest preview." };
4779
- } catch (err) {
4780
- if (err instanceof GitHubError && err.status === 409) return { status: "conflict", sha: null, said: `Your branch and the library branch change the same lines. A developer merges ${input.libraryBranch} into ${input.productBranch} by hand.` };
4781
- throw err;
4782
- }
4783
- }
4784
- function productBranchRef(pull, product) {
4785
- const name = /<!-- bridge-exploration (\{[\s\S]*?\}) -->/.exec(pull.body ?? "");
4786
- let title = pull.title.replace(/^[A-Z][A-Z0-9_]*-\d+\s+/, "").replace(/^Exploration:\s*/i, "");
4787
- let author = pull.author;
4788
- if (name) {
4789
- try {
4790
- const e = JSON.parse(name[1]);
4791
- if (e.name) title = e.name;
4792
- if (e.author) author = e.author;
4793
- } catch {
4794
- }
4795
- }
4796
- return { pr: pull.number, title: title.charAt(0).toUpperCase() + title.slice(1), branch: pull.headRef, url: pull.url, productKey: product.key, productName: product.name, ...author ? { author } : {} };
4797
- }
4798
- async function openLibraryBranchOnGitHub(gh, input) {
4799
- const pull = await gh.getPullRequest(input.pr);
4800
- const record = parsePiecesRecord(pull.body);
4801
- const piece = record?.pieces.find((p2) => p2.id === input.piece);
4802
- if (!record || !piece) throw new GitHubError(404, record ? `No piece "${input.piece}" on #${input.pr}. Pieces: ${record.pieces.map((p2) => p2.id).join(", ") || "none"}.` : `#${input.pr} was not checked yet: find its missing pieces first (bridge_missing_pieces with record, or \`bridge pieces ${input.pr} --record\`).`);
4803
- const product = productOf(input.products, pull, await gh.listPullRequestFiles(input.pr).then((f) => f.map((x) => x.filename), () => []));
4804
- const mode = input.mode ?? "direct";
4805
- const plan = libraryBranchPlan(piece, productBranchRef(pull, product), { source: input.source, requestedBy: input.openedBy, mode });
4806
- const existing = record.links.find((l) => l.piece === piece.id);
4807
- if (existing) return { status: "already-linked", plan, link: existing, preview: null, warnings: [] };
4808
- if (input.dryRun) return { status: "dry-run", plan, link: null, preview: null, warnings: [] };
4809
- if (!gh.canWrite) throw new GitHubError(401, mode === "suggest" ? "Suggesting a library change needs a GitHub token (issues: write)." : "Opening a library branch needs a GitHub token (contents, pull requests and issues: write).");
4810
- const warnings = [];
4811
- const at2 = (/* @__PURE__ */ new Date()).toISOString();
4812
- const who = { ...input.openedBy ? { openedBy: input.openedBy } : {}, ...input.agent ? { agent: input.agent } : {} };
4813
- let link;
4814
- if (plan.kind === "token-change") {
4815
- if (!input.writeTokens) throw new GitHubError(422, "This piece is a token: open it where the token files are read (Bridge Studio, bridge_open_library_branch or `bridge pieces open`).");
4816
- const r = await input.writeTokens(plan, { mode });
4817
- link = r.status === "suggested" ? { piece: piece.id, kind: "suggestion", number: r.number, url: r.url, title: plan.title, at: at2, ...who } : { piece: piece.id, kind: "token-change", number: r.number, url: r.url, title: plan.title, branch: r.branch, at: at2, ...who };
4818
- } else if (plan.kind === "exploration") {
4819
- const r = await startExplorationOnGitHub(gh, { ...plan.exploration, ...input.openedBy ? { author: input.openedBy } : {}, ...input.agent ? { agent: input.agent } : {} }, { base: input.base, ...input.exploration, labels: [NEEDED_BY_LABEL] });
4820
- link = { piece: piece.id, kind: "exploration", number: r.pr, url: r.url, title: plan.title, branch: r.branch, at: at2, ...who };
4821
- } else {
4822
- const issue = composeRequestIssue(plan.request, { labels: [NEEDED_BY_LABEL] });
4823
- const r = await gh.createIssue(issue);
4824
- link = { piece: piece.id, kind: "request", number: r.number, url: r.url, title: plan.title, at: at2, ...who };
4825
- }
4826
- try {
4827
- if (link.kind === "request" || link.kind === "suggestion") {
4828
- const issue = await gh.getIssue(link.number);
4829
- await gh.updateIssue(link.number, { body: withNeededBy(issue.body, plan.neededBy) });
4830
- if (link.kind === "suggestion" && !issue.labels.includes(NEEDED_BY_LABEL)) await gh.addLabels(link.number, [NEEDED_BY_LABEL]);
4831
- } else {
4832
- const lib = await gh.getPullRequest(link.number);
4833
- await gh.updatePullRequest(link.number, { body: withNeededBy(lib.body, plan.neededBy) });
4834
- await gh.addLabels(link.number, [NEEDED_BY_LABEL]);
4835
- }
4836
- } catch (err) {
4837
- if (err instanceof GitHubError && err.auth === "rejected") throw err;
4838
- warnings.push(`The library side does not say who needs it yet (${err.message}). Add "Needed by #${input.pr}" to #${link.number} on GitHub.`);
4839
- }
4840
- try {
4841
- const fresh = await gh.getPullRequest(input.pr);
4842
- const next = withLink(parsePiecesRecord(fresh.body) ?? record, link);
4843
- await gh.updatePullRequest(input.pr, { body: withPiecesRecord(fresh.body, next) });
4844
- } catch (err) {
4845
- if (mode !== "suggest") throw err;
4846
- warnings.push(`#${input.pr} does not list #${link.number} yet (${err.message}). Add "Library: #${link.number}" to #${input.pr} on GitHub, so this piece is not sent twice.`);
4847
- }
4848
- let preview = null;
4849
- if ((input.usePreview ?? true) && link.kind === "token-change" && link.branch) {
4850
- preview = await useLibraryPreviewOnGitHub(gh, { productBranch: pull.headRef, libraryBranch: link.branch, libraryNumber: link.number }).catch((err) => {
4851
- warnings.push(`Your branch does not use the preview yet: ${err.message}`);
4852
- return null;
4853
- });
4854
- }
4855
- return { status: link.kind === "suggestion" ? "suggested" : "opened", plan, link, preview, warnings };
4856
- }
4857
- const isNote = (path) => path.startsWith(`${EXPLORATION_NOTES_DIR}/`);
4858
- async function linkedLibraryBranchOnGitHub(gh, link, product, piece) {
4859
- const nearest = piece?.nearest?.label;
4860
- if (link.kind === "suggestion") {
4861
- const issue = await gh.getIssue(link.number);
4862
- const cached = suggestionStateFromLabels(issue.labels);
4863
- const s = suggestionStatus({ state: issue.state, stateReason: issue.stateReason, applied: cached === "applying", review: cached === "applying" ? void 0 : cached });
4864
- const status2 = linkStatus({ kind: "suggestion", status: s });
4865
- const lifecycle2 = s === "applied" ? "merged" : issue.state === "closed" ? "closed" : "open";
4866
- const behindMain2 = lifecycle2 === "merged" ? await gh.compare(product.baseRef, product.headSha).then((c) => c.behindBy, () => null) : null;
4867
- const preview2 = libraryPreviewState({ kind: "suggestion", lifecycle: lifecycle2, hasChanges: false, missing: null, total: null, behindMain: behindMain2, nearest });
4868
- return { link, piece, status: status2, preview: preview2, people: status2.people };
4869
- }
4870
- if (link.kind === "request") {
4871
- const issue = await gh.getIssue(link.number);
4872
- const status2 = linkStatus({ kind: "request", state: issue.state, stateReason: issue.stateReason });
4873
- const lifecycle2 = issue.state === "closed" ? issue.stateReason === "not_planned" ? "closed" : "merged" : "open";
4874
- const behindMain2 = lifecycle2 === "merged" ? await gh.compare(product.baseRef, product.headSha).then((c) => c.behindBy, () => null) : null;
4875
- const preview2 = libraryPreviewState({ kind: "request", lifecycle: lifecycle2, hasChanges: false, missing: null, total: null, behindMain: behindMain2, nearest });
4876
- return { link, piece, status: status2, preview: preview2, people: status2.people };
4877
- }
4878
- const lib = await gh.getPullRequest(link.number);
4879
- const reviews = await gh.listReviews(link.number).catch(() => []);
4880
- const status = linkStatus({ kind: link.kind, state: lib.state, draft: lib.draft, labels: lib.labels, requestedReviewers: lib.requestedReviewers, reviews: reviews.map((r) => ({ user: r.user, state: r.state })) });
4881
- const lifecycle = lib.state === "merged" ? "merged" : lib.state === "open" ? "open" : "closed";
4882
- let missing = null;
4883
- let total = null;
4884
- let behindMain = null;
4885
- let hasChanges = true;
4886
- if (lifecycle === "open") {
4887
- const [files, own, inProduct] = await Promise.all([
4888
- gh.listPullRequestFiles(link.number).catch(() => null),
4889
- gh.compare(lib.baseRef, lib.headSha).catch(() => null),
4890
- gh.compare(product.headSha, lib.headSha).catch(() => null)
4891
- ]);
4892
- hasChanges = files ? files.some((f) => !isNote(f.filename)) : true;
4893
- total = own?.aheadBy ?? null;
4894
- missing = inProduct?.aheadBy ?? null;
4895
- } else if (lifecycle === "merged") {
4896
- behindMain = await gh.compare(product.baseRef, product.headSha).then((c) => c.behindBy, () => null);
4897
- }
4898
- const preview = libraryPreviewState({ kind: link.kind, lifecycle, hasChanges, missing, total, behindMain, nearest });
4899
- return { link, piece, status, preview, people: status.people };
4900
- }
4901
- async function productBranchViewOnGitHub(gh, pr, opts) {
4902
- const pull = await gh.getPullRequest(pr);
4903
- const files = await gh.listPullRequestFiles(pr).then((f) => f.map((x) => x.filename), () => []);
4904
- const product = productOf(opts.products, pull, files);
4905
- const record = parsePiecesRecord(pull.body);
4906
- const linked = await Promise.all(
4907
- (record?.links ?? []).map((l) => linkedLibraryBranchOnGitHub(gh, l, { headSha: pull.headSha, baseRef: pull.baseRef }, record?.pieces.find((p2) => p2.id === l.piece)))
4908
- );
4909
- const open = (record?.pieces ?? []).filter((p2) => !record.links.some((l) => l.piece === p2.id));
4910
- return { pull, product, record, sentence: productBranchSentence(record, { viewer: opts.viewer, agentName: opts.agentName }), linked, open };
4911
- }
4912
- async function switchToPublishedOnGitHub(gh, pr) {
4913
- if (!gh.canWrite) throw new GitHubError(401, "Switching needs a GitHub token (contents and pull requests: write).");
4914
- const pull = await gh.getPullRequest(pr);
4915
- if (pull.state !== "open") throw new GitHubError(422, `#${pr} is ${pull.state}: only an open branch switches.`);
4916
- const cmp = await gh.compare(pull.baseRef, pull.headSha);
4917
- if (cmp.behindBy === 0) return { status: "up-to-date", said: "Your branch already uses the published version." };
4918
- try {
4919
- await gh.updatePullRequestBranch(pr, pull.headSha);
4920
- } catch (err) {
4921
- if (err instanceof GitHubError && err.status === 422) throw new GitHubError(422, `Your branch and ${pull.baseRef} change the same lines, so Bridge cannot switch it alone. Use "Update from main" to pick the token values, or ask a developer.`, err.detail);
4922
- throw err;
4923
- }
4924
- return { status: "switched", said: `Your branch now uses the published version from ${pull.baseRef}.` };
4925
- }
4926
5684
  const issueKey = (i) => `${i.code}|${i.path ?? ""}|${i.file ?? ""}|${i.message}`;
4927
5685
  function planTokenProposal(input) {
4928
5686
  const { before, config } = input;
@@ -4939,15 +5697,15 @@ function planTokenProposal(input) {
4939
5697
  const tier = classifyLayered(changes, [], config);
4940
5698
  const summary = input.summary?.trim() || input.title;
4941
5699
  const body = [composeProposalBody({ summary, changes, contrast, impact, tier, validationErrors: newErrors.length, tool: input.tool ?? "Bridge MCP", ...input.fromFigma ? { fromFigma: true } : {} }), ...input.bodyExtra ?? []].filter(Boolean).join("\n\n");
4942
- const slug = slugify(input.title, 48);
4943
- const changesetPath = `${config.changesets?.dir ?? ".changeset"}/bridge-${slug}.md`;
5700
+ const slug2 = slugify(input.title, 48);
5701
+ const changesetPath = `${config.changesets?.dir ?? ".changeset"}/bridge-${slug2}.md`;
4944
5702
  const touched = r.touched.map((f) => after.repoPath(f));
4945
5703
  const files = [
4946
5704
  ...r.touched.map((f) => ({ path: after.repoPath(f), content: after.files.get(f) })),
4947
5705
  ...config.changesets ? [{ path: changesetPath, content: formatChangeset(config.changesets.package, tier.bump, summary) }] : []
4948
5706
  ];
4949
5707
  const blocked = newErrors.length || newFailures.length ? [newErrors.length ? `${newErrors.length} new validation error(s)` : "", newFailures.length ? `${newFailures.length} contrast pair(s) would fail` : ""].filter(Boolean).join(" and ") : null;
4950
- return { title: input.title, summary, edits: input.edits, before, after, changes, tier, contrast, newFailures, newErrors, impact, notes: r.notes, touched, files, body, slug, blocked };
5708
+ return { title: input.title, summary, edits: input.edits, before, after, changes, tier, contrast, newFailures, newErrors, impact, notes: r.notes, touched, files, body, slug: slug2, blocked };
4951
5709
  }
4952
5710
  async function writeTokenProposalOnGitHub(gh, plan, opts) {
4953
5711
  if (plan.blocked) throw new Error(`Not opened: ${plan.blocked}. Bridge never opens a branch that breaks the gate.`);
@@ -5656,6 +6414,7 @@ function siteRepoFromConfig(config, raw, fallback = REPO_MODEL_DEFAULTS.repo) {
5656
6414
  override: g.override ? { ...g.override } : { ...DEFAULT_OVERRIDE },
5657
6415
  rights: g.rights ?? null,
5658
6416
  soloOwnerCanApproveBoth: g.soloOwnerCanApproveBoth === true,
6417
+ leadsPublishTier1: g.leadsPublishTier1 !== false,
5659
6418
  visualImpact: { mode: config.visualImpact?.mode === "on-demand" ? "on-demand" : "auto" },
5660
6419
  tokensRoot: config.tokens.root,
5661
6420
  integrations: config.integrations ?? {},
@@ -6123,14 +6882,14 @@ const semverDesc = (a2, b) => {
6123
6882
  for (let i = 0; i < 3; i++) if (pa[i] !== pb[i]) return pb[i] - pa[i];
6124
6883
  return 0;
6125
6884
  };
6126
- function groupReleases(sections, dateOf, libraryPackage = "@bridge-ds/react") {
6885
+ function groupReleases(sections, dateOf2, libraryPackage = "@bridge-ds/react") {
6127
6886
  const all = sections;
6128
6887
  const parent = all.map((_, i) => i);
6129
6888
  const find = (i) => parent[i] === i ? i : parent[i] = find(parent[i]);
6130
6889
  const union = (a2, b) => {
6131
6890
  parent[find(a2)] = find(b);
6132
6891
  };
6133
- const dates = all.map((s) => dateOf(s.pkg, s.version));
6892
+ const dates = all.map((s) => dateOf2(s.pkg, s.version));
6134
6893
  const byKey = /* @__PURE__ */ new Map();
6135
6894
  all.forEach((s, i) => {
6136
6895
  const keys = [...dates[i] ? [`d:${dates[i]}`] : [], ...s.notes.map((n) => `n:${n.id}`)];
@@ -6563,8 +7322,8 @@ function activityKey(url) {
6563
7322
  parts[2] = parts[2].toLowerCase();
6564
7323
  parts[3] = parts[3].toLowerCase();
6565
7324
  } else if (parts[1] === "users" && parts.length > 2) parts[2] = parts[2].toLowerCase();
6566
- const q = [...u.searchParams].sort(([a2, av2], [b, bv3]) => a2 === b ? av2 < bv3 ? -1 : 1 : a2 < b ? -1 : 1);
6567
- const search = q.length ? `?${new URLSearchParams(q).toString()}` : "";
7325
+ const q2 = [...u.searchParams].sort(([a2, av2], [b, bv3]) => a2 === b ? av2 < bv3 ? -1 : 1 : a2 < b ? -1 : 1);
7326
+ const search = q2.length ? `?${new URLSearchParams(q2).toString()}` : "";
6568
7327
  return parts.join("/") + search;
6569
7328
  }
6570
7329
  const KEPT_URLS = /* @__PURE__ */ new Set(["html_url", "avatar_url", "target_url", "details_url", "environment_url", "raw_url"]);
@@ -7386,8 +8145,8 @@ async function libraryArrivalOnGitHub(gh, n) {
7386
8145
  return libraryArrival({ state: pr.state, mergeable: pr.mergeable, ci: ci3, changesRequested: approvals.changesRequested.length });
7387
8146
  }
7388
8147
  export {
7389
- am as AAA_MIN,
7390
- an as AA_MIN,
8148
+ ar as AAA_MIN,
8149
+ as as AA_MIN,
7391
8150
  ACTIVITY_FILE,
7392
8151
  ACTIVITY_LIMITS,
7393
8152
  ACTIVITY_MAX_AGE_MS,
@@ -7407,39 +8166,41 @@ export {
7407
8166
  BUILD_STALE_DAYS,
7408
8167
  BUILD_STATUS_LABEL,
7409
8168
  BUILD_STEPS,
7410
- aE2 as BULK_LIMIT,
8169
+ aL2 as BULK_LIMIT,
7411
8170
  CANT_CHECK_COPY,
7412
8171
  CHANGELOG_DETAIL_MAX,
7413
- ao as CHANGES_MODES,
7414
- aF2 as CI_STATE_LABEL,
8172
+ at as CHANGES_MODES,
8173
+ aM2 as CI_STATE_LABEL,
7415
8174
  COLOR_FIELDS,
7416
8175
  COMBINE_COMPARE_LIMIT,
7417
8176
  COMPARE_FILE_LIMIT,
7418
- aG2 as COMPONENT_SPECS_VERSION,
8177
+ aN2 as COMPONENT_SPECS_VERSION,
7419
8178
  CONFIG_FILE,
7420
- aH2 as CONFLICT_REASON,
7421
- ap as CONTRAST_CELL_MAX,
7422
- aq as CONTRAST_CHECKS_FILE,
8179
+ aO2 as CONFLICT_REASON,
8180
+ CONTINUE_PHRASE,
8181
+ au as CONTRAST_CELL_MAX,
8182
+ av as CONTRAST_CHECKS_FILE,
7423
8183
  CONTRAST_THRESHOLDS,
7424
8184
  ConfigError,
7425
8185
  DEFAULT_CHANGES_MODE,
7426
- aI2 as DEFAULT_COMPONENT_SPECS_FILE,
8186
+ aP2 as DEFAULT_COMPONENT_SPECS_FILE,
7427
8187
  DEFAULT_FIDELITY_RUN_FILE,
7428
8188
  DEFAULT_FIGMA_FIXES_FILE,
7429
- ar as DEFAULT_GOVERNANCE,
8189
+ aw as DEFAULT_GOVERNANCE,
8190
+ DEFAULT_MEASURED_SPECS_FILE,
7430
8191
  DEFAULT_OVERRIDE,
7431
8192
  DEFAULT_PRODUCTS,
7432
- aJ2 as DEFAULT_PROTECTED_BRANCHES,
7433
- as as DEFAULT_RIGHTS,
8193
+ aQ2 as DEFAULT_PROTECTED_BRANCHES,
8194
+ ax as DEFAULT_RIGHTS,
7434
8195
  DEFAULT_TOKEN_PATHS,
7435
- at as DEFAULT_VISUAL_IMPACT,
8196
+ ay as DEFAULT_VISUAL_IMPACT,
7436
8197
  DEMO_RELEASES,
7437
- aK2 as DOCS_CHANGE_BUMP,
7438
- aL2 as DOCS_CLI_ORIGIN,
8198
+ aR2 as DOCS_CHANGE_BUMP,
8199
+ aS2 as DOCS_CLI_ORIGIN,
7439
8200
  DOCS_DRAFT_MODEL,
7440
- aM2 as DOCS_EDITOR_ORIGIN,
7441
- aN2 as DOCS_KEYS,
7442
- aO2 as DOCS_MCP_ORIGIN,
8201
+ aT2 as DOCS_EDITOR_ORIGIN,
8202
+ aU2 as DOCS_KEYS,
8203
+ aV2 as DOCS_MCP_ORIGIN,
7443
8204
  DOCS_SELECTION_MODES,
7444
8205
  DRAFTED_BY_CLAUDE_LABEL,
7445
8206
  DRAFTS_DIR,
@@ -7452,19 +8213,24 @@ export {
7452
8213
  DRAFT_SUMMARY_SENTENCES,
7453
8214
  DRAFT_TITLE_MAX,
7454
8215
  DRAFT_VOICE_RULES,
7455
- au as DTCG_TYPES,
8216
+ az as DTCG_TYPES,
7456
8217
  DraftPoller,
7457
- aP2 as EXPLORATION_CHECK,
8218
+ EXPLORATION_ARCHIVED_LABEL,
8219
+ aW2 as EXPLORATION_CHECK,
7458
8220
  EXPLORATION_LABEL,
7459
8221
  EXPLORATION_NOTES_DIR,
7460
8222
  EXPLORATION_PREFIX,
7461
- aQ2 as EXPLORATION_STATUS_LABEL,
7462
- aR2 as EXPLORATION_TIER_LABEL,
7463
- aS2 as EXPLORATION_USE_CASES,
8223
+ aX2 as EXPLORATION_READY_LABEL,
8224
+ aY2 as EXPLORATION_SLUG_MAX,
8225
+ aZ2 as EXPLORATION_STARTING_POINT,
8226
+ a_2 as EXPLORATION_STATUS_LABEL,
8227
+ a$2 as EXPLORATION_TIER_LABEL,
8228
+ b02 as EXPLORATION_USE_CASES,
7464
8229
  EXT_ID,
7465
8230
  EXT_SCOPES,
7466
8231
  FIDELITY_DEFAULTS,
7467
8232
  FIGMA_FILL_WIDTH_TOKEN,
8233
+ FIGMA_FIXES_LABEL,
7468
8234
  FIGMA_FIXES_VERSION,
7469
8235
  FIGMA_REFERENCES_BRANCH,
7470
8236
  FIGMA_REFERENCES_DIR,
@@ -7475,53 +8241,64 @@ export {
7475
8241
  FIX_LOOP_MAX_ROUNDS,
7476
8242
  FIX_LOOP_MODEL,
7477
8243
  FIX_SCHEMA_SUMMARY,
7478
- aT2 as FORMER_MEMBER,
8244
+ b12 as FORMER_MEMBER,
7479
8245
  FROM_FIGMA_LABEL,
7480
8246
  FigmaFixesError,
7481
8247
  GAP_CASE_VERSION,
7482
8248
  GitHubClient,
7483
8249
  GitHubError,
7484
8250
  HISTORY_MAX_CHANGES,
7485
- av as JsonParseError,
8251
+ aA as JsonParseError,
7486
8252
  LIGHTNESS,
7487
- aw as LINK_STATUS_TONE,
8253
+ aB as LINK_STATUS_TONE,
7488
8254
  LLMS_MAX_TOKENS,
7489
- aU2 as LOOK_AGAIN_STATUS_LABEL,
8255
+ b22 as LOOK_AGAIN_STATUS_LABEL,
7490
8256
  LayeredTokenSet,
7491
8257
  MAX_TOGETHER,
8258
+ MEASURED_SHOTS_DIR,
8259
+ MEASURE_BRANCH,
8260
+ MEASURE_CLI_PACKAGE,
8261
+ MEASURE_DEFAULT_PATHS,
8262
+ MEASURE_FLAGS,
8263
+ MEASURE_MISSING_TEXT,
8264
+ MEASURE_SETUP_ACTION,
8265
+ MEASURE_WORKFLOW_FILE,
8266
+ MEASURE_WORKFLOW_HEADER,
7492
8267
  MIN_TOGETHER,
7493
- aV2 as MeasuredSpecsError,
8268
+ MeasureMissingError,
8269
+ b32 as MeasuredSpecsError,
7494
8270
  NEEDED_BY_LABEL,
7495
8271
  NEUTRAL_ANCHORS,
7496
8272
  OVERRIDE_HEADING,
7497
- ax as OWNER_GROUPS,
8273
+ aC as OWNER_GROUPS,
7498
8274
  OverrideMergeError,
7499
8275
  PIECES_FILE_LIMIT,
7500
8276
  PIN_TOLERANCE,
7501
- ay as PREVIEW_APP_LABELS,
7502
- az as PREVIEW_COMMENT_MARKER,
7503
- aA as PREVIEW_PLACEHOLDERS,
7504
- aB as PRIMITIVE_MODE,
7505
- aC as PRODUCT_LABEL_PREFIX,
8277
+ aD as PREVIEW_APP_LABELS,
8278
+ aE as PREVIEW_COMMENT_MARKER,
8279
+ aF as PREVIEW_PLACEHOLDERS,
8280
+ aG as PRIMITIVE_MODE,
8281
+ aH as PRODUCT_LABEL_PREFIX,
7506
8282
  PROSE_KEYS,
7507
8283
  PROTECTION_PARTS,
7508
8284
  PROTECTION_PART_PHRASE,
8285
+ PUBLISH_LABEL,
7509
8286
  PUBLISH_ROUTE_COPY,
7510
- aW2 as PULL_STATE_PILL,
7511
- aX2 as ProtectedBranchError,
8287
+ b42 as PULL_STATE_PILL,
8288
+ b52 as ProtectedBranchError,
7512
8289
  RADIUS_PRESETS,
7513
- aY2 as REDUCED_MOTION_NOTE,
8290
+ b62 as REDUCED_MOTION_NOTE,
7514
8291
  REPO_MODEL_DEFAULTS,
7515
8292
  REQUESTS_BRANCH,
7516
8293
  REQUEST_KINDS,
7517
8294
  REQUEST_LABEL,
7518
- aZ2 as REQUEST_STATUS_LABEL,
7519
- a_2 as REVIEW_STATUS_LABEL,
7520
- a$2 as REVIEW_STATUS_ORDER,
7521
- aD as RIGHTS,
7522
- aE as RIGHT_LABEL,
8295
+ b72 as REQUEST_STATUS_LABEL,
8296
+ b82 as REVIEW_STATUS_LABEL,
8297
+ b92 as REVIEW_STATUS_ORDER,
8298
+ aI as RIGHTS,
8299
+ aJ as RIGHT_LABEL,
7523
8300
  RISK_LABELS,
7524
- aF as RISK_TIER,
8301
+ aK as RISK_TIER,
7525
8302
  SHARE_EXPLAIN,
7526
8303
  SIMILARITY_THRESHOLD,
7527
8304
  SINGLE_MODE,
@@ -7536,36 +8313,38 @@ export {
7536
8313
  SOURCE_FIGMA_LABEL,
7537
8314
  SPEC_FIELDS,
7538
8315
  STEPS,
7539
- b02 as SUGGESTION_APPLIED_MARK,
7540
- b12 as SUGGESTION_APPLY_EXIT,
7541
- b22 as SUGGESTION_FINAL_LABELS,
7542
- b32 as SUGGESTION_KINDS,
7543
- b42 as SUGGESTION_LABEL,
7544
- b52 as SUGGESTION_LIMITS,
7545
- b62 as SUGGESTION_REVIEW_MARK,
8316
+ ba2 as SUGGESTION_APPLIED_MARK,
8317
+ bb2 as SUGGESTION_APPLY_EXIT,
8318
+ bc2 as SUGGESTION_FINAL_LABELS,
8319
+ bd2 as SUGGESTION_KINDS,
8320
+ be2 as SUGGESTION_LABEL,
8321
+ bf2 as SUGGESTION_LIMITS,
8322
+ bg2 as SUGGESTION_OVERRIDE_MARK,
8323
+ bh2 as SUGGESTION_REVIEW_MARK,
7546
8324
  SUGGESTION_SCHEMA,
7547
- b72 as SUGGESTION_STATE_LABELS,
7548
- b82 as SUGGESTION_STATUS_LABEL,
7549
- b92 as SUGGESTION_STATUS_LABELS,
7550
- ba2 as SUGGESTION_TOO_LARGE,
8325
+ bi2 as SUGGESTION_STATE_LABELS,
8326
+ bj2 as SUGGESTION_STATUS_LABEL,
8327
+ bk2 as SUGGESTION_STATUS_LABELS,
8328
+ bl2 as SUGGESTION_TOO_LARGE,
7551
8329
  SnapshotError,
7552
- bb2 as StorybookIndexError,
7553
- bc2 as SuggestionError,
7554
- aG as TEAM_NAME_MAX,
8330
+ bm2 as StorybookIndexError,
8331
+ bn2 as SuggestionError,
8332
+ aL as TEAM_NAME_MAX,
8333
+ TIER1_PUBLISH_HEADING,
7555
8334
  TIER_AXIS,
7556
8335
  TIER_NAMES,
7557
- aH as TIER_ORDER,
7558
- bd2 as TOKEN_REJECTED_MESSAGE,
7559
- be2 as TOKEN_SETTINGS_URL,
7560
- bf2 as TRIGGER_LABELS,
7561
- aI as TeamDirectory,
7562
- bg2 as UNRELEASED,
7563
- aJ as VISUAL_CHECKS_DOCS,
7564
- aK as VISUAL_CHECK_LABEL,
7565
- aL as VISUAL_WORKFLOW_FILES,
8336
+ aM as TIER_ORDER,
8337
+ bo2 as TOKEN_REJECTED_MESSAGE,
8338
+ bp2 as TOKEN_SETTINGS_URL,
8339
+ bq2 as TRIGGER_LABELS,
8340
+ aN as TeamDirectory,
8341
+ br2 as UNRELEASED,
8342
+ aO as VISUAL_CHECKS_DOCS,
8343
+ aP as VISUAL_CHECK_LABEL,
8344
+ aQ as VISUAL_WORKFLOW_FILES,
7566
8345
  WAITING_FOR_CLAUDE_LABEL,
7567
- bh2 as acceptDocsDraft,
7568
- bi2 as acceptDocsText,
8346
+ bs2 as acceptDocsDraft,
8347
+ bt2 as acceptDocsText,
7569
8348
  accessAtLeast,
7570
8349
  accessState,
7571
8350
  activityAge,
@@ -7576,47 +8355,50 @@ export {
7576
8355
  activityRawUrl,
7577
8356
  activityShaper,
7578
8357
  activityTreeFilter,
8358
+ actorName,
7579
8359
  addBrandToConfigText,
7580
8360
  addedComment,
7581
- aM as affectedContrast,
7582
- aN as aliasOf,
8361
+ aR as affectedContrast,
8362
+ aS as aliasOf,
7583
8363
  aliasTarget,
7584
- bj2 as allowedMergeMethods,
7585
- bk2 as anatomyLabel,
8364
+ bu2 as allowedMergeMethods,
8365
+ bv2 as anatomyLabel,
7586
8366
  anchoredRamp,
7587
8367
  anthropicReplyText,
7588
8368
  anthropicRequest,
7589
- aO as appForProject,
7590
- aP as appUrl,
8369
+ aT as appForProject,
8370
+ aU as appUrl,
7591
8371
  appliedFixLines,
7592
- bl2 as appliedStatus,
8372
+ bw2 as appliedStatus,
7593
8373
  applyConfirmedRenames,
7594
8374
  applyDocsSections,
7595
8375
  applyFixes,
7596
8376
  applyTokenEdits,
7597
8377
  applyTokenMerge,
7598
- bm2 as approvedSuggestionNumber,
7599
- bn2 as approvedSuggestionOnGitHub,
7600
- aQ as approverEntries,
7601
- aR as approverLists,
8378
+ bx2 as approvedSuggestionNumber,
8379
+ by2 as approvedSuggestionOnGitHub,
8380
+ aV as approverEntries,
8381
+ aW as approverLists,
8382
+ archiveExplorationOnGitHub,
7602
8383
  askOthersToReviewOnGitHub,
7603
8384
  assembleBuildRequests,
7604
- bo2 as assertSafeBranch,
7605
- bp2 as assignRequestOnGitHub,
7606
- bq2 as assignVersions,
8385
+ bz2 as assertSafeBranch,
8386
+ bA2 as assignRequestOnGitHub,
8387
+ bB2 as assignVersions,
7607
8388
  auditId,
7608
- br2 as base64ToUtf8,
7609
- aS as baseBranchOf,
8389
+ bC2 as base64ToUtf8,
8390
+ baseBranchOf,
7610
8391
  bellChroma,
7611
8392
  boundedCombos,
7612
- aT as branchName,
8393
+ boxSized,
8394
+ aX as branchName,
7613
8395
  branchPreviewUrls,
7614
- aU as branchSlug,
7615
- bs2 as branchVerdict,
8396
+ aY as branchSlug,
8397
+ bD2 as branchVerdict,
7616
8398
  branchWithJiraKey,
7617
8399
  brandDisplayName,
7618
8400
  brandPreviewVariables,
7619
- bt2 as bridgeMcpInstallCommand,
8401
+ bE2 as bridgeMcpInstallCommand,
7620
8402
  buildFidelityReport,
7621
8403
  buildGapCase,
7622
8404
  buildHistoryEntry,
@@ -7631,30 +8413,32 @@ export {
7631
8413
  buildTokenSet,
7632
8414
  buildUsageIndex,
7633
8415
  canAddTo,
7634
- bu2 as canOverride,
8416
+ bF2 as canOverride,
8417
+ canPublishTier1,
7635
8418
  canShare,
7636
- aV as canShip,
7637
- bv2 as canonicalJson,
8419
+ aZ as canShip,
8420
+ bG2 as canonicalJson,
7638
8421
  capHistory,
7639
8422
  changeEdits,
7640
- aW as changeKind,
8423
+ a_ as changeKind,
7641
8424
  changeList,
7642
- bw2 as changeTable,
7643
- aX as changedModes,
7644
- aY as changesMode,
7645
- bx2 as changesetBump,
8425
+ bH2 as changeTable,
8426
+ a$ as changedModes,
8427
+ changedThings,
8428
+ b0 as changesMode,
8429
+ bI2 as changesetBump,
7646
8430
  checkBrandSlug,
7647
8431
  checkLayeredPair,
7648
8432
  choiceFor,
7649
- by2 as classifyAuth,
8433
+ bJ2 as classifyAuth,
7650
8434
  classifyLayered,
7651
8435
  classifyRisk,
7652
- bz2 as claudeStage,
7653
- bA2 as cleanDocs,
8436
+ bK2 as claudeStage,
8437
+ bL2 as cleanDocs,
7654
8438
  cleanDocsText,
7655
8439
  cleanDraftText,
7656
- aZ as clearRawDeep,
7657
- bB2 as clearSharedEtags,
8440
+ b1 as clearRawDeep,
8441
+ bM2 as clearSharedEtags,
7658
8442
  closeExplorationOnGitHub,
7659
8443
  closeOnGitHub,
7660
8444
  closingIssueNumbers,
@@ -7664,445 +8448,494 @@ export {
7664
8448
  collectRefs,
7665
8449
  collectReleases,
7666
8450
  colorDelta,
7667
- a_ as colorToCss,
7668
- a$ as colorsEqual,
8451
+ b2 as colorToCss,
8452
+ b3 as colorsEqual,
7669
8453
  combineChanges,
7670
8454
  combineConflicts,
7671
- b0 as comboKey,
8455
+ b4 as comboKey,
7672
8456
  commentOnGitHub,
7673
- bC2 as commitFileOnGitHub,
8457
+ bN2 as commitFileOnGitHub,
7674
8458
  commitLibraryResolution,
7675
- b1 as commitMessage,
7676
- b2 as compactJson,
8459
+ b5 as commitMessage,
8460
+ b6 as compactJson,
7677
8461
  compareImages,
7678
8462
  compareSpecToMeasured,
7679
- bD2 as componentContrastPairs,
8463
+ bO2 as componentContrastPairs,
7680
8464
  componentForPath,
7681
8465
  componentMotion,
7682
8466
  componentSlug,
7683
8467
  componentSpecSuggestionPayload,
7684
8468
  componentStatus,
7685
- bE2 as componentsUsingMotionToken,
8469
+ bP2 as componentsUsingMotionToken,
7686
8470
  composeBuildRequestIssue,
7687
- bF2 as composeChangeset,
8471
+ bQ2 as composeChangeset,
7688
8472
  composeFiles,
7689
8473
  composeNewBrandBody,
7690
8474
  composePendingDrafts,
7691
- b3 as composePrBody,
8475
+ b7 as composePrBody,
7692
8476
  composeProposalBody,
7693
8477
  composeRequestIssue,
7694
- bG2 as composeSuggestionAppliedComment,
7695
- bH2 as composeSuggestionIssue,
7696
- bI2 as composeSuggestionReviewComment,
7697
- b4 as composite,
8478
+ bR2 as composeSuggestionAppliedComment,
8479
+ bS2 as composeSuggestionIssue,
8480
+ bT2 as composeSuggestionOverrideComment,
8481
+ bU2 as composeSuggestionReviewComment,
8482
+ b8 as composite,
7698
8483
  conflictRoute,
7699
- b5 as contextFor,
8484
+ b9 as contextFor,
7700
8485
  contextLabel,
7701
- bJ2 as continueDocsProposalOnGitHub,
8486
+ bV2 as continueDocsProposalOnGitHub,
8487
+ continueLines,
8488
+ continueRef,
7702
8489
  contrastDelta,
7703
- bK2 as contrastDeltaTable,
7704
- b6 as contrastLevel,
7705
- b7 as contrastPair,
7706
- b8 as contrastRatio,
8490
+ bW2 as contrastDeltaTable,
8491
+ ba as contrastLevel,
8492
+ bb as contrastPair,
8493
+ bc as contrastRatio,
7707
8494
  contrastScope,
7708
- bL2 as countChanges,
7709
- bM2 as coverageSummary,
8495
+ bX2 as countChanges,
8496
+ bY2 as coverageSummary,
7710
8497
  createBuildRequestOnGitHub,
7711
8498
  createDocsDraftSession,
7712
8499
  createPendingDrafts,
7713
8500
  createSuggestionOnGitHub,
7714
- bN2 as cssColorToRgb,
7715
- bO2 as cssColorsToRgb,
7716
- bP2 as cssModuleClasses,
8501
+ bZ2 as cssColorToRgb,
8502
+ b_2 as cssColorsToRgb,
8503
+ b$2 as cssModuleClasses,
7717
8504
  cssOf,
7718
8505
  cssVarOf,
7719
8506
  cssVarsIn,
7720
- bQ2 as declineComment,
7721
- bR2 as declineReasonProblems,
7722
- bS2 as declineRequestOnGitHub,
7723
- bT2 as declineSuggestionOnGitHub,
8507
+ c02 as declineComment,
8508
+ c12 as declineReasonProblems,
8509
+ c22 as declineRequestOnGitHub,
8510
+ c32 as declineSuggestionOnGitHub,
7724
8511
  decodePicks,
7725
8512
  defaultChoiceFor,
7726
- b9 as defaultContext,
7727
- bU2 as defaultDocsFooter,
8513
+ bd as defaultContext,
8514
+ c42 as defaultDocsFooter,
7728
8515
  defaultFile,
7729
8516
  defaultModelConfig,
7730
8517
  defaultProduct,
7731
8518
  defaultScopes,
7732
- ba as defaultTitle,
8519
+ be as defaultTitle,
7733
8520
  defineConfig,
7734
8521
  deletePendingDrafts,
7735
8522
  demoHistory,
7736
8523
  describeDiff,
7737
8524
  describePartFix,
7738
- bb as detectLayout,
8525
+ bf as detectLayout,
7739
8526
  diffLayered,
7740
8527
  dismissStaleFrom,
7741
- bV2 as docsAuthorLine,
7742
- bW2 as docsFileText,
8528
+ c52 as docsAuthorLine,
8529
+ c62 as docsFileText,
7743
8530
  docsPrompt,
7744
- bX2 as docsProposal,
7745
- bY2 as docsSectionShapes,
8531
+ c72 as docsProposal,
8532
+ c82 as docsSectionShapes,
7746
8533
  docsSectionsAnswer,
7747
8534
  docsSectionsResult,
7748
8535
  docsSelectionPrompt,
7749
- bZ2 as docsSlug,
7750
- b_2 as docsSuggestionPayload,
8536
+ c92 as docsSlug,
8537
+ ca2 as docsSuggestionPayload,
7751
8538
  docsTaskKeys,
7752
8539
  docsTaskPrompt,
7753
- b$2 as docsText,
7754
- c02 as draftChangeLine,
8540
+ cb2 as docsText,
8541
+ doneExploringOnGitHub,
8542
+ doneExploringPlan,
8543
+ cc2 as draftChangeLine,
7755
8544
  draftImagePath,
7756
8545
  draftItemBrief,
7757
- c12 as draftPushDescription,
7758
- c22 as draftRequestText,
8546
+ cd2 as draftPushDescription,
8547
+ ce2 as draftRequestText,
7759
8548
  draftSessionPath,
7760
8549
  dtcgColor,
7761
8550
  durationValue,
7762
8551
  easingValue,
7763
8552
  effectiveScopes,
7764
8553
  effectiveTier,
7765
- c32 as eligibleReviewers,
8554
+ cf2 as eligibleReviewers,
7766
8555
  emptyTokenFile,
7767
8556
  encodePicks,
7768
8557
  ensureRequestsBranch,
7769
- bc as entriesFor,
7770
- bd as entryLabel,
7771
- c42 as evalCalc,
8558
+ bg as entriesFor,
8559
+ bh as entryLabel,
8560
+ cg2 as evalCalc,
7772
8561
  evaluateContrastPairs,
7773
8562
  evaluatePairs,
7774
- be as expandEntries,
7775
- bf as expandOwners,
7776
- c52 as explorationBranch,
8563
+ bi as expandEntries,
8564
+ bj as expandOwners,
8565
+ explorationActivity,
8566
+ ch2 as explorationBranch,
7777
8567
  explorationNote,
7778
8568
  explorationNotePath,
7779
8569
  explorationPrBody,
7780
8570
  explorationPrTitle,
7781
8571
  explorationProblems,
7782
- c62 as explorationRules,
7783
- c72 as explorationShareMessage,
8572
+ explorationRefLabel,
8573
+ ci2 as explorationRules,
8574
+ cj2 as explorationShareMessage,
7784
8575
  explorationSlug,
7785
- c82 as explorationSlugFromBranch,
8576
+ ck2 as explorationSlugFromBranch,
8577
+ explorationStage,
8578
+ explorationStartMessage,
8579
+ explorationStartingPoint,
7786
8580
  explorationStatus,
7787
8581
  explorationSummary,
8582
+ cl2 as explorationTakeCommands,
7788
8583
  explorationTestBuildOnGitHub,
7789
8584
  explorationTryIt,
7790
8585
  explorationTryItOnGitHub,
7791
- c92 as fetchStorybookIndex,
8586
+ cm2 as fetchStorybookIndex,
7792
8587
  fidelityComponentLine,
7793
8588
  fidelityVerdict,
7794
- bg as fieldChanges,
8589
+ bk as fieldChanges,
7795
8590
  figmaAheadLine,
7796
8591
  figmaBranchTitle,
7797
- ca2 as figmaCell,
8592
+ cn2 as figmaCell,
7798
8593
  figmaChangeOnGitHub,
7799
8594
  figmaDraftBody,
7800
8595
  figmaDraftBrief,
8596
+ figmaFixesBranchStem,
7801
8597
  figmaFixesPrBody,
8598
+ figmaFixesProposal,
7802
8599
  figmaGaps,
7803
8600
  figmaNodeUrl,
7804
8601
  figmaNodes,
7805
8602
  figmaOriginSection,
7806
- bh as figmaPushNextLine,
8603
+ bl as figmaPushNextLine,
7807
8604
  figmaPushOf,
7808
8605
  figmaPushScope,
7809
8606
  figmaPushStatus,
7810
8607
  figmaPushStatusesOnGitHub,
7811
- bi as figmaPushTierLabel,
8608
+ bm as figmaPushTierLabel,
7812
8609
  figmaShipReviewBody,
7813
8610
  figmaSpecPath,
7814
8611
  figmaSpecSlug,
7815
- cb2 as fileModes,
7816
- cc2 as filterHistory,
8612
+ co2 as fileModes,
8613
+ cp2 as filterHistory,
7817
8614
  findExplorationOnGitHub,
7818
- cd2 as findOpenDocsProposal,
7819
- ce2 as findOpenDocsProposalOnGitHub,
8615
+ findMeasure,
8616
+ cq2 as findOpenDocsProposal,
8617
+ cr2 as findOpenDocsProposalOnGitHub,
7820
8618
  findPreviewComment,
7821
- bj as findProduct,
8619
+ bn as findProduct,
7822
8620
  findReferenceProblems,
7823
- bk as findToken,
7824
- bl as findVercelComment,
7825
- cf2 as firstParagraph,
8621
+ bo as findToken,
8622
+ bp as findVercelComment,
8623
+ cs2 as firstParagraph,
7826
8624
  fitActivity,
8625
+ fixLoopOutcome,
8626
+ fixLoopRound,
7827
8627
  fixPrompt,
7828
8628
  fixWords,
7829
- bm as flattenLayerFile,
8629
+ bq as flattenLayerFile,
7830
8630
  flattenTokenFile,
7831
8631
  flattenTokens,
7832
8632
  formatChangeset,
7833
- cg2 as formatDocsJson,
8633
+ ct2 as formatDocsJson,
7834
8634
  formatFigmaSpec,
7835
- bn as formatNumber,
7836
- bo as formatOwnerEntry,
8635
+ br as formatNumber,
8636
+ bs as formatOwnerEntry,
7837
8637
  formatValue,
7838
8638
  freshness,
7839
- ch2 as friendlyMessage,
8639
+ cu2 as friendlyMessage,
7840
8640
  fromHex,
7841
- bp as fromPlain,
8641
+ bt as fromPlain,
7842
8642
  gapCaseFor,
7843
8643
  generateRamp,
7844
- bq as getChild,
7845
- br as getEntry,
8644
+ bu as getChild,
8645
+ bv as getEntry,
7846
8646
  getSuggestionOnGitHub,
7847
8647
  gitBlobSha,
7848
8648
  gitHubHistoryBackend,
7849
8649
  gitHubSource,
7850
- ci2 as githubSuggestionWorkspace,
8650
+ cv2 as githubSuggestionWorkspace,
7851
8651
  globBase,
7852
8652
  globToRegExp,
7853
8653
  groupReleases,
7854
- cj2 as groupStorybookComponents,
7855
- bs as groupsPhrase,
7856
- ck2 as hasOwnerApproval,
7857
- cl2 as hasSuggestionBlock,
7858
- bt as hasVisualWorkflow,
7859
- cm2 as hasWriteAccess,
8654
+ cw2 as groupStorybookComponents,
8655
+ bw as groupsPhrase,
8656
+ cx2 as hasOwnerApproval,
8657
+ cy2 as hasSuggestionBlock,
8658
+ bx as hasVisualWorkflow,
8659
+ cz2 as hasWriteAccess,
7860
8660
  hashSpec,
7861
8661
  hexContrastRatio,
7862
8662
  hexLuminance,
7863
- bu as hexToRgba,
7864
- bv as holdersOf,
7865
- cn2 as iconComponentName,
8663
+ by as hexToRgba,
8664
+ bz as holdersOf,
8665
+ humanAuthorOf,
8666
+ cA2 as iconComponentName,
7866
8667
  idExtensionFor,
7867
8668
  impactedModules,
7868
8669
  importsIn,
7869
- bw as indexByPath,
7870
- bx as inheritedTypeAt,
7871
- by as installCommand,
7872
- bz as installCommandFromPreviewComment,
7873
- co2 as installationFor,
7874
- cp2 as isActiveExploration,
8670
+ inProductRepo,
8671
+ bA as indexByPath,
8672
+ bB as inheritedTypeAt,
8673
+ bC as installCommand,
8674
+ bD as installCommandFromPreviewComment,
8675
+ cB2 as installationFor,
8676
+ cC2 as isActiveExploration,
7875
8677
  isAlias$1 as isAlias,
7876
- cq2 as isAutomationPull,
8678
+ isArchivedExploration,
8679
+ cD2 as isAutomationPull,
7877
8680
  isBuildRequestIssue,
7878
- cr2 as isDocsProposalBranch,
8681
+ isClaudeAssisted,
8682
+ isClaudeCommit,
8683
+ cE2 as isDocsProposalBranch,
7879
8684
  isDocsSelectionMode,
7880
- cs2 as isDraft,
8685
+ cF2 as isDraft,
7881
8686
  isDraftSessionId,
7882
8687
  isExplorationBranch,
7883
8688
  isExplorationPull,
7884
8689
  isFigmaPush,
7885
8690
  isGuardrail,
7886
- ct2 as isGuardrailPath,
8691
+ cG as isGuardrailPath,
8692
+ cH as isHandedOff,
8693
+ cI as isHeldExploration,
7887
8694
  isHex,
7888
- cu2 as isJiraKey,
7889
- cv2 as isMissingPermission,
8695
+ isHiddenRepoError,
8696
+ cJ as isJiraKey,
8697
+ cK as isMissingPermission,
7890
8698
  isMultiMode,
7891
8699
  isPlanRefusal,
7892
- cw2 as isPlatformRefusal,
8700
+ cL as isPlatformRefusal,
7893
8701
  isProseKey,
7894
- cx2 as isReleasePull,
7895
- bA as isTeamEntry,
7896
- bB as isTokenFile,
8702
+ cM as isReleasePull,
8703
+ isStartCommit,
8704
+ bE as isTeamEntry,
8705
+ bF as isTokenFile,
7897
8706
  isTokenPath,
7898
- cy2 as isTokenRejected,
8707
+ cN as isTokenRejected,
7899
8708
  isTokenValueChange,
7900
8709
  isUnder,
7901
- bC as isVercelComment,
7902
- cz2 as jiraIssueFields,
7903
- cA2 as jiraKeyOf,
8710
+ bG as isVercelComment,
8711
+ cO as jiraIssueFields,
8712
+ cP as jiraKeyOf,
7904
8713
  jiraKeyOfLabels,
7905
- cB as joinRepoPath,
7906
- bD as kindForMin,
8714
+ cQ as joinRepoPath,
8715
+ bH as kindForMin,
8716
+ lastActivity,
7907
8717
  layeredImpact,
7908
- bE as layeredParityIssues,
8718
+ bI as layeredParityIssues,
7909
8719
  layoutOptions,
8720
+ leadsPublishTier1On,
7910
8721
  levenshtein,
7911
8722
  libraryArrival,
7912
8723
  libraryArrivalOnGitHub,
7913
8724
  libraryBranchPlan,
7914
- bF as libraryIsPublic,
8725
+ bJ as libraryIsPublic,
7915
8726
  libraryPreviewState,
7916
- cC as linkRequestToExplorationOnGitHub,
8727
+ cR as linkRequestToExplorationOnGitHub,
7917
8728
  linkStatus,
7918
- cD as linkSuggestionPrOnGitHub,
8729
+ cS as linkSuggestionPrOnGitHub,
7919
8730
  linkedIssueNumbers,
7920
8731
  linkedLibraryBranchOnGitHub,
7921
- cE as linkedPullsOf,
8732
+ cT as linkedPullsOf,
7922
8733
  listBrands,
7923
8734
  listBuildRequestsOnGitHub,
7924
8735
  listExplorationsOnGitHub,
7925
8736
  listFigmaChangesOnGitHub,
7926
8737
  listPendingDrafts,
8738
+ listProductExplorationsOnGitHub,
7927
8739
  listStylesheets,
7928
8740
  listSuggestionsOnGitHub,
8741
+ listThings,
7929
8742
  loadConfig,
7930
8743
  loadGitHubHistory,
7931
8744
  loadProtection,
7932
- cF as loadRepoFiles,
8745
+ cU as loadRepoFiles,
7933
8746
  loadRepoModel,
7934
8747
  loadRequiredChecks,
7935
8748
  localTryCommand,
7936
- cG as lookAgainReviewers,
7937
- cH as lowerFirstWord,
7938
- bG as luminance,
8749
+ cV as lookAgainReviewers,
8750
+ cW as lowerFirstWord,
8751
+ bK as luminance,
7939
8752
  mailtoHref,
7940
8753
  mapPropsToFigma,
7941
8754
  markClaudeDraft,
7942
8755
  markFigmaChangeDraftedOnGitHub,
7943
- cI as markSuggestionsAppliedOnGitHub,
7944
- cJ as markUsedTokens,
8756
+ cX as markSuggestionsAppliedOnGitHub,
8757
+ cY as markUsedTokens,
8758
+ matchVariant,
7945
8759
  matchesBuildFilter,
7946
8760
  mayBeTokenValueChange,
8761
+ measureDir,
8762
+ measureFromWords,
8763
+ measureSetupSteps,
8764
+ measureWorkflowOptionsFromConfig,
8765
+ measureWorkflowYaml,
7947
8766
  measuredResolver,
7948
- cK as memoryEtagStore,
7949
- cL as memorySource,
8767
+ cZ as memoryEtagStore,
8768
+ c_ as memorySource,
7950
8769
  mergeDocsDraft,
7951
8770
  mergeDraftResults,
7952
8771
  mergeFigmaFixes,
7953
- cM as mergeGate,
8772
+ c$ as mergeGate,
7954
8773
  mergeOnGitHub,
7955
8774
  mergePiecesRecord,
7956
8775
  mergeReleases,
7957
8776
  missingChoices,
7958
- cN as missingPermissionMessage,
7959
- cO as missingProse,
8777
+ d0 as missingPermissionMessage,
8778
+ d1 as missingProse,
7960
8779
  modeDisplayName,
7961
8780
  modeFile,
7962
- cP as modeLabel,
8781
+ d2 as modeLabel,
7963
8782
  modesOf$1 as modesOf,
7964
8783
  motionDocs,
7965
8784
  motionMoments,
7966
8785
  moveToken,
7967
- bH as nearestQuestion,
8786
+ bL as nearestQuestion,
7968
8787
  nearestStep,
7969
- bI as neededBySection,
7970
- cQ as needsDocsReview,
8788
+ bM as neededBySection,
8789
+ d3 as needsDocsReview,
7971
8790
  neutralChroma,
7972
8791
  neutralOffset,
7973
8792
  newBrandSection,
7974
8793
  newDraftSessionId,
7975
8794
  noOnlinePreview,
7976
- bJ as nodesEqual,
8795
+ bN as nodesEqual,
7977
8796
  normalizeRepoPath,
7978
- bK as normalizeRights,
7979
- cR as normalizeStorybookUrl,
8797
+ bO as normalizeRights,
8798
+ d4 as normalizeStorybookUrl,
7980
8799
  noteSentences,
7981
8800
  oklch,
7982
8801
  oklchToLinear,
8802
+ onlyNotes,
7983
8803
  onlyTokens,
7984
8804
  openBuildRequests,
7985
8805
  openComponentPushOnGitHub,
7986
8806
  openLibraryBranchOnGitHub,
7987
- cS as openPulls,
8807
+ d5 as openPulls,
7988
8808
  overlap,
7989
8809
  overrideComment,
7990
- cT as overrideHolders,
7991
- bL as overrideKey,
8810
+ d6 as overrideHolders,
8811
+ bP as overrideKey,
7992
8812
  overrideMergeOnGitHub,
7993
- cU as overrideReasonMin,
7994
- cV as overrideReasonProblem,
8813
+ d7 as overrideReasonMin,
8814
+ d8 as overrideReasonProblem,
7995
8815
  overrideRefusedComment,
7996
- bM as ownerEntryProblems,
7997
- cW as ownerOverridePolicy,
8816
+ d9 as overrideSuggestionOnGitHub,
8817
+ bQ as ownerEntryProblems,
8818
+ da as ownerOverridePolicy,
7998
8819
  packageDir,
7999
8820
  packageDirCandidates,
8000
- bN as pairKey,
8821
+ bR as pairKey,
8001
8822
  pairParts,
8002
8823
  parentDir,
8003
8824
  parseActivity,
8004
- cX as parseBoxShadow,
8825
+ db as parseBoxShadow,
8005
8826
  parseBuildRequestIssue,
8006
8827
  parseBuildSpec,
8007
8828
  parseChangelog,
8008
8829
  parseChangelogSections,
8009
8830
  parseChangeset,
8010
- cY as parseChangesetText,
8831
+ dc as parseChangesetText,
8011
8832
  parseColor,
8012
8833
  parseComponentCss,
8013
8834
  parseComponentFamilies,
8014
8835
  parseComponentSpecs,
8015
8836
  parseComponentStories,
8016
8837
  parseComponentTsx,
8838
+ parseContinueLine,
8017
8839
  parseContrastChecks,
8018
8840
  parseContrastSpecs,
8019
- bO as parseDimension,
8841
+ bS as parseDimension,
8020
8842
  parseDocsReply,
8021
8843
  parseDocsTaskAnswer,
8022
8844
  parseExplorationBody,
8845
+ dd as parseExplorationRef,
8023
8846
  parseFidelityRun,
8024
8847
  parseFigmaFixes,
8025
8848
  parseFigmaOrigin,
8026
8849
  parseFigmaSpec,
8027
8850
  parseFixReply,
8028
- cZ as parseJiraKey,
8029
- bP as parseJson,
8030
- c_ as parseMeasuredSpecsFile,
8851
+ de as parseJiraKey,
8852
+ bT as parseJson,
8853
+ df as parseMeasuredSpecsFile,
8031
8854
  parseModelConfig,
8032
- bQ as parseNeededBy,
8033
- c$ as parseOverrideComment,
8034
- bR as parseOwnerEntry,
8855
+ bU as parseNeededBy,
8856
+ dg as parseOverrideComment,
8857
+ bV as parseOwnerEntry,
8035
8858
  parsePendingDrafts,
8036
8859
  parsePiecesRecord,
8037
- d0 as parseRepo,
8860
+ dh as parseRepo,
8038
8861
  parseRequestIssue,
8039
8862
  parseSnapshot,
8040
- d1 as parseStorybookIndex,
8041
- d2 as parseStructureChange,
8042
- d3 as parseSuggestionAppliedComment,
8043
- d4 as parseSuggestionIssue,
8044
- d5 as parseSuggestionReviewComment,
8045
- d6 as parseTier,
8863
+ di as parseStorybookIndex,
8864
+ dj as parseStructureChange,
8865
+ dk as parseSuggestionAppliedComment,
8866
+ dl as parseSuggestionIssue,
8867
+ dm as parseSuggestionOverrideComment,
8868
+ dn as parseSuggestionReviewComment,
8869
+ dp as parseTier,
8046
8870
  parseTokenFile,
8047
- bS as parseVercelComment,
8871
+ bW as parseVercelComment,
8048
8872
  partLabel,
8049
8873
  partWords,
8050
- d7 as patchDocsJson,
8874
+ dq as patchDocsJson,
8051
8875
  pathId,
8052
8876
  pathSimilarity,
8053
- d8 as pendingApprovals,
8054
- d9 as permissionLabel,
8877
+ dr as pendingApprovals,
8878
+ ds as permissionLabel,
8055
8879
  pickedOption,
8056
8880
  p as pieceId,
8057
- bT as piecesSection,
8058
- bU as plainEqual,
8881
+ pieceRequest,
8882
+ bX as pieceValueField,
8883
+ bY as piecesSection,
8884
+ bZ as plainEqual,
8059
8885
  planComponentPush,
8060
8886
  planNewBrand,
8061
8887
  planReference,
8062
8888
  planReferences,
8063
- da as planRestore,
8064
- db as planRevert,
8065
- dc as planSuggestionApply,
8066
- dd as planToJson,
8889
+ dt as planRestore,
8890
+ du as planRevert,
8891
+ dv as planSuggestionApply,
8892
+ dw as planToJson,
8067
8893
  planTokenMerge,
8068
8894
  planTokenProposal,
8069
8895
  planVariantMeasures,
8070
- de as prNumberFromSubject,
8071
- df as prefillDocs,
8072
- bV as previewComment,
8896
+ dx as prNumberFromSubject,
8897
+ dy as prefillDocs,
8898
+ b_ as previewComment,
8073
8899
  previewLinks,
8074
- bW as previewLinksOf,
8900
+ b$ as previewLinksOf,
8075
8901
  previewOverrides,
8076
- bX as previewUrl,
8902
+ previewUrl,
8077
8903
  previewsSettled,
8078
8904
  productBranchRef,
8079
8905
  productBranchSentence,
8080
8906
  productBranchViewOnGitHub,
8081
- bY as productForPlace,
8082
- bZ as productLabel,
8083
- b_ as productMarks,
8907
+ c0 as productForPlace,
8908
+ c1 as productLabel,
8909
+ c2 as productMarks,
8084
8910
  productOfBranch,
8911
+ productOfRepo,
8085
8912
  productPreview,
8086
- b$ as productProblems,
8087
- c0 as productsOf,
8088
- c1 as projectOfLabel,
8913
+ productPreviewOnGitHub,
8914
+ c3 as productProblems,
8915
+ productRepoAccess,
8916
+ c4 as productRepoOf,
8917
+ productRepos,
8918
+ c5 as productsOf,
8919
+ c6 as projectOfLabel,
8089
8920
  promoteExplorationOnGitHub,
8090
8921
  promotionPlan,
8091
8922
  propertyWords,
8092
- dg as proposeDocsOnGitHub,
8923
+ dz as proposeDocsOnGitHub,
8924
+ proposePiecesOnGitHub,
8093
8925
  a as proposedTokenPath,
8094
- dh as propsTable,
8926
+ dA as propsTable,
8095
8927
  protectLibrary,
8096
8928
  protectRefusal,
8097
- c2 as protectedBranchesOf,
8929
+ c7 as protectedBranchesOf,
8098
8930
  protectionRuleset,
8099
8931
  protectionRulesetName,
8100
8932
  protectionSentence,
8101
8933
  protectionState,
8102
8934
  publishSummary,
8935
+ publishedWithoutReview,
8103
8936
  pullState,
8104
- di as pushChangeRows,
8105
- dj as rateLimitMessage,
8937
+ dB as pushChangeRows,
8938
+ dC as rateLimitMessage,
8106
8939
  readCombineBranch,
8107
8940
  readLibraryConflicts,
8108
8941
  readPendingDrafts,
@@ -8118,24 +8951,24 @@ export {
8118
8951
  referenceImagePath,
8119
8952
  releaseAnnouncement,
8120
8953
  releasePackagesLabel,
8121
- dk as releasePull,
8954
+ dD as releasePull,
8122
8955
  releasesFromTags,
8123
8956
  remoteRenames,
8124
8957
  removeToken,
8125
8958
  removedSide,
8126
- c3 as renderedColor,
8127
- dl as reopenEligibility,
8959
+ c8 as renderedColor,
8960
+ dE as reopenEligibility,
8128
8961
  reopenOnGitHub,
8129
- dm as requestBrief,
8130
- dn as requestImagePath,
8962
+ dF as requestBrief,
8963
+ dG as requestImagePath,
8131
8964
  requestIssueTitle,
8132
8965
  requestStatus,
8133
- dp as requesterFirstName,
8134
- dq as requesterLine,
8135
- dr as requesterOf,
8136
- ds as requiredApprovals,
8966
+ dH as requesterFirstName,
8967
+ dI as requesterLine,
8968
+ dJ as requesterOf,
8969
+ dK as requiredApprovals,
8137
8970
  requiredCheckNames,
8138
- dt as requiredPermission,
8971
+ dL as requiredPermission,
8139
8972
  resolutionMessage,
8140
8973
  resolve,
8141
8974
  resolveBranchPreviews,
@@ -8143,21 +8976,21 @@ export {
8143
8976
  resolveRepoAccess,
8144
8977
  resolvedTokenFiles,
8145
8978
  reviewOnGitHub,
8146
- du as reviewQueue,
8147
- dv as reviewStatusKind,
8148
- dw as reviewStatusLabel,
8149
- dx as reviewSuggestionOnGitHub,
8150
- dy as reviewersToAsk,
8151
- c4 as rgbaToDtcg,
8979
+ dM as reviewQueue,
8980
+ dN as reviewStatusKind,
8981
+ dO as reviewStatusLabel,
8982
+ dP as reviewSuggestionOnGitHub,
8983
+ dQ as reviewersToAsk,
8984
+ c9 as rgbaToDtcg,
8152
8985
  rgbaToHex,
8153
- c5 as rightsProblems,
8986
+ ca as rightsProblems,
8154
8987
  round3,
8155
8988
  rulesSettingsUrl,
8156
8989
  rulesetTargetsDefault,
8157
8990
  runLayeredContrast,
8158
- c6 as sameProducts,
8991
+ cb as sameProducts,
8159
8992
  sameScopes,
8160
- c7 as sameToken,
8993
+ cc as sameToken,
8161
8994
  sanitizeDocsReplacement,
8162
8995
  sanitizeDocsResult,
8163
8996
  sanitizeDocsSections,
@@ -8166,24 +8999,26 @@ export {
8166
8999
  sanitizeDraftTitle,
8167
9000
  sanitizeResultLines,
8168
9001
  scanComponents,
8169
- dz as sectionsNeedingReview,
9002
+ dR as sectionsNeedingReview,
8170
9003
  selectTokenFiles,
8171
- c8 as serializeJson,
9004
+ cd as serializeJson,
8172
9005
  serializePendingDrafts,
8173
9006
  serializeTokenFile,
8174
- dA as sha256Hex,
9007
+ dS as sha256Hex,
8175
9008
  shadowedEdits,
8176
9009
  shareDescription,
8177
9010
  shareMessage,
8178
9011
  shareState,
8179
- dB as sharedTokenPrefix,
8180
- c9 as shipPolicy,
9012
+ dT as sharedTokenPrefix,
9013
+ ce as shipPolicy,
8181
9014
  shipsByItself,
9015
+ shortAgo,
8182
9016
  shortPackage,
8183
- dC as shortVariant,
8184
- dD as shortVariants,
9017
+ dU as shortVariant,
9018
+ dV as shortVariants,
8185
9019
  showShare,
8186
9020
  siteRepoFromConfig,
9021
+ sizeGap,
8187
9022
  slugify,
8188
9023
  snapshotAudit,
8189
9024
  snapshotIsFresh,
@@ -8191,84 +9026,95 @@ export {
8191
9026
  snapshotMotion,
8192
9027
  snapshotSource,
8193
9028
  snapshotSummary,
8194
- ca as snapshotUrl,
8195
- cb as soloApprover,
8196
- cc as soloOwnerOf,
8197
- cd as soloOwnerSentence,
9029
+ cf as snapshotUrl,
9030
+ cg as soloApprover,
9031
+ ch as soloOwnerOf,
9032
+ ci as soloOwnerSentence,
8198
9033
  sortChangelogEntries,
8199
- dE as specHash,
9034
+ dW as specHash,
8200
9035
  specsFromMeasure,
8201
- dF as specsUsage,
9036
+ dX as specsUsage,
8202
9037
  splitNote,
8203
9038
  stableCommand,
8204
- dG as stalenessStatus,
9039
+ dY as stalenessStatus,
8205
9040
  startExplorationOnGitHub,
9041
+ startFixLoop,
9042
+ startedWhen,
8206
9043
  stateSentence,
8207
9044
  statusFixFor,
8208
9045
  storiesOfComponent,
8209
9046
  storyForVariant,
8210
9047
  storyMatchesWhere,
8211
9048
  storyState,
8212
- dH as storybookIframeUrl,
8213
- dI as storybookIndexUrls,
8214
- dJ as storybookManagerUrl,
8215
- ce as storybookUrl,
9049
+ dZ as storybookIframeUrl,
9050
+ d_ as storybookIndexUrls,
9051
+ d$ as storybookManagerUrl,
9052
+ cj as storybookUrl,
8216
9053
  styleGlobs,
8217
- dK as subjectTitle,
8218
- cf as suggestNearestComment,
9054
+ e0 as subjectTitle,
9055
+ subjectWords,
9056
+ ck as suggestNearestComment,
8219
9057
  suggestTokenProposalOnGitHub,
8220
- dL as suggestionAllowedRoots,
8221
- dM as suggestionAppliedComment,
8222
- dN as suggestionApplyTable,
8223
- dO as suggestionIsTokenValues,
8224
- dP as suggestionIssueTitle,
8225
- dQ as suggestionKindFromLabels,
8226
- dR as suggestionLabelChanges,
8227
- dS as suggestionLabels,
8228
- dT as suggestionPathProblems,
8229
- dU as suggestionPayloadSha,
8230
- dV as suggestionReviewState,
8231
- dW as suggestionShapeProblems,
8232
- dX as suggestionStalenessOnGitHub,
9058
+ e1 as suggestionAllowedRoots,
9059
+ e2 as suggestionAppliedComment,
9060
+ e3 as suggestionApplyTable,
9061
+ e4 as suggestionIsTokenValues,
9062
+ e5 as suggestionIssueTitle,
9063
+ e6 as suggestionKindFromLabels,
9064
+ e7 as suggestionLabelChanges,
9065
+ e8 as suggestionLabels,
9066
+ e9 as suggestionOverrideOf,
9067
+ ea as suggestionOverrideProblem,
9068
+ eb as suggestionPathProblems,
9069
+ ec as suggestionPayloadSha,
9070
+ ed as suggestionReviewState,
9071
+ ee as suggestionShapeProblems,
9072
+ ef as suggestionStalenessOnGitHub,
8233
9073
  suggestionStateFromLabels,
8234
- dY as suggestionStateLabel,
9074
+ eg as suggestionStateLabel,
8235
9075
  suggestionStatus,
8236
- dZ as suggestionSubject,
8237
- d_ as suggestionTouches,
9076
+ eh as suggestionSubject,
9077
+ ei as suggestionTouches,
8238
9078
  summarizeApprovals,
8239
9079
  summarizeCi,
8240
9080
  summarizeComponent,
8241
- d$ as summarizeSuggestionApprovals,
8242
- cg as summarizeTokenChange,
9081
+ ej as summarizeSuggestionApprovals,
9082
+ cl as summarizeTokenChange,
8243
9083
  switchToPublishedOnGitHub,
8244
- e0 as syncSuggestionStateLabel,
9084
+ ek as syncSuggestionStateLabel,
8245
9085
  takeMine,
8246
- ch as tarballAssetName,
8247
- ci as tarballUrl,
8248
- cj as teamKey,
8249
- ck as teamKeyOf,
8250
- cl as teamName,
8251
- cm as teamUnreadableMessage,
8252
- cn as teamsIn,
9086
+ cm as tarballAssetName,
9087
+ cn as tarballUrl,
9088
+ co as teamKey,
9089
+ cp as teamKeyOf,
9090
+ cq as teamName,
9091
+ cr as teamUnreadableMessage,
9092
+ cs as teamsIn,
9093
+ thingOf,
8253
9094
  threeWayDiff,
9095
+ tier1Domain,
9096
+ tier1PublishOf,
9097
+ tier1PublishReviewBody,
9098
+ tier1PublishSentence,
9099
+ tier1PublishedBy,
8254
9100
  tintedNeutral,
8255
- e1 as titleOf,
9101
+ el as titleOf,
8256
9102
  titleWithJiraKey,
8257
- co as toAlias,
8258
- e2 as toChangeSummary,
9103
+ ct as toAlias,
9104
+ em as toChangeSummary,
8259
9105
  toConfigJson,
8260
9106
  toHex,
8261
- cp as toMap,
9107
+ cu as toMap,
8262
9108
  toOklch,
8263
- cq as toPlain,
8264
- cr as tokenBump,
8265
- e3 as tokenCssVar,
9109
+ cv as toPlain,
9110
+ cw as tokenBump,
9111
+ en as tokenCssVar,
8266
9112
  tokenFileChanges,
8267
9113
  tokenFilesOf,
8268
- e4 as tokenFingerprint,
8269
- e5 as tokenHistory,
9114
+ eo as tokenFingerprint,
9115
+ ep as tokenHistory,
8270
9116
  t as tokenLabel,
8271
- cs as tokenNode,
9117
+ cx as tokenNode,
8272
9118
  tokenPublishRoute,
8273
9119
  tokenRepoFromMeasure,
8274
9120
  tokenRowAxes,
@@ -8279,7 +9125,7 @@ export {
8279
9125
  tokenSuggestionPayload,
8280
9126
  tokenUsage,
8281
9127
  tokenValueCss,
8282
- e6 as tokenVariablePath,
9128
+ eq as tokenVariablePath,
8283
9129
  tokensPath,
8284
9130
  touchesLayout,
8285
9131
  touchesPackages,
@@ -8288,50 +9134,53 @@ export {
8288
9134
  tryInProductUrl,
8289
9135
  tryTogetherUrl,
8290
9136
  unpicked,
8291
- ct as unreadableShipTeams,
8292
- cu as unreadableTeams,
8293
- e7 as unsafePathProblem,
9137
+ cy as unreadableShipTeams,
9138
+ cz as unreadableTeams,
9139
+ er as unsafePathProblem,
8294
9140
  updatePendingDrafts,
8295
9141
  upsertToken,
8296
9142
  useLibraryPreviewOnGitHub,
8297
- e8 as utf8ToBase64,
8298
- e9 as validateComponentSpec,
8299
- ea as validateDocs,
9143
+ es as utf8ToBase64,
9144
+ et as validateComponentSpec,
9145
+ eu as validateDocs,
8300
9146
  validateDocsTask,
8301
9147
  validateFigmaFixes,
8302
- eb as validateIntegrations,
9148
+ ev as validateIntegrations,
8303
9149
  validateLayered,
8304
9150
  validatePendingDrafts,
8305
9151
  validateProposal,
8306
9152
  validateSet,
8307
- cv as valueIn,
8308
- cw as valueToCss,
9153
+ cA as valueIn,
9154
+ cB as valueToCss,
8309
9155
  valuesEqual,
8310
9156
  variantCombosFromMapping,
8311
9157
  variantDifferences,
8312
9158
  variantKeyOf,
8313
- ec as variantName,
9159
+ ew as variantName,
8314
9160
  variantValuesFromMapping,
8315
- ed as versionTimeline,
8316
- cx as visualImpactProblems,
8317
- cy as visualOffer,
8318
- ee as webStorageEtagStore,
8319
- cz as whoCanShipSentence,
8320
- cA as whyItExists,
8321
- ef as withAuthorLine,
9161
+ ex as versionTimeline,
9162
+ cC as visualImpactProblems,
9163
+ cD as visualOffer,
9164
+ ey as webStorageEtagStore,
9165
+ whereItStands,
9166
+ cE as whoCanShipSentence,
9167
+ cF as whyItExists,
9168
+ ez as withAuthorLine,
9169
+ withDoneExploring,
8322
9170
  withFigmaOrigin,
8323
- eg as withFigmaPushApprovals,
9171
+ eA as withFigmaPushApprovals,
8324
9172
  withLink,
8325
9173
  withNeededBy,
8326
- eh as withOutdated,
9174
+ eB as withOutdated,
8327
9175
  withPiecesRecord,
8328
9176
  withRepoTokenPaths,
8329
- ei as withRequestExploration,
8330
- ej as withSrgbColors,
8331
- ek as withSuggestionApprovals,
8332
- el as withTokenValueApprovals,
9177
+ eC as withRequestExploration,
9178
+ eD as withSrgbColors,
9179
+ eE as withSuggestionApprovals,
9180
+ eF as withTokenValueApprovals,
8333
9181
  withoutAddedWrappers,
8334
- em as withoutJiraKey,
9182
+ eG as withoutJiraKey,
9183
+ wordsOf,
8335
9184
  writeDraftResults,
8336
9185
  writeTokenProposalOnGitHub
8337
9186
  };