@tempo-ai/visual-review 0.0.1 → 0.0.107-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -6,11 +6,11 @@ async function main(argv = process.argv.slice(2)) {
6
6
  const [command, ...commandArgs] = argv;
7
7
  switch (command) {
8
8
  case "capture":
9
- return await (await import("./cli-3HH2BY7Y.js")).main(commandArgs);
9
+ return await (await import("./cli-JCGZRFSY.js")).main(commandArgs);
10
10
  case "claim":
11
11
  return await (await import("./claim-WI2ZVIDS.js")).main(commandArgs);
12
12
  case "report":
13
- return await (await import("./report-RCKB2CVS.js")).main(commandArgs);
13
+ return await (await import("./report-RD2OQ5TP.js")).main(commandArgs);
14
14
  case "install-browsers":
15
15
  return await (await import("./install-browsers-5RCJN3AK.js")).main(commandArgs);
16
16
  default:
@@ -7,6 +7,13 @@ import {
7
7
  import { execFileSync } from "child_process";
8
8
  var STICKY_MARKER = "<!-- tempo-visual-review -->";
9
9
  var STATUS_CONTEXT = "visual-review";
10
+ function publisherDeliveryFailure(status) {
11
+ const publishError = status.lastPublishError;
12
+ if (publishError == null) return null;
13
+ if (publishError.revision < (status.lastPublish?.revision ?? 0)) return null;
14
+ if (status.statusPublishing !== false) return null;
15
+ return publishError;
16
+ }
10
17
  function parseArgs(argv) {
11
18
  const args = {
12
19
  pollTimeoutS: 900,
@@ -34,7 +41,7 @@ function parseArgs(argv) {
34
41
  return args;
35
42
  }
36
43
  function gh(...ghArgs) {
37
- return execFileSync("gh", ghArgs, { encoding: "utf-8" });
44
+ return execFileSync("gh", ghArgs, { encoding: "utf-8", windowsHide: true });
38
45
  }
39
46
  function postCommitStatus(repo, sha, state, description, targetUrl) {
40
47
  gh(
@@ -254,16 +261,15 @@ async function main(argv = process.argv.slice(2)) {
254
261
  await new Promise((resolve) => setTimeout(resolve, 5e3));
255
262
  }
256
263
  const verdict = describeGate(status.gate ?? {}, status.state);
257
- const publishError = status.lastPublishError;
258
- const publishFailing = publishError !== void 0 && publishError.revision >= (status.lastPublish?.revision ?? 0) && status.statusPublishing === false;
259
- if (status.publisherConfigured && !publishFailing) {
264
+ const publishFailure = publisherDeliveryFailure(status);
265
+ if (status.publisherConfigured && publishFailure === null) {
260
266
  console.log(
261
267
  `verdict (${verdict.state}) owned by the Convex publisher \u2014 skipping CI status write`
262
268
  );
263
269
  } else {
264
- if (publishFailing) {
270
+ if (publishFailure !== null) {
265
271
  console.log(
266
- `Convex publisher reported a delivery failure (${publishError.error}) \u2014 writing the status from CI as a fallback`
272
+ `Convex publisher reported a delivery failure (${publishFailure.error}) \u2014 writing the status from CI as a fallback`
267
273
  );
268
274
  }
269
275
  postCommitStatus(
@@ -283,6 +289,7 @@ async function main(argv = process.argv.slice(2)) {
283
289
  }
284
290
 
285
291
  export {
292
+ publisherDeliveryFailure,
286
293
  main
287
294
  };
288
- //# sourceMappingURL=chunk-HMPQK5ZG.js.map
295
+ //# sourceMappingURL=chunk-4KKBAMNP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/report.ts"],"sourcesContent":["import { execFileSync } from \"node:child_process\";\n\nimport { visualReviewPrDeclaration } from \"./github-actions\";\n\nconst STICKY_MARKER = \"<!-- tempo-visual-review -->\";\nconst STATUS_CONTEXT = \"visual-review\";\n\ntype ReportArgs = {\n pollTimeoutS: number;\n bypass: boolean;\n noVisualChange: boolean;\n fallbackFailure: boolean;\n statusUrl?: string;\n pr?: number;\n sha?: string;\n};\n\ntype Gate = {\n satisfied?: boolean;\n required?: number;\n approvalsNeeded?: number;\n rejected?: number;\n blockingErrors?: number;\n};\n\n/**\n * Shape of the `/visual-review/status` poll. Convex serialises absent\n * optional fields as `null`, not `undefined` — every optional here must be\n * read null-safely.\n */\nexport type ReportStatus = {\n state?: \"complete\" | \"error\" | string;\n reportUrl?: string;\n trackedTotal?: number;\n counts?: Record<string, number | undefined>;\n gate?: Gate;\n topChanged?: Array<{\n name?: string;\n storyboardId: string;\n diffRatio?: number | null;\n verdict: string;\n error?: string;\n baselineHash?: string;\n candidateHash?: string;\n }>;\n blobUrlTemplate?: string;\n approximateBase?: boolean;\n freshEpoch?: boolean;\n publisherConfigured?: boolean;\n lastPublishError?: { revision: number; error: string } | null;\n lastPublish?: { revision: number } | null;\n statusPublishing?: boolean | null;\n};\n\n/**\n * `publisherConfigured` only means the GitHub App env is set GLOBALLY — it\n * does NOT mean the app can post on THIS repo (it may not be installed\n * there, or lack the permission). When the publisher has a TERMINAL\n * outstanding failure for the current revision, CI writes the status itself\n * so the check can't hang pending forever. Terminal means the error is\n * current (revision-compare, mirroring the portal's \"delivery failing\"\n * banner) AND no publish worker holds the singleflight claim\n * (`statusPublishing === false`). A transient failure mid-retry-ladder keeps\n * the claim, so falling back then would race the publisher's own retry and\n * could land a stale snapshot after a newer portal flip.\n *\n * Returns the terminal failure, or `null` when Convex still owns the verdict.\n */\nexport function publisherDeliveryFailure(\n status: ReportStatus,\n): { revision: number; error: string } | null {\n const publishError = status.lastPublishError;\n // A healthy run reports `lastPublishError: null` (0.0.1 compared against\n // `undefined` only and crashed on `.revision`).\n if (publishError == null) return null;\n if (publishError.revision < (status.lastPublish?.revision ?? 0)) return null;\n if (status.statusPublishing !== false) return null;\n return publishError;\n}\n\nfunction parseArgs(argv: string[]): ReportArgs {\n const args: ReportArgs = {\n pollTimeoutS: 900,\n bypass: false,\n noVisualChange: false,\n fallbackFailure: false,\n };\n for (let index = 0; index < argv.length; index += 1) {\n const flag = argv[index];\n const next = () => {\n const value = argv[++index];\n if (value === undefined) throw new Error(`${flag} needs a value`);\n return value;\n };\n if (flag === \"--status-url\") args.statusUrl = next();\n else if (flag === \"--pr\") args.pr = Number(next());\n else if (flag === \"--sha\") args.sha = next();\n else if (flag === \"--poll-timeout-s\") args.pollTimeoutS = Number(next());\n else if (flag === \"--bypass\") args.bypass = true;\n else if (flag === \"--no-visual-change\") args.noVisualChange = true;\n else if (flag === \"--fallback-failure\") args.fallbackFailure = true;\n else throw new Error(`unknown flag ${flag}`);\n }\n if (!args.sha) throw new Error(\"--sha is required\");\n return args;\n}\n\nfunction gh(...ghArgs: string[]): string {\n return execFileSync(\"gh\", ghArgs, { encoding: \"utf-8\", windowsHide: true });\n}\n\nfunction postCommitStatus(\n repo: string,\n sha: string,\n state: \"success\" | \"failure\" | \"pending\",\n description: string,\n targetUrl?: string,\n): void {\n gh(\n \"api\",\n `repos/${repo}/statuses/${sha}`,\n \"-f\",\n `state=${state}`,\n \"-f\",\n `context=${STATUS_CONTEXT}`,\n \"-f\",\n `description=${description.slice(0, 140)}`,\n ...(targetUrl ? [\"-f\", `target_url=${targetUrl}`] : []),\n );\n}\n\nfunction upsertStickyComment(repo: string, pr: number, body: string): void {\n const comments = (\n JSON.parse(\n gh(\"api\", `repos/${repo}/issues/${pr}/comments`, \"--paginate\", \"--slurp\"),\n ) as Array<Array<{ id: number; body?: string }>>\n ).flat();\n const existing = comments.find(\n (comment) =>\n typeof comment.body === \"string\" && comment.body.includes(STICKY_MARKER),\n );\n if (existing) {\n gh(\n \"api\",\n \"--method\",\n \"PATCH\",\n `repos/${repo}/issues/comments/${existing.id}`,\n \"-f\",\n `body=${body}`,\n );\n } else {\n gh(\"api\", `repos/${repo}/issues/${pr}/comments`, \"-f\", `body=${body}`);\n }\n}\n\nfunction describeGate(\n gate: Gate,\n state: ReportStatus[\"state\"],\n): { state: \"success\" | \"failure\" | \"pending\"; description: string } {\n if (state === \"error\") {\n return {\n state: \"failure\",\n description: \"visual review run failed — see report\",\n };\n }\n if (state !== \"complete\") {\n return {\n state: \"pending\",\n description: \"capturing & comparing storyboards…\",\n };\n }\n if (gate.satisfied) {\n return {\n state: \"success\",\n description:\n gate.required === 0\n ? \"no visual changes\"\n : `${gate.required} visual change${gate.required === 1 ? \"\" : \"s\"} approved`,\n };\n }\n const parts: string[] = [];\n if ((gate.approvalsNeeded ?? 0) > 0) {\n parts.push(`${gate.approvalsNeeded} of ${gate.required} need approval`);\n }\n if ((gate.rejected ?? 0) > 0) parts.push(`${gate.rejected} rejected`);\n if ((gate.blockingErrors ?? 0) > 0) {\n parts.push(\n `${gate.blockingErrors} render error${gate.blockingErrors === 1 ? \"\" : \"s\"}`,\n );\n }\n return {\n state: \"failure\",\n description: `visual review: ${parts.join(\", \")} — see report`,\n };\n}\n\nfunction buildCommentBody(status: ReportStatus): string {\n const counts = status.counts ?? {};\n const gateLine = status.gate?.satisfied\n ? \"✅ **Check passing** — every visual change is approved.\"\n : `🔴 **Check failing** — ${status.gate?.approvalsNeeded ?? \"?\"} of ${status.gate?.required ?? \"?\"} storyboards need approval${status.gate?.blockingErrors ? `, ${status.gate.blockingErrors} render error(s)` : \"\"}${status.gate?.rejected ? `, ${status.gate.rejected} rejected` : \"\"}. Approving in the report flips the check instantly — no CI re-run.`;\n const summary = `| tracked | changed | new | removed | render errors | unchanged |\n| --- | --- | --- | --- | --- | --- |\n| ${status.trackedTotal ?? 0} | ${counts.changed ?? 0} | ${counts.added ?? 0} | ${counts.removed ?? 0} | ${counts.errors ?? 0} | ${counts.unchanged ?? 0} |`;\n\n const thumbs = (status.topChanged ?? [])\n .slice(0, 6)\n .map((board) => {\n const title = board.name ?? board.storyboardId;\n const meta =\n board.diffRatio !== undefined && board.diffRatio !== null\n ? ` · ${(board.diffRatio * 100).toFixed(2)}%`\n : \"\";\n if (board.verdict === \"render-error\") {\n return `**${title}** — render error${board.error ? `: \\`${board.error.slice(0, 120)}\\`` : \"\"}`;\n }\n const cell = (hash: string | undefined, label: string) =>\n hash && status.blobUrlTemplate\n ? `<img src=\"${status.blobUrlTemplate}${hash}\" alt=\"${label}\" width=\"360\" />`\n : \"—\";\n return `<details><summary><b>${title}</b> — ${board.verdict}${meta}</summary>\n\n| baseline | candidate |\n| --- | --- |\n| ${cell(board.baselineHash, \"baseline\")} | ${cell(board.candidateHash, \"candidate\")} |\n\n</details>`;\n })\n .join(\"\\n\");\n\n const notes: string[] = [];\n if (status.approximateBase) {\n notes.push(\n \"⚠ exact merge-base had no visual run — compared against the nearest captured ancestor.\",\n );\n }\n if (status.freshEpoch) {\n notes.push(\n \"ℹ fresh baseline epoch (capture environment changed) — everything reports as new.\",\n );\n }\n\n return `${STICKY_MARKER}\n## 👁 Visual review\n\n${gateLine}\n\n${summary}\n\n${notes.length > 0 ? `${notes.join(\"\\n\")}\\n` : \"\"}${thumbs}\n\n📷 **[Open the visual review report](${status.reportUrl})** — side-by-side, onion-skin and diff views; approve, reject, and comment there.`;\n}\n\nfunction buildNoVisualChangeComment(): string {\n return `${STICKY_MARKER}\n## 👁 Visual review\n\n✅ **Not applicable — no storyboard changes expected.** Storyboard capture was skipped from the PR declaration.`;\n}\n\nfunction latestContextState(repo: string, sha: string): string | null {\n const statuses = (\n JSON.parse(\n gh(\n \"api\",\n `repos/${repo}/commits/${sha}/statuses`,\n \"--paginate\",\n \"--slurp\",\n ),\n ) as Array<Array<{ context?: string; state?: string }>>\n ).flat();\n return (\n statuses.find((status) => status.context === STATUS_CONTEXT)?.state ?? null\n );\n}\n\nfunction isPermanentError(error: unknown): boolean {\n return (\n error instanceof Error && \"permanent\" in error && error.permanent === true\n );\n}\n\nexport async function main(argv = process.argv.slice(2)): Promise<number> {\n const args = parseArgs(argv);\n const repo = process.env.GITHUB_REPOSITORY;\n if (!repo) throw new Error(\"GITHUB_REPOSITORY is not set\");\n const sha = args.sha;\n if (!sha) throw new Error(\"--sha is required\");\n const declaration = visualReviewPrDeclaration();\n\n if (args.bypass || declaration.bypass) {\n postCommitStatus(\n repo,\n sha,\n \"success\",\n \"bypassed via visual-review:bypass label\",\n );\n console.log(\"bypass label present — posted success and skipped the report\");\n return 0;\n }\n\n if (args.noVisualChange || declaration.noVisualChange) {\n postCommitStatus(\n repo,\n sha,\n \"success\",\n \"not applicable — no storyboard changes expected\",\n );\n if (args.pr)\n upsertStickyComment(repo, args.pr, buildNoVisualChangeComment());\n console.log(\n \"no-visual-change declaration present — posted success and skipped capture\",\n );\n return 0;\n }\n\n if (args.fallbackFailure) {\n const current = latestContextState(repo, sha);\n if (current === null || current === \"pending\") {\n postCommitStatus(\n repo,\n sha,\n \"failure\",\n \"visual review reporting failed — see workflow logs\",\n );\n console.log(\n `reporting fallback: latest state was ${current ?? \"absent\"} — posted failure`,\n );\n } else {\n console.log(\n `reporting fallback: latest '${STATUS_CONTEXT}' state is '${current}' — leaving it`,\n );\n }\n return 0;\n }\n\n const statusUrl = args.statusUrl;\n if (!statusUrl) throw new Error(\"--status-url is required\");\n const deadline = Date.now() + args.pollTimeoutS * 1_000;\n let status: ReportStatus | undefined;\n for (;;) {\n try {\n const response = await fetch(statusUrl);\n if (response.status === 404) {\n const error = new Error(\"status poll 404 — wrong report key\");\n Object.assign(error, { permanent: true });\n throw error;\n }\n if (!response.ok)\n throw new Error(`status poll failed: ${response.status}`);\n status = (await response.json()) as ReportStatus;\n } catch (error) {\n if (isPermanentError(error)) throw error;\n console.warn(\n `transient status poll failure, retrying: ${error instanceof Error ? error.message : error}`,\n );\n }\n if (status && (status.state === \"complete\" || status.state === \"error\")) {\n break;\n }\n if (Date.now() > deadline) {\n postCommitStatus(\n repo,\n sha,\n \"failure\",\n \"visual review timed out waiting for compare — see report\",\n status?.reportUrl,\n );\n throw new Error(\"timed out waiting for the run to complete\");\n }\n await new Promise((resolve) => setTimeout(resolve, 5_000));\n }\n\n const verdict = describeGate(status.gate ?? {}, status.state);\n const publishFailure = publisherDeliveryFailure(status);\n if (status.publisherConfigured && publishFailure === null) {\n console.log(\n `verdict (${verdict.state}) owned by the Convex publisher — skipping CI status write`,\n );\n } else {\n if (publishFailure !== null) {\n console.log(\n `Convex publisher reported a delivery failure (${publishFailure.error}) — writing the status from CI as a fallback`,\n );\n }\n postCommitStatus(\n repo,\n sha,\n verdict.state,\n verdict.description,\n status.reportUrl,\n );\n console.log(`commit status: ${verdict.state} — ${verdict.description}`);\n }\n\n if (args.pr) {\n upsertStickyComment(repo, args.pr, buildCommentBody(status));\n console.log(`sticky comment upserted on #${args.pr}`);\n }\n return 0;\n}\n"],"mappings":";;;;;;AAAA,SAAS,oBAAoB;AAI7B,IAAM,gBAAgB;AACtB,IAAM,iBAAiB;AA+DhB,SAAS,yBACd,QAC4C;AAC5C,QAAM,eAAe,OAAO;AAG5B,MAAI,gBAAgB,KAAM,QAAO;AACjC,MAAI,aAAa,YAAY,OAAO,aAAa,YAAY,GAAI,QAAO;AACxE,MAAI,OAAO,qBAAqB,MAAO,QAAO;AAC9C,SAAO;AACT;AAEA,SAAS,UAAU,MAA4B;AAC7C,QAAM,OAAmB;AAAA,IACvB,cAAc;AAAA,IACd,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,EACnB;AACA,WAAS,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS,GAAG;AACnD,UAAM,OAAO,KAAK,KAAK;AACvB,UAAM,OAAO,MAAM;AACjB,YAAM,QAAQ,KAAK,EAAE,KAAK;AAC1B,UAAI,UAAU,OAAW,OAAM,IAAI,MAAM,GAAG,IAAI,gBAAgB;AAChE,aAAO;AAAA,IACT;AACA,QAAI,SAAS,eAAgB,MAAK,YAAY,KAAK;AAAA,aAC1C,SAAS,OAAQ,MAAK,KAAK,OAAO,KAAK,CAAC;AAAA,aACxC,SAAS,QAAS,MAAK,MAAM,KAAK;AAAA,aAClC,SAAS,mBAAoB,MAAK,eAAe,OAAO,KAAK,CAAC;AAAA,aAC9D,SAAS,WAAY,MAAK,SAAS;AAAA,aACnC,SAAS,qBAAsB,MAAK,iBAAiB;AAAA,aACrD,SAAS,qBAAsB,MAAK,kBAAkB;AAAA,QAC1D,OAAM,IAAI,MAAM,gBAAgB,IAAI,EAAE;AAAA,EAC7C;AACA,MAAI,CAAC,KAAK,IAAK,OAAM,IAAI,MAAM,mBAAmB;AAClD,SAAO;AACT;AAEA,SAAS,MAAM,QAA0B;AACvC,SAAO,aAAa,MAAM,QAAQ,EAAE,UAAU,SAAS,aAAa,KAAK,CAAC;AAC5E;AAEA,SAAS,iBACP,MACA,KACA,OACA,aACA,WACM;AACN;AAAA,IACE;AAAA,IACA,SAAS,IAAI,aAAa,GAAG;AAAA,IAC7B;AAAA,IACA,SAAS,KAAK;AAAA,IACd;AAAA,IACA,WAAW,cAAc;AAAA,IACzB;AAAA,IACA,eAAe,YAAY,MAAM,GAAG,GAAG,CAAC;AAAA,IACxC,GAAI,YAAY,CAAC,MAAM,cAAc,SAAS,EAAE,IAAI,CAAC;AAAA,EACvD;AACF;AAEA,SAAS,oBAAoB,MAAc,IAAY,MAAoB;AACzE,QAAM,WACJ,KAAK;AAAA,IACH,GAAG,OAAO,SAAS,IAAI,WAAW,EAAE,aAAa,cAAc,SAAS;AAAA,EAC1E,EACA,KAAK;AACP,QAAM,WAAW,SAAS;AAAA,IACxB,CAAC,YACC,OAAO,QAAQ,SAAS,YAAY,QAAQ,KAAK,SAAS,aAAa;AAAA,EAC3E;AACA,MAAI,UAAU;AACZ;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS,IAAI,oBAAoB,SAAS,EAAE;AAAA,MAC5C;AAAA,MACA,QAAQ,IAAI;AAAA,IACd;AAAA,EACF,OAAO;AACL,OAAG,OAAO,SAAS,IAAI,WAAW,EAAE,aAAa,MAAM,QAAQ,IAAI,EAAE;AAAA,EACvE;AACF;AAEA,SAAS,aACP,MACA,OACmE;AACnE,MAAI,UAAU,SAAS;AACrB,WAAO;AAAA,MACL,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,EACF;AACA,MAAI,UAAU,YAAY;AACxB,WAAO;AAAA,MACL,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,EACF;AACA,MAAI,KAAK,WAAW;AAClB,WAAO;AAAA,MACL,OAAO;AAAA,MACP,aACE,KAAK,aAAa,IACd,sBACA,GAAG,KAAK,QAAQ,iBAAiB,KAAK,aAAa,IAAI,KAAK,GAAG;AAAA,IACvE;AAAA,EACF;AACA,QAAM,QAAkB,CAAC;AACzB,OAAK,KAAK,mBAAmB,KAAK,GAAG;AACnC,UAAM,KAAK,GAAG,KAAK,eAAe,OAAO,KAAK,QAAQ,gBAAgB;AAAA,EACxE;AACA,OAAK,KAAK,YAAY,KAAK,EAAG,OAAM,KAAK,GAAG,KAAK,QAAQ,WAAW;AACpE,OAAK,KAAK,kBAAkB,KAAK,GAAG;AAClC,UAAM;AAAA,MACJ,GAAG,KAAK,cAAc,gBAAgB,KAAK,mBAAmB,IAAI,KAAK,GAAG;AAAA,IAC5E;AAAA,EACF;AACA,SAAO;AAAA,IACL,OAAO;AAAA,IACP,aAAa,kBAAkB,MAAM,KAAK,IAAI,CAAC;AAAA,EACjD;AACF;AAEA,SAAS,iBAAiB,QAA8B;AACtD,QAAM,SAAS,OAAO,UAAU,CAAC;AACjC,QAAM,WAAW,OAAO,MAAM,YAC1B,qEACA,sCAA0B,OAAO,MAAM,mBAAmB,GAAG,OAAO,OAAO,MAAM,YAAY,GAAG,6BAA6B,OAAO,MAAM,iBAAiB,KAAK,OAAO,KAAK,cAAc,qBAAqB,EAAE,GAAG,OAAO,MAAM,WAAW,KAAK,OAAO,KAAK,QAAQ,cAAc,EAAE;AACzR,QAAM,UAAU;AAAA;AAAA,IAEd,OAAO,gBAAgB,CAAC,MAAM,OAAO,WAAW,CAAC,MAAM,OAAO,SAAS,CAAC,MAAM,OAAO,WAAW,CAAC,MAAM,OAAO,UAAU,CAAC,MAAM,OAAO,aAAa,CAAC;AAEtJ,QAAM,UAAU,OAAO,cAAc,CAAC,GACnC,MAAM,GAAG,CAAC,EACV,IAAI,CAAC,UAAU;AACd,UAAM,QAAQ,MAAM,QAAQ,MAAM;AAClC,UAAM,OACJ,MAAM,cAAc,UAAa,MAAM,cAAc,OACjD,UAAO,MAAM,YAAY,KAAK,QAAQ,CAAC,CAAC,MACxC;AACN,QAAI,MAAM,YAAY,gBAAgB;AACpC,aAAO,KAAK,KAAK,yBAAoB,MAAM,QAAQ,OAAO,MAAM,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,EAAE;AAAA,IAC9F;AACA,UAAM,OAAO,CAAC,MAA0B,UACtC,QAAQ,OAAO,kBACX,aAAa,OAAO,eAAe,GAAG,IAAI,UAAU,KAAK,qBACzD;AACN,WAAO,wBAAwB,KAAK,eAAU,MAAM,OAAO,GAAG,IAAI;AAAA;AAAA;AAAA;AAAA,IAIpE,KAAK,MAAM,cAAc,UAAU,CAAC,MAAM,KAAK,MAAM,eAAe,WAAW,CAAC;AAAA;AAAA;AAAA,EAGhF,CAAC,EACA,KAAK,IAAI;AAEZ,QAAM,QAAkB,CAAC;AACzB,MAAI,OAAO,iBAAiB;AAC1B,UAAM;AAAA,MACJ;AAAA,IACF;AAAA,EACF;AACA,MAAI,OAAO,YAAY;AACrB,UAAM;AAAA,MACJ;AAAA,IACF;AAAA,EACF;AAEA,SAAO,GAAG,aAAa;AAAA;AAAA;AAAA,EAGvB,QAAQ;AAAA;AAAA,EAER,OAAO;AAAA;AAAA,EAEP,MAAM,SAAS,IAAI,GAAG,MAAM,KAAK,IAAI,CAAC;AAAA,IAAO,EAAE,GAAG,MAAM;AAAA;AAAA,8CAEnB,OAAO,SAAS;AACvD;AAEA,SAAS,6BAAqC;AAC5C,SAAO,GAAG,aAAa;AAAA;AAAA;AAAA;AAIzB;AAEA,SAAS,mBAAmB,MAAc,KAA4B;AACpE,QAAM,WACJ,KAAK;AAAA,IACH;AAAA,MACE;AAAA,MACA,SAAS,IAAI,YAAY,GAAG;AAAA,MAC5B;AAAA,MACA;AAAA,IACF;AAAA,EACF,EACA,KAAK;AACP,SACE,SAAS,KAAK,CAAC,WAAW,OAAO,YAAY,cAAc,GAAG,SAAS;AAE3E;AAEA,SAAS,iBAAiB,OAAyB;AACjD,SACE,iBAAiB,SAAS,eAAe,SAAS,MAAM,cAAc;AAE1E;AAEA,eAAsB,KAAK,OAAO,QAAQ,KAAK,MAAM,CAAC,GAAoB;AACxE,QAAM,OAAO,UAAU,IAAI;AAC3B,QAAM,OAAO,QAAQ,IAAI;AACzB,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,8BAA8B;AACzD,QAAM,MAAM,KAAK;AACjB,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,mBAAmB;AAC7C,QAAM,cAAc,0BAA0B;AAE9C,MAAI,KAAK,UAAU,YAAY,QAAQ;AACrC;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,YAAQ,IAAI,mEAA8D;AAC1E,WAAO;AAAA,EACT;AAEA,MAAI,KAAK,kBAAkB,YAAY,gBAAgB;AACrD;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,QAAI,KAAK;AACP,0BAAoB,MAAM,KAAK,IAAI,2BAA2B,CAAC;AACjE,YAAQ;AAAA,MACN;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,MAAI,KAAK,iBAAiB;AACxB,UAAM,UAAU,mBAAmB,MAAM,GAAG;AAC5C,QAAI,YAAY,QAAQ,YAAY,WAAW;AAC7C;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,cAAQ;AAAA,QACN,wCAAwC,WAAW,QAAQ;AAAA,MAC7D;AAAA,IACF,OAAO;AACL,cAAQ;AAAA,QACN,+BAA+B,cAAc,eAAe,OAAO;AAAA,MACrE;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,KAAK;AACvB,MAAI,CAAC,UAAW,OAAM,IAAI,MAAM,0BAA0B;AAC1D,QAAM,WAAW,KAAK,IAAI,IAAI,KAAK,eAAe;AAClD,MAAI;AACJ,aAAS;AACP,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,SAAS;AACtC,UAAI,SAAS,WAAW,KAAK;AAC3B,cAAM,QAAQ,IAAI,MAAM,yCAAoC;AAC5D,eAAO,OAAO,OAAO,EAAE,WAAW,KAAK,CAAC;AACxC,cAAM;AAAA,MACR;AACA,UAAI,CAAC,SAAS;AACZ,cAAM,IAAI,MAAM,uBAAuB,SAAS,MAAM,EAAE;AAC1D,eAAU,MAAM,SAAS,KAAK;AAAA,IAChC,SAAS,OAAO;AACd,UAAI,iBAAiB,KAAK,EAAG,OAAM;AACnC,cAAQ;AAAA,QACN,4CAA4C,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AAAA,MAC5F;AAAA,IACF;AACA,QAAI,WAAW,OAAO,UAAU,cAAc,OAAO,UAAU,UAAU;AACvE;AAAA,IACF;AACA,QAAI,KAAK,IAAI,IAAI,UAAU;AACzB;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,MACV;AACA,YAAM,IAAI,MAAM,2CAA2C;AAAA,IAC7D;AACA,UAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,GAAK,CAAC;AAAA,EAC3D;AAEA,QAAM,UAAU,aAAa,OAAO,QAAQ,CAAC,GAAG,OAAO,KAAK;AAC5D,QAAM,iBAAiB,yBAAyB,MAAM;AACtD,MAAI,OAAO,uBAAuB,mBAAmB,MAAM;AACzD,YAAQ;AAAA,MACN,YAAY,QAAQ,KAAK;AAAA,IAC3B;AAAA,EACF,OAAO;AACL,QAAI,mBAAmB,MAAM;AAC3B,cAAQ;AAAA,QACN,iDAAiD,eAAe,KAAK;AAAA,MACvE;AAAA,IACF;AACA;AAAA,MACE;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,OAAO;AAAA,IACT;AACA,YAAQ,IAAI,kBAAkB,QAAQ,KAAK,WAAM,QAAQ,WAAW,EAAE;AAAA,EACxE;AAEA,MAAI,KAAK,IAAI;AACX,wBAAoB,MAAM,KAAK,IAAI,iBAAiB,MAAM,CAAC;AAC3D,YAAQ,IAAI,+BAA+B,KAAK,EAAE,EAAE;AAAA,EACtD;AACA,SAAO;AACT;","names":[]}
@@ -534,6 +534,9 @@ function installRenderProfileInPage() {
534
534
  const renderers = /* @__PURE__ */ new Map();
535
535
  profileWindow.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
536
536
  supportsFiber: true,
537
+ // Consumers that also load `modules/iframe-projection` see this window
538
+ // property through its global `DevToolsHook` augmentation, where
539
+ // `renderers` is typed by ReactRenderer; this stub only counts them.
537
540
  renderers,
538
541
  inject(renderer) {
539
542
  profile.reactInjectedMs ??= performance.now() - profile.startedAtMs;
@@ -4603,7 +4606,7 @@ async function parcelEventToFileEvent(event, rootInfo) {
4603
4606
  // ../tempo-sdk/package.json
4604
4607
  var package_default = {
4605
4608
  name: "tempo-sdk",
4606
- version: "0.0.38",
4609
+ version: "0.0.40",
4607
4610
  type: "module",
4608
4611
  description: "Tempo SDK \u2014 Vite, Next.js, and Expo plugins for JSX annotation and shared page/storyboard types",
4609
4612
  repository: {
@@ -8009,7 +8012,7 @@ ${output}` : "";
8009
8012
  this.notifyExitListeners(code, signal, "launcher-finished", false);
8010
8013
  return;
8011
8014
  }
8012
- this.lastExitCrashed = !this.intentionalStop && (code !== 0 || signal != null) && !isWindowsTeardownExitCode(code);
8015
+ this.lastExitCrashed = !this.intentionalStop && !isWindowsTeardownExitCode(code) && (code !== 0 || signal != null || this.readinessConfirmed);
8013
8016
  if (cleanLauncherExit) {
8014
8017
  this.launcherStopRequired = true;
8015
8018
  }
@@ -8051,11 +8054,17 @@ ${output}` : "";
8051
8054
  onExit(listener) {
8052
8055
  if (this.launcherFinished && !this.terminalExitNotified) {
8053
8056
  this.exitListeners.push(listener);
8054
- listener(0, null, false, "launcher-finished");
8057
+ listener(0, null, false, "launcher-finished", false);
8055
8058
  return;
8056
8059
  }
8057
8060
  if (this.stopped) {
8058
- listener(null, null, this.lastExitCrashed, "process-exit");
8061
+ listener(
8062
+ null,
8063
+ null,
8064
+ this.lastExitCrashed,
8065
+ "process-exit",
8066
+ this.intentionalStop
8067
+ );
8059
8068
  return;
8060
8069
  }
8061
8070
  this.exitListeners.push(listener);
@@ -8178,6 +8187,19 @@ Hint: ${current.hint}` : "";
8178
8187
  this.lastExitCrashed = true;
8179
8188
  this.notifyExitListeners(null, null, "probe-death");
8180
8189
  }
8190
+ /**
8191
+ * The supervisor's liveness check found this child gone without an `exit`
8192
+ * event having reached us (a lost SIGCHLD). Deliver the one terminal exit
8193
+ * its consumers are owed, so the adapter and pool release the host instead
8194
+ * of keeping a dead incarnation `running`; an `exit` event that arrives
8195
+ * late is then a no-op (`terminalExitNotified`).
8196
+ */
8197
+ reportObservedChildDeath() {
8198
+ if (this.stopped || this.terminalExitNotified) return;
8199
+ this.stopped = true;
8200
+ this.lastExitCrashed = !this.intentionalStop;
8201
+ this.notifyExitListeners(null, null, "process-exit");
8202
+ }
8181
8203
  /** Whether the most recent exit looked like an unexpected crash. */
8182
8204
  didLastExitCrash() {
8183
8205
  return this.lastExitCrashed;
@@ -8434,7 +8456,13 @@ Hint: ${current.hint}` : "";
8434
8456
  }
8435
8457
  for (const listener of listeners) {
8436
8458
  try {
8437
- listener(code, signal, this.lastExitCrashed, reason);
8459
+ listener(
8460
+ code,
8461
+ signal,
8462
+ this.lastExitCrashed,
8463
+ reason,
8464
+ this.intentionalStop
8465
+ );
8438
8466
  } catch {
8439
8467
  }
8440
8468
  }
@@ -8811,13 +8839,13 @@ var TempoDevServer = class _TempoDevServer {
8811
8839
  const hintListeners = [];
8812
8840
  const exitListeners = [];
8813
8841
  const attachProcessListeners = (activeProcess) => {
8814
- activeProcess.onExit((code, signal, crashed, reason) => {
8842
+ activeProcess.onExit((code, signal, crashed, reason, intentional) => {
8815
8843
  if (reason === "launcher-finished") {
8816
8844
  devLog("supervisor:launcher_finished", {
8817
8845
  pid: activeProcess.getPid()
8818
8846
  });
8819
8847
  for (const listener of exitListeners) {
8820
- listener(code, signal, crashed, reason);
8848
+ listener(code, signal, crashed, reason, intentional);
8821
8849
  }
8822
8850
  return;
8823
8851
  }
@@ -8850,7 +8878,7 @@ var TempoDevServer = class _TempoDevServer {
8850
8878
  this.persistStatus().catch(() => {
8851
8879
  });
8852
8880
  for (const listener of exitListeners) {
8853
- listener(code, signal, crashed, reason);
8881
+ listener(code, signal, crashed, reason, intentional);
8854
8882
  }
8855
8883
  });
8856
8884
  for (const listener of outputListeners) {
@@ -8911,6 +8939,14 @@ var TempoDevServer = class _TempoDevServer {
8911
8939
  this.devserverLaunchPid = activeProcess.getPid();
8912
8940
  this.devserverLaunchProcessStartedAt = this.devserverLaunchPid ? await readProcessStartedAt(this.devserverLaunchPid) : null;
8913
8941
  attachProcessListeners(activeProcess);
8942
+ try {
8943
+ options?.onProcessSpawned?.();
8944
+ } catch (error) {
8945
+ devLog("supervisor:on_process_spawned_threw", {
8946
+ pid: activeProcess.getPid(),
8947
+ error: error instanceof Error ? error.message : String(error)
8948
+ });
8949
+ }
8914
8950
  await this.writeProcessMarker(activeProcess.getPid()).catch(() => {
8915
8951
  });
8916
8952
  if (options?.signal?.aborted) {
@@ -8987,7 +9023,7 @@ var TempoDevServer = class _TempoDevServer {
8987
9023
  onExit(listener) {
8988
9024
  exitListeners.push(listener);
8989
9025
  if (process2.getLivenessSource() === "probe") {
8990
- listener(0, null, false, "launcher-finished");
9026
+ listener(0, null, false, "launcher-finished", false);
8991
9027
  }
8992
9028
  },
8993
9029
  onOutput(listener) {
@@ -9833,6 +9869,7 @@ var TempoDevServer = class _TempoDevServer {
9833
9869
  this.stopUrlHealthProbe();
9834
9870
  return;
9835
9871
  }
9872
+ this.activeHostProcess?.reportObservedChildDeath();
9836
9873
  this.state.devserverUrl = null;
9837
9874
  this.state.urlUnreachableSince = null;
9838
9875
  this.idleProcessStatus = "exited";
@@ -12199,6 +12236,44 @@ async function readTempoDevserverConfig(tempoConfigPath) {
12199
12236
  tempoHostUrl: config.urls?.tempoHost ?? null
12200
12237
  };
12201
12238
  }
12239
+ function projectRootForTempoDir(tempoDir) {
12240
+ const resolved = path14.resolve(tempoDir);
12241
+ return path14.basename(resolved) === "tempo" ? path14.dirname(resolved) : resolved;
12242
+ }
12243
+ async function resolveCanvasHostLayout(options) {
12244
+ const repoRoot = path14.resolve(options.repoRoot);
12245
+ const candidates = options.tempoDir ? [path14.join(path14.resolve(options.tempoDir), "tempo.config.json")] : [
12246
+ path14.join(repoRoot, "tempo", "tempo.config.json"),
12247
+ path14.join(repoRoot, "tempo.config.json")
12248
+ ];
12249
+ for (const configPath of candidates) {
12250
+ let parsed;
12251
+ try {
12252
+ parsed = JSON.parse(await readFile10(configPath, "utf-8"));
12253
+ } catch {
12254
+ continue;
12255
+ }
12256
+ if (typeof parsed !== "object" || parsed === null || !("scripts" in parsed)) {
12257
+ continue;
12258
+ }
12259
+ const tempoDir2 = path14.dirname(configPath);
12260
+ const paths = parsed.paths;
12261
+ const canvases = typeof paths?.canvases === "string" && paths.canvases.trim().length > 0 ? paths.canvases.trim().split("\\").join("/") : "designs";
12262
+ return {
12263
+ tempoDir: tempoDir2,
12264
+ projectRoot: projectRootForTempoDir(tempoDir2),
12265
+ pagesDir: path14.resolve(tempoDir2, canvases),
12266
+ configPath
12267
+ };
12268
+ }
12269
+ const tempoDir = options.tempoDir ? path14.resolve(options.tempoDir) : path14.join(repoRoot, "tempo");
12270
+ return {
12271
+ tempoDir,
12272
+ projectRoot: projectRootForTempoDir(tempoDir),
12273
+ pagesDir: path14.join(tempoDir, "designs"),
12274
+ configPath: null
12275
+ };
12276
+ }
12202
12277
  function resolveTempoDir(options) {
12203
12278
  if (options.tempoConfigPath) return path14.dirname(options.tempoConfigPath);
12204
12279
  if (options.tempoDir) return options.tempoDir;
@@ -12283,7 +12358,7 @@ async function startDevserverSession(options) {
12283
12358
  scripts: config.scripts
12284
12359
  });
12285
12360
  await devServer.initialize();
12286
- const projectRoot = path14.dirname(tempoDir);
12361
+ const projectRoot = projectRootForTempoDir(tempoDir);
12287
12362
  for (const hostCanvasPath of hostCanvasPaths) {
12288
12363
  await devServer.ensureCanvasRegistered(
12289
12364
  path14.join(projectRoot, hostCanvasPath)
@@ -12359,14 +12434,14 @@ async function startDevserverSession(options) {
12359
12434
  }
12360
12435
 
12361
12436
  // ../canvas-capture-headless/src/enumerate.ts
12362
- import { readFile as readFile11 } from "fs/promises";
12437
+ import { access as access4, readFile as readFile11 } from "fs/promises";
12363
12438
  import path15 from "path";
12364
12439
  function toPosixPath2(value) {
12365
12440
  return value.split(path15.sep).join("/");
12366
12441
  }
12367
12442
  async function enumerateCanvasStoryboards(options) {
12368
12443
  const pagesDir = options.pagesDir ?? path15.join(options.tempoDir, "designs");
12369
- const projectRoot = path15.dirname(options.tempoDir);
12444
+ const projectRoot = projectRootForTempoDir(options.tempoDir);
12370
12445
  const canvasFiles = await scanCanvasFiles(pagesDir);
12371
12446
  const storyboards = [];
12372
12447
  const structuralErrors = [];
@@ -12431,6 +12506,61 @@ async function enumerateCanvasStoryboards(options) {
12431
12506
  vrtConfigErrors
12432
12507
  };
12433
12508
  }
12509
+ async function enumerateTrackedStoryboards(options) {
12510
+ try {
12511
+ await access4(options.tempoDir);
12512
+ } catch {
12513
+ return {
12514
+ tracked: [],
12515
+ paused: [],
12516
+ totalStoryboards: 0,
12517
+ totalCanvases: 0,
12518
+ errors: []
12519
+ };
12520
+ }
12521
+ const enumeration = await enumerateCanvasStoryboards(options);
12522
+ const tracked = [];
12523
+ const paused = [];
12524
+ const errors = [
12525
+ ...enumeration.structuralErrors,
12526
+ ...enumeration.vrtConfigErrors
12527
+ ];
12528
+ for (const storyboard of enumeration.storyboards) {
12529
+ if (!storyboard.vrt) continue;
12530
+ const board = {
12531
+ canvasPath: storyboard.canvasPath,
12532
+ hostCanvasPath: storyboard.hostCanvasPath,
12533
+ storyboardId: storyboard.storyboardId,
12534
+ name: storyboard.name,
12535
+ layout: storyboard.layout,
12536
+ threshold: storyboard.vrt.threshold,
12537
+ ignore: storyboard.vrt.ignore
12538
+ };
12539
+ const maskedArea = (storyboard.vrt.ignore ?? []).reduce(
12540
+ (total, region) => total + Math.max(0, Math.min(region.w, board.layout.width)) * Math.max(0, Math.min(region.h, board.layout.height)),
12541
+ 0
12542
+ );
12543
+ if (maskedArea >= 0.9 * board.layout.width * board.layout.height) {
12544
+ errors.push({
12545
+ filePath: storyboard.filePath,
12546
+ message: `<Storyboard id="${storyboard.storyboardId}"> vrt.ignore masks \u226590% of the board \u2014 untrack it instead`
12547
+ });
12548
+ continue;
12549
+ }
12550
+ if (storyboard.vrt.skip) {
12551
+ paused.push(board);
12552
+ continue;
12553
+ }
12554
+ tracked.push(board);
12555
+ }
12556
+ return {
12557
+ tracked,
12558
+ paused,
12559
+ totalStoryboards: enumeration.totalStoryboards,
12560
+ totalCanvases: enumeration.totalCanvases,
12561
+ errors
12562
+ };
12563
+ }
12434
12564
 
12435
12565
  // ../canvas-capture-headless/src/timing.ts
12436
12566
  function percentile(sortedValues, p) {
@@ -12510,64 +12640,8 @@ var TimingReport = class {
12510
12640
  };
12511
12641
 
12512
12642
  // src/enumerate.ts
12513
- import { access as access4 } from "fs/promises";
12514
- async function enumerateTrackedStoryboards(options) {
12515
- try {
12516
- await access4(options.tempoDir);
12517
- } catch {
12518
- return {
12519
- tracked: [],
12520
- paused: [],
12521
- totalStoryboards: 0,
12522
- totalCanvases: 0,
12523
- errors: []
12524
- };
12525
- }
12526
- const enumeration = await enumerateCanvasStoryboards({
12527
- tempoDir: options.tempoDir,
12528
- pagesDir: options.pagesDir
12529
- });
12530
- const tracked = [];
12531
- const paused = [];
12532
- const errors = [
12533
- ...enumeration.structuralErrors,
12534
- ...enumeration.vrtConfigErrors
12535
- ];
12536
- for (const storyboard of enumeration.storyboards) {
12537
- if (!storyboard.vrt) continue;
12538
- const board = {
12539
- canvasPath: storyboard.canvasPath,
12540
- hostCanvasPath: storyboard.hostCanvasPath,
12541
- storyboardId: storyboard.storyboardId,
12542
- name: storyboard.name,
12543
- layout: storyboard.layout,
12544
- threshold: storyboard.vrt.threshold,
12545
- ignore: storyboard.vrt.ignore
12546
- };
12547
- const maskedArea = (storyboard.vrt.ignore ?? []).reduce(
12548
- (total, region) => total + Math.max(0, Math.min(region.w, board.layout.width)) * Math.max(0, Math.min(region.h, board.layout.height)),
12549
- 0
12550
- );
12551
- if (maskedArea >= 0.9 * board.layout.width * board.layout.height) {
12552
- errors.push({
12553
- filePath: storyboard.filePath,
12554
- message: `<Storyboard id="${storyboard.storyboardId}"> vrt.ignore masks \u226590% of the board \u2014 untrack it instead`
12555
- });
12556
- continue;
12557
- }
12558
- if (storyboard.vrt.skip) {
12559
- paused.push(board);
12560
- continue;
12561
- }
12562
- tracked.push(board);
12563
- }
12564
- return {
12565
- tracked,
12566
- paused,
12567
- totalStoryboards: enumeration.totalStoryboards,
12568
- totalCanvases: enumeration.totalCanvases,
12569
- errors
12570
- };
12643
+ async function enumerateTrackedStoryboards2(options) {
12644
+ return await enumerateTrackedStoryboards(options);
12571
12645
  }
12572
12646
 
12573
12647
  // src/fingerprint.ts
@@ -12790,6 +12864,7 @@ function parseArgs(argv) {
12790
12864
  const args = {
12791
12865
  repoRoot: inferRepoRoot(),
12792
12866
  tempoDir: "",
12867
+ tempoDirExplicit: false,
12793
12868
  // The published CLI is intentionally near-argumentless in Actions. Local
12794
12869
  // capture remains Convex-free unless --upload is explicit.
12795
12870
  upload: process.env.GITHUB_ACTIONS === "true" && inferredTrigger !== "manual",
@@ -12820,6 +12895,7 @@ function parseArgs(argv) {
12820
12895
  break;
12821
12896
  case "--tempo-dir":
12822
12897
  args.tempoDir = path16.resolve(next());
12898
+ args.tempoDirExplicit = true;
12823
12899
  break;
12824
12900
  case "--out":
12825
12901
  args.out = path16.resolve(next());
@@ -13012,6 +13088,12 @@ function startClaimHeartbeat(options) {
13012
13088
  }
13013
13089
  };
13014
13090
  }
13091
+ function explicitTempoDirRelative(args) {
13092
+ if (!args.tempoDirExplicit) return void 0;
13093
+ const relative2 = path16.relative(args.repoRoot, args.tempoDir);
13094
+ if (relative2.startsWith("..") || path16.isAbsolute(relative2)) return void 0;
13095
+ return relative2;
13096
+ }
13015
13097
  function buildBaselineCaptureArgs(input) {
13016
13098
  const args = [
13017
13099
  "capture",
@@ -13033,6 +13115,12 @@ function buildBaselineCaptureArgs(input) {
13033
13115
  ];
13034
13116
  if (input.url) args.push("--url", input.url);
13035
13117
  if (input.githubRepo) args.push("--github-repo", input.githubRepo);
13118
+ if (input.tempoDirRelative !== void 0) {
13119
+ args.push(
13120
+ "--tempo-dir",
13121
+ path16.join(input.worktreeRoot, input.tempoDirRelative || ".")
13122
+ );
13123
+ }
13036
13124
  if (input.repoKey) args.push("--repo-key", input.repoKey);
13037
13125
  if (input.settleTimeoutMs !== void 0) {
13038
13126
  args.push("--settle-timeout", String(input.settleTimeoutMs));
@@ -13067,7 +13155,8 @@ async function runBaselineCaptureInWorktree(options) {
13067
13155
  url: options.args.url,
13068
13156
  githubRepo: options.args.githubRepo,
13069
13157
  repoKey: options.args.repoKey,
13070
- settleTimeoutMs: options.args.settleTimeoutMs
13158
+ settleTimeoutMs: options.args.settleTimeoutMs,
13159
+ tempoDirRelative: explicitTempoDirRelative(options.args)
13071
13160
  })
13072
13161
  ];
13073
13162
  await new Promise((resolve2, reject) => {
@@ -13118,10 +13207,15 @@ async function main(argv = process.argv.slice(2)) {
13118
13207
  if (args.trigger === "pr" && baseCandidates.length === 0) {
13119
13208
  throw new Error("--trigger pr requires --base <merge-base sha>");
13120
13209
  }
13121
- const pagesDir = path16.join(args.tempoDir, "designs");
13210
+ const layout = await resolveCanvasHostLayout({
13211
+ repoRoot: args.repoRoot,
13212
+ tempoDir: args.tempoDirExplicit ? args.tempoDir : void 0
13213
+ });
13214
+ args.tempoDir = layout.tempoDir;
13215
+ const pagesDir = layout.pagesDir;
13122
13216
  const enumeration = await timing.phase(
13123
13217
  "enumerate",
13124
- () => enumerateTrackedStoryboards({ tempoDir: args.tempoDir, pagesDir })
13218
+ () => enumerateTrackedStoryboards2({ tempoDir: args.tempoDir, pagesDir })
13125
13219
  );
13126
13220
  if (enumeration.errors.length > 0) {
13127
13221
  for (const error of enumeration.errors) {
@@ -13574,13 +13668,14 @@ export {
13574
13668
  percentile,
13575
13669
  summarize,
13576
13670
  TimingReport,
13577
- enumerateTrackedStoryboards,
13671
+ enumerateTrackedStoryboards2 as enumerateTrackedStoryboards,
13578
13672
  scaleIgnoreRegionsToDevicePx,
13579
13673
  VisualReviewHttpError,
13580
13674
  VisualReviewIngestClient,
13581
13675
  resolveOrClaimBaseline,
13676
+ explicitTempoDirRelative,
13582
13677
  buildBaselineCaptureArgs,
13583
13678
  main,
13584
13679
  VISUAL_REVIEW_OIDC_AUDIENCE
13585
13680
  };
13586
- //# sourceMappingURL=chunk-4QAZFBC3.js.map
13681
+ //# sourceMappingURL=chunk-MW6S36CX.js.map