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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -6,7 +6,7 @@ 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-JCGZRFSY.js")).main(commandArgs);
9
+ return await (await import("./cli-DPVTULCN.js")).main(commandArgs);
10
10
  case "claim":
11
11
  return await (await import("./claim-WI2ZVIDS.js")).main(commandArgs);
12
12
  case "report":
@@ -15,6 +15,63 @@ import path16 from "path";
15
15
 
16
16
  // ../canvas-capture-headless/src/capture.ts
17
17
  import { createHash } from "crypto";
18
+
19
+ // ../../modules/iframe-projection/combined/projection-pending.ts
20
+ var PROJECTION_PENDING_ATTRIBUTE = "data-tempo-projection-pending";
21
+
22
+ // ../../modules/component-render-pool/direct-render.ts
23
+ var DIRECT_RENDER_PARAMS = {
24
+ canvasPath: "canvasPath",
25
+ storyboardId: "storyboardId",
26
+ width: "width",
27
+ height: "height",
28
+ intrinsicSize: "intrinsicSize",
29
+ idle: "tempoCaptureIdle"
30
+ };
31
+ var STORYBOARD_RENDER_ID_ATTRIBUTE = "data-tempo-storyboard-render-id";
32
+ var STORYBOARD_PENDING_SELECTOR = `[${PROJECTION_PENDING_ATTRIBUTE}]`;
33
+ function buildCanvasStoryboardRenderId(canvasPath, storyboardId) {
34
+ return `canvas:${encodeURIComponent(canvasPath.trim())}#storyboard:${encodeURIComponent(
35
+ storyboardId.trim()
36
+ )}`;
37
+ }
38
+ function buildStoryboardRenderSelector(renderId) {
39
+ return `[${STORYBOARD_RENDER_ID_ATTRIBUTE}=${JSON.stringify(renderId)}]`;
40
+ }
41
+ function buildDirectRenderUrl(hostUrl, target) {
42
+ const hasWidth = target.width !== void 0;
43
+ const hasHeight = target.height !== void 0;
44
+ if (hasWidth !== hasHeight) {
45
+ throw new Error("Direct render width and height must be provided together");
46
+ }
47
+ const url = new URL(hostUrl);
48
+ url.searchParams.delete(DIRECT_RENDER_PARAMS.idle);
49
+ url.searchParams.set(DIRECT_RENDER_PARAMS.canvasPath, target.canvasPath);
50
+ url.searchParams.set(DIRECT_RENDER_PARAMS.storyboardId, target.storyboardId);
51
+ if (hasWidth && hasHeight) {
52
+ url.searchParams.set(DIRECT_RENDER_PARAMS.width, String(target.width));
53
+ url.searchParams.set(DIRECT_RENDER_PARAMS.height, String(target.height));
54
+ } else {
55
+ url.searchParams.delete(DIRECT_RENDER_PARAMS.width);
56
+ url.searchParams.delete(DIRECT_RENDER_PARAMS.height);
57
+ }
58
+ if (target.intrinsicSize) {
59
+ url.searchParams.set(DIRECT_RENDER_PARAMS.intrinsicSize, "1");
60
+ } else {
61
+ url.searchParams.delete(DIRECT_RENDER_PARAMS.intrinsicSize);
62
+ }
63
+ return url.toString();
64
+ }
65
+ function buildDirectRenderIdleUrl(hostUrl) {
66
+ const url = new URL(hostUrl);
67
+ for (const param of Object.values(DIRECT_RENDER_PARAMS)) {
68
+ url.searchParams.delete(param);
69
+ }
70
+ url.searchParams.set(DIRECT_RENDER_PARAMS.idle, "1");
71
+ return url.toString();
72
+ }
73
+
74
+ // ../canvas-capture-headless/src/capture.ts
18
75
  import { PNG } from "pngjs";
19
76
 
20
77
  // ../../modules/frame-pool/capture-determinism.ts
@@ -70,100 +127,240 @@ var FREEZE_MOTION_CSS = `
70
127
  }
71
128
  `;
