harnery 0.37.0 → 0.38.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.
- package/dist/commander.d.ts +9 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +7 -2
- package/dist/commands/admission.d.ts +21 -0
- package/dist/commands/admission.d.ts.map +1 -0
- package/dist/commands/admission.js +565 -0
- package/dist/commands/agents.d.ts +7 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +61 -1
- package/dist/commands/artifacts.d.ts.map +1 -1
- package/dist/commands/artifacts.js +48 -2
- package/dist/commands/browse-ai.d.ts +2 -2
- package/dist/commands/browse-ai.d.ts.map +1 -1
- package/dist/commands/browse-ai.js +6 -4
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +349 -21
- package/dist/commands/fetch.js +1 -0
- package/dist/commands/qa-record.d.ts +144 -0
- package/dist/commands/qa-record.d.ts.map +1 -0
- package/dist/commands/qa-record.js +0 -0
- package/dist/commands/qa-run.d.ts +7 -3
- package/dist/commands/qa-run.d.ts.map +1 -1
- package/dist/commands/qa-run.js +268 -13
- package/dist/commands/qa-status.d.ts +71 -0
- package/dist/commands/qa-status.d.ts.map +1 -0
- package/dist/commands/qa-status.js +490 -0
- package/dist/commands/qa-verify.d.ts +40 -0
- package/dist/commands/qa-verify.d.ts.map +1 -0
- package/dist/commands/qa-verify.js +180 -0
- package/dist/commands/review-pack.d.ts +4 -0
- package/dist/commands/review-pack.d.ts.map +1 -0
- package/dist/commands/review-pack.js +1001 -0
- package/dist/core/agents/qa-signal.d.ts +111 -0
- package/dist/core/agents/qa-signal.d.ts.map +1 -0
- package/dist/core/agents/qa-signal.js +231 -0
- package/dist/core/agents/session-name-display.d.ts +20 -5
- package/dist/core/agents/session-name-display.d.ts.map +1 -1
- package/dist/core/agents/session-name-display.js +67 -7
- package/dist/core/agents/state/heartbeat-reader.d.ts +7 -0
- package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.d.ts +11 -0
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +17 -0
- package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.js +1 -0
- package/dist/core/agents/state/live-coordination-writer.js +5 -0
- package/dist/core/artifacts/constants.d.ts +1 -1
- package/dist/core/artifacts/constants.js +1 -1
- package/dist/core/artifacts/index.d.ts +57 -6
- package/dist/core/artifacts/index.d.ts.map +1 -1
- package/dist/core/artifacts/index.js +265 -10
- package/dist/core/config.d.ts +8 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +16 -0
- package/dist/core/diagnostics/bundle.d.ts +16 -0
- package/dist/core/diagnostics/bundle.d.ts.map +1 -1
- package/dist/core/diagnostics/bundle.js +101 -7
- package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
- package/dist/core/events/v3/bootstrap.js +10 -0
- package/dist/core/events/v3/coordination-view.d.ts +3 -0
- package/dist/core/events/v3/coordination-view.d.ts.map +1 -1
- package/dist/core/events/v3/coordination-view.js +69 -8
- package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
- package/dist/core/events/v3/producers/intake.js +9 -2
- package/dist/core/events/v3/producers/recorder.d.ts +23 -0
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +264 -18
- package/dist/core/hooks/cli.js +120 -27
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
- package/dist/core/hooks/resolve/transcript.js +10 -3
- package/dist/core/hooks/session-name-presence.d.ts.map +1 -1
- package/dist/core/hooks/session-name-presence.js +4 -1
- package/dist/core/qa-artifacts.d.ts +20 -0
- package/dist/core/qa-artifacts.d.ts.map +1 -0
- package/dist/core/qa-artifacts.js +110 -0
- package/dist/core/resources/contract.d.ts +6 -0
- package/dist/core/resources/contract.d.ts.map +1 -1
- package/dist/core/resources/sampler.d.ts +7 -0
- package/dist/core/resources/sampler.d.ts.map +1 -1
- package/dist/core/resources/sampler.js +106 -5
- package/dist/lib/admission.d.ts +71 -0
- package/dist/lib/admission.d.ts.map +1 -0
- package/dist/lib/admission.js +264 -0
- package/dist/lib/agent-browser/client.d.ts +1 -1
- package/dist/lib/agent-browser/client.d.ts.map +1 -1
- package/dist/lib/agent-browser/client.js +1 -5
- package/dist/lib/browser/capture-fidelity.d.ts +39 -0
- package/dist/lib/browser/capture-fidelity.d.ts.map +1 -0
- package/dist/lib/browser/capture-fidelity.js +84 -0
- package/dist/lib/browser/client.d.ts +41 -1
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +167 -9
- package/dist/lib/browser/critique.d.ts +38 -1
- package/dist/lib/browser/critique.d.ts.map +1 -1
- package/dist/lib/browser/critique.js +34 -6
- package/dist/lib/browser/index.d.ts +4 -2
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +2 -0
- package/dist/lib/browser/page-review-judge.d.ts +64 -0
- package/dist/lib/browser/page-review-judge.d.ts.map +1 -0
- package/dist/lib/browser/page-review-judge.js +270 -0
- package/dist/lib/browser/page-review-pack.d.ts +613 -0
- package/dist/lib/browser/page-review-pack.d.ts.map +1 -0
- package/dist/lib/browser/page-review-pack.js +1751 -0
- package/dist/lib/browser/qa-run-contracts.d.ts +214 -10
- package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
- package/dist/lib/browser/qa-run-contracts.js +136 -1
- package/dist/lib/browser/qa-run.d.ts +100 -10
- package/dist/lib/browser/qa-run.d.ts.map +1 -1
- package/dist/lib/browser/qa-run.js +768 -169
- package/dist/lib/browser/request-diagnostics.d.ts +13 -0
- package/dist/lib/browser/request-diagnostics.d.ts.map +1 -0
- package/dist/lib/browser/request-diagnostics.js +18 -0
- package/dist/lib/browser/tiling.d.ts +19 -0
- package/dist/lib/browser/tiling.d.ts.map +1 -1
- package/dist/lib/browser/tiling.js +28 -0
- package/dist/lib/cookies/client.d.ts +9 -0
- package/dist/lib/cookies/client.d.ts.map +1 -1
- package/dist/lib/cookies/client.js +197 -44
- package/dist/lib/cookies/extra.d.ts +18 -0
- package/dist/lib/cookies/extra.d.ts.map +1 -0
- package/dist/lib/cookies/extra.js +14 -0
- package/dist/lib/cookies/index.d.ts +2 -1
- package/dist/lib/cookies/index.d.ts.map +1 -1
- package/dist/lib/cookies/index.js +2 -1
- package/dist/lib/durable-job.d.ts +124 -0
- package/dist/lib/durable-job.d.ts.map +1 -0
- package/dist/lib/durable-job.js +296 -0
- package/dist/lib/http/client.d.ts +7 -1
- package/dist/lib/http/client.d.ts.map +1 -1
- package/dist/lib/http/client.js +2 -0
- package/dist/lib/instructions/templates.d.ts.map +1 -1
- package/dist/lib/instructions/templates.js +5 -2
- package/package.json +8 -2
- package/src/commander.ts +50 -2
- package/src/commands/admission.ts +699 -0
- package/src/commands/agents.ts +87 -1
- package/src/commands/artifacts.ts +97 -21
- package/src/commands/browse-ai.ts +10 -5
- package/src/commands/browse.ts +481 -20
- package/src/commands/fetch.ts +1 -0
- package/src/commands/qa-record.ts +682 -0
- package/src/commands/qa-run.ts +335 -16
- package/src/commands/qa-status.ts +608 -0
- package/src/commands/qa-verify.ts +238 -0
- package/src/commands/review-pack.ts +1281 -0
- package/src/core/agents/qa-signal.ts +261 -0
- package/src/core/agents/session-name-display.ts +78 -7
- package/src/core/agents/state/heartbeat-reader.ts +7 -0
- package/src/core/agents/state/heartbeat-writer.ts +23 -0
- package/src/core/agents/state/live-coordination-view.ts +1 -0
- package/src/core/agents/state/live-coordination-writer.ts +5 -0
- package/src/core/artifacts/constants.ts +1 -1
- package/src/core/artifacts/index.ts +370 -21
- package/src/core/config.ts +23 -0
- package/src/core/diagnostics/bundle.ts +119 -11
- package/src/core/events/v3/bootstrap.ts +10 -0
- package/src/core/events/v3/coordination-view.ts +100 -11
- package/src/core/events/v3/producers/intake.ts +9 -2
- package/src/core/events/v3/producers/recorder.ts +312 -18
- package/src/core/hooks/cli.ts +140 -32
- package/src/core/hooks/resolve/transcript.ts +10 -3
- package/src/core/hooks/session-name-presence.ts +6 -1
- package/src/core/qa-artifacts.ts +126 -0
- package/src/core/resources/contract.ts +7 -0
- package/src/core/resources/sampler.ts +138 -6
- package/src/lib/admission.ts +347 -0
- package/src/lib/agent-browser/client.ts +2 -10
- package/src/lib/browser/capture-fidelity.ts +98 -0
- package/src/lib/browser/client.ts +206 -10
- package/src/lib/browser/critique.ts +62 -7
- package/src/lib/browser/index.ts +36 -0
- package/src/lib/browser/page-review-judge.ts +360 -0
- package/src/lib/browser/page-review-pack.ts +2384 -0
- package/src/lib/browser/qa-run-contracts.ts +366 -3
- package/src/lib/browser/qa-run.ts +868 -190
- package/src/lib/browser/request-diagnostics.ts +27 -0
- package/src/lib/browser/tiling.ts +32 -0
- package/src/lib/cookies/client.ts +228 -42
- package/src/lib/cookies/extra.ts +28 -0
- package/src/lib/cookies/index.ts +2 -0
- package/src/lib/durable-job.ts +407 -0
- package/src/lib/http/client.ts +13 -1
- package/src/lib/instructions/templates.ts +5 -2
|
@@ -3,10 +3,13 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
4
|
import axe from "axe-core";
|
|
5
5
|
import { chromium, } from "playwright";
|
|
6
|
+
import { CookieStoreParseError, } from "../cookies/index.js";
|
|
6
7
|
import { buildAssertCheck } from "./asserts.js";
|
|
8
|
+
import { stitchPngRows } from "./capture-fidelity.js";
|
|
7
9
|
import { buildClearContentAnnotationsScript, buildContentAnnotateScript, buildContentChecks, } from "./content-checks.js";
|
|
8
10
|
import { buildClearLayoutLintAnnotationsScript, buildLayoutLintAnnotateScript, buildLayoutLintCheck, } from "./geometry.js";
|
|
9
11
|
import { buildClearLayoutAnnotationsScript, buildLayoutAnnotateScript, buildOverflowCheck, buildWidthCheck, } from "./layout.js";
|
|
12
|
+
import { isBenignNextRscAbort } from "./request-diagnostics.js";
|
|
10
13
|
import { buildClearRuntsAnnotationsScript, buildRuntsAnnotateScript, buildRuntsCheck, } from "./runts.js";
|
|
11
14
|
import { buildStandaloneHtmlScript, DEFAULT_MAX_RESOURCE_BYTES, DEFAULT_MAX_TOTAL_RESOURCE_BYTES, } from "./standalone-html.js";
|
|
12
15
|
import { buildClearTargetSizeAnnotationsScript, buildTargetSizeAnnotateScript, buildTargetSizeCheck, } from "./target-size.js";
|
|
@@ -81,12 +84,16 @@ export class Browser {
|
|
|
81
84
|
* the whole sequence — not just the launch — can be retried as a unit.
|
|
82
85
|
*/
|
|
83
86
|
async openOnce() {
|
|
87
|
+
const jarCookies = this.opts.jar?.list() ?? [];
|
|
84
88
|
// Hold a launch slot only across the spawn itself, not the browser's life.
|
|
85
89
|
await acquireLaunchSlot();
|
|
86
90
|
try {
|
|
87
91
|
this.context = await chromium.launchPersistentContext(this.profileDir, {
|
|
88
92
|
headless: !this.opts.headed,
|
|
89
93
|
viewport: this.opts.viewport ?? { width: 1280, height: 800 },
|
|
94
|
+
...(this.opts.deviceScaleFactor !== undefined
|
|
95
|
+
? { deviceScaleFactor: this.opts.deviceScaleFactor }
|
|
96
|
+
: {}),
|
|
90
97
|
...(this.opts.colorScheme ? { colorScheme: this.opts.colorScheme } : {}),
|
|
91
98
|
...(this.opts.launchTimeout !== undefined ? { timeout: this.opts.launchTimeout } : {}),
|
|
92
99
|
...(this.opts.launchArgs && this.opts.launchArgs.length > 0
|
|
@@ -102,11 +109,8 @@ export class Browser {
|
|
|
102
109
|
releaseLaunchSlot();
|
|
103
110
|
}
|
|
104
111
|
this.context.setDefaultNavigationTimeout(this.opts.navigationTimeout ?? 30_000);
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
if (jarCookies.length > 0) {
|
|
108
|
-
await this.context.addCookies(jarCookies.map(toPWCookie));
|
|
109
|
-
}
|
|
112
|
+
if (jarCookies.length > 0) {
|
|
113
|
+
await this.context.addCookies(jarCookies.map(toPWCookie));
|
|
110
114
|
}
|
|
111
115
|
// Caller-injected extraHeaders callback (e.g., for Cloudflare-bypass
|
|
112
116
|
// or custom auth headers). Per-request route handler so headers only
|
|
@@ -171,6 +175,11 @@ export class Browser {
|
|
|
171
175
|
this.consoleEvents = [];
|
|
172
176
|
this.pageErrors = [];
|
|
173
177
|
this.failedRequests = [];
|
|
178
|
+
if (err instanceof CookieStoreParseError) {
|
|
179
|
+
throw new Error(`Failed to open browser during cookie-store load: ${err.message}`, {
|
|
180
|
+
cause: err,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
174
183
|
if (attempt < maxAttempts) {
|
|
175
184
|
await new Promise((resolve) => setTimeout(resolve, attempt * 250));
|
|
176
185
|
}
|
|
@@ -197,11 +206,16 @@ export class Browser {
|
|
|
197
206
|
this.pageErrors.push({ message: err.message, stack: err.stack });
|
|
198
207
|
});
|
|
199
208
|
page.on("requestfailed", (req) => {
|
|
200
|
-
|
|
209
|
+
const failedRequest = {
|
|
201
210
|
url: req.url(),
|
|
202
211
|
method: req.method(),
|
|
203
212
|
failure: req.failure()?.errorText ?? "unknown",
|
|
204
213
|
resourceType: req.resourceType(),
|
|
214
|
+
};
|
|
215
|
+
if (isBenignNextRscAbort(failedRequest))
|
|
216
|
+
return;
|
|
217
|
+
this.failedRequests.push({
|
|
218
|
+
...failedRequest,
|
|
205
219
|
status: null,
|
|
206
220
|
kind: "network",
|
|
207
221
|
});
|
|
@@ -357,6 +371,152 @@ export class Browser {
|
|
|
357
371
|
const buf = await this.currentPage.screenshot({ type: "png", clip: rect });
|
|
358
372
|
return buf.toString("base64");
|
|
359
373
|
}
|
|
374
|
+
/**
|
|
375
|
+
* PNG of a document-space rect captured the way a reader sees it: scroll so
|
|
376
|
+
* the rect's top sits at the top of the viewport, wait for the scroll to
|
|
377
|
+
* settle (instant scroll, then rAF frames until `scrollY` stops moving),
|
|
378
|
+
* and clip the viewport. Restores the scroll position afterwards.
|
|
379
|
+
*
|
|
380
|
+
* Bands are taller than the viewport. Rather than resizing the viewport to
|
|
381
|
+
* the band height (which reflows `vh`-sized layout, moves sticky and fixed
|
|
382
|
+
* elements, and so stops matching what the user sees), the rect is captured
|
|
383
|
+
* in viewport-height pieces at the real viewport and the pieces are
|
|
384
|
+
* stitched with pngjs. The final piece of a multi-piece rect is anchored to
|
|
385
|
+
* the viewport bottom so a fixed header, which sits at the viewport top, is
|
|
386
|
+
* not repeated in the middle of the band. No viewport change is made.
|
|
387
|
+
*
|
|
388
|
+
* Pixels come back at the page's device scale factor, so a rect at DPR 2
|
|
389
|
+
* yields an image twice the rect's CSS size, the same as a full-page
|
|
390
|
+
* screenshot cropped in pixel space would.
|
|
391
|
+
*
|
|
392
|
+
* Parity with the full-page screenshot: a full-page capture renders the
|
|
393
|
+
* whole document as one viewport, so a fixed header or a stuck sticky bar
|
|
394
|
+
* appears once, at its document position. Scrolled, the same element would
|
|
395
|
+
* ride along at the top of every piece and differ from the full-page crop
|
|
396
|
+
* on every band but the first. So while a piece is captured at a non-zero
|
|
397
|
+
* scroll, every `position: fixed`/`sticky` element that has left its
|
|
398
|
+
* scroll-0 document position is given `visibility: hidden` (layout kept,
|
|
399
|
+
* inline style restored afterwards). The result is the region as a
|
|
400
|
+
* full-page screenshot would show it, without the full-page raster.
|
|
401
|
+
*/
|
|
402
|
+
async captureRegionByScroll(rect) {
|
|
403
|
+
const page = this.currentPage;
|
|
404
|
+
if (!(rect.width > 0) || !(rect.height > 0)) {
|
|
405
|
+
throw new Error(`captureRegionByScroll: rect must have a positive size (got ${rect.width}×${rect.height})`);
|
|
406
|
+
}
|
|
407
|
+
const settle = (top) => page.evaluate(async (target) => {
|
|
408
|
+
window.scrollTo({ left: window.scrollX, top: target, behavior: "instant" });
|
|
409
|
+
const frame = () => new Promise((r) => requestAnimationFrame(() => r()));
|
|
410
|
+
let last = Number.NaN;
|
|
411
|
+
for (let i = 0; i < 8; i++) {
|
|
412
|
+
await frame();
|
|
413
|
+
const now = window.scrollY;
|
|
414
|
+
if (now === last)
|
|
415
|
+
break;
|
|
416
|
+
last = now;
|
|
417
|
+
}
|
|
418
|
+
return {
|
|
419
|
+
scrollX: window.scrollX,
|
|
420
|
+
scrollY: window.scrollY,
|
|
421
|
+
innerWidth: window.innerWidth,
|
|
422
|
+
innerHeight: window.innerHeight,
|
|
423
|
+
};
|
|
424
|
+
}, top);
|
|
425
|
+
const original = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY }));
|
|
426
|
+
const pieces = [];
|
|
427
|
+
// Viewport-pinned candidates, measured at scroll 0 so each later capture
|
|
428
|
+
// can tell "still where the full page shows it" from "riding along".
|
|
429
|
+
await settle(0);
|
|
430
|
+
// Hiding uses opacity, not visibility: `visibility: hidden` on the pinned
|
|
431
|
+
// element is undone by any descendant that sets `visibility: visible`
|
|
432
|
+
// (a revealed brand label inside a sticky bar did exactly that), while
|
|
433
|
+
// opacity is a group effect no descendant can override. Transitions are
|
|
434
|
+
// switched off inline so the capture never lands mid-fade.
|
|
435
|
+
const pinned = await page.evaluateHandle(() => {
|
|
436
|
+
const out = [];
|
|
437
|
+
const all = document.querySelectorAll("body *");
|
|
438
|
+
const limit = Math.min(all.length, 20_000);
|
|
439
|
+
for (let i = 0; i < limit; i++) {
|
|
440
|
+
const el = all[i];
|
|
441
|
+
const position = getComputedStyle(el).position;
|
|
442
|
+
if (position !== "fixed" && position !== "sticky")
|
|
443
|
+
continue;
|
|
444
|
+
out.push({
|
|
445
|
+
el,
|
|
446
|
+
top: el.getBoundingClientRect().top + window.scrollY,
|
|
447
|
+
priorOpacity: el.style.getPropertyValue("opacity"),
|
|
448
|
+
priorOpacityPriority: el.style.getPropertyPriority("opacity"),
|
|
449
|
+
priorTransition: el.style.getPropertyValue("transition"),
|
|
450
|
+
priorTransitionPriority: el.style.getPropertyPriority("transition"),
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
return out;
|
|
454
|
+
});
|
|
455
|
+
const syncPinned = (mode) => page.evaluate(async ([entries, action]) => {
|
|
456
|
+
for (const entry of entries) {
|
|
457
|
+
const restore = () => {
|
|
458
|
+
entry.el.style.setProperty("opacity", entry.priorOpacity, entry.priorOpacityPriority);
|
|
459
|
+
entry.el.style.setProperty("transition", entry.priorTransition, entry.priorTransitionPriority);
|
|
460
|
+
};
|
|
461
|
+
if (action === "restore") {
|
|
462
|
+
restore();
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
const now = entry.el.getBoundingClientRect().top + window.scrollY;
|
|
466
|
+
if (Math.abs(now - entry.top) > 1) {
|
|
467
|
+
entry.el.style.setProperty("transition", "none", "important");
|
|
468
|
+
entry.el.style.setProperty("opacity", "0", "important");
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
restore();
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
// Let the style change paint before the screenshot is taken.
|
|
475
|
+
await new Promise((r) => requestAnimationFrame(() => r()));
|
|
476
|
+
}, [pinned, mode]);
|
|
477
|
+
try {
|
|
478
|
+
const bottom = rect.y + rect.height;
|
|
479
|
+
let cursor = rect.y;
|
|
480
|
+
let first = true;
|
|
481
|
+
while (cursor < bottom) {
|
|
482
|
+
const remaining = bottom - cursor;
|
|
483
|
+
// Top-align the piece; a final partial piece is then re-anchored to
|
|
484
|
+
// the viewport bottom once the viewport height is known.
|
|
485
|
+
let view = await settle(cursor);
|
|
486
|
+
if (!first && remaining < view.innerHeight) {
|
|
487
|
+
view = await settle(Math.max(0, bottom - view.innerHeight));
|
|
488
|
+
}
|
|
489
|
+
await syncPinned("hide-moved");
|
|
490
|
+
const offset = cursor - view.scrollY;
|
|
491
|
+
if (offset < 0 || offset >= view.innerHeight) {
|
|
492
|
+
throw new Error(`captureRegionByScroll: document y ${cursor} is not reachable in the viewport ` +
|
|
493
|
+
`(scrollY ${view.scrollY}, innerHeight ${view.innerHeight})`);
|
|
494
|
+
}
|
|
495
|
+
const pieceHeight = Math.min(remaining, view.innerHeight - offset);
|
|
496
|
+
const clipX = Math.max(0, rect.x - view.scrollX);
|
|
497
|
+
const clipWidth = Math.min(rect.width, view.innerWidth - clipX);
|
|
498
|
+
if (!(clipWidth > 0)) {
|
|
499
|
+
throw new Error(`captureRegionByScroll: document x ${rect.x} is outside the viewport (innerWidth ${view.innerWidth})`);
|
|
500
|
+
}
|
|
501
|
+
pieces.push(await page.screenshot({
|
|
502
|
+
type: "png",
|
|
503
|
+
clip: { x: clipX, y: offset, width: clipWidth, height: pieceHeight },
|
|
504
|
+
}));
|
|
505
|
+
cursor += pieceHeight;
|
|
506
|
+
first = false;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
finally {
|
|
510
|
+
try {
|
|
511
|
+
await syncPinned("restore");
|
|
512
|
+
}
|
|
513
|
+
finally {
|
|
514
|
+
await pinned.dispose();
|
|
515
|
+
await page.evaluate(({ x, y }) => window.scrollTo({ left: x, top: y, behavior: "instant" }), original);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return stitchPngRows(pieces);
|
|
519
|
+
}
|
|
360
520
|
/** Document-space rects + labels for each element matching `selector` (semantic tiling). */
|
|
361
521
|
async elementTiles(selector) {
|
|
362
522
|
return await this.currentPage.evaluate((sel) => {
|
|
@@ -1230,9 +1390,7 @@ export class Browser {
|
|
|
1230
1390
|
if (this.opts.jar) {
|
|
1231
1391
|
try {
|
|
1232
1392
|
const live = await this.context.cookies();
|
|
1233
|
-
|
|
1234
|
-
this.opts.jar.set(toJarCookie(c));
|
|
1235
|
-
}
|
|
1393
|
+
this.opts.jar.merge(live.map(toJarCookie));
|
|
1236
1394
|
}
|
|
1237
1395
|
catch {
|
|
1238
1396
|
// Cookie persist is best-effort; never block close on it.
|
|
@@ -19,10 +19,26 @@ export interface CritiqueFinding {
|
|
|
19
19
|
category: string;
|
|
20
20
|
description: string;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* How much of the page the tiles actually cover. The tiler keeps at most
|
|
24
|
+
* `maxTiles` bands and drops the rest, so on a tall page `capped` is true and
|
|
25
|
+
* `reviewed_height_px` stops short of `page_height_px`. Callers that turn a
|
|
26
|
+
* critique into a verdict must read this: a pass over the first 24 bands is
|
|
27
|
+
* not a pass over the page.
|
|
28
|
+
*/
|
|
29
|
+
export interface CritiqueCoverage {
|
|
30
|
+
page_height_px: number;
|
|
31
|
+
reviewed_height_px: number;
|
|
32
|
+
bands_total: number;
|
|
33
|
+
bands_reviewed: number;
|
|
34
|
+
capped: boolean;
|
|
35
|
+
}
|
|
22
36
|
export interface CritiqueResult {
|
|
23
37
|
rule: "critique";
|
|
24
38
|
/** Number of tiles the page was cut into. */
|
|
25
39
|
tiles: number;
|
|
40
|
+
/** Tile coverage of the page; absent only for results built without tiles. */
|
|
41
|
+
coverage?: CritiqueCoverage;
|
|
26
42
|
/** Whether a provider was available. False → outcome "skipped". */
|
|
27
43
|
provider: boolean;
|
|
28
44
|
findings: CritiqueFinding[];
|
|
@@ -82,6 +98,15 @@ export declare function bandRects(pageHeight: number, pageWidth: number, bandHei
|
|
|
82
98
|
* the provider would downscale. Atom coordinates must be in the same pixel
|
|
83
99
|
* space as the screenshot (identical at deviceScaleFactor 1; scale them if
|
|
84
100
|
* the capture DPR differs).
|
|
101
|
+
*
|
|
102
|
+
* `hitRects` (document-space rectangles a deterministic gate already flagged)
|
|
103
|
+
* add bands past the `maxTiles` cap: every full-page band that intersects a
|
|
104
|
+
* hit rect and was dropped by the cap is cut as well, labelled `hit band N`
|
|
105
|
+
* (N is the band's 1-based position in the full band list, and the tile's
|
|
106
|
+
* `index` is that position minus one), and appended after the kept bands.
|
|
107
|
+
* `coverage` stays honest about the kept prefix; `hitBands` counts the
|
|
108
|
+
* extras. Ignored for selector tiling (`elementRects`), which has no cap to
|
|
109
|
+
* look past in that sense.
|
|
85
110
|
*/
|
|
86
111
|
export declare function tilesFromFullPage(fullPageBuffer: Buffer, opts: {
|
|
87
112
|
elementRects?: Array<{
|
|
@@ -95,7 +120,19 @@ export declare function tilesFromFullPage(fullPageBuffer: Buffer, opts: {
|
|
|
95
120
|
overlap?: number;
|
|
96
121
|
maxTiles: number;
|
|
97
122
|
atoms?: VisualAtom[];
|
|
98
|
-
|
|
123
|
+
hitRects?: Array<{
|
|
124
|
+
x: number;
|
|
125
|
+
y: number;
|
|
126
|
+
width: number;
|
|
127
|
+
height: number;
|
|
128
|
+
}>;
|
|
129
|
+
/** Cap on hit bands appended past `maxTiles` (default 50). */
|
|
130
|
+
maxHitBands?: number;
|
|
131
|
+
}): {
|
|
132
|
+
tiles: CritiqueTile[];
|
|
133
|
+
coverage: CritiqueCoverage;
|
|
134
|
+
hitBands: number;
|
|
135
|
+
};
|
|
99
136
|
/**
|
|
100
137
|
* Validate + normalize a provider's raw output into CritiqueFindings. Tolerant
|
|
101
138
|
* of a model returning a bare array or wrapping it in `{ findings: [...] }`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"critique.d.ts","sourceRoot":"","sources":["../../../src/lib/browser/critique.ts"],"names":[],"mappings":"AAoBA,OAAO,
|
|
1
|
+
{"version":3,"file":"critique.d.ts","sourceRoot":"","sources":["../../../src/lib/browser/critique.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// false pass. That keeps this portable — the tiling, prompt, and
|
|
18
18
|
// orchestration live here; the model call lives in the host.
|
|
19
19
|
import { PNG } from "pngjs";
|
|
20
|
-
import { bandOversizedRect, snappedBandRects } from "./tiling.js";
|
|
20
|
+
import { bandOversizedRect, bandsCoveringRects, snappedBandRects, } from "./tiling.js";
|
|
21
21
|
export const DEFAULT_CRITIQUE_RUBRIC = [
|
|
22
22
|
"You are reviewing one vertical slice of a rendered web page for visual defects.",
|
|
23
23
|
"Report ONLY concrete, visible problems a careful designer would flag:",
|
|
@@ -71,6 +71,15 @@ export function bandRects(pageHeight, pageWidth, bandHeight, overlap) {
|
|
|
71
71
|
* the provider would downscale. Atom coordinates must be in the same pixel
|
|
72
72
|
* space as the screenshot (identical at deviceScaleFactor 1; scale them if
|
|
73
73
|
* the capture DPR differs).
|
|
74
|
+
*
|
|
75
|
+
* `hitRects` (document-space rectangles a deterministic gate already flagged)
|
|
76
|
+
* add bands past the `maxTiles` cap: every full-page band that intersects a
|
|
77
|
+
* hit rect and was dropped by the cap is cut as well, labelled `hit band N`
|
|
78
|
+
* (N is the band's 1-based position in the full band list, and the tile's
|
|
79
|
+
* `index` is that position minus one), and appended after the kept bands.
|
|
80
|
+
* `coverage` stays honest about the kept prefix; `hitBands` counts the
|
|
81
|
+
* extras. Ignored for selector tiling (`elementRects`), which has no cap to
|
|
82
|
+
* look past in that sense.
|
|
74
83
|
*/
|
|
75
84
|
export function tilesFromFullPage(fullPageBuffer, opts) {
|
|
76
85
|
const png = PNG.sync.read(fullPageBuffer);
|
|
@@ -105,8 +114,7 @@ export function tilesFromFullPage(fullPageBuffer, opts) {
|
|
|
105
114
|
height: b.height,
|
|
106
115
|
}));
|
|
107
116
|
}
|
|
108
|
-
const
|
|
109
|
-
for (const rect of rects.slice(0, opts.maxTiles)) {
|
|
117
|
+
const cutTile = (rect) => {
|
|
110
118
|
const sx = Math.max(0, Math.min(Math.round(rect.x), png.width - 1));
|
|
111
119
|
const sy = Math.max(0, Math.min(Math.round(rect.y), png.height - 1));
|
|
112
120
|
const w = Math.max(1, Math.min(Math.round(rect.width), png.width - sx));
|
|
@@ -119,7 +127,7 @@ export function tilesFromFullPage(fullPageBuffer, opts) {
|
|
|
119
127
|
const dstStart = row * w * 4;
|
|
120
128
|
png.data.copy(dst.data, dstStart, srcStart, srcStart + w * 4);
|
|
121
129
|
}
|
|
122
|
-
|
|
130
|
+
return {
|
|
123
131
|
index: rect.index,
|
|
124
132
|
label: rect.label,
|
|
125
133
|
scrollY: sy,
|
|
@@ -127,9 +135,29 @@ export function tilesFromFullPage(fullPageBuffer, opts) {
|
|
|
127
135
|
width: w,
|
|
128
136
|
height: h,
|
|
129
137
|
pngBase64: PNG.sync.write(dst).toString("base64"),
|
|
130
|
-
}
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
const kept = rects.slice(0, opts.maxTiles);
|
|
141
|
+
const tiles = kept.map(cutTile);
|
|
142
|
+
let hitBands = 0;
|
|
143
|
+
const banding = !(opts.elementRects && opts.elementRects.length > 0);
|
|
144
|
+
if (banding && opts.hitRects && opts.hitRects.length > 0) {
|
|
145
|
+
for (const index of bandsCoveringRects(rects, kept.length, opts.hitRects, opts.maxHitBands)) {
|
|
146
|
+
const band = rects[index];
|
|
147
|
+
if (!band)
|
|
148
|
+
continue;
|
|
149
|
+
tiles.push(cutTile({ ...band, label: `hit band ${index + 1}` }));
|
|
150
|
+
hitBands++;
|
|
151
|
+
}
|
|
131
152
|
}
|
|
132
|
-
|
|
153
|
+
const coverage = {
|
|
154
|
+
page_height_px: png.height,
|
|
155
|
+
reviewed_height_px: kept.reduce((max, rect) => Math.max(max, Math.min(png.height, Math.round(rect.y + rect.height))), 0),
|
|
156
|
+
bands_total: rects.length,
|
|
157
|
+
bands_reviewed: kept.length,
|
|
158
|
+
capped: rects.length > kept.length,
|
|
159
|
+
};
|
|
160
|
+
return { tiles, coverage, hitBands };
|
|
133
161
|
}
|
|
134
162
|
/**
|
|
135
163
|
* Validate + normalize a provider's raw output into CritiqueFindings. Tolerant
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
export { type AssertOp, type AssertResult, type AssertSpec, buildAssertCheck, parseAssertSpec, } from "./asserts.js";
|
|
2
2
|
export { Browser, type BrowserOptions, BrowserSessionActionError, type BrowserSessionControl, type BrowserSessionInspection, type BrowserSessionLocator, type BrowserSessionNavigation, type BrowserSessionNavigationType, type BrowserSessionScreenshot, type BrowserSessionStatus, type BrowserSessionTab, type ConsoleEvent, type Diagnostics, type FailedRequest, type NavigateResult, type PageErrorEvent, } from "./client.js";
|
|
3
3
|
export type { CheckRect, ContentAnnotationBox, ContentChecksRequest, ContentChecksResult, ContrastHit, ContrastResult, ImageHealthResult, ImageHit, PlaceholderHit, PlaceholderResult, TruncationHit, TruncationResult, } from "./content-checks.js";
|
|
4
|
-
export { bandRects, type CritiqueFinding, type CritiqueProvider, type CritiqueResult, type CritiqueTile, DEFAULT_CRITIQUE_RUBRIC, normalizeFindings, runCritique, tilesFromFullPage, } from "./critique.js";
|
|
4
|
+
export { bandRects, type CritiqueCoverage, type CritiqueFinding, type CritiqueProvider, type CritiqueResult, type CritiqueTile, DEFAULT_CRITIQUE_RUBRIC, normalizeFindings, runCritique, tilesFromFullPage, } from "./critique.js";
|
|
5
5
|
export { captureDevOverlay, type DevOverlayError, type DevOverlayResult, } from "./dev-overlay.js";
|
|
6
6
|
export type { AlignChild, AlignCluster, AlignResult, ClipIssue, ClipResult, CrowdPair, CrowdResult, GapCluster, GapPair, GapResult, LayoutAxis, LayoutElementMeasurement, LayoutExclusion, LayoutLintRequest, LayoutLintResult, LayoutOutcome, LayoutRect, OverlapIssue, OverlapResult, } from "./geometry.js";
|
|
7
7
|
export { buildLayoutLintCheck } from "./geometry.js";
|
|
8
8
|
export { isWSL, wslHeadedLaunchArgs } from "./launch-args.js";
|
|
9
9
|
export type { OverflowElement, OverflowResult, WidthResult, } from "./layout.js";
|
|
10
10
|
export { serializeNetscapeCookies, writeNetscapeCookieFile, } from "./netscape-cookies.js";
|
|
11
|
+
export { type JudgedContext, type JudgePageReviewPackOptions, type JudgePageReviewPackResult, judgePageReviewPack, toCritiqueRecords, } from "./page-review-judge.js";
|
|
12
|
+
export { buildInspectionPlan, type FinalizePageReviewPackInput, finalizePageReviewPack, findingsSchemaDocument, listPackContexts, PAGE_REVIEW_FINDINGS_SCHEMA, PAGE_REVIEW_PACK_DIRNAME, PAGE_REVIEW_PACK_SCHEMA, type PageReviewContextCapture, type PageReviewContextRecord, type PageReviewCritiqueRecord, type PageReviewGateRecord, type PageReviewInspectionPlan, type PageReviewPackManifest, type PageReviewTileRecord, packPaths, readPackContext, readPackDom, readPackFullPage, readPackManifest, readPackSignature, readPackTiles, renderReviewMarkdown, writePackContext, } from "./page-review-pack.js";
|
|
11
13
|
export { type BrowserProxy, type BrowserProxyGate, browserProxyFromEnv, browserProxyGateFromEnv, extractObservedIp, WEBRTC_PROXY_ONLY_ARG, } from "./proxy.js";
|
|
12
14
|
export { buildQaManifest, type ChangeClass, type ClassifyOptions, classifySignatures, fnv1a32, type ManifestOptions, type QaClassification, type QaContext, type QaManifest, type QaNodeSignature, type QaScope, type QaSignature, type QaStylesheetSignature, } from "./qa-plan.js";
|
|
13
15
|
export { defaultQaRunExec, QA_RUN_HEADLESS_ONLY_ENV, QA_RUN_RESULT_FILENAME, type QaRunExec, type QaRunExecOptions, type QaRunExecResult, type QaRunMatrixOptions, runQaMatrix, } from "./qa-run.js";
|
|
14
|
-
export { computeVerdict, contextIdFor, mergeCoverage, QA_RUN_JOB_SCHEMA_VERSION, QA_RUN_RESULT_SCHEMA_VERSION, type QaRunBlocker, type QaRunCheck, type QaRunCommandOutcome, type QaRunContext, type QaRunCritiqueOutcome, type QaRunInteraction, type QaRunJob, type QaRunJobValidation, type QaRunPolicy, type QaRunResult, type QaRunVerdict, validateQaRunJob, } from "./qa-run-contracts.js";
|
|
16
|
+
export { computeVerdict, contextIdFor, mergeCoverage, QA_RUN_JOB_SCHEMA_VERSION, QA_RUN_RESULT_SCHEMA_VERSION, type QaRunBlocker, type QaRunCheck, type QaRunCommandOutcome, type QaRunContext, type QaRunCritiqueOutcome, type QaRunCritiquePool, type QaRunInteraction, type QaRunJob, type QaRunJobValidation, type QaRunPolicy, type QaRunResult, type QaRunReviewPack, type QaRunVerdict, validateQaRunJob, } from "./qa-run-contracts.js";
|
|
15
17
|
export { listQaSnapshotTargets, loadQaSnapshot, QA_SNAPSHOT_DIR, type QaSnapshotStoreOptions, qaSnapshotKey, type ResolvedQaBaseline, resolveQaBaseline, type StoredQaSnapshot, saveQaSnapshot, } from "./qa-snapshot.js";
|
|
16
18
|
export type { RuntHit, RuntsResult, } from "./runts.js";
|
|
17
19
|
export { BROWSER_SESSION_MAX_FILL_BYTES, BROWSER_SESSION_MAX_FRAME_BYTES, BROWSER_SESSION_MAX_INSPECTION_TEXT_BYTES, BROWSER_SESSION_PROTOCOL_VERSION, type BrowserSessionDescriptor, BrowserSessionError, type BrowserSessionRequest, type BrowserSessionResponse, type BrowserSessionResult, type BrowserSessionServer, type BrowserSessionTarget, browserSessionTransport, readBrowserSessionDescriptor, sendBrowserSessionRequest, startBrowserSessionServer, } from "./session-control.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/browser/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/browser/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,6 +5,8 @@ export { captureDevOverlay, } from "./dev-overlay.js";
|
|
|
5
5
|
export { buildLayoutLintCheck } from "./geometry.js";
|
|
6
6
|
export { isWSL, wslHeadedLaunchArgs } from "./launch-args.js";
|
|
7
7
|
export { serializeNetscapeCookies, writeNetscapeCookieFile, } from "./netscape-cookies.js";
|
|
8
|
+
export { judgePageReviewPack, toCritiqueRecords, } from "./page-review-judge.js";
|
|
9
|
+
export { buildInspectionPlan, finalizePageReviewPack, findingsSchemaDocument, listPackContexts, PAGE_REVIEW_FINDINGS_SCHEMA, PAGE_REVIEW_PACK_DIRNAME, PAGE_REVIEW_PACK_SCHEMA, packPaths, readPackContext, readPackDom, readPackFullPage, readPackManifest, readPackSignature, readPackTiles, renderReviewMarkdown, writePackContext, } from "./page-review-pack.js";
|
|
8
10
|
export { browserProxyFromEnv, browserProxyGateFromEnv, extractObservedIp, WEBRTC_PROXY_ONLY_ARG, } from "./proxy.js";
|
|
9
11
|
export { buildQaManifest, classifySignatures, fnv1a32, } from "./qa-plan.js";
|
|
10
12
|
export { defaultQaRunExec, QA_RUN_HEADLESS_ONLY_ENV, QA_RUN_RESULT_FILENAME, runQaMatrix, } from "./qa-run.js";
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { CritiqueProvider } from "./critique.js";
|
|
2
|
+
import { type PageReviewContextRecord, type PageReviewCritiqueRecord } from "./page-review-pack.js";
|
|
3
|
+
import { type CritiqueReusePlan } from "./qa-reuse.js";
|
|
4
|
+
import { type QaSnapshotStoreOptions } from "./qa-snapshot.js";
|
|
5
|
+
export interface JudgePageReviewPackOptions {
|
|
6
|
+
packDir: string;
|
|
7
|
+
/** Host-injected vision call. Absent: every context reports `skipped`. */
|
|
8
|
+
provider: CritiqueProvider | undefined;
|
|
9
|
+
rubric: string;
|
|
10
|
+
/** Vision calls in flight across the whole pack. Default: the provider's
|
|
11
|
+
* own concurrency, then 4. */
|
|
12
|
+
concurrency?: number;
|
|
13
|
+
/** Subset of context ids to judge (default: every context in the pack). */
|
|
14
|
+
contextIds?: string[];
|
|
15
|
+
/** Band-diff reuse against persisted snapshots keyed by `target`. */
|
|
16
|
+
reuse?: {
|
|
17
|
+
target: string;
|
|
18
|
+
store?: QaSnapshotStoreOptions;
|
|
19
|
+
mismatchThreshold?: number;
|
|
20
|
+
};
|
|
21
|
+
/** Absolute epoch-ms deadline. Workers stop claiming tiles once past it;
|
|
22
|
+
* a context with unjudged tiles reports `incomplete`, never `pass`. */
|
|
23
|
+
deadlineAt?: number;
|
|
24
|
+
onLog?: (message: string) => void;
|
|
25
|
+
}
|
|
26
|
+
export interface JudgedContext extends PageReviewCritiqueRecord {
|
|
27
|
+
record: PageReviewContextRecord;
|
|
28
|
+
/** Tiles selected for review that the pool never reached (deadline). */
|
|
29
|
+
tiles_unjudged: number;
|
|
30
|
+
reuse?: Pick<CritiqueReusePlan, "tiles_total" | "tiles_reused" | "tiles_reviewed" | "invalidation" | "mismatch_threshold">;
|
|
31
|
+
}
|
|
32
|
+
export interface JudgePageReviewPackResult {
|
|
33
|
+
contexts: JudgedContext[];
|
|
34
|
+
pool: {
|
|
35
|
+
concurrency: number;
|
|
36
|
+
wall_time_ms: number;
|
|
37
|
+
provider: string;
|
|
38
|
+
};
|
|
39
|
+
tiles_total: number;
|
|
40
|
+
tiles_reviewed: number;
|
|
41
|
+
tiles_reused: number;
|
|
42
|
+
outcome: "pass" | "fail" | "skipped" | "incomplete";
|
|
43
|
+
provider_meta?: Record<string, unknown>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Orientation the provider reads before the rubric: which band this tile is,
|
|
47
|
+
* where it sits on the page, how much of it repeats a neighbour, whether it
|
|
48
|
+
* touches the page top or bottom, and how it was captured. A tile edge that
|
|
49
|
+
* continues in the next band is not a clipped element, and a fixed header at
|
|
50
|
+
* the top of a scrolled-band capture is not a layout defect; the preamble
|
|
51
|
+
* says so where the pixels cannot. Adds context only: the finding taxonomy
|
|
52
|
+
* the rubric defines is unchanged. Empty for an unknown tile id.
|
|
53
|
+
*/
|
|
54
|
+
export declare function tileRubricPreamble(record: PageReviewContextRecord, tileId: string): string;
|
|
55
|
+
/**
|
|
56
|
+
* Judge every tile in the pack through one pool. Findings keep the tiler
|
|
57
|
+
* index (`tile`) for compatibility with `CritiqueFinding` and add the pack's
|
|
58
|
+
* stable `tile_id`. A provider throw on one tile becomes a `high`
|
|
59
|
+
* `provider-error` finding for that tile; nothing else is affected.
|
|
60
|
+
*/
|
|
61
|
+
export declare function judgePageReviewPack(options: JudgePageReviewPackOptions): Promise<JudgePageReviewPackResult>;
|
|
62
|
+
/** Strip the in-memory `record` so the result can be written into the pack. */
|
|
63
|
+
export declare function toCritiqueRecords(result: JudgePageReviewPackResult): PageReviewCritiqueRecord[];
|
|
64
|
+
//# sourceMappingURL=page-review-judge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-review-judge.d.ts","sourceRoot":"","sources":["../../../src/lib/browser/page-review-judge.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAmB,gBAAgB,EAAgB,MAAM,eAAe,CAAC;AACrF,OAAO,EAEL,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAI9B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,KAAK,iBAAiB,EAAqB,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAkB,KAAK,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE/E,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,QAAQ,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf;kCAC8B;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,qEAAqE;IACrE,KAAK,CAAC,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,sBAAsB,CAAC;QAC/B,iBAAiB,CAAC,EAAE,MAAM,CAAC;KAC5B,CAAC;IACF;2EACuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,aAAc,SAAQ,wBAAwB;IAC7D,MAAM,EAAE,uBAAuB,CAAC;IAChC,wEAAwE;IACxE,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,IAAI,CACV,iBAAiB,EACjB,aAAa,GAAG,cAAc,GAAG,gBAAgB,GAAG,cAAc,GAAG,oBAAoB,CAC1F,CAAC;CACH;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,IAAI,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,YAAY,CAAC;IACpD,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AAUD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,uBAAuB,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CA2E1F;AAaD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,yBAAyB,CAAC,CA4KpC;AAED,+EAA+E;AAC/E,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,yBAAyB,GAAG,wBAAwB,EAAE,CAI/F"}
|