72
129
 
73
- // ../canvas-capture-headless/src/determinism.ts
74
- var CHROMIUM_LAUNCH_ARGS = [
75
- "--force-color-profile=srgb",
76
- "--force-device-scale-factor=2",
77
- "--hide-scrollbars",
78
- "--disable-lcd-text"
130
+ // ../../modules/frame-pool/settle-script.ts
131
+ var SETTLE_STEPS = [
132
+ "target",
133
+ "fonts",
134
+ "suspense",
135
+ "content-quiet",
136
+ "paint"
79
137
  ];
80
- async function runSettleStep(name, work, onStep) {
81
- const startedAt = Date.now();
82
- try {
83
- return await work();
84
- } catch (error) {
85
- const message = error instanceof Error ? error.message : String(error);
86
- throw new Error(`settle.${name}: ${message}`);
87
- } finally {
88
- onStep?.(name, Date.now() - startedAt);
89
- }
90
- }
91
- async function settlePage(page, targetSelector, options = {}) {
92
- const quietMs = options.quietMs ?? 300;
93
- const timeoutMs = options.timeoutMs ?? 15e3;
94
- await runSettleStep(
95
- "target",
96
- () => page.waitForSelector(targetSelector, {
97
- state: "attached",
98
- timeout: timeoutMs
99
- }),
100
- options.onStep
101
- );
102
- await runSettleStep(
103
- "fonts",
104
- () => page.waitForFunction(() => document.fonts.status === "loaded", {
105
- timeout: timeoutMs
106
- }),
107
- options.onStep
138
+ var DEFAULT_SETTLE_QUIET_MS = 300;
139
+ var DEFAULT_SETTLE_STEP_TIMEOUT_MS = 15e3;
140
+ var DEFAULT_SETTLE_POLL_MS = 50;
141
+ function buildSettleWaitScript(options) {
142
+ const quietMs = Math.max(0, Math.round(options.quietMs ?? DEFAULT_SETTLE_QUIET_MS));
143
+ const stepTimeoutMs = Math.max(
144
+ 1,
145
+ Math.round(options.stepTimeoutMs ?? DEFAULT_SETTLE_STEP_TIMEOUT_MS)
108
146
  );
109
- await runSettleStep(
110
- "suspense",
111
- () => page.waitForFunction(
112
- (selector) => {
113
- const target = document.querySelector(selector);
114
- if (!target) return false;
115
- return !target.querySelector("[data-tempo-projection-pending]");
116
- },
117
- targetSelector,
118
- { timeout: timeoutMs }
119
- ),
120
- options.onStep
147
+ const quietTimeoutMs = Math.max(
148
+ 1,
149
+ Math.round(options.quietTimeoutMs ?? stepTimeoutMs)
121
150
  );
122
- await runSettleStep(
123
- "content-quiet",
124
- () => page.waitForFunction(
125
- ({ selector, quiet }) => {
126
- const target = document.querySelector(selector);
151
+ const pollMs = Math.max(1, Math.round(options.pollMs ?? DEFAULT_SETTLE_POLL_MS));
152
+ return `
153
+ (function() {
154
+ var selector = ${JSON.stringify(options.selector)};
155
+ var pendingSelector = ${JSON.stringify(options.pendingSelector ?? null)};
156
+ var quietMs = ${quietMs};
157
+ var stepTimeoutMs = ${stepTimeoutMs};
158
+ var quietTimeoutMs = ${quietTimeoutMs};
159
+ var quietBestEffort = ${options.quietBestEffort === true ? "true" : "false"};
160
+ var pollMs = ${pollMs};
161
+ var runPaint = ${options.paint === false ? "false" : "true"};
162
+ var steps = {};
163
+ // Cleared when the content-quiet step gives up under best effort.
164
+ var quiet = true;
165
+ // Monotonic and immune to the determinism kit's frozen Date.
166
+ var now = typeof performance !== 'undefined' && performance && typeof performance.now === 'function'
167
+ ? function() { return performance.now(); }
168
+ : function() { return Date.now(); };
169
+
170
+ function failure(step, message) {
171
+ return { ok: false, step: step, message: message, steps: steps };
172
+ }
173
+
174
+ // Polls \`check\` until it answers true, or \`budgetMs\` elapses. The
175
+ // first check runs synchronously so an already-settled step costs no
176
+ // poll interval. A step run with \`giveUp\` resolves at its budget
177
+ // instead of failing, recording that the content never held still.
178
+ function until(step, check, budgetMs, giveUp) {
179
+ var started = now();
180
+ return new Promise(function(resolve, reject) {
181
+ function tick() {
182
+ var done;
183
+ try {
184
+ done = check();
185
+ } catch (error) {
186
+ steps[step] = Math.round(now() - started);
187
+ reject(failure(step, String((error && error.message) || error)));
188
+ return;
189
+ }
190
+ if (done) {
191
+ steps[step] = Math.round(now() - started);
192
+ resolve();
193
+ return;
194
+ }
195
+ if (now() - started >= budgetMs) {
196
+ steps[step] = Math.round(now() - started);
197
+ if (giveUp) {
198
+ quiet = false;
199
+ resolve();
200
+ return;
201
+ }
202
+ reject(failure(step, 'Timeout ' + budgetMs + 'ms exceeded'));
203
+ return;
204
+ }
205
+ setTimeout(tick, pollMs);
206
+ }
207
+ tick();
208
+ });
209
+ }
210
+
211
+ var quietKey = null;
212
+ var quietSince = 0;
213
+ // Images that take part in the capture: laid out and not hidden. A
214
+ // hidden responsive variant never starts native lazy loading and must
215
+ // not hold the window; a rendered lazy image never starts either,
216
+ // because a capture does not scroll, so it is asked for eagerly.
217
+ function renderedImages(target) {
218
+ var all = Array.prototype.slice.call(target.querySelectorAll('img'));
219
+ var rendered = [];
220
+ for (var i = 0; i < all.length; i++) {
221
+ var img = all[i];
222
+ if (img.getClientRects().length === 0) continue;
223
+ var visibility = getComputedStyle(img).visibility;
224
+ if (visibility === 'hidden' || visibility === 'collapse') continue;
225
+ rendered.push(img);
226
+ }
227
+ return rendered;
228
+ }
229
+ function contentQuiet() {
230
+ var target = document.querySelector(selector);
127
231
  if (!target) return false;
128
- const images = Array.from(target.querySelectorAll("img"));
129
- if (images.some((img) => !img.complete)) return false;
130
- const rect = target.getBoundingClientRect();
131
- const text = target.innerText ?? "";
132
- const key2 = [
232
+ var images = renderedImages(target);
233
+ for (var i = 0; i < images.length; i++) {
234
+ if (images[i].loading === 'lazy') images[i].loading = 'eager';
235
+ }
236
+ for (var j = 0; j < images.length; j++) {
237
+ if (!images[j].complete) {
238
+ // Load/error handlers can replace content without moving its
239
+ // geometry: start a fresh quiet window once they have run.
240
+ quietKey = null;
241
+ return false;
242
+ }
243
+ }
244
+ var rect = target.getBoundingClientRect();
245
+ var text = target.innerText || '';
246
+ var key = [
133
247
  rect.x,
134
248
  rect.y,
135
249
  rect.width,
136
250
  rect.height,
137
- target.querySelectorAll("*").length,
251
+ target.querySelectorAll('*').length,
138
252
  text.length,
139
253
  images.length
140
- ].join(",");
141
- const state = window.__tempoVisualReviewQuiet ?? {};
142
- window.__tempoVisualReviewQuiet = state;
143
- const now = performance.now();
144
- if (state.key !== key2) {
145
- state.key = key2;
146
- state.since = now;
254
+ ].join(',');
255
+ var at = now();
256
+ if (quietKey !== key) {
257
+ quietKey = key;
258
+ quietSince = at;
147
259
  return false;
148
260
  }
149
- return now - state.since >= quiet;
150
- },
151
- { selector: targetSelector, quiet: quietMs },
152
- { timeout: timeoutMs, polling: 50 }
153
- ),
154
- options.onStep
155
- );
156
- await runSettleStep(
157
- "paint",
158
- () => page.evaluate(
159
- () => new Promise(
160
- (resolve2) => requestAnimationFrame(() => requestAnimationFrame(() => resolve2()))
161
- )
162
- ),
163
- options.onStep
261
+ return at - quietSince >= quietMs;
262
+ }
263
+
264
+ function paint() {
265
+ if (!runPaint) return Promise.resolve();
266
+ var started = now();
267
+ return new Promise(function(resolve) {
268
+ requestAnimationFrame(function() {
269
+ requestAnimationFrame(function() {
270
+ steps.paint = Math.round(now() - started);
271
+ resolve();
272
+ });
273
+ });
274
+ });
275
+ }
276
+
277
+ return until('target', function() {
278
+ return document.querySelector(selector) !== null;
279
+ }, stepTimeoutMs, false)
280
+ .then(function() {
281
+ return until('fonts', function() {
282
+ var fonts = document.fonts;
283
+ return !fonts || fonts.status === 'loaded';
284
+ }, stepTimeoutMs, false);
285
+ })
286
+ .then(function() {
287
+ return until('suspense', function() {
288
+ if (!pendingSelector) return true;
289
+ var target = document.querySelector(selector);
290
+ if (!target) return false;
291
+ return !target.querySelector(pendingSelector);
292
+ }, stepTimeoutMs, false);
293
+ })
294
+ .then(function() {
295
+ return until('content-quiet', contentQuiet, quietTimeoutMs, quietBestEffort);
296
+ })
297
+ .then(paint)
298
+ .then(
299
+ function() {
300
+ return { ok: true, steps: steps, quiet: quiet };
301
+ },
302
+ function(caught) {
303
+ if (caught && caught.ok === false) return caught;
304
+ return failure('paint', String((caught && caught.message) || caught));
305
+ }
306
+ );
307
+ })()
308
+ `;
309
+ }
310
+
311
+ // ../canvas-capture-headless/src/navigation-errors.ts
312
+ function isNavigationInterruptedError(message) {
313
+ return /Execution context was destroyed|most likely because of a navigation|Cannot find context with specified id|[Ff]rame was detached|Navigation interrupted|is interrupted by another navigation|Inspected target navigated or closed|Cannot find object with id|context was destroyed/.test(
314
+ message
164
315
  );
165
316
  }
166
317
 
318
+ // ../canvas-capture-headless/src/determinism.ts
319
+ var CHROMIUM_LAUNCH_ARGS = [
320
+ "--force-color-profile=srgb",
321
+ "--force-device-scale-factor=2",
322
+ "--hide-scrollbars",
323
+ "--disable-lcd-text"
324
+ ];
325
+ async function settlePage(page, targetSelector, options = {}) {
326
+ const quietMs = options.quietMs ?? 300;
327
+ const timeoutMs = options.timeoutMs ?? 15e3;
328
+ const budgetMs = timeoutMs * SETTLE_STEPS.length;
329
+ const deadline = Date.now() + budgetMs;
330
+ let resumes = 0;
331
+ for (; ; ) {
332
+ let report;
333
+ try {
334
+ report = await page.evaluate(
335
+ buildSettleWaitScript({
336
+ selector: targetSelector,
337
+ pendingSelector: STORYBOARD_PENDING_SELECTOR,
338
+ quietMs,
339
+ stepTimeoutMs: Math.max(1, Math.min(timeoutMs, deadline - Date.now()))
340
+ })
341
+ );
342
+ } catch (error) {
343
+ const message = error instanceof Error ? error.message : String(error);
344
+ if (!isNavigationInterruptedError(message)) throw error;
345
+ resumes += 1;
346
+ if (Date.now() >= deadline) {
347
+ throw new Error(
348
+ `settle: Timeout ${budgetMs}ms exceeded; the document was replaced ${resumes} times while waiting`
349
+ );
350
+ }
351
+ continue;
352
+ }
353
+ for (const step of SETTLE_STEPS) {
354
+ const ms = report.steps[step];
355
+ if (ms !== void 0) options.onStep?.(step, ms);
356
+ }
357
+ if (!report.ok) {
358
+ throw new Error(`settle.${report.step}: ${report.message}`);
359
+ }
360
+ return;
361
+ }
362
+ }
363
+
167
364
  // ../canvas-capture-headless/src/ensure-chromium.ts
168
365
  import { spawn } from "child_process";
169
366
  import { createRequire } from "module";
@@ -223,7 +420,7 @@ async function installChromium(log, spawnImpl = spawn) {
223
420
  }
224
421
 
225
422
  // ../canvas-capture-headless/src/fingerprint.ts
226
- var KIT_VERSION = "2";
423
+ var KIT_VERSION = "3";
227
424
  var FONTS_BUNDLE_VERSION = "system-1";
228
425
  var DEVICE_SCALE_FACTOR = 2;
229
426
  function buildEnvFingerprint(input) {
@@ -610,15 +807,15 @@ function readRenderProfileInPage(targetCanvasPath) {
610
807
 
611
808
  // ../canvas-capture-headless/src/capture.ts
612
809
  function storyboardRenderId(hostCanvasPath, storyboardId) {
613
- return `canvas:${encodeURIComponent(hostCanvasPath.trim())}#storyboard:${encodeURIComponent(storyboardId.trim())}`;
614
- }
615
- function buildDirectRenderUrl(hostUrl, target) {
616
- const url = new URL(hostUrl);
617
- url.searchParams.set("canvasPath", target.hostCanvasPath);
618
- url.searchParams.set("storyboardId", target.storyboardId);
619
- url.searchParams.set("width", String(target.width));
620
- url.searchParams.set("height", String(target.height));
621
- return url.toString();
810
+ return buildCanvasStoryboardRenderId(hostCanvasPath, storyboardId);
811
+ }
812
+ function buildDirectRenderUrl2(hostUrl, target) {
813
+ return buildDirectRenderUrl(hostUrl, {
814
+ canvasPath: target.hostCanvasPath,
815
+ storyboardId: target.storyboardId,
816
+ width: target.width,
817
+ height: target.height
818
+ });
622
819
  }
623
820
  function sha256Hex(bytes) {
624
821
  return createHash("sha256").update(bytes).digest("hex");
@@ -639,11 +836,6 @@ function looksBlank(pngBytes) {
639
836
  }
640
837
  return true;
641
838
  }
642
- function isNavigationInterruptedError(message) {
643
- return /Execution context was destroyed|most likely because of a navigation|Cannot find context with specified id|[Ff]rame was detached|Navigation interrupted|is interrupted by another navigation|Inspected target navigated or closed|Cannot find object with id|context was destroyed/.test(
644
- message
645
- );
646
- }
647
839
  function isPageGoneError(message) {
648
840
  return /Target closed|Target page, context or browser has been closed|Page closed|Target crashed/.test(
649
841
  message
@@ -838,7 +1030,7 @@ async function startCaptureSession(options) {
838
1030
  target.hostCanvasPath,
839
1031
  target.storyboardId
840
1032
  );
841
- const selector = `[data-tempo-storyboard-render-id=${JSON.stringify(renderId)}]`;
1033
+ const selector = buildStoryboardRenderSelector(renderId);
842
1034
  const ownsWorker = reusableWorker === void 0;
843
1035
  const worker = reusableWorker ?? await createCaptureWorker(capture);
844
1036
  const { page } = worker;
@@ -852,19 +1044,17 @@ async function startCaptureSession(options) {
852
1044
  try {
853
1045
  const navStart = Date.now();
854
1046
  try {
855
- const targetUrl = buildDirectRenderUrl(options.hostUrl, target);
1047
+ const targetUrl = buildDirectRenderUrl2(options.hostUrl, target);
856
1048
  if (!worker.hasNavigated) {
857
1049
  await page.goto(targetUrl, { waitUntil: "domcontentloaded" });
858
1050
  await page.addStyleTag({ content: FREEZE_MOTION_CSS });
859
1051
  worker.hasNavigated = true;
860
1052
  } else {
861
1053
  if (worker.currentSelector === selector) {
862
- const idleUrl = new URL(options.hostUrl);
863
- idleUrl.searchParams.set("tempoCaptureIdle", "1");
864
1054
  await page.evaluate((url) => {
865
1055
  history.replaceState(null, "", url);
866
1056
  window.dispatchEvent(new PopStateEvent("popstate"));
867
- }, idleUrl.toString());
1057
+ }, buildDirectRenderIdleUrl(options.hostUrl));
868
1058
  worker.counters.sameDocumentIssued += 1;
869
1059
  await page.waitForSelector(worker.currentSelector, {
870
1060
  state: "detached",
@@ -875,7 +1065,6 @@ async function startCaptureSession(options) {
875
1065
  ({ url, nextSelector }) => {
876
1066
  const captureWindow = window;
877
1067
  captureWindow.__tempoResetCaptureDeterminism?.();
878
- delete captureWindow.__tempoVisualReviewQuiet;
879
1068
  captureWindow.__tempoCaptureRenderProfile?.begin(nextSelector);
880
1069
  history.replaceState(null, "", url);
881
1070
  window.dispatchEvent(new PopStateEvent("popstate"));
@@ -1440,6 +1629,7 @@ function abortableWait(cancelledMessage) {
1440
1629
  import { spawn as spawn3 } from "child_process";
1441
1630
  import path2 from "path";
1442
1631
  var BUNDLED_NODE_BIN_DIR_ENV = "TEMPO_NODE_TOOLCHAIN_BIN_DIR";
1632
+ var BUNDLED_NODE_RUNTIME_ENV = "TEMPO_NODE_TOOLCHAIN_RUNTIME";
1443
1633
  function getBundledNodeBinDir() {
1444
1634
  const dir = process.env[BUNDLED_NODE_BIN_DIR_ENV];
1445
1635
  return dir && dir.trim().length > 0 ? dir : null;
@@ -1458,10 +1648,13 @@ function shouldUseBundledNode(args) {
1458
1648
  }
1459
1649
  function applyBundledNodeEnv(env, binDir) {
1460
1650
  if (!binDir) return env;
1461
- return {
1462
- ...prependDirToPathEnv(env, binDir),
1463
- ELECTRON_RUN_AS_NODE: "1"
1464
- };
1651
+ const next = prependDirToPathEnv(env, binDir);
1652
+ if (env[BUNDLED_NODE_RUNTIME_ENV] === "native") {
1653
+ delete next.ELECTRON_RUN_AS_NODE;
1654
+ } else {
1655
+ next.ELECTRON_RUN_AS_NODE = "1";
1656
+ }
1657
+ return next;
1465
1658
  }
1466
1659
  function prependDirToPathEnv(env, dir) {
1467
1660
  const pathKey = Object.keys(env).find((key2) => key2.toLowerCase() === "path");
@@ -4606,7 +4799,7 @@ async function parcelEventToFileEvent(event, rootInfo) {
4606
4799
  // ../tempo-sdk/package.json
4607
4800
  var package_default = {
4608
4801
  name: "tempo-sdk",
4609
- version: "0.0.40",
4802
+ version: "0.0.42",
4610
4803
  type: "module",
4611
4804
  description: "Tempo SDK \u2014 Vite, Next.js, and Expo plugins for JSX annotation and shared page/storyboard types",
4612
4805
  repository: {
@@ -13678,4 +13871,4 @@ export {
13678
13871
  main,
13679
13872
  VISUAL_REVIEW_OIDC_AUDIENCE
13680
13873
  };
13681
- //# sourceMappingURL=chunk-MW6S36CX.js.map
13874
+ //# sourceMappingURL=chunk-WGNWCWMJ.js.map