@vosjs/render-core 0.3.0 → 0.3.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/record/index.d.ts +14 -1
- package/dist/record/index.js +36 -5
- package/dist/record/index.js.map +1 -1
- package/package.json +2 -2
package/dist/record/index.d.ts
CHANGED
|
@@ -143,9 +143,22 @@ declare const PRESS_LEAD_MS = 80;
|
|
|
143
143
|
declare const PRESS_HOLD_MS = 70;
|
|
144
144
|
/** The pause between a selector lookup that scrolled the page and the move. */
|
|
145
145
|
declare const SCROLL_SETTLE_MS = 120;
|
|
146
|
+
/**
|
|
147
|
+
* A scroll travels by the clock too, at a wheel's pace: 0.9 ms per px,
|
|
148
|
+
* floored so a short flick still reads as motion and capped so a long page
|
|
149
|
+
* does not crawl. Before this the recorder sent 120 px wheel chunks with a
|
|
150
|
+
* 40 ms sleep and left the in-between frames to the browser's own smooth
|
|
151
|
+
* scroll, which a software-composited fleet browser never drew: five jumps,
|
|
152
|
+
* held by the encoder, and a torn composite where the sticky nav and the
|
|
153
|
+
* content were captured at different offsets.
|
|
154
|
+
*/
|
|
155
|
+
declare const SCROLL_MS_PER_PX = 0.9;
|
|
156
|
+
declare const SCROLL_MIN_MS = 240;
|
|
157
|
+
declare const SCROLL_MAX_MS = 1000;
|
|
146
158
|
/** The hold after the last step, so the take does not cut on a press. */
|
|
147
159
|
declare const TRAILING_HOLD_MS = 400;
|
|
148
160
|
/** How long the pointer takes to travel `dist` CSS px. */
|
|
161
|
+
declare function scrollTravelMs(px: number): number;
|
|
149
162
|
declare function pointerTravelMs(dist: number): number;
|
|
150
163
|
/**
|
|
151
164
|
* A click's `ms` is READING time, held from the page's last visual change,
|
|
@@ -810,4 +823,4 @@ interface FreshPlanOptions {
|
|
|
810
823
|
}
|
|
811
824
|
declare function planFreshTake(artifact: RecordingArtifact, recordingName: string, opts?: FreshPlanOptions): ProjectDoc;
|
|
812
825
|
|
|
813
|
-
export { type ActionStep, type ActionsFile, type Arrival, DEAD_SHARE_WARN_PCT, DEAD_STRETCH_WARN_MS, type DeadReport, type DeadStep, EXPOSURE_ADVICE, EXPOSURE_MATCHERS_SRC, EXPOSURE_PROBE, type EncodePageOptions, type Exposure, type ExposureKind, ExposureLog, type FrameRec, type FrameSink, type FreezeSpan, type FreshPlanOptions, MOTION_TICK_MS, type MaskReport, type MaskRule, POINTER_MAX_MS, POINTER_MIN_MS, POINTER_MS_PER_PX, PRESS_HOLD_MS, PRESS_LEAD_MS, type PaceReport, QUIET_MS, READ_BEAT_MS, RESPONSE_MS, type RecordOpts, type RecordResult, type RecorderBrowser, type RecorderCdp, type RecorderClock, type RecorderConsoleMessage, type RecorderContext, type RecorderLocator, type RecorderLocatorRoot, type RecorderPage, type RecorderRect, type RecorderResponse, SCROLL_SETTLE_MS, SETTLE_CAP_MS, SETTLE_MS, SetupEnvError, SetupError, type SetupResult, type SetupStep, type SetupText, type SkippedStep, type StepPace, TRAILING_HOLD_MS, WALL_PROBE, WallError, type WallKind, type WallVerdict, askedMs, base64Bytes, buildEncodePage, capReached, cappedLine, clampWait, clockMotion, clockTyping, deadLine, deadTime, deadWarns, easeInOutCubic, exposureLine, formatDurationCap, holdLeftMs, isIdpHost, isSignInPath, jpegDims, maskInitScript, paceLine, paceReport, parseHeaders, planFreshTake, pointerTravelMs, realSleep, recordTake, redirectedAway, resolveSetupText, runSetup, settleMs, settleVerdict, setupEnvNames, validateActions, validateSetup, wallVerdict };
|
|
826
|
+
export { type ActionStep, type ActionsFile, type Arrival, DEAD_SHARE_WARN_PCT, DEAD_STRETCH_WARN_MS, type DeadReport, type DeadStep, EXPOSURE_ADVICE, EXPOSURE_MATCHERS_SRC, EXPOSURE_PROBE, type EncodePageOptions, type Exposure, type ExposureKind, ExposureLog, type FrameRec, type FrameSink, type FreezeSpan, type FreshPlanOptions, MOTION_TICK_MS, type MaskReport, type MaskRule, POINTER_MAX_MS, POINTER_MIN_MS, POINTER_MS_PER_PX, PRESS_HOLD_MS, PRESS_LEAD_MS, type PaceReport, QUIET_MS, READ_BEAT_MS, RESPONSE_MS, type RecordOpts, type RecordResult, type RecorderBrowser, type RecorderCdp, type RecorderClock, type RecorderConsoleMessage, type RecorderContext, type RecorderLocator, type RecorderLocatorRoot, type RecorderPage, type RecorderRect, type RecorderResponse, SCROLL_MAX_MS, SCROLL_MIN_MS, SCROLL_MS_PER_PX, SCROLL_SETTLE_MS, SETTLE_CAP_MS, SETTLE_MS, SetupEnvError, SetupError, type SetupResult, type SetupStep, type SetupText, type SkippedStep, type StepPace, TRAILING_HOLD_MS, WALL_PROBE, WallError, type WallKind, type WallVerdict, askedMs, base64Bytes, buildEncodePage, capReached, cappedLine, clampWait, clockMotion, clockTyping, deadLine, deadTime, deadWarns, easeInOutCubic, exposureLine, formatDurationCap, holdLeftMs, isIdpHost, isSignInPath, jpegDims, maskInitScript, paceLine, paceReport, parseHeaders, planFreshTake, pointerTravelMs, realSleep, recordTake, redirectedAway, resolveSetupText, runSetup, scrollTravelMs, settleMs, settleVerdict, setupEnvNames, validateActions, validateSetup, wallVerdict };
|
package/dist/record/index.js
CHANGED
|
@@ -34,7 +34,16 @@ var SETTLE_MS = {
|
|
|
34
34
|
var PRESS_LEAD_MS = 80;
|
|
35
35
|
var PRESS_HOLD_MS = 70;
|
|
36
36
|
var SCROLL_SETTLE_MS = 120;
|
|
37
|
+
var SCROLL_MS_PER_PX = 0.9;
|
|
38
|
+
var SCROLL_MIN_MS = 240;
|
|
39
|
+
var SCROLL_MAX_MS = 1e3;
|
|
37
40
|
var TRAILING_HOLD_MS = 400;
|
|
41
|
+
function scrollTravelMs(px) {
|
|
42
|
+
return Math.min(
|
|
43
|
+
SCROLL_MAX_MS,
|
|
44
|
+
Math.max(SCROLL_MIN_MS, Math.round(Math.abs(px) * SCROLL_MS_PER_PX))
|
|
45
|
+
);
|
|
46
|
+
}
|
|
38
47
|
function pointerTravelMs(dist) {
|
|
39
48
|
return Math.min(
|
|
40
49
|
POINTER_MAX_MS,
|
|
@@ -780,6 +789,9 @@ async function recordTake(browser, url, actions, sink, log, opts = {}) {
|
|
|
780
789
|
clock
|
|
781
790
|
);
|
|
782
791
|
};
|
|
792
|
+
const paintFrame = () => page.evaluate(
|
|
793
|
+
() => new Promise((r) => requestAnimationFrame(() => r()))
|
|
794
|
+
).catch(() => void 0);
|
|
783
795
|
const boxOf = async (selector) => {
|
|
784
796
|
const loc = page.locator(selector).first();
|
|
785
797
|
try {
|
|
@@ -916,11 +928,26 @@ async function recordTake(browser, url, actions, sink, log, opts = {}) {
|
|
|
916
928
|
case "scroll": {
|
|
917
929
|
const total = Math.abs(step.dy);
|
|
918
930
|
const dir = Math.sign(step.dy);
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
931
|
+
let sent = 0;
|
|
932
|
+
await gesture(
|
|
933
|
+
() => clockMotion(
|
|
934
|
+
dry ? 0 : scrollTravelMs(total),
|
|
935
|
+
async (u) => {
|
|
936
|
+
const target = Math.round(total * u);
|
|
937
|
+
const delta = target - sent;
|
|
938
|
+
if (delta <= 0) return;
|
|
939
|
+
await page.mouse.wheel(0, dir * delta);
|
|
940
|
+
sent = target;
|
|
941
|
+
emit({
|
|
942
|
+
x: Math.round(cur.x),
|
|
943
|
+
y: Math.round(cur.y),
|
|
944
|
+
type: "scroll"
|
|
945
|
+
});
|
|
946
|
+
await paintFrame();
|
|
947
|
+
},
|
|
948
|
+
clock
|
|
949
|
+
)
|
|
950
|
+
);
|
|
924
951
|
await gesture(() => sleep(settleMs(step)));
|
|
925
952
|
break;
|
|
926
953
|
}
|
|
@@ -1277,6 +1304,9 @@ export {
|
|
|
1277
1304
|
QUIET_MS,
|
|
1278
1305
|
READ_BEAT_MS,
|
|
1279
1306
|
RESPONSE_MS,
|
|
1307
|
+
SCROLL_MAX_MS,
|
|
1308
|
+
SCROLL_MIN_MS,
|
|
1309
|
+
SCROLL_MS_PER_PX,
|
|
1280
1310
|
SCROLL_SETTLE_MS,
|
|
1281
1311
|
SETTLE_CAP_MS,
|
|
1282
1312
|
SETTLE_MS,
|
|
@@ -1314,6 +1344,7 @@ export {
|
|
|
1314
1344
|
redirectedAway,
|
|
1315
1345
|
resolveSetupText,
|
|
1316
1346
|
runSetup,
|
|
1347
|
+
scrollTravelMs,
|
|
1317
1348
|
settleMs,
|
|
1318
1349
|
settleVerdict,
|
|
1319
1350
|
setupEnvNames,
|
package/dist/record/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/record/driver.ts","../../src/record/cap.ts","../../src/record/pace.ts","../../src/record/wall.ts","../../src/record/exposure.ts","../../src/record/setup.ts","../../src/record/recorder.ts","../../src/record/actions.ts","../../src/record/encodePage.ts","../../src/record/plan.ts"],"sourcesContent":["/**\n * The recorder's driver: what it needs from a browser, as a STRUCTURAL\n * contract. Playwright's own `Browser`/`BrowserContext`/`Page` satisfy it\n * unchanged, and so does `@cloudflare/playwright`'s (the same client\n * surface over Browser Rendering), so the CLI hands in a launched Node\n * browser and the fleet hands in a connected one, and the recorder never\n * imports either package. Only the members the recorder calls are named\n * here; a driver that offers more is fine, one that offers less fails to\n * type.\n *\n * Frames leave through a `FrameSink`, never through a filesystem: the CLI's\n * sink writes a take directory, the fleet's puts each JPEG to R2.\n */\n\nexport interface RecorderRect {\n x: number\n y: number\n width: number\n height: number\n}\n\nexport interface RecorderLocator {\n waitFor(opts: { state: 'visible'; timeout: number }): Promise<unknown>\n scrollIntoViewIfNeeded(): Promise<unknown>\n boundingBox(): Promise<RecorderRect | null>\n click(): Promise<unknown>\n fill(value: string): Promise<unknown>\n}\n\nexport interface RecorderLocatorRoot {\n first(): RecorderLocator\n}\n\nexport interface RecorderResponse {\n status(): number\n}\n\nexport interface RecorderConsoleMessage {\n type(): string\n text(): string\n}\n\nexport interface RecorderPage {\n goto(\n url: string,\n opts: { waitUntil: 'networkidle'; timeout: number },\n ): Promise<RecorderResponse | null>\n url(): string\n title(): Promise<string>\n // Playwright's evaluate takes a function or an expression string; the\n // recorder hands it strings (the probes) and small functions.\n evaluate<T>(pageFunction: string | (() => T)): Promise<T>\n on(event: 'console', cb: (m: RecorderConsoleMessage) => void): unknown\n locator(selector: string): RecorderLocatorRoot\n mouse: {\n move(x: number, y: number): Promise<void>\n down(): Promise<void>\n up(): Promise<void>\n wheel(dx: number, dy: number): Promise<void>\n }\n keyboard: {\n type(text: string): Promise<void>\n press(key: string): Promise<void>\n }\n}\n\nexport interface RecorderCdp {\n on(\n event: 'Page.screencastFrame',\n cb: (ev: {\n data: string\n sessionId: number\n metadata: { timestamp?: number }\n }) => void,\n ): unknown\n send(method: string, params?: Record<string, unknown>): Promise<unknown>\n}\n\nexport interface RecorderContext {\n newPage(): Promise<RecorderPage>\n addInitScript(script: string): Promise<unknown>\n setExtraHTTPHeaders(headers: Record<string, string>): Promise<unknown>\n // The page handed back is the one `newPage()` answered; typed loosely so\n // Playwright's `Page | Frame` parameter satisfies the contract.\n newCDPSession(page: unknown): Promise<RecorderCdp>\n close(): Promise<void>\n}\n\nexport interface RecorderBrowser {\n newContext(opts: {\n viewport: { width: number; height: number }\n deviceScaleFactor: number\n storageState?: string\n extraHTTPHeaders?: Record<string, string>\n }): Promise<RecorderContext>\n}\n\n/**\n * Where the screencast's frames go. `frame` is called in order with the\n * JPEG bytes; the recorder keeps the index (`file`, `tMs`) itself. The CLI\n * writes `frames/<file>`; the fleet puts to R2. A sink may be async and\n * slow: the recorder never awaits it on the screencast's own thread, so a\n * slow store costs no frames, only memory until it catches up.\n */\nexport interface FrameSink {\n frame(file: string, bytes: Uint8Array): Promise<void> | void\n}\n\n/** The clock and the pauses, injectable so a rehearsal runs fast. */\nexport interface RecorderClock {\n now(): number\n sleep(ms: number): Promise<void>\n}\n\nexport const realSleep = (ms: number): Promise<void> =>\n new Promise<void>((r) => setTimeout(r, ms))\n","/**\n * The recording cap as the recorder applies it, pure. WHAT the cap is\n * (the hosted plan's number, read from `GET /api/limits`) is a vos.so fact\n * and stays in the CLI's platform client; the recorder only knows a number\n * of seconds and stops there.\n */\n\n/** True once the take has reached the cap — the recorder stops driving steps. */\nexport function capReached(elapsedMs: number, capSeconds: number): boolean {\n return elapsedMs >= capSeconds * 1000\n}\n\n/** A wait step never sleeps past the cap. */\nexport function clampWait(\n waitMs: number,\n elapsedMs: number,\n capSeconds: number,\n): number {\n return Math.max(0, Math.min(waitMs, capSeconds * 1000 - elapsedMs))\n}\n\n/** `30 min`, `1 h 30 min`, `45 s` — the duration cap in words. */\nexport function formatDurationCap(seconds: number): string {\n if (seconds < 60) return `${seconds} s`\n const m = Math.round(seconds / 60)\n if (m < 60) return `${m} min`\n const h = Math.floor(m / 60)\n const rest = m % 60\n return rest ? `${h} h ${rest} min` : `${h} h`\n}\n\n/** The one line printed when the cap stopped the take. */\nexport function cappedLine(capSeconds: number): string {\n return `stopped at ${formatDurationCap(capSeconds)} (--max-duration ${capSeconds}); the remaining steps did not run`\n}\n","/**\n * The recorder's pace — the pure parts.\n *\n * A take of a scripted flow should run as long as the script asks, plus the\n * gestures a person makes between the asks (the pointer's travel, the press,\n * the settle after it) and no more. Measured before this module (§4.5 of\n * the utility-clip retrospective), hovers ran 2× their ask, drags 4.8× and\n * a 25 s script recorded 42 s, for two reasons the numbers here answer:\n *\n * - The motion loops counted STEPS (one per 16 ms of the asked duration)\n * and awaited a `mouse.move` round trip per step, so a page that\n * re-renders per move (a slider) stretched every gesture by the round\n * trip. A gesture is now driven by the CLOCK: its position is a function\n * of the elapsed time, and it ends when the asked duration has elapsed,\n * however many samples the page allowed.\n * - The settles after a gesture were fixed sleeps (500 ms after a click,\n * 250 after every selector lookup). A settle is DATA: `ms` on the step\n * overrides a small default, and the script's own `wait` steps carry\n * the intended pauses.\n */\n\n/** Pointer travel: ms per CSS px, floor and ceiling — a person's hand. */\nexport const POINTER_MS_PER_PX = 0.7\nexport const POINTER_MIN_MS = 250\nexport const POINTER_MAX_MS = 800\n/** The sample cadence a motion loop aims for. */\nexport const MOTION_TICK_MS = 16\n\n/** The settle after a gesture when the step names none, by verb. */\nexport const SETTLE_MS = {\n click: 150,\n type: 150,\n scroll: 200,\n drag: 80,\n} as const\n\n/** The press: the pause before the button goes down, and the hold. */\nexport const PRESS_LEAD_MS = 80\nexport const PRESS_HOLD_MS = 70\n/** The pause between a selector lookup that scrolled the page and the move. */\nexport const SCROLL_SETTLE_MS = 120\n/** The hold after the last step, so the take does not cut on a press. */\nexport const TRAILING_HOLD_MS = 400\n\n/** How long the pointer takes to travel `dist` CSS px. */\nexport function pointerTravelMs(dist: number): number {\n return Math.min(\n POINTER_MAX_MS,\n Math.max(POINTER_MIN_MS, Math.round(dist * POINTER_MS_PER_PX)),\n )\n}\n\n/**\n * A click's `ms` is READING time, held from the page's last visual change,\n * and the recorder pays the settle itself: the same `ms` was read on one\n * run and dead on the next because the hold started at the press and the\n * page's settle varied run to run (0.3 s to 0.7 s on the same step).\n *\n * The settle is watched on the screencast, which emits only on change:\n * after the press, a change that arrives within RESPONSE_MS says the page\n * is answering, and it has settled once QUIET_MS pass with no new frame.\n * No change within RESPONSE_MS means the page did not change (a click that\n * only moves state) and the settle is over. SETTLE_CAP_MS bounds a page\n * that never stops changing (a playing preview), which is a page nothing\n * is dead on, so the hold simply starts.\n */\nexport const RESPONSE_MS = 400\nexport const QUIET_MS = 250\nexport const SETTLE_CAP_MS = 1200\n\n/**\n * Is the page settled? Pure: the recorder feeds it what it knows at each\n * tick since the press.\n */\nexport function settleVerdict(\n t: {\n /** ms since the press. */\n sincePress: number\n /** ms since the last screencast frame, or null when none arrived since the press. */\n sinceChange: number | null\n },\n c = { response: RESPONSE_MS, quiet: QUIET_MS, cap: SETTLE_CAP_MS },\n): 'wait' | 'settled' {\n if (t.sincePress >= c.cap) return 'settled'\n if (t.sinceChange === null)\n return t.sincePress >= c.response ? 'settled' : 'wait'\n return t.sinceChange >= c.quiet ? 'settled' : 'wait'\n}\n\n/**\n * What is left of a hold once the settle is detected: the hold is measured\n * from the last change, so the quiet already spent counts toward it.\n */\nexport function holdLeftMs(holdMs: number, sinceChange: number | null): number {\n return Math.max(0, holdMs - (sinceChange ?? 0))\n}\n\n/** The settle after a step: its own `ms` when it names one, else the verb's. */\nexport function settleMs(step: { do: string; ms?: number }): number {\n if (typeof step.ms === 'number' && step.ms >= 0) return step.ms\n const d = step.do as keyof typeof SETTLE_MS\n return SETTLE_MS[d] ?? 0\n}\n\nexport const easeInOutCubic = (u: number): number =>\n u < 0.5 ? 4 * u * u * u : 1 - Math.pow(-2 * u + 2, 3) / 2\n\n/**\n * Drive a motion by the clock: `at(u)` is called with the eased progress\n * for each sample, and the loop ends when `dur` ms have elapsed, with the\n * last call at u = 1 exactly. Between samples it sleeps to the next tick\n * only when the sample came back early; a slow sample (a page busy\n * re-rendering) is followed at once, so the gesture keeps its length and\n * loses samples, never the other way round.\n */\nexport async function clockMotion(\n dur: number,\n at: (u: number) => Promise<void>,\n clock: { now: () => number; sleep: (ms: number) => Promise<void> },\n): Promise<number> {\n const start = clock.now()\n let samples = 0\n for (;;) {\n const elapsed = clock.now() - start\n if (elapsed >= dur) break\n await at(easeInOutCubic(Math.min(1, elapsed / dur)))\n samples++\n const next =\n start + Math.ceil((clock.now() - start) / MOTION_TICK_MS) * MOTION_TICK_MS\n const wait = Math.min(next, start + dur) - clock.now()\n if (wait > 0) await clock.sleep(wait)\n }\n await at(1)\n return samples + 1\n}\n\n/**\n * Typing paced by the clock: the i-th character is due at `start + i·delay`;\n * after each keystroke the loop sleeps to the next due time only if the\n * keystroke came back early. A slow field (an editor re-rendering per key)\n * types as fast as it can and says so in the pace report.\n */\nexport async function clockTyping(\n chars: readonly string[],\n delay: number,\n type: (ch: string, index: number) => Promise<void>,\n clock: { now: () => number; sleep: (ms: number) => Promise<void> },\n): Promise<void> {\n const start = clock.now()\n for (let i = 0; i < chars.length; i++) {\n await type(chars[i], i)\n const due = start + (i + 1) * delay\n const wait = due - clock.now()\n if (wait > 0 && i < chars.length - 1) await clock.sleep(wait)\n }\n}\n\n/** One step's pace: what the script asked for it and what it took. */\nexport interface StepPace {\n step: number\n do: string\n /** The script's own ask: a wait's ms, a hover's dwell, a drag's ms, typing's chars × delay, a click's read after the settle; 0 for a scroll. */\n askedMs: number\n /** The gesture the recorder adds by design: pointer travel, the press, the settle. */\n gestureMs: number\n wallMs: number\n}\n\nexport interface PaceReport {\n askedMs: number\n gestureMs: number\n wallMs: number\n /** Wall time neither asked nor a gesture: what the page and the round trips cost. */\n overheadMs: number\n overheadPct: number\n /** The steps whose wall ran past their ask plus gesture by more than a third. */\n slow: { step: number; do: string; askedMs: number; wallMs: number }[]\n}\n\n/** What a script asks of a step, in ms: the part of its wall time that is the author's. */\nexport function askedMs(step: {\n do: string\n ms?: number\n text?: string\n delayMs?: number\n}): number {\n switch (step.do) {\n case 'wait':\n return step.ms ?? 0\n // A click's ms is the read after the page settled: the author's.\n case 'click':\n return step.ms ?? SETTLE_MS.click\n case 'hover':\n return step.ms ?? 700\n case 'drag':\n return step.ms ?? 700\n case 'type':\n return (step.text?.length ?? 0) * (step.delayMs ?? 40)\n default:\n return 0\n }\n}\n\nexport function paceReport(steps: readonly StepPace[]): PaceReport {\n const askedMs = steps.reduce((a, s) => a + s.askedMs, 0)\n const gestureMs = steps.reduce((a, s) => a + s.gestureMs, 0)\n const wallMs = steps.reduce((a, s) => a + s.wallMs, 0)\n const overheadMs = Math.max(0, wallMs - askedMs - gestureMs)\n const slow = steps\n .filter((s) => s.wallMs > (s.askedMs + s.gestureMs) * 1.34 + 60)\n .map((s) => ({\n step: s.step,\n do: s.do,\n askedMs: s.askedMs,\n wallMs: s.wallMs,\n }))\n return {\n askedMs,\n gestureMs,\n wallMs,\n overheadMs,\n overheadPct: wallMs > 0 ? Math.round((overheadMs / wallMs) * 100) : 0,\n slow,\n }\n}\n\n/** The pace in one line for the log. */\nexport function paceLine(r: PaceReport): string {\n const s = (ms: number) => `${(ms / 1000).toFixed(1)} s`\n const slow = r.slow.length\n ? `; slow: ${r.slow.map((x) => `#${x.step} ${x.do} ${s(x.wallMs)} for ${s(x.askedMs)} asked`).join(', ')}`\n : ''\n return `pace: the script asked ${s(r.askedMs)}, the gestures added ${s(r.gestureMs)}, the take ran ${s(r.wallMs)} (${r.overheadPct} % overhead${slow})`\n}\n\n/**\n * DEAD TIME, the smoothness fact that replaces the freeze share as the\n * warning. A still frame is not a defect: a page the viewer is reading is\n * content, and a workspace tour is mostly still. What is dead is a still\n * frame under a PARKED cursor past the beat it takes to read what changed:\n * after that the picture, the pointer and the camera all hold and nothing\n * is being said. So the derivation reads, per step, when the page settled\n * after the step's act, how long the settled frame was held, and whether\n * the cursor moved in that hold; a hold past the reading beat is dead.\n *\n * The beat is longer after a page changed (a navigation shows a whole new\n * page to take in) than after a control changed (one thing to see).\n */\nexport const READ_BEAT_MS = { page: 1000, control: 600 } as const\n/** A dead stretch this long is a warning on its own, whatever the share. */\nexport const DEAD_STRETCH_WARN_MS = 1500\n/** The share of the take past which dead time is a warning. */\nexport const DEAD_SHARE_WARN_PCT = 20\n\nexport interface DeadStep {\n step: number\n id?: string\n do: string\n /** ms from the step's act (its press, else its start) to the last frame change in the step. */\n settledMs: number\n /** ms the settled frame was held before the step ended. */\n heldMs: number\n /** the hold past the reading beat, while the cursor was parked; 0 when the hold was read or the pointer moved. */\n deadMs: number\n}\n\nexport interface DeadReport {\n ms: number\n pct: number\n /** the steps that carried any dead time, in order. */\n steps: DeadStep[]\n /** the longest single dead hold. */\n longestMs: number\n}\n\nexport function deadTime(\n steps: readonly {\n step: number\n id?: string\n do: string\n tStart: number\n tEnd: number\n navigated?: boolean\n }[],\n frames: readonly { tMs: number }[],\n events: readonly { t: number; type: string }[],\n durationMs: number,\n): DeadReport {\n const out: DeadStep[] = []\n let ms = 0\n let longestMs = 0\n for (const s of steps) {\n const t0 = s.tStart * 1000\n const t1 = s.tEnd * 1000\n if (!(t1 > t0)) continue\n // The act: the step's first press, else its start (a wait, a move).\n const press = events.find(\n (e) => e.type === 'down' && e.t >= t0 && e.t <= t1,\n )\n const act = press ? press.t : t0\n // The last visual change inside the step, after the act.\n let last = act\n for (const f of frames) {\n if (f.tMs > act && f.tMs <= t1) last = f.tMs\n }\n const settledMs = Math.round(last - act)\n const heldMs = Math.round(t1 - last)\n // Parked: no pointer motion while the settled frame was held.\n const moved = events.some(\n (e) =>\n (e.type === 'move' || e.type === 'scroll') && e.t > last && e.t <= t1,\n )\n // The opening step shows a page the viewer has not seen: a page beat.\n const beat =\n s.navigated || s.step === 0 ? READ_BEAT_MS.page : READ_BEAT_MS.control\n const deadMs = moved ? 0 : Math.max(0, heldMs - beat)\n if (deadMs > 0) {\n out.push({\n step: s.step,\n ...(s.id ? { id: s.id } : {}),\n do: s.do,\n settledMs,\n heldMs,\n deadMs,\n })\n ms += deadMs\n longestMs = Math.max(longestMs, deadMs)\n }\n }\n return {\n ms,\n pct: durationMs > 0 ? Math.round((ms / durationMs) * 100) : 0,\n steps: out,\n longestMs,\n }\n}\n\n/** Whether a dead report earns a warning: the share, or one long hold. */\nexport function deadWarns(r: DeadReport): boolean {\n return r.pct >= DEAD_SHARE_WARN_PCT || r.longestMs >= DEAD_STRETCH_WARN_MS\n}\n\n/** The dead time in words, naming the steps and what to cut. */\nexport function deadLine(r: DeadReport): string {\n const s = (ms: number) => `${(ms / 1000).toFixed(1)} s`\n if (!r.steps.length) return 'dead time: none'\n const parts = r.steps.map(\n (d) =>\n `#${d.step}${d.id ? ` (${d.id})` : ''} ${d.do} held ${s(d.heldMs)} after it settled, ${s(d.deadMs)} past the beat`,\n )\n return `dead time: ${s(r.ms)} (${r.pct} %), a still frame under a parked cursor past the reading beat: ${parts.join('; ')}. Cut those steps' ms; a hold is what it takes to read what changed.`\n}\n","/**\n * The wall check: did the recorder land where it was asked, or in front of\n * a sign-in? A take of a product behind a login, recorded without a session,\n * used to succeed: the footage was the login page (or wherever the site\n * sends a stranger), and the only symptom was a skipped selector. This says\n * it in words, before a frame is captured and before a re-record clears the\n * footage it would have replaced.\n *\n * Pure: the recorder gathers the evidence, this decides. A heuristic is\n * acceptable because both failure directions are cheap: a false refusal\n * costs one flag (`--allow-wall`), a false pass costs what every such take\n * cost before.\n */\n\n/** What the recorder saw once the first navigation settled. */\nexport interface Arrival {\n askedUrl: string\n landedUrl: string\n /** HTTP status of the main document, when the navigation produced one. */\n status?: number\n /** `input[type=password]` fields that are visible on the page. */\n passwordFields: number\n /** A password field marked `autocomplete=\"new-password\"` (a settings or sign-up form). */\n newPasswordField: boolean\n /** A visible one-time-code field (`autocomplete=\"one-time-code\"`). */\n oneTimeCodeField: boolean\n}\n\nexport type WallKind =\n /** the asked URL answered 401 or 403 */\n | 'status'\n /** landed on an identity provider's host */\n | 'idp'\n /** landed on a sign-in form or a sign-in path */\n | 'signin'\n /** sent somewhere else, with no sign-in in sight (a site that shows strangers a public page) */\n | 'redirect'\n\nexport interface WallVerdict {\n kind: WallKind\n /**\n * `hard` is refused always; `soft` is refused under `--strict` and said as\n * a warning otherwise, because a redirect alone is not proof of a wall.\n */\n level: 'hard' | 'soft'\n /** origin + path, never the query or hash (they can carry tokens). */\n asked: string\n landed: string\n message: string\n}\n\n/** Hosts that only ever mean \"sign in\". Suffix match on the label boundary. */\nconst IDP_HOSTS = [\n 'accounts.google.com',\n 'login.microsoftonline.com',\n 'login.live.com',\n 'appleid.apple.com',\n 'auth0.com',\n 'okta.com',\n 'onelogin.com',\n 'clerk.accounts.dev',\n 'accounts.dev',\n 'b2clogin.com',\n 'amazoncognito.com',\n 'workos.com',\n 'stytch.com',\n]\n\n/** Path segments that name a sign-in surface. Whole segments, never substrings. */\nconst SIGNIN_SEGMENTS = new Set([\n 'login',\n 'log-in',\n 'signin',\n 'sign-in',\n 'sign_in',\n 'sso',\n 'auth',\n 'authenticate',\n 'authorize',\n 'oauth',\n 'oauth2',\n 'saml',\n])\n\nfunction parse(url: string): URL | null {\n try {\n return new URL(url)\n } catch {\n return null\n }\n}\n\nconst bareHost = (h: string) => h.replace(/^www\\./, '').toLowerCase()\nconst trimSlash = (p: string) => (p.length > 1 ? p.replace(/\\/+$/, '') : p)\nconst place = (u: URL) => `${u.origin}${trimSlash(u.pathname)}`\n\nexport function isIdpHost(host: string): boolean {\n const h = host.toLowerCase()\n if (h.startsWith('clerk.')) return true\n return IDP_HOSTS.some((idp) => h === idp || h.endsWith(`.${idp}`))\n}\n\nexport function isSignInPath(pathname: string): boolean {\n const segs = pathname.toLowerCase().split('/').filter(Boolean)\n return segs.some((s) => SIGNIN_SEGMENTS.has(s))\n}\n\n/**\n * Sent AWAY, as opposed to sent deeper. `/` → `/en`, `/docs` → `/docs/start`\n * and `/pricing` → `/en/pricing` are a site arranging itself; `/dashboard` →\n * `/gallery` is a site declining to show the page. `www.` and the apex are\n * one host.\n */\nexport function redirectedAway(asked: URL, landed: URL): boolean {\n if (bareHost(asked.hostname) !== bareHost(landed.hostname)) return true\n const a = trimSlash(asked.pathname).toLowerCase()\n const l = trimSlash(landed.pathname).toLowerCase()\n if (a === l || a === '/') return false\n if (l.startsWith(`${a}/`)) return false\n if (l.endsWith(a)) return false\n return true\n}\n\nconst LADDER =\n 'This is a missing or expired session, not a script bug. Ways past, cheapest first: mint a session from the test auth the project already has; script the sign-in off camera and save the storage state; or have a person sign in once (npx playwright open --channel chrome --save-storage=<file> <url>). Then pass --storage-state <file>. The ladder: https://vos.so/llms-full.txt, \"Sessions\". A take OF this page is --allow-wall.'\n\nexport function wallVerdict(a: Arrival): WallVerdict | null {\n const asked = parse(a.askedUrl)\n const landed = parse(a.landedUrl)\n // A file: or data: take, or a URL we cannot read, has no wall to speak of.\n if (!asked || !landed) return null\n if (!/^https?:$/.test(asked.protocol)) return null\n\n const away = redirectedAway(asked, landed)\n const where = { asked: place(asked), landed: place(landed) }\n const host = bareHost(asked.hostname)\n\n if (a.status === 401 || a.status === 403) {\n return {\n kind: 'status',\n level: 'hard',\n ...where,\n message: `asked for ${asked.pathname}, answered ${a.status}: no session for ${host}. ${LADDER}`,\n }\n }\n\n // Someone who ASKED for the sign-in page and got it wants that take.\n const askedForSignIn =\n isSignInPath(asked.pathname) || isIdpHost(asked.hostname)\n if (askedForSignIn && !away) return null\n\n if (away && isIdpHost(landed.hostname) && !isIdpHost(asked.hostname)) {\n return {\n kind: 'idp',\n level: 'hard',\n ...where,\n message: `asked for ${asked.pathname}, landed on ${landed.hostname}: no session for ${host}. ${LADDER}`,\n }\n }\n\n // One password field that is not a new-password field is a sign-in form.\n // Two, or a new-password one, is a settings page or a sign-up.\n const signInForm =\n (a.passwordFields === 1 && !a.newPasswordField) || a.oneTimeCodeField\n if (\n (away && isSignInPath(landed.pathname)) ||\n (signInForm && !askedForSignIn)\n ) {\n const seen = away\n ? `landed on ${landed.pathname}`\n : 'the page is a sign-in form'\n return {\n kind: 'signin',\n level: 'hard',\n ...where,\n message: `asked for ${asked.pathname}, ${seen}: no session for ${host}. ${LADDER}`,\n }\n }\n\n if (away) {\n return {\n kind: 'redirect',\n level: 'soft',\n ...where,\n message: `asked for ${asked.pathname}, landed on ${landed.pathname}: the site sent the recorder elsewhere, which is what a page behind a login does to a stranger. If that is the page you meant, record it by its own URL. ${LADDER}`,\n }\n }\n return null\n}\n\n/** A take refused at the wall. Carries the verdict for `--json`. */\nexport class WallError extends Error {\n constructor(public verdict: WallVerdict) {\n super(verdict.message)\n this.name = 'WallError'\n }\n}\n\n/**\n * Evidence gathered in the page. A STRING, never a function: a serialized\n * function picks up the bundler's `__name` helper and dies in the page.\n */\nexport const WALL_PROBE = `(() => {\n const shown = (el) => {\n const r = el.getBoundingClientRect()\n if (r.width < 2 || r.height < 2) return false\n const s = getComputedStyle(el)\n return s.visibility !== 'hidden' && s.display !== 'none'\n }\n const pw = [...document.querySelectorAll('input[type=\"password\"]')].filter(shown)\n const otp = [...document.querySelectorAll('input[autocomplete=\"one-time-code\"]')].filter(shown)\n return {\n passwordFields: pw.length,\n newPasswordField: pw.some((el) => (el.getAttribute('autocomplete') || '').includes('new-password')),\n oneTimeCodeField: otp.length > 0,\n }\n})()`\n","/**\n * What a signed-in take SHOWS. Getting past the login is half the problem;\n * the other half is that the account's own data is now in the frame: an\n * email address in the header, a key on a settings page, a card number.\n * Asking nicely does not hold this line (a person asked to sign in with a\n * demo account signs in as themselves, because it is the account they have),\n * so the recorder looks.\n *\n * Two pieces, both run IN the page:\n *\n * - the EXPOSURE scan reads the text that is visible in the viewport after\n * each step and reports the KIND of thing it saw and where. Never the\n * string itself: a report that quotes the secret is a second leak.\n * - a MASK hides a selector before the first frame is captured and keeps it\n * hidden across navigations and re-renders, so the real value is never in\n * a frame, never in the recording, never pushed. `as: 'text'` swaps the\n * words (a product reads better than a redaction); `as: 'blur'` blurs.\n *\n * The matchers are ONE plain-JavaScript source string, evaluated in the page\n * and, by the tests, in Node: a function serialized out of a bundle picks up\n * the bundler's `__name` helper and dies in the page, and two copies of a\n * regex drift.\n */\nimport type { RecordingMeta } from '@vosjs/studio-core'\n\nexport type ExposureKind = 'email' | 'key' | 'card' | 'card-tail'\nexport type Exposure = NonNullable<RecordingMeta['exposures']>[number]\nexport type MaskReport = NonNullable<RecordingMeta['masks']>[number]\n\nexport interface MaskRule {\n selector: string\n /** `blur` (default) blurs the element; `text` swaps its words for `text`. */\n as?: 'blur' | 'text'\n text?: string\n}\n\n/**\n * `kindsIn(text)` → the kinds of sensitive-looking strings in `text`.\n *\n * - email: any address, EXCEPT one on a domain that can reach no one\n * (example.com/.org/.net, and the reserved .test/.example/.invalid/\n * .localhost TLDs), which is what demo data should use.\n * - key: the prefixes of keys people actually paste into dashboards, and a\n * JWT's three base64url parts.\n * - card: 13 to 19 digits that pass the Luhn check, so an order number or a\n * phone number is not a card.\n * - card-tail: a masked number's visible tail (•••• 4242).\n */\nexport const EXPOSURE_MATCHERS_SRC = `(() => {\n const EMAIL = /[A-Za-z0-9._%+-]+@((?:[A-Za-z0-9-]+\\\\.)+[A-Za-z]{2,})/g\n const SAFE_HOST = /(^|\\\\.)example\\\\.(com|org|net)$|\\\\.(test|example|invalid|localhost)$/i\n const KEY = new RegExp([\n '\\\\\\\\b[srp]k_(?:live|test)_[A-Za-z0-9]{8,}',\n '\\\\\\\\bgh[pousr]_[A-Za-z0-9]{20,}',\n '\\\\\\\\bgithub_pat_[A-Za-z0-9_]{20,}',\n '\\\\\\\\bxox[abprs]-[A-Za-z0-9-]{10,}',\n '\\\\\\\\bAKIA[0-9A-Z]{16}\\\\\\\\b',\n '\\\\\\\\bAIza[0-9A-Za-z_-]{30,}',\n '\\\\\\\\bvos_(?:sk|rg|ho)_[A-Za-z0-9]{8,}',\n '\\\\\\\\beyJ[A-Za-z0-9_-]{8,}\\\\\\\\.eyJ[A-Za-z0-9_-]{8,}\\\\\\\\.[A-Za-z0-9_-]{8,}',\n ].join('|'))\n const CARD = /(?:^|[^0-9])((?:[0-9][ -]?){12,18}[0-9])(?![0-9])/g\n const TAIL = /[\\\\u2022\\\\u00b7*xX]{2,}[ -]?[0-9]{4}(?![0-9])/\n const luhn = (digits) => {\n let sum = 0, alt = false\n for (let i = digits.length - 1; i >= 0; i--) {\n let d = digits.charCodeAt(i) - 48\n if (alt) { d *= 2; if (d > 9) d -= 9 }\n sum += d; alt = !alt\n }\n return sum % 10 === 0\n }\n const kindsIn = (text) => {\n const kinds = []\n if (!text || text.length < 6) return kinds\n EMAIL.lastIndex = 0\n for (let m; (m = EMAIL.exec(text)); ) {\n if (!SAFE_HOST.test(m[1])) { kinds.push('email'); break }\n }\n if (KEY.test(text)) kinds.push('key')\n CARD.lastIndex = 0\n for (let m; (m = CARD.exec(text)); ) {\n const digits = m[1].replace(/[ -]/g, '')\n if (digits.length >= 13 && digits.length <= 19 && !/^0+$/.test(digits) && luhn(digits)) { kinds.push('card'); break }\n }\n if (TAIL.test(text)) kinds.push('card-tail')\n return kinds\n }\n return { kindsIn }\n})()`\n\n/**\n * The scan: visible text in the viewport, plus the values of text inputs.\n * Skips anything inside a masked element, password fields, and text the eye\n * cannot see. Reports kind + a selector-ish name for the element + its rect.\n */\nexport const EXPOSURE_PROBE = `(() => {\n const { kindsIn } = ${EXPOSURE_MATCHERS_SRC}\n const vw = innerWidth, vh = innerHeight\n // A selector that reaches THIS element and no other, so it can be pasted\n // into \"mask\" as it is: climb until the path is unique, and say which\n // sibling wherever a tag-and-class alone would match several.\n const segment = (el) => {\n const tid = el.getAttribute('data-testid')\n if (tid) return el.tagName.toLowerCase() + '[data-testid=\"' + tid + '\"]'\n if (el.id && /^[A-Za-z_][\\\\w-]*$/.test(el.id)) return el.tagName.toLowerCase() + '#' + el.id\n const cls = [...el.classList].filter((c) => /^[A-Za-z_-][\\\\w-]*$/.test(c)).slice(0, 2)\n let seg = el.tagName.toLowerCase() + (cls.length ? '.' + cls.join('.') : '')\n const p = el.parentElement\n if (p && [...p.children].filter((c) => c.matches(seg)).length > 1)\n seg += ':nth-child(' + ([...p.children].indexOf(el) + 1) + ')'\n return seg\n }\n const name = (el) => {\n const parts = []\n for (let cur = el; cur && cur !== document.body && cur !== document.documentElement; cur = cur.parentElement) {\n parts.unshift(segment(cur))\n let n = 2\n try { n = document.querySelectorAll(parts.join(' > ')).length } catch {}\n // a bare tag is unique HERE and nowhere else: give it its parent too\n const bare = parts.length === 1 && !/[#.\\\\[]/.test(parts[0])\n if ((n === 1 && !bare) || parts.length >= 5) break\n }\n return parts.join(' > ')\n }\n const seen = (el) => {\n if (el.closest('[data-vos-masked]')) return null\n const r = el.getBoundingClientRect()\n if (r.width < 2 || r.height < 2) return null\n if (r.bottom <= 0 || r.right <= 0 || r.top >= vh || r.left >= vw) return null\n const s = getComputedStyle(el)\n if (s.visibility === 'hidden' || s.display === 'none' || +s.opacity === 0) return null\n return r\n }\n const out = [], keys = new Set()\n const add = (el, text) => {\n const kinds = kindsIn(text)\n if (!kinds.length) return\n const r = seen(el)\n if (!r) return\n const selector = name(el)\n for (const kind of kinds) {\n const k = kind + '|' + selector\n if (keys.has(k) || out.length >= 40) continue\n keys.add(k)\n out.push({ kind, selector, rect: { x: Math.round(r.left), y: Math.round(r.top), w: Math.round(r.width), h: Math.round(r.height) } })\n }\n }\n const walker = document.createTreeWalker(document.body || document.documentElement, NodeFilter.SHOW_TEXT)\n for (let n; (n = walker.nextNode()); ) {\n const el = n.parentElement\n if (!el || /^(SCRIPT|STYLE|NOSCRIPT|TEMPLATE)$/.test(el.tagName)) continue\n add(el, n.nodeValue)\n }\n for (const el of document.querySelectorAll('input, textarea')) {\n if (/^(password|hidden|file|checkbox|radio)$/.test(el.type)) continue\n add(el, el.value)\n }\n const hits = {}\n for (const [sel, n] of Object.entries(window.__vosMaskHits || {})) hits[sel] = n\n return { exposures: out, maskHits: hits }\n})()`\n\n/**\n * The mask, as an init script: it runs at document start on EVERY navigation,\n * so a masked value is hidden before the page's first paint, and a\n * MutationObserver re-applies it after a client-side re-render. A form\n * control is blurred even when `as: 'text'` was asked for: writing into an\n * input would change what the app submits.\n */\nexport function maskInitScript(masks: MaskRule[]): string {\n const rules = masks.map((m) => ({\n selector: m.selector,\n as: m.as === 'text' ? 'text' : 'blur',\n text: m.text ?? '',\n }))\n return `(() => {\n const RULES = ${JSON.stringify(rules)}\n const hits = (window.__vosMaskHits = window.__vosMaskHits || {})\n for (const r of RULES) hits[r.selector] = hits[r.selector] || 0\n const css = RULES.map((r) => r.selector + '[data-vos-masked=\"blur\"]{filter:blur(7px) !important;user-select:none !important}').join('\\\\n')\n const paint = () => {\n const root = document.head || document.documentElement\n if (!root || document.getElementById('__vos-mask-style')) return\n const style = document.createElement('style')\n style.id = '__vos-mask-style'\n style.textContent = css\n root.appendChild(style)\n }\n const apply = () => {\n paint()\n for (const r of RULES) {\n let found\n try { found = document.querySelectorAll(r.selector) } catch { continue }\n if (found.length > hits[r.selector]) hits[r.selector] = found.length\n for (const el of found) {\n const form = /^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName)\n const mode = r.as === 'text' && !form ? 'text' : 'blur'\n if (el.getAttribute('data-vos-masked') !== mode) el.setAttribute('data-vos-masked', mode)\n if (mode !== 'text') continue\n const texts = []\n const w = document.createTreeWalker(el, NodeFilter.SHOW_TEXT)\n for (let n; (n = w.nextNode()); ) texts.push(n)\n if (!texts.length) { if (el.textContent !== r.text) el.textContent = r.text; continue }\n texts.forEach((n, i) => { const want = i === 0 ? r.text : ''; if (n.nodeValue !== want) n.nodeValue = want })\n }\n }\n }\n new MutationObserver(apply).observe(document, { childList: true, subtree: true, characterData: true })\n document.addEventListener('DOMContentLoaded', apply)\n apply()\n})()`\n}\n\n/**\n * Accumulates scans across a take: an exposure is reported ONCE, at the\n * first step it was seen, with how many scans saw it.\n */\nexport class ExposureLog {\n private byKey = new Map<string, Exposure>()\n private maskHits = new Map<string, number>()\n\n constructor(masks: MaskRule[] = []) {\n for (const m of masks) this.maskHits.set(m.selector, 0)\n }\n\n add(\n step: number,\n scan: {\n exposures?: Omit<Exposure, 'step' | 'seen'>[]\n maskHits?: Record<string, number>\n } | null,\n ): void {\n if (!scan) return\n for (const e of scan.exposures ?? []) {\n const key = `${e.kind}|${e.selector}`\n const had = this.byKey.get(key)\n if (had) had.seen += 1\n else this.byKey.set(key, { step, ...e, seen: 1 })\n }\n for (const [sel, n] of Object.entries(scan.maskHits ?? {})) {\n if (n > (this.maskHits.get(sel) ?? 0)) this.maskHits.set(sel, n)\n }\n }\n\n exposures(): Exposure[] {\n return [...this.byKey.values()]\n }\n\n masks(rules: MaskRule[]): MaskReport[] {\n return rules.map((m) => ({\n selector: m.selector,\n as: m.as === 'text' ? 'text' : 'blur',\n hits: this.maskHits.get(m.selector) ?? 0,\n }))\n }\n}\n\nconst KIND_WORDS: Record<ExposureKind, string> = {\n email: 'an email address',\n key: 'something shaped like an API key or token',\n card: 'a card number',\n 'card-tail': \"a card's last digits\",\n}\n\n/** One exposure, in words. `step` -1 is the page as it opened. */\nexport function exposureLine(e: Exposure): string {\n const when = e.step < 0 ? 'as the page opened' : `at step #${e.step}`\n return `${KIND_WORDS[e.kind]} in ${e.selector} (${when}, ${e.rect.x},${e.rect.y} ${e.rect.w}×${e.rect.h})`\n}\n\nexport const EXPOSURE_ADVICE =\n 'The recording shows these. Record from a demo account, or hide them before the camera rolls with \"mask\" in actions.json: { \"selector\": \"…\", \"as\": \"text\", \"text\": \"demo@acme.test\" } swaps the words, \"as\": \"blur\" blurs. Then re-record.'\n","/**\n * `setup`: the steps that run BEFORE the camera rolls. A sign-in form, a\n * cookie banner, the \"choose your editor\" modal, an onboarding tour: things\n * a take must get past and must not show. They run after the first\n * navigation and before `Page.startScreencast`, with no cursor synthesis,\n * no pace accounting and nothing written to `meta.steps`. Then the recorder\n * navigates to `url` again and the take begins where the setup left it.\n *\n * A `type` step's `text` may be `{ \"env\": \"DEMO_PASSWORD\" }`: resolved at run\n * time from the `env` the caller hands in (the shell's for the CLI, the\n * job's own map for a hosted take), never logged, never stored. The\n * guarantee is narrow and real: the secret is never in a file that travels\n * (`actions.json` is committed and pushed with the take; `meta.json` is read\n * by the digest) and never in the footage.\n */\nimport type { RecorderPage } from './driver'\n\nexport type SetupText = string | { env: string }\n\nexport type SetupStep = (\n | { do: 'wait'; ms: number }\n | { do: 'click'; selector: string; ms?: number }\n | { do: 'type'; selector: string; text: SetupText; ms?: number }\n | { do: 'press'; key: string; ms?: number }\n | { do: 'goto'; url: string }\n) & { id?: string }\n\nconst VERBS = new Set(['wait', 'click', 'type', 'press', 'goto'])\n\n/** Structural validation, mirrored on `validateActions` for `steps`. */\nexport function validateSetup(value: unknown): string[] {\n const errors: string[] = []\n if (!Array.isArray(value)) return ['setup must be an array of steps']\n value.forEach((raw, i) => {\n const at = `setup[${i}]`\n if (typeof raw !== 'object' || raw === null) {\n errors.push(`${at}: must be an object`)\n return\n }\n const s = raw as Record<string, unknown>\n if (typeof s.do !== 'string' || !VERBS.has(s.do)) {\n errors.push(`${at}: \"do\" must be one of ${[...VERBS].join(', ')}`)\n return\n }\n if ((s.do === 'click' || s.do === 'type') && typeof s.selector !== 'string')\n errors.push(`${at}: ${s.do} needs a selector`)\n if (s.do === 'wait' && typeof s.ms !== 'number')\n errors.push(`${at}: wait needs ms`)\n if (s.do === 'press' && typeof s.key !== 'string')\n errors.push(`${at}: press needs a key`)\n if (s.do === 'goto' && typeof s.url !== 'string')\n errors.push(`${at}: goto needs a url`)\n if (s.do === 'type') {\n const t = s.text\n const isEnv =\n typeof t === 'object' &&\n t !== null &&\n typeof (t as { env?: unknown }).env === 'string' &&\n Object.keys(t).length === 1\n if (typeof t !== 'string' && !isEnv)\n errors.push(\n `${at}: type needs text, a string or { \"env\": \"NAME\" } read at run time`,\n )\n if (\n typeof t === 'string' &&\n /passw|secret|token/i.test(String(s.selector)) &&\n t.length > 0\n )\n errors.push(\n `${at}: a literal value typed into ${String(s.selector)} would live in actions.json, which is committed and pushed. Use { \"env\": \"NAME\" }`,\n )\n }\n })\n return errors\n}\n\n/** The names a setup reads from its env, so a host can say what it must supply. */\nexport function setupEnvNames(steps: SetupStep[] | undefined): string[] {\n const out = new Set<string>()\n for (const s of steps ?? []) {\n if (s.do === 'type' && typeof s.text === 'object') out.add(s.text.env)\n }\n return [...out]\n}\n\n/** A `{ env }` text resolved, or thrown in words. The value is never returned to a log. */\nexport function resolveSetupText(\n t: SetupText,\n env: Record<string, string | undefined>,\n): { value: string; secret: boolean } {\n if (typeof t === 'string') return { value: t, secret: false }\n const v = env[t.env]\n if (v === undefined || v === '')\n throw new SetupEnvError(\n `setup: the environment variable ${t.env} is not set, and a type step reads it. Export it in the shell that runs vos record; it is never written to a file`,\n )\n return { value: v, secret: true }\n}\n\nexport interface SetupResult {\n ran: number\n /** what the log may say about each step: the verb and the selector, never a typed value */\n lines: string[]\n /** a selector that never appeared; the take does not start */\n failed?: { step: number; do: string; selector?: string }\n}\n\n/**\n * Run the setup on a page, with plain Playwright actions: no humanized\n * pointer, no cursor events, no frames. A selector that never appears\n * fails the setup, because a take that begins at a half-finished sign-in is\n * the wall by another name.\n */\nexport async function runSetup(\n page: RecorderPage,\n steps: SetupStep[],\n opts: {\n sleep: (ms: number) => Promise<void>\n /** Absent means no env at all: a `{ env }` text then fails in words. */\n env?: Record<string, string | undefined>\n },\n): Promise<SetupResult> {\n const env = opts.env ?? {}\n const lines: string[] = []\n for (const [i, step] of steps.entries()) {\n const settle = 'ms' in step && typeof step.ms === 'number' ? step.ms : 150\n try {\n switch (step.do) {\n case 'wait':\n await opts.sleep(step.ms)\n lines.push(`setup #${i} wait ${step.ms}ms`)\n break\n case 'goto':\n await page\n .goto(step.url, { waitUntil: 'networkidle', timeout: 45000 })\n .catch(() => null)\n lines.push(`setup #${i} goto ${step.url}`)\n break\n case 'press':\n await page.keyboard.press(step.key)\n await opts.sleep(settle)\n lines.push(`setup #${i} press ${step.key}`)\n break\n case 'click': {\n const loc = page.locator(step.selector).first()\n await loc.waitFor({ state: 'visible', timeout: 8000 })\n await loc.click()\n await opts.sleep(settle)\n lines.push(`setup #${i} click ${step.selector}`)\n break\n }\n case 'type': {\n const loc = page.locator(step.selector).first()\n await loc.waitFor({ state: 'visible', timeout: 8000 })\n const { value, secret } = resolveSetupText(step.text, env)\n await loc.fill(value)\n await opts.sleep(settle)\n // The log names the field, never what went into it.\n lines.push(\n `setup #${i} type ${secret ? `\\${${(step.text as { env: string }).env}}` : `${value.length} chars`} into ${step.selector}`,\n )\n break\n }\n }\n } catch (e) {\n if (e instanceof SetupEnvError) throw e\n return {\n ran: i,\n lines,\n failed: {\n step: i,\n do: step.do,\n ...('selector' in step ? { selector: step.selector } : {}),\n },\n }\n }\n }\n return { ran: steps.length, lines }\n}\n\n/** A `{ env }` the shell did not provide: a usage error, not a page's. */\nexport class SetupEnvError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'SetupEnvError'\n }\n}\n\n/** A setup step whose selector never appeared: the take does not start. */\nexport class SetupError extends Error {\n constructor(public failed: NonNullable<SetupResult['failed']>) {\n super(\n `setup #${failed.step} ${failed.do}${failed.selector ? ` ${failed.selector}` : ''}: the selector never appeared, so the take would begin at a half-finished setup. Nothing was recorded. Fix the setup step (rehearse with --dry-run), then record.`,\n )\n this.name = 'SetupError'\n }\n}\n\n/** `--header name=value`, repeatable: extra request headers on every request. */\nexport function parseHeaders(\n given: string[] | undefined,\n): Record<string, string> {\n const out: Record<string, string> = {}\n for (const h of given ?? []) {\n const i = h.indexOf('=')\n if (i <= 0) throw new Error(`--header wants name=value, got \"${h}\"`)\n out[h.slice(0, i).trim()] = h.slice(i + 1)\n }\n return out\n}\n","/**\n * The recorder — a browser drives the action script while:\n * - CDP Page.startScreencast collects JPEG frames (epoch-timestamped;\n * hold-last-frame gaps are filled at encode time), and\n * - every dispatched input is logged as a CursorEvent (the synthesized track:\n * exact coords, exact times, fresh element rects — no capture needed).\n *\n * The MECHANISM, host-free (AN4): the browser arrives as a structural driver\n * (a launched Node Playwright browser from the CLI, a connected\n * @cloudflare/playwright browser from the fleet), frames leave through a\n * sink, the platform and the setup env are handed in, and nothing here\n * touches a filesystem or a process. The CLI's `recordTake` wraps this with\n * a take directory; the fleet's consumer wraps it with R2. Both produce the\n * same events, meta and frame index for the same script.\n */\nimport type {\n FrameSink,\n RecorderBrowser,\n RecorderClock,\n RecorderContext,\n} from './driver'\nimport { realSleep } from './driver'\nimport { capReached, cappedLine, clampWait } from './cap'\nimport {\n PRESS_HOLD_MS,\n PRESS_LEAD_MS,\n SCROLL_SETTLE_MS,\n TRAILING_HOLD_MS,\n askedMs,\n deadLine,\n deadTime,\n deadWarns,\n holdLeftMs,\n settleVerdict,\n clockMotion,\n clockTyping,\n paceLine,\n paceReport,\n pointerTravelMs,\n settleMs,\n} from './pace'\nimport type { DeadReport, PaceReport, StepPace } from './pace'\nimport type {\n CursorEvent,\n RecordingMeta,\n Rect,\n StepSpan,\n} from '@vosjs/studio-core'\nimport { WALL_PROBE } from './wall'\nimport { EXPOSURE_PROBE, ExposureLog, maskInitScript } from './exposure'\nimport { SetupError, runSetup } from './setup'\nimport type { ActionsFile } from './actions'\nimport type { Arrival, WallVerdict } from './wall'\n\nexport interface FrameRec {\n file: string\n /** ms since t0 */\n tMs: number\n}\n\nexport interface SkippedStep {\n /** index into actions.steps */\n step: number\n do: string\n selector: string\n}\n\n/** A stretch with no screencast frames — the page pixels did not change. */\nexport interface FreezeSpan {\n /** seconds into the take */\n from: number\n to: number\n ms: number\n}\n\nexport interface RecordResult {\n events: CursorEvent[]\n frames: FrameRec[]\n meta: RecordingMeta\n /** The take's pace: what the script asked, what the gestures added, what the page cost. */\n pace: PaceReport\n /** steps whose selector never became visible — the take continued without them. */\n skipped: SkippedStep[]\n /** the initial goto never reached networkidle (recording proceeded anyway). */\n navTimeout: boolean\n /** A wall the caller let through; null when the recorder landed where it was asked. */\n wall: WallVerdict | null\n /** Sensitive-looking text seen in the frame (kind + place, never the string). */\n exposures: NonNullable<RecordingMeta['exposures']>\n /** The script's masks with how many elements each reached; hits 0 hid nothing. */\n masks: NonNullable<RecordingMeta['masks']>\n /**\n * Smoothness telemetry: stretches ≥400ms with no visual change. Frozen\n * footage is the #1 enemy of a smooth product video — either the flow should\n * keep motion in frame (animate, hover a preview, scroll) or the doc should\n * trim/speed through these. freezePct = share of the take that is frozen.\n */\n freezes: FreezeSpan[]\n freezePct: number\n /**\n * The smoothness WARNING: still footage under a parked cursor past the\n * beat it takes to read what changed, per step (`deadTime`). A still page\n * being read is content; this is the part of a hold nobody is reading.\n */\n dead: DeadReport\n /** The take reached --max-duration and stopped there; later steps did not run. */\n capped: boolean\n /** The actions as recorded, `url` resolved: what a take directory writes back. */\n actions: ActionsFile\n}\n\nexport interface RecordOpts {\n /** Stop the capture at this many seconds (the hosted cap). */\n maxDurationSeconds?: number\n /**\n * Path to a Playwright storage state (cookies + origin storage), so the\n * recorder drives a SIGNED-IN product. A demo of anything behind a login\n * needs it, and a sign-in form cannot always be scripted (an emailed code,\n * an SSO hop). Export one from a real browser, or with\n * `context.storageState({ path })`.\n */\n storageState?: string\n /**\n * REHEARSE the flow: every step runs against the real page, in order,\n * because a later selector usually exists only after an earlier click. But\n * nothing is captured (no screencast, no frames), nothing is written, the\n * pointer lands instead of travelling and every pause is cut to a beat, so\n * a script that misses a selector says so in seconds instead of after a\n * full real-time take and its encode. Selector lookups keep their whole\n * timeout, so a miss here is a miss in the take.\n */\n dryRun?: boolean\n /**\n * Called once the first navigation has settled, BEFORE a frame is captured.\n * The caller judges the wall here and only then prepares the take\n * directory, so a take refused at a sign-in never clears the footage a\n * re-record would have replaced. A throw closes the context and propagates.\n */\n onArrival?: (\n arrival: Arrival,\n ) => Promise<WallVerdict | null | void> | WallVerdict | null | void\n /** Extra request headers on every request (`--header name=value`). */\n headers?: Record<string, string>\n /**\n * A ready-made context to record IN, instead of one made from `browser`:\n * `--session <name>`, a persistent profile the person signed in to. The\n * recorder still sizes the viewport and closes it at the end.\n */\n context?: RecorderContext\n /**\n * The environment a setup's `{ env }` texts resolve from: the shell's\n * for the CLI, the job's own map on the fleet. Absent means no env at\n * all, so a setup that reads one fails in words.\n */\n env?: Record<string, string | undefined>\n /** The platform the take is recorded on, for `meta.platform`. */\n platform?: RecordingMeta['platform']\n}\n\n/** The longest any pause lasts in a rehearsal. */\nconst DRY_PAUSE_MS = 120\n\n/** Minimal JPEG SOF parse for real encoded dimensions. */\nexport function jpegDims(buf: Uint8Array): { w: number; h: number } | null {\n const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength)\n let i = 2\n while (i < buf.length - 9) {\n if (buf[i] !== 0xff) return null\n const marker = buf[i + 1]\n const len = view.getUint16(i + 2)\n if (\n marker >= 0xc0 &&\n marker <= 0xcf &&\n marker !== 0xc4 &&\n marker !== 0xc8 &&\n marker !== 0xcc\n ) {\n return { h: view.getUint16(i + 5), w: view.getUint16(i + 7) }\n }\n i += 2 + len\n }\n return null\n}\n\n/** Base64 to bytes without Node's Buffer, so the fleet's isolate runs it too. */\nexport function base64Bytes(data: string): Uint8Array {\n const bin = atob(data)\n const out = new Uint8Array(bin.length)\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i)\n return out\n}\n\nexport async function recordTake(\n browser: RecorderBrowser,\n url: string,\n actions: ActionsFile,\n sink: FrameSink,\n log: (msg: string) => void,\n opts: RecordOpts = {},\n): Promise<RecordResult> {\n const dry = opts.dryRun === true\n const sleep = dry\n ? (ms: number) => realSleep(Math.min(ms, DRY_PAUSE_MS))\n : realSleep\n // A rehearsal is not held to the recording cap: its clock is not the take's.\n const maxSeconds = dry ? Infinity : (opts.maxDurationSeconds ?? Infinity)\n const vw = actions.viewport?.width ?? 1280\n const vh = actions.viewport?.height ?? 720\n const context =\n opts.context ??\n (await browser.newContext({\n viewport: { width: vw, height: vh },\n deviceScaleFactor: 1,\n ...(opts.storageState ? { storageState: opts.storageState } : {}),\n ...(opts.headers && Object.keys(opts.headers).length\n ? { extraHTTPHeaders: opts.headers }\n : {}),\n }))\n if (opts.context && opts.headers && Object.keys(opts.headers).length)\n await context.setExtraHTTPHeaders(opts.headers)\n // Masks go in as an init script, so they are in force at document start on\n // every navigation: the real value never reaches a painted frame.\n const masks = actions.mask ?? []\n if (masks.length) await context.addInitScript(maskInitScript(masks))\n const exposureLog = new ExposureLog(masks)\n const page = await context.newPage()\n // What the frame shows, read after each step. A few ms in the page; a scan\n // that fails (mid-navigation) is skipped, never fatal.\n const scanExposures = async (step: number) => {\n const scan = await page.evaluate(EXPOSURE_PROBE).catch(() => null)\n exposureLog.add(step, scan as Parameters<ExposureLog['add']>[1])\n }\n page.on('console', (m) => {\n if (m.type() === 'error') log(` [page error] ${m.text()}`)\n })\n\n log(`goto ${url}`)\n let navTimeout = false\n let response = await page\n .goto(url, { waitUntil: 'networkidle', timeout: 45000 })\n .catch(() => {\n navTimeout = true\n log(' (networkidle timeout — continuing)')\n return null\n })\n await sleep(800) // hydration settle\n\n // The setup: off camera, before the wall is judged, because a setup that\n // signs in is exactly what makes the wall not there. A selector that never\n // appears fails the take here, in words, before anything is captured.\n const setup = actions.setup ?? []\n if (setup.length) {\n log(`setup: ${setup.length} step(s), off camera`)\n const result = await runSetup(page, setup, {\n sleep: realSleep,\n env: opts.env ?? {},\n })\n for (const line of result.lines) log(` ${line}`)\n if (result.failed) {\n await context.close().catch(() => {})\n throw new SetupError(result.failed)\n }\n // Back to the page the take is OF, signed in and past the banners.\n log(`goto ${url} (after setup)`)\n response = await page\n .goto(url, { waitUntil: 'networkidle', timeout: 45000 })\n .catch(() => null)\n await sleep(800)\n }\n\n // A wall the caller let through (--allow-wall, or a soft one without\n // --strict) rides the take's meta, so a digest made later still says so.\n let wall: WallVerdict | null = null\n if (opts.onArrival) {\n const seen = (await page.evaluate(WALL_PROBE).catch(() => null)) as Pick<\n Arrival,\n 'passwordFields' | 'newPasswordField' | 'oneTimeCodeField'\n > | null\n try {\n wall =\n (await opts.onArrival({\n askedUrl: url,\n landedUrl: page.url(),\n ...(response ? { status: response.status() } : {}),\n passwordFields: seen?.passwordFields ?? 0,\n newPasswordField: seen?.newPasswordField ?? false,\n oneTimeCodeField: seen?.oneTimeCodeField ?? false,\n })) ?? null\n } catch (e) {\n await context.close().catch(() => {})\n throw e\n }\n }\n\n await scanExposures(-1)\n\n const cdp = await context.newCDPSession(page)\n const frames: FrameRec[] = []\n let frameIdx = 0\n let t0 = 0\n // Written from the screencast callback, so the read below does not\n // narrow: a box, not a plain local.\n const first: { dims: { w: number; h: number } | null } = { dims: null }\n // Frames are handed to the sink in order; the chain keeps a slow sink\n // (R2) from reordering them and is awaited before the meta is built.\n let sinkChain: Promise<void> = Promise.resolve()\n if (!dry)\n cdp.on('Page.screencastFrame', (ev) => {\n const tsMs = ev.metadata.timestamp\n ? ev.metadata.timestamp * 1000\n : Date.now()\n const file = `frame-${String(frameIdx++).padStart(5, '0')}.jpg`\n const bytes = base64Bytes(ev.data)\n if (!first.dims) first.dims = jpegDims(bytes)\n frames.push({ file, tMs: Math.max(0, Math.round(tsMs - t0)) })\n sinkChain = sinkChain.then(() => sink.frame(file, bytes)).catch(() => {})\n cdp\n .send('Page.screencastFrameAck', { sessionId: ev.sessionId })\n .catch(() => {})\n })\n\n t0 = Date.now()\n if (!dry)\n await cdp.send('Page.startScreencast', {\n format: 'jpeg',\n quality: 90,\n maxWidth: vw,\n maxHeight: vh,\n everyNthFrame: 1,\n })\n\n const events: CursorEvent[] = []\n const cur = { x: 48, y: 48 }\n const now = () => Date.now() - t0\n const emit = (e: Omit<CursorEvent, 't'>) => events.push({ t: now(), ...e })\n\n await page.mouse.move(cur.x, cur.y)\n emit({ x: cur.x, y: cur.y, type: 'move' })\n\n const clock: RecorderClock = { now: () => Date.now(), sleep }\n // The pointer's travel is driven by the CLOCK (pace.ts): its position is\n // a function of the elapsed time and it ends when the travel's duration\n // has elapsed, so a page that answers each mouse.move slowly costs\n // samples, never seconds. Every sample the page took is a cursor event\n // at its real time, which is what the smoothing and the follow read.\n const moveTo = async (tx: number, ty: number) => {\n const dist = Math.hypot(tx - cur.x, ty - cur.y)\n const from = { ...cur }\n await clockMotion(\n dry ? 0 : pointerTravelMs(dist),\n async (u) => {\n cur.x = from.x + (tx - from.x) * u\n cur.y = from.y + (ty - from.y) * u\n await page.mouse.move(cur.x, cur.y)\n emit({ x: Math.round(cur.x), y: Math.round(cur.y), type: 'move' })\n },\n clock,\n )\n }\n\n const boxOf = async (selector: string): Promise<Rect | null> => {\n const loc = page.locator(selector).first()\n try {\n await loc.waitFor({ state: 'visible', timeout: 8000 })\n const before = await page\n .evaluate(() => [window.scrollX, window.scrollY])\n .catch(() => [0, 0])\n await loc.scrollIntoViewIfNeeded()\n const after = await page\n .evaluate(() => [window.scrollX, window.scrollY])\n .catch(() => before)\n // A lookup that scrolled the page lets the scroll land; one that\n // did not costs nothing.\n if (before[0] !== after[0] || before[1] !== after[1])\n await sleep(SCROLL_SETTLE_MS)\n const bb = await loc.boundingBox()\n if (!bb) return null\n return { x: bb.x, y: bb.y, w: bb.width, h: bb.height }\n } catch {\n log(` (selector not found, skipping: ${selector})`)\n return null\n }\n }\n\n const clickAt = async (rect: Rect) => {\n await moveTo(rect.x + rect.w / 2, rect.y + rect.h / 2)\n await sleep(PRESS_LEAD_MS)\n emit({\n x: Math.round(cur.x),\n y: Math.round(cur.y),\n type: 'down',\n button: 0,\n rect,\n })\n await page.mouse.down()\n await sleep(PRESS_HOLD_MS)\n await page.mouse.up()\n emit({\n x: Math.round(cur.x),\n y: Math.round(cur.y),\n type: 'up',\n button: 0,\n rect,\n })\n }\n\n /**\n * Wait for the page to settle after a press, reading the screencast:\n * returns ms since the last change (null when nothing changed). A dry\n * run has no frames and settles at once.\n */\n const waitSettled = async (): Promise<number | null> => {\n if (dry) return null\n const pressAt = now()\n const before = frames.length\n for (;;) {\n const t = now()\n const last = frames.length > before ? frames[frames.length - 1] : null\n const sinceChange = last ? t - last.tMs : null\n if (settleVerdict({ sincePress: t - pressAt, sinceChange }) === 'settled')\n return sinceChange\n await sleep(40)\n }\n }\n\n const skipped: SkippedStep[] = []\n // The step timeline: when each step ran, in source seconds — what\n // lets a cut anchored to steps re-time onto a NEW recording of the same\n // script (`vos plan --reuse`). Every step that STARTED is recorded; a\n // skipped selector is marked, never silently absent.\n const stepSpans: StepSpan[] = []\n const paces: StepPace[] = []\n let capped = false\n for (const [stepIdx, step] of actions.steps.entries()) {\n // The cap is checked between steps (a step is one gesture and runs\n // whole); only a wait is cut short, since it is the one step that can\n // sit past the cap on its own.\n if (capReached(now(), maxSeconds)) {\n capped = true\n break\n }\n const stepStart = now()\n // The gesture the recorder adds by design around this step's ask: the\n // pointer's travel, the press, the settle. Everything else the step's\n // wall time holds is the page's (a slow round trip, a scroll landing).\n let gestureMs = 0\n const gesture = async <T>(fn: () => Promise<T>): Promise<T> => {\n const t = now()\n const out = await fn()\n gestureMs += now() - t\n return out\n }\n const skippedBefore = skipped.length\n // A step that changes the page's URL (a click that navigates, the\n // wait that lets the load land) is marked: the planner proposes a\n // transition at the page change.\n const urlBefore = page.url()\n // The selector's box at the gesture: kept on the step so a layer can\n // name what the step touched (`overlays[].pin.step`).\n let stepRect: Rect | null = null\n switch (step.do) {\n case 'wait':\n await sleep(clampWait(step.ms, now(), maxSeconds))\n break\n case 'move':\n await moveTo(step.x, step.y)\n break\n case 'hover': {\n const rect = await boxOf(step.selector)\n stepRect = rect\n if (rect) {\n await gesture(() => moveTo(rect.x + rect.w / 2, rect.y + rect.h / 2))\n log(`hover ${step.selector}`)\n await sleep(step.ms ?? 700) // parked cursor = dwell signal for the planner\n } else\n skipped.push({ step: stepIdx, do: step.do, selector: step.selector })\n break\n }\n case 'click': {\n const rect = await boxOf(step.selector)\n stepRect = rect\n if (rect) {\n await gesture(() => clickAt(rect))\n log(`click ${step.selector}`)\n // The settle is the recorder's (watched on the screencast); the\n // hold is the author's, from the page's last change.\n const sinceChange = await gesture(() => waitSettled())\n await sleep(holdLeftMs(settleMs(step), sinceChange))\n } else\n skipped.push({ step: stepIdx, do: step.do, selector: step.selector })\n break\n }\n case 'type': {\n const rect = await boxOf(step.selector)\n stepRect = rect\n if (rect) {\n // The click is what opens the typing zoom on the field. A step that\n // only finishes earlier typing (a submitting Enter) passes\n // focus:false: the field is already focused and a second click\n // rings a click effect on empty space beside the text.\n if (step.focus !== false) await gesture(() => clickAt(rect))\n // Typing-activity pings (TZ): one per ~350ms while characters land,\n // plus one at completion so the planner's hold starts at the true\n // typing end. When-and-where only — a ping never carries the text.\n // ~0.6s+ of typing earns the zoom (TYPING_MIN_DUR + ping floor), so\n // pace delayMs like a person for a camera moment, not a paste.\n const delay = step.delayMs ?? 40\n const ping = () =>\n emit({\n x: Math.round(rect.x + rect.w / 2),\n y: Math.round(rect.y + rect.h / 2),\n type: 'key',\n rect,\n })\n let lastPing = -Infinity\n await clockTyping(\n [...step.text],\n delay,\n async (ch) => {\n if (now() - lastPing >= 350) {\n lastPing = now()\n ping()\n }\n await page.keyboard.type(ch)\n },\n clock,\n )\n ping()\n log(`type \"${step.text}\" into ${step.selector}`)\n await gesture(() => sleep(settleMs(step)))\n } else\n skipped.push({ step: stepIdx, do: step.do, selector: step.selector })\n break\n }\n case 'scroll': {\n const total = Math.abs(step.dy)\n const dir = Math.sign(step.dy)\n for (let done = 0; done < total; done += 120) {\n await page.mouse.wheel(0, dir * Math.min(120, total - done))\n emit({ x: Math.round(cur.x), y: Math.round(cur.y), type: 'scroll' })\n await sleep(40)\n }\n await gesture(() => sleep(settleMs(step)))\n break\n }\n case 'drag': {\n // Start point: selector center when given, else explicit coords.\n let start: { x: number; y: number } | null = null\n let rect: Rect | undefined\n if (step.selector) {\n const box = await boxOf(step.selector)\n stepRect = box\n if (box) {\n rect = box\n start = { x: box.x + box.w / 2, y: box.y + box.h / 2 }\n } else {\n skipped.push({\n step: stepIdx,\n do: step.do,\n selector: step.selector,\n })\n break\n }\n } else if (typeof step.x === 'number' && typeof step.y === 'number') {\n start = { x: step.x, y: step.y }\n }\n if (!start) break\n await gesture(() => moveTo(start.x, start.y))\n await gesture(() => sleep(PRESS_LEAD_MS))\n emit({\n x: Math.round(cur.x),\n y: Math.round(cur.y),\n type: 'down',\n button: 0,\n rect,\n })\n await page.mouse.down()\n await gesture(() => sleep(PRESS_HOLD_MS))\n // The drag is the ask: `ms` of travel by the clock, however slowly\n // the page answers each move (a slider re-rendering per sample).\n const dur = step.ms ?? 700\n const from = { ...cur }\n await clockMotion(\n dur,\n async (u) => {\n cur.x = from.x + (step.tx - from.x) * u\n cur.y = from.y + (step.ty - from.y) * u\n await page.mouse.move(cur.x, cur.y)\n emit({ x: Math.round(cur.x), y: Math.round(cur.y), type: 'move' })\n },\n clock,\n )\n await gesture(() => sleep(settleMs({ do: 'drag' })))\n await page.mouse.up()\n emit({\n x: Math.round(cur.x),\n y: Math.round(cur.y),\n type: 'up',\n button: 0,\n rect,\n })\n log(\n `drag ${step.selector ?? `${start.x},${start.y}`} → ${step.tx},${step.ty}`,\n )\n await sleep(400)\n break\n }\n }\n const stepInfo = step as { id?: string; selector?: string }\n stepSpans.push({\n step: stepIdx,\n ...(stepInfo.id ? { id: stepInfo.id } : {}),\n do: step.do,\n ...(stepInfo.selector ? { selector: stepInfo.selector } : {}),\n tStart: +(stepStart / 1000).toFixed(3),\n tEnd: +(now() / 1000).toFixed(3),\n ...(skipped.length > skippedBefore ? { skipped: true } : {}),\n ...(page.url() !== urlBefore ? { navigated: true } : {}),\n ...(stepRect\n ? {\n rect: {\n x: +stepRect.x.toFixed(1),\n y: +stepRect.y.toFixed(1),\n w: +stepRect.w.toFixed(1),\n h: +stepRect.h.toFixed(1),\n },\n }\n : {}),\n })\n paces.push({\n step: stepIdx,\n do: step.do,\n askedMs: askedMs(step as Parameters<typeof askedMs>[0]),\n gestureMs,\n wallMs: now() - stepStart,\n })\n // After the pace is taken: the scan's few ms are not the step's.\n await scanExposures(stepIdx)\n }\n if (!capped) await sleep(TRAILING_HOLD_MS) // trailing hold\n if (capped || capReached(now(), maxSeconds)) {\n capped = true\n log(` ${cappedLine(maxSeconds)}`)\n }\n\n const durationMs = capped ? Math.min(now(), maxSeconds * 1000) : now()\n await cdp.send('Page.stopScreencast').catch(() => {})\n await sleep(200)\n const pageTitle = await page.title().catch(() => '')\n await context.close()\n // Every frame the screencast delivered has reached the sink.\n await sinkChain\n\n const meta: RecordingMeta = {\n dpr: 1,\n zoom: 1,\n t0,\n durationMs,\n width: vw,\n height: vh,\n fps: 30,\n hasAudio: false,\n captureWidth: first.dims?.w ?? vw,\n captureHeight: first.dims?.h ?? vh,\n captureSurface: 'tab',\n pageUrl: url,\n pageTitle,\n platform: opts.platform ?? 'linux',\n producer: 'cli',\n steps: stepSpans,\n ...(wall\n ? { wall: { kind: wall.kind, asked: wall.asked, landed: wall.landed } }\n : {}),\n ...(exposureLog.exposures().length\n ? { exposures: exposureLog.exposures() }\n : {}),\n ...(masks.length ? { masks: exposureLog.masks(masks) } : {}),\n }\n\n // Smoothness telemetry: screencast emits only on visual change, so frame\n // gaps = frozen pixels. ≥400ms reads as dead time in a product video.\n const freezes: FreezeSpan[] = []\n for (let i = 1; i < frames.length; i++) {\n const gap = frames[i].tMs - frames[i - 1].tMs\n if (gap >= 400) {\n freezes.push({\n from: +(frames[i - 1].tMs / 1000).toFixed(2),\n to: +(frames[i].tMs / 1000).toFixed(2),\n ms: gap,\n })\n }\n }\n const frozenMs = freezes.reduce((sum, f) => sum + f.ms, 0)\n const freezePct =\n durationMs > 0 ? Math.round((frozenMs / durationMs) * 100) : 0\n // The freeze share is a FACT (a workspace tour is mostly still and fine);\n // the warning is dead time: a hold nobody is reading, named per step.\n const dead = deadTime(stepSpans, frames, events, durationMs)\n if (deadWarns(dead)) log(` WARNING: ${deadLine(dead)}`)\n\n const pace = paceReport(paces)\n if (!dry) log(` ${paceLine(pace)}`)\n return {\n events,\n frames,\n meta,\n pace,\n skipped,\n navTimeout,\n wall,\n exposures: exposureLog.exposures(),\n masks: exposureLog.masks(masks),\n freezes,\n freezePct,\n dead,\n capped,\n actions: { ...actions, url },\n }\n}\n","import type { MaskRule } from './exposure'\nimport type { SetupStep } from './setup'\nimport { validateSetup } from './setup'\n\n/**\n * The action script — the declarative recipe an agent (or human) writes to\n * drive a take. Small on purpose: selectors + a handful of verbs. The recorder\n * executes it with humanized cursor motion and synthesizes the CursorTrack\n * from its own dispatches.\n */\nexport interface ActionsFile {\n /** Page to record. `--url` overrides. */\n url?: string\n /** Recording viewport in CSS px (default 1280x720). */\n viewport?: { width: number; height: number }\n /**\n * Hidden BEFORE the first frame is captured and kept hidden across\n * navigations: what a signed-in account shows that must not ship. `blur`\n * (default) blurs the element; `text` swaps its words for `text`, which is\n * for IDENTIFIERS (an email, a name, an account id), never for product\n * copy or numbers: the video must stay true to the product.\n */\n mask?: MaskRule[]\n /**\n * Run BEFORE the camera rolls, after the first navigation: a sign-in form,\n * a cookie banner, an onboarding tour. Plain actions with no cursor, no\n * frames, no pace and nothing in `meta.steps`; then the recorder opens\n * `url` again and the take begins where the setup left it. A `type`\n * step's text may be `{ env: 'NAME' }`, read at run time and never logged\n * or stored.\n */\n setup?: SetupStep[]\n steps: ActionStep[]\n}\n\nexport type ActionStep = (\n | { do: 'wait'; ms: number }\n | { do: 'hover'; selector: string; ms?: number }\n /** `ms` is the settle after the press (default 150). */\n | { do: 'click'; selector: string; ms?: number }\n /**\n * Type into `selector`. The recorder clicks the field first, which is what\n * opens the typing zoom on it; `focus: false` types into the field as it is\n * already focused, for a keystroke that follows earlier typing (a submitting\n * Enter) rather than starting it — a second click there rings a click effect\n * on empty space beside the text.\n */\n | {\n do: 'type'\n selector: string\n text: string\n delayMs?: number\n focus?: boolean\n /** the settle after the last character (default 150) */\n ms?: number\n }\n /** `ms` is the settle after the scroll lands (default 200). */\n | { do: 'scroll'; dy: number; ms?: number }\n | { do: 'move'; x: number; y: number }\n /**\n * Press-move-release — real edits (drag an element on the stage canvas,\n * slide a range input, move a timeline clip). Start = the selector's center\n * when given, else (x, y); end = (tx, ty); eased over ms (default 700).\n */\n | {\n do: 'drag'\n selector?: string\n x?: number\n y?: number\n tx: number\n ty: number\n ms?: number\n }\n) & {\n /**\n * Optional stable identity: anchors in doc.json name a step by this\n * id (else by index), so a step can move or gain neighbours across script\n * edits without breaking the cut anchored to it. Unique when present.\n */\n id?: string\n /**\n * A beat's caption, two to eight words: `vos deliver` lands it as a\n * lower-third at this step's moment on the cuts that take words, and\n * `vos actions script` leads the beat with it.\n */\n caption?: string\n}\n\nconst VERBS = new Set([\n 'wait',\n 'hover',\n 'click',\n 'type',\n 'scroll',\n 'move',\n 'drag',\n])\n\n/** Structural validation with actionable messages. Returns [] when valid. */\nexport function validateActions(value: unknown): string[] {\n const errors: string[] = []\n if (typeof value !== 'object' || value === null)\n return ['actions file must be a JSON object']\n const obj = value as Record<string, unknown>\n if (obj.url !== undefined && typeof obj.url !== 'string')\n errors.push('url must be a string')\n if (obj.viewport !== undefined) {\n const v: unknown = obj.viewport\n if (typeof v !== 'object' || v === null) {\n errors.push('viewport must be { width, height }')\n } else {\n const vp = v as Record<string, unknown>\n if (typeof vp.width !== 'number' || typeof vp.height !== 'number') {\n errors.push('viewport must be { width, height }')\n }\n }\n }\n if (obj.setup !== undefined) errors.push(...validateSetup(obj.setup))\n if (obj.mask !== undefined) {\n if (!Array.isArray(obj.mask)) {\n errors.push('mask must be an array of { selector, as?, text? }')\n } else {\n obj.mask.forEach((raw, i) => {\n const at = `mask[${i}]`\n const m = (raw ?? {}) as Record<string, unknown>\n if (typeof m.selector !== 'string' || !m.selector.trim())\n errors.push(`${at}: needs a selector`)\n if (m.as !== undefined && m.as !== 'blur' && m.as !== 'text')\n errors.push(`${at}: as must be \"blur\" or \"text\"`)\n if (m.as === 'text' && (typeof m.text !== 'string' || !m.text))\n errors.push(`${at}: as \"text\" needs the text to show instead`)\n if (m.as !== 'text' && m.text !== undefined)\n errors.push(`${at}: text is only read with as \"text\"`)\n })\n }\n }\n if (!Array.isArray(obj.steps) || obj.steps.length === 0) {\n errors.push('steps must be a non-empty array')\n return errors\n }\n const seenIds = new Set<string>()\n obj.steps.forEach((raw, i) => {\n const at = `steps[${i}]`\n if (typeof raw !== 'object' || raw === null) {\n errors.push(`${at}: must be an object`)\n return\n }\n const s = raw as Record<string, unknown>\n if (typeof s.do !== 'string' || !VERBS.has(s.do)) {\n errors.push(`${at}: \"do\" must be one of ${[...VERBS].join(', ')}`)\n return\n }\n if (s.id !== undefined) {\n if (typeof s.id !== 'string' || !s.id.trim()) {\n errors.push(`${at}: id must be a non-empty string`)\n } else if (seenIds.has(s.id)) {\n errors.push(\n `${at}: duplicate id \"${s.id}\" — an anchor could not tell the two steps apart`,\n )\n } else {\n seenIds.add(s.id)\n }\n }\n const needSelector = s.do === 'hover' || s.do === 'click' || s.do === 'type'\n if (needSelector && typeof s.selector !== 'string')\n errors.push(`${at}: ${s.do} needs a selector`)\n if (s.do === 'wait' && typeof s.ms !== 'number')\n errors.push(`${at}: wait needs ms`)\n if (\n s.ms !== undefined &&\n (typeof s.ms !== 'number' || !Number.isFinite(s.ms) || s.ms < 0)\n )\n errors.push(`${at}: ms must be a number of milliseconds ≥ 0`)\n if (s.do === 'type' && typeof s.text !== 'string')\n errors.push(`${at}: type needs text`)\n if (\n s.do === 'type' &&\n s.focus !== undefined &&\n typeof s.focus !== 'boolean'\n )\n errors.push(`${at}: type focus must be true or false`)\n if (s.do === 'scroll' && typeof s.dy !== 'number')\n errors.push(`${at}: scroll needs dy`)\n if (\n s.do === 'move' &&\n (typeof s.x !== 'number' || typeof s.y !== 'number')\n ) {\n errors.push(`${at}: move needs x and y (CSS px)`)\n }\n if (s.do === 'drag') {\n if (typeof s.tx !== 'number' || typeof s.ty !== 'number') {\n errors.push(`${at}: drag needs tx and ty (CSS px)`)\n }\n const hasStart =\n typeof s.selector === 'string' ||\n (typeof s.x === 'number' && typeof s.y === 'number')\n if (!hasStart)\n errors.push(\n `${at}: drag needs a selector OR x and y as the start point`,\n )\n }\n })\n return errors\n}\n","/**\n * Frames → CFR WebM, as a PAGE. The VFR screencast JPEGs are drawn\n * hold-last-frame onto a canvas at a fixed 30fps and encoded with\n * mediabunny (WebCodecs) inside a headless page — no ffmpeg dependency.\n * The CLI serves the take directory to it and reads the bytes back through\n * its own `/save`; the fleet serves the frames through the ingest route and\n * the page PUTs the recording to the same route. One page, two hosts: the\n * URLs and the way out are the host's, the encode is not.\n */\n\nconst MEDIABUNNY_URL = 'https://esm.sh/mediabunny@1.55.7?target=es2022'\n\nexport interface EncodePageOptions {\n /** Where `frames.json` (the index `[{file, tMs}]`) is fetched from. */\n framesIndexUrl: string\n /** Where `meta.json` is fetched from. */\n metaUrl: string\n /** `<framesBaseUrl><file>` is each JPEG. */\n framesBaseUrl: string\n /** The way out: a POST to the CLI's take server, or a PUT to the ingest route. */\n save: { method: 'POST' | 'PUT'; url: string }\n /** Optional `?rt=`-style query the frame fetches must carry (the fleet's token). */\n frameQuery?: string\n}\n\nexport function buildEncodePage(o: EncodePageOptions): string {\n const q = o.frameQuery ? `?${o.frameQuery}` : ''\n return `<!doctype html><html><head><meta charset=\"utf-8\"></head><body>\n<script type=\"module\">\ntry {\n const MB = await import('${MEDIABUNNY_URL}')\n const frames = await (await fetch(${JSON.stringify(o.framesIndexUrl)})).json()\n const meta = await (await fetch(${JSON.stringify(o.metaUrl)})).json()\n const W = meta.captureWidth ?? meta.width, H = meta.captureHeight ?? meta.height\n const FPS = 30\n const durSec = meta.durationMs / 1000\n const cv = document.createElement('canvas')\n cv.width = W; cv.height = H\n const c2 = cv.getContext('2d')\n const output = new MB.Output({ format: new MB.WebMOutputFormat(), target: new MB.BufferTarget() })\n const src = new MB.CanvasSource(cv, { codec: 'vp9', bitrate: 8_000_000 })\n output.addVideoTrack(src, { frameRate: FPS })\n await output.start()\n let fi = -1\n const total = Math.ceil(durSec * FPS)\n for (let i = 0; i < total; i++) {\n const t = i / FPS\n let next = fi\n while (next + 1 < frames.length && frames[next + 1].tMs <= t * 1000) next++\n if (next < 0) next = 0\n if (next !== fi) {\n fi = next\n const blob = await (await fetch(${JSON.stringify(o.framesBaseUrl)} + frames[fi].file + ${JSON.stringify(q)})).blob()\n const bmp = await createImageBitmap(blob)\n c2.drawImage(bmp, 0, 0, W, H)\n bmp.close()\n }\n await src.add(t, 1 / FPS)\n window.__progress = i / total\n }\n await output.finalize()\n const buf = output.target.buffer\n const res = await fetch(${JSON.stringify(o.save.url)}, { method: ${JSON.stringify(o.save.method)}, headers: { 'content-type': 'video/webm' }, body: buf })\n if (!res.ok) throw new Error('save failed: ' + res.status)\n // Two hosts, two completion words: the CLI's take server polls __done,\n // the fleet's page runner polls __renderComplete.\n window.__done = { ok: true, bytes: buf.byteLength }\n window.__renderComplete = { success: true, size: buf.byteLength, uploaded: true }\n} catch (e) {\n window.__error = String(e && e.stack || e)\n window.__renderComplete = { success: false, error: String(e && e.message || e) }\n}\n</script></body></html>`\n}\n","/**\n * The FRESH plan of a take: what `vos record` writes as doc.json the moment\n * a recording lands, and what a hosted take gets on the fleet. One\n * composition, so a take recorded anywhere opens on the same camera: the\n * artifact becomes a document (`projectFromArtifact`), the zooms are\n * planned from the cursor track, the speed spans from the same track. The\n * backdrop is a host's PICK (vosso's house set, the CLI's `--background`),\n * handed in as a wire row; studio-core carries only the mechanism.\n *\n * The CLI's `vos plan` is the fuller verb (reuse, style, motion, a still);\n * this is its fresh branch, byte-identical to it.\n */\nimport {\n DEFAULT_FRAME_STYLE,\n planAutoSpeed,\n planAutoZoom,\n projectFromArtifact,\n withBackdrop,\n} from '@vosjs/studio-core'\nimport type {\n Backdrop,\n ProjectDoc,\n RecordingArtifact,\n} from '@vosjs/studio-core'\n\nexport interface FreshPlanOptions {\n /** The backdrop the document opens on; absent keeps the bare frame. */\n backdrop?: Backdrop | null\n /** A digest's activity bins, when one exists (the speed planner's witness). */\n activity?: readonly number[] | null\n}\n\nexport function planFreshTake(\n artifact: RecordingArtifact,\n recordingName: string,\n opts: FreshPlanOptions = {},\n): ProjectDoc {\n const ingest = opts.backdrop\n ? { frame: withBackdrop(DEFAULT_FRAME_STYLE, opts.backdrop) }\n : {}\n const doc = projectFromArtifact(artifact, recordingName, ingest).doc\n doc.zoom = planAutoZoom(doc.source.cursor, {\n width: doc.source.meta.width,\n height: doc.source.meta.height,\n duration: doc.source.meta.durationMs / 1000,\n style: doc.zoomStyle,\n params: doc.zoomParams,\n })\n doc.speed = planAutoSpeed(doc.source.cursor, {\n durationMs: doc.source.meta.durationMs,\n params: doc.speedParams,\n activity: opts.activity,\n })\n return doc\n}\n"],"mappings":"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","names":["askedMs","VERBS"]}
|
|
1
|
+
{"version":3,"sources":["../../src/record/driver.ts","../../src/record/cap.ts","../../src/record/pace.ts","../../src/record/wall.ts","../../src/record/exposure.ts","../../src/record/setup.ts","../../src/record/recorder.ts","../../src/record/actions.ts","../../src/record/encodePage.ts","../../src/record/plan.ts"],"sourcesContent":["/**\n * The recorder's driver: what it needs from a browser, as a STRUCTURAL\n * contract. Playwright's own `Browser`/`BrowserContext`/`Page` satisfy it\n * unchanged, and so does `@cloudflare/playwright`'s (the same client\n * surface over Browser Rendering), so the CLI hands in a launched Node\n * browser and the fleet hands in a connected one, and the recorder never\n * imports either package. Only the members the recorder calls are named\n * here; a driver that offers more is fine, one that offers less fails to\n * type.\n *\n * Frames leave through a `FrameSink`, never through a filesystem: the CLI's\n * sink writes a take directory, the fleet's puts each JPEG to R2.\n */\n\nexport interface RecorderRect {\n x: number\n y: number\n width: number\n height: number\n}\n\nexport interface RecorderLocator {\n waitFor(opts: { state: 'visible'; timeout: number }): Promise<unknown>\n scrollIntoViewIfNeeded(): Promise<unknown>\n boundingBox(): Promise<RecorderRect | null>\n click(): Promise<unknown>\n fill(value: string): Promise<unknown>\n}\n\nexport interface RecorderLocatorRoot {\n first(): RecorderLocator\n}\n\nexport interface RecorderResponse {\n status(): number\n}\n\nexport interface RecorderConsoleMessage {\n type(): string\n text(): string\n}\n\nexport interface RecorderPage {\n goto(\n url: string,\n opts: { waitUntil: 'networkidle'; timeout: number },\n ): Promise<RecorderResponse | null>\n url(): string\n title(): Promise<string>\n // Playwright's evaluate takes a function or an expression string; the\n // recorder hands it strings (the probes) and small functions.\n evaluate<T>(pageFunction: string | (() => T)): Promise<T>\n on(event: 'console', cb: (m: RecorderConsoleMessage) => void): unknown\n locator(selector: string): RecorderLocatorRoot\n mouse: {\n move(x: number, y: number): Promise<void>\n down(): Promise<void>\n up(): Promise<void>\n wheel(dx: number, dy: number): Promise<void>\n }\n keyboard: {\n type(text: string): Promise<void>\n press(key: string): Promise<void>\n }\n}\n\nexport interface RecorderCdp {\n on(\n event: 'Page.screencastFrame',\n cb: (ev: {\n data: string\n sessionId: number\n metadata: { timestamp?: number }\n }) => void,\n ): unknown\n send(method: string, params?: Record<string, unknown>): Promise<unknown>\n}\n\nexport interface RecorderContext {\n newPage(): Promise<RecorderPage>\n addInitScript(script: string): Promise<unknown>\n setExtraHTTPHeaders(headers: Record<string, string>): Promise<unknown>\n // The page handed back is the one `newPage()` answered; typed loosely so\n // Playwright's `Page | Frame` parameter satisfies the contract.\n newCDPSession(page: unknown): Promise<RecorderCdp>\n close(): Promise<void>\n}\n\nexport interface RecorderBrowser {\n newContext(opts: {\n viewport: { width: number; height: number }\n deviceScaleFactor: number\n storageState?: string\n extraHTTPHeaders?: Record<string, string>\n }): Promise<RecorderContext>\n}\n\n/**\n * Where the screencast's frames go. `frame` is called in order with the\n * JPEG bytes; the recorder keeps the index (`file`, `tMs`) itself. The CLI\n * writes `frames/<file>`; the fleet puts to R2. A sink may be async and\n * slow: the recorder never awaits it on the screencast's own thread, so a\n * slow store costs no frames, only memory until it catches up.\n */\nexport interface FrameSink {\n frame(file: string, bytes: Uint8Array): Promise<void> | void\n}\n\n/** The clock and the pauses, injectable so a rehearsal runs fast. */\nexport interface RecorderClock {\n now(): number\n sleep(ms: number): Promise<void>\n}\n\nexport const realSleep = (ms: number): Promise<void> =>\n new Promise<void>((r) => setTimeout(r, ms))\n","/**\n * The recording cap as the recorder applies it, pure. WHAT the cap is\n * (the hosted plan's number, read from `GET /api/limits`) is a vos.so fact\n * and stays in the CLI's platform client; the recorder only knows a number\n * of seconds and stops there.\n */\n\n/** True once the take has reached the cap — the recorder stops driving steps. */\nexport function capReached(elapsedMs: number, capSeconds: number): boolean {\n return elapsedMs >= capSeconds * 1000\n}\n\n/** A wait step never sleeps past the cap. */\nexport function clampWait(\n waitMs: number,\n elapsedMs: number,\n capSeconds: number,\n): number {\n return Math.max(0, Math.min(waitMs, capSeconds * 1000 - elapsedMs))\n}\n\n/** `30 min`, `1 h 30 min`, `45 s` — the duration cap in words. */\nexport function formatDurationCap(seconds: number): string {\n if (seconds < 60) return `${seconds} s`\n const m = Math.round(seconds / 60)\n if (m < 60) return `${m} min`\n const h = Math.floor(m / 60)\n const rest = m % 60\n return rest ? `${h} h ${rest} min` : `${h} h`\n}\n\n/** The one line printed when the cap stopped the take. */\nexport function cappedLine(capSeconds: number): string {\n return `stopped at ${formatDurationCap(capSeconds)} (--max-duration ${capSeconds}); the remaining steps did not run`\n}\n","/**\n * The recorder's pace — the pure parts.\n *\n * A take of a scripted flow should run as long as the script asks, plus the\n * gestures a person makes between the asks (the pointer's travel, the press,\n * the settle after it) and no more. Measured before this module (§4.5 of\n * the utility-clip retrospective), hovers ran 2× their ask, drags 4.8× and\n * a 25 s script recorded 42 s, for two reasons the numbers here answer:\n *\n * - The motion loops counted STEPS (one per 16 ms of the asked duration)\n * and awaited a `mouse.move` round trip per step, so a page that\n * re-renders per move (a slider) stretched every gesture by the round\n * trip. A gesture is now driven by the CLOCK: its position is a function\n * of the elapsed time, and it ends when the asked duration has elapsed,\n * however many samples the page allowed.\n * - The settles after a gesture were fixed sleeps (500 ms after a click,\n * 250 after every selector lookup). A settle is DATA: `ms` on the step\n * overrides a small default, and the script's own `wait` steps carry\n * the intended pauses.\n */\n\n/** Pointer travel: ms per CSS px, floor and ceiling — a person's hand. */\nexport const POINTER_MS_PER_PX = 0.7\nexport const POINTER_MIN_MS = 250\nexport const POINTER_MAX_MS = 800\n/** The sample cadence a motion loop aims for. */\nexport const MOTION_TICK_MS = 16\n\n/** The settle after a gesture when the step names none, by verb. */\nexport const SETTLE_MS = {\n click: 150,\n type: 150,\n scroll: 200,\n drag: 80,\n} as const\n\n/** The press: the pause before the button goes down, and the hold. */\nexport const PRESS_LEAD_MS = 80\nexport const PRESS_HOLD_MS = 70\n/** The pause between a selector lookup that scrolled the page and the move. */\nexport const SCROLL_SETTLE_MS = 120\n\n/**\n * A scroll travels by the clock too, at a wheel's pace: 0.9 ms per px,\n * floored so a short flick still reads as motion and capped so a long page\n * does not crawl. Before this the recorder sent 120 px wheel chunks with a\n * 40 ms sleep and left the in-between frames to the browser's own smooth\n * scroll, which a software-composited fleet browser never drew: five jumps,\n * held by the encoder, and a torn composite where the sticky nav and the\n * content were captured at different offsets.\n */\nexport const SCROLL_MS_PER_PX = 0.9\nexport const SCROLL_MIN_MS = 240\nexport const SCROLL_MAX_MS = 1000\n/** The hold after the last step, so the take does not cut on a press. */\nexport const TRAILING_HOLD_MS = 400\n\n/** How long the pointer takes to travel `dist` CSS px. */\nexport function scrollTravelMs(px: number): number {\n return Math.min(\n SCROLL_MAX_MS,\n Math.max(SCROLL_MIN_MS, Math.round(Math.abs(px) * SCROLL_MS_PER_PX)),\n )\n}\n\nexport function pointerTravelMs(dist: number): number {\n return Math.min(\n POINTER_MAX_MS,\n Math.max(POINTER_MIN_MS, Math.round(dist * POINTER_MS_PER_PX)),\n )\n}\n\n/**\n * A click's `ms` is READING time, held from the page's last visual change,\n * and the recorder pays the settle itself: the same `ms` was read on one\n * run and dead on the next because the hold started at the press and the\n * page's settle varied run to run (0.3 s to 0.7 s on the same step).\n *\n * The settle is watched on the screencast, which emits only on change:\n * after the press, a change that arrives within RESPONSE_MS says the page\n * is answering, and it has settled once QUIET_MS pass with no new frame.\n * No change within RESPONSE_MS means the page did not change (a click that\n * only moves state) and the settle is over. SETTLE_CAP_MS bounds a page\n * that never stops changing (a playing preview), which is a page nothing\n * is dead on, so the hold simply starts.\n */\nexport const RESPONSE_MS = 400\nexport const QUIET_MS = 250\nexport const SETTLE_CAP_MS = 1200\n\n/**\n * Is the page settled? Pure: the recorder feeds it what it knows at each\n * tick since the press.\n */\nexport function settleVerdict(\n t: {\n /** ms since the press. */\n sincePress: number\n /** ms since the last screencast frame, or null when none arrived since the press. */\n sinceChange: number | null\n },\n c = { response: RESPONSE_MS, quiet: QUIET_MS, cap: SETTLE_CAP_MS },\n): 'wait' | 'settled' {\n if (t.sincePress >= c.cap) return 'settled'\n if (t.sinceChange === null)\n return t.sincePress >= c.response ? 'settled' : 'wait'\n return t.sinceChange >= c.quiet ? 'settled' : 'wait'\n}\n\n/**\n * What is left of a hold once the settle is detected: the hold is measured\n * from the last change, so the quiet already spent counts toward it.\n */\nexport function holdLeftMs(holdMs: number, sinceChange: number | null): number {\n return Math.max(0, holdMs - (sinceChange ?? 0))\n}\n\n/** The settle after a step: its own `ms` when it names one, else the verb's. */\nexport function settleMs(step: { do: string; ms?: number }): number {\n if (typeof step.ms === 'number' && step.ms >= 0) return step.ms\n const d = step.do as keyof typeof SETTLE_MS\n return SETTLE_MS[d] ?? 0\n}\n\nexport const easeInOutCubic = (u: number): number =>\n u < 0.5 ? 4 * u * u * u : 1 - Math.pow(-2 * u + 2, 3) / 2\n\n/**\n * Drive a motion by the clock: `at(u)` is called with the eased progress\n * for each sample, and the loop ends when `dur` ms have elapsed, with the\n * last call at u = 1 exactly. Between samples it sleeps to the next tick\n * only when the sample came back early; a slow sample (a page busy\n * re-rendering) is followed at once, so the gesture keeps its length and\n * loses samples, never the other way round.\n */\nexport async function clockMotion(\n dur: number,\n at: (u: number) => Promise<void>,\n clock: { now: () => number; sleep: (ms: number) => Promise<void> },\n): Promise<number> {\n const start = clock.now()\n let samples = 0\n for (;;) {\n const elapsed = clock.now() - start\n if (elapsed >= dur) break\n await at(easeInOutCubic(Math.min(1, elapsed / dur)))\n samples++\n const next =\n start + Math.ceil((clock.now() - start) / MOTION_TICK_MS) * MOTION_TICK_MS\n const wait = Math.min(next, start + dur) - clock.now()\n if (wait > 0) await clock.sleep(wait)\n }\n await at(1)\n return samples + 1\n}\n\n/**\n * Typing paced by the clock: the i-th character is due at `start + i·delay`;\n * after each keystroke the loop sleeps to the next due time only if the\n * keystroke came back early. A slow field (an editor re-rendering per key)\n * types as fast as it can and says so in the pace report.\n */\nexport async function clockTyping(\n chars: readonly string[],\n delay: number,\n type: (ch: string, index: number) => Promise<void>,\n clock: { now: () => number; sleep: (ms: number) => Promise<void> },\n): Promise<void> {\n const start = clock.now()\n for (let i = 0; i < chars.length; i++) {\n await type(chars[i], i)\n const due = start + (i + 1) * delay\n const wait = due - clock.now()\n if (wait > 0 && i < chars.length - 1) await clock.sleep(wait)\n }\n}\n\n/** One step's pace: what the script asked for it and what it took. */\nexport interface StepPace {\n step: number\n do: string\n /** The script's own ask: a wait's ms, a hover's dwell, a drag's ms, typing's chars × delay, a click's read after the settle; 0 for a scroll. */\n askedMs: number\n /** The gesture the recorder adds by design: pointer travel, the press, the settle. */\n gestureMs: number\n wallMs: number\n}\n\nexport interface PaceReport {\n askedMs: number\n gestureMs: number\n wallMs: number\n /** Wall time neither asked nor a gesture: what the page and the round trips cost. */\n overheadMs: number\n overheadPct: number\n /** The steps whose wall ran past their ask plus gesture by more than a third. */\n slow: { step: number; do: string; askedMs: number; wallMs: number }[]\n}\n\n/** What a script asks of a step, in ms: the part of its wall time that is the author's. */\nexport function askedMs(step: {\n do: string\n ms?: number\n text?: string\n delayMs?: number\n}): number {\n switch (step.do) {\n case 'wait':\n return step.ms ?? 0\n // A click's ms is the read after the page settled: the author's.\n case 'click':\n return step.ms ?? SETTLE_MS.click\n case 'hover':\n return step.ms ?? 700\n case 'drag':\n return step.ms ?? 700\n case 'type':\n return (step.text?.length ?? 0) * (step.delayMs ?? 40)\n default:\n return 0\n }\n}\n\nexport function paceReport(steps: readonly StepPace[]): PaceReport {\n const askedMs = steps.reduce((a, s) => a + s.askedMs, 0)\n const gestureMs = steps.reduce((a, s) => a + s.gestureMs, 0)\n const wallMs = steps.reduce((a, s) => a + s.wallMs, 0)\n const overheadMs = Math.max(0, wallMs - askedMs - gestureMs)\n const slow = steps\n .filter((s) => s.wallMs > (s.askedMs + s.gestureMs) * 1.34 + 60)\n .map((s) => ({\n step: s.step,\n do: s.do,\n askedMs: s.askedMs,\n wallMs: s.wallMs,\n }))\n return {\n askedMs,\n gestureMs,\n wallMs,\n overheadMs,\n overheadPct: wallMs > 0 ? Math.round((overheadMs / wallMs) * 100) : 0,\n slow,\n }\n}\n\n/** The pace in one line for the log. */\nexport function paceLine(r: PaceReport): string {\n const s = (ms: number) => `${(ms / 1000).toFixed(1)} s`\n const slow = r.slow.length\n ? `; slow: ${r.slow.map((x) => `#${x.step} ${x.do} ${s(x.wallMs)} for ${s(x.askedMs)} asked`).join(', ')}`\n : ''\n return `pace: the script asked ${s(r.askedMs)}, the gestures added ${s(r.gestureMs)}, the take ran ${s(r.wallMs)} (${r.overheadPct} % overhead${slow})`\n}\n\n/**\n * DEAD TIME, the smoothness fact that replaces the freeze share as the\n * warning. A still frame is not a defect: a page the viewer is reading is\n * content, and a workspace tour is mostly still. What is dead is a still\n * frame under a PARKED cursor past the beat it takes to read what changed:\n * after that the picture, the pointer and the camera all hold and nothing\n * is being said. So the derivation reads, per step, when the page settled\n * after the step's act, how long the settled frame was held, and whether\n * the cursor moved in that hold; a hold past the reading beat is dead.\n *\n * The beat is longer after a page changed (a navigation shows a whole new\n * page to take in) than after a control changed (one thing to see).\n */\nexport const READ_BEAT_MS = { page: 1000, control: 600 } as const\n/** A dead stretch this long is a warning on its own, whatever the share. */\nexport const DEAD_STRETCH_WARN_MS = 1500\n/** The share of the take past which dead time is a warning. */\nexport const DEAD_SHARE_WARN_PCT = 20\n\nexport interface DeadStep {\n step: number\n id?: string\n do: string\n /** ms from the step's act (its press, else its start) to the last frame change in the step. */\n settledMs: number\n /** ms the settled frame was held before the step ended. */\n heldMs: number\n /** the hold past the reading beat, while the cursor was parked; 0 when the hold was read or the pointer moved. */\n deadMs: number\n}\n\nexport interface DeadReport {\n ms: number\n pct: number\n /** the steps that carried any dead time, in order. */\n steps: DeadStep[]\n /** the longest single dead hold. */\n longestMs: number\n}\n\nexport function deadTime(\n steps: readonly {\n step: number\n id?: string\n do: string\n tStart: number\n tEnd: number\n navigated?: boolean\n }[],\n frames: readonly { tMs: number }[],\n events: readonly { t: number; type: string }[],\n durationMs: number,\n): DeadReport {\n const out: DeadStep[] = []\n let ms = 0\n let longestMs = 0\n for (const s of steps) {\n const t0 = s.tStart * 1000\n const t1 = s.tEnd * 1000\n if (!(t1 > t0)) continue\n // The act: the step's first press, else its start (a wait, a move).\n const press = events.find(\n (e) => e.type === 'down' && e.t >= t0 && e.t <= t1,\n )\n const act = press ? press.t : t0\n // The last visual change inside the step, after the act.\n let last = act\n for (const f of frames) {\n if (f.tMs > act && f.tMs <= t1) last = f.tMs\n }\n const settledMs = Math.round(last - act)\n const heldMs = Math.round(t1 - last)\n // Parked: no pointer motion while the settled frame was held.\n const moved = events.some(\n (e) =>\n (e.type === 'move' || e.type === 'scroll') && e.t > last && e.t <= t1,\n )\n // The opening step shows a page the viewer has not seen: a page beat.\n const beat =\n s.navigated || s.step === 0 ? READ_BEAT_MS.page : READ_BEAT_MS.control\n const deadMs = moved ? 0 : Math.max(0, heldMs - beat)\n if (deadMs > 0) {\n out.push({\n step: s.step,\n ...(s.id ? { id: s.id } : {}),\n do: s.do,\n settledMs,\n heldMs,\n deadMs,\n })\n ms += deadMs\n longestMs = Math.max(longestMs, deadMs)\n }\n }\n return {\n ms,\n pct: durationMs > 0 ? Math.round((ms / durationMs) * 100) : 0,\n steps: out,\n longestMs,\n }\n}\n\n/** Whether a dead report earns a warning: the share, or one long hold. */\nexport function deadWarns(r: DeadReport): boolean {\n return r.pct >= DEAD_SHARE_WARN_PCT || r.longestMs >= DEAD_STRETCH_WARN_MS\n}\n\n/** The dead time in words, naming the steps and what to cut. */\nexport function deadLine(r: DeadReport): string {\n const s = (ms: number) => `${(ms / 1000).toFixed(1)} s`\n if (!r.steps.length) return 'dead time: none'\n const parts = r.steps.map(\n (d) =>\n `#${d.step}${d.id ? ` (${d.id})` : ''} ${d.do} held ${s(d.heldMs)} after it settled, ${s(d.deadMs)} past the beat`,\n )\n return `dead time: ${s(r.ms)} (${r.pct} %), a still frame under a parked cursor past the reading beat: ${parts.join('; ')}. Cut those steps' ms; a hold is what it takes to read what changed.`\n}\n","/**\n * The wall check: did the recorder land where it was asked, or in front of\n * a sign-in? A take of a product behind a login, recorded without a session,\n * used to succeed: the footage was the login page (or wherever the site\n * sends a stranger), and the only symptom was a skipped selector. This says\n * it in words, before a frame is captured and before a re-record clears the\n * footage it would have replaced.\n *\n * Pure: the recorder gathers the evidence, this decides. A heuristic is\n * acceptable because both failure directions are cheap: a false refusal\n * costs one flag (`--allow-wall`), a false pass costs what every such take\n * cost before.\n */\n\n/** What the recorder saw once the first navigation settled. */\nexport interface Arrival {\n askedUrl: string\n landedUrl: string\n /** HTTP status of the main document, when the navigation produced one. */\n status?: number\n /** `input[type=password]` fields that are visible on the page. */\n passwordFields: number\n /** A password field marked `autocomplete=\"new-password\"` (a settings or sign-up form). */\n newPasswordField: boolean\n /** A visible one-time-code field (`autocomplete=\"one-time-code\"`). */\n oneTimeCodeField: boolean\n}\n\nexport type WallKind =\n /** the asked URL answered 401 or 403 */\n | 'status'\n /** landed on an identity provider's host */\n | 'idp'\n /** landed on a sign-in form or a sign-in path */\n | 'signin'\n /** sent somewhere else, with no sign-in in sight (a site that shows strangers a public page) */\n | 'redirect'\n\nexport interface WallVerdict {\n kind: WallKind\n /**\n * `hard` is refused always; `soft` is refused under `--strict` and said as\n * a warning otherwise, because a redirect alone is not proof of a wall.\n */\n level: 'hard' | 'soft'\n /** origin + path, never the query or hash (they can carry tokens). */\n asked: string\n landed: string\n message: string\n}\n\n/** Hosts that only ever mean \"sign in\". Suffix match on the label boundary. */\nconst IDP_HOSTS = [\n 'accounts.google.com',\n 'login.microsoftonline.com',\n 'login.live.com',\n 'appleid.apple.com',\n 'auth0.com',\n 'okta.com',\n 'onelogin.com',\n 'clerk.accounts.dev',\n 'accounts.dev',\n 'b2clogin.com',\n 'amazoncognito.com',\n 'workos.com',\n 'stytch.com',\n]\n\n/** Path segments that name a sign-in surface. Whole segments, never substrings. */\nconst SIGNIN_SEGMENTS = new Set([\n 'login',\n 'log-in',\n 'signin',\n 'sign-in',\n 'sign_in',\n 'sso',\n 'auth',\n 'authenticate',\n 'authorize',\n 'oauth',\n 'oauth2',\n 'saml',\n])\n\nfunction parse(url: string): URL | null {\n try {\n return new URL(url)\n } catch {\n return null\n }\n}\n\nconst bareHost = (h: string) => h.replace(/^www\\./, '').toLowerCase()\nconst trimSlash = (p: string) => (p.length > 1 ? p.replace(/\\/+$/, '') : p)\nconst place = (u: URL) => `${u.origin}${trimSlash(u.pathname)}`\n\nexport function isIdpHost(host: string): boolean {\n const h = host.toLowerCase()\n if (h.startsWith('clerk.')) return true\n return IDP_HOSTS.some((idp) => h === idp || h.endsWith(`.${idp}`))\n}\n\nexport function isSignInPath(pathname: string): boolean {\n const segs = pathname.toLowerCase().split('/').filter(Boolean)\n return segs.some((s) => SIGNIN_SEGMENTS.has(s))\n}\n\n/**\n * Sent AWAY, as opposed to sent deeper. `/` → `/en`, `/docs` → `/docs/start`\n * and `/pricing` → `/en/pricing` are a site arranging itself; `/dashboard` →\n * `/gallery` is a site declining to show the page. `www.` and the apex are\n * one host.\n */\nexport function redirectedAway(asked: URL, landed: URL): boolean {\n if (bareHost(asked.hostname) !== bareHost(landed.hostname)) return true\n const a = trimSlash(asked.pathname).toLowerCase()\n const l = trimSlash(landed.pathname).toLowerCase()\n if (a === l || a === '/') return false\n if (l.startsWith(`${a}/`)) return false\n if (l.endsWith(a)) return false\n return true\n}\n\nconst LADDER =\n 'This is a missing or expired session, not a script bug. Ways past, cheapest first: mint a session from the test auth the project already has; script the sign-in off camera and save the storage state; or have a person sign in once (npx playwright open --channel chrome --save-storage=<file> <url>). Then pass --storage-state <file>. The ladder: https://vos.so/llms-full.txt, \"Sessions\". A take OF this page is --allow-wall.'\n\nexport function wallVerdict(a: Arrival): WallVerdict | null {\n const asked = parse(a.askedUrl)\n const landed = parse(a.landedUrl)\n // A file: or data: take, or a URL we cannot read, has no wall to speak of.\n if (!asked || !landed) return null\n if (!/^https?:$/.test(asked.protocol)) return null\n\n const away = redirectedAway(asked, landed)\n const where = { asked: place(asked), landed: place(landed) }\n const host = bareHost(asked.hostname)\n\n if (a.status === 401 || a.status === 403) {\n return {\n kind: 'status',\n level: 'hard',\n ...where,\n message: `asked for ${asked.pathname}, answered ${a.status}: no session for ${host}. ${LADDER}`,\n }\n }\n\n // Someone who ASKED for the sign-in page and got it wants that take.\n const askedForSignIn =\n isSignInPath(asked.pathname) || isIdpHost(asked.hostname)\n if (askedForSignIn && !away) return null\n\n if (away && isIdpHost(landed.hostname) && !isIdpHost(asked.hostname)) {\n return {\n kind: 'idp',\n level: 'hard',\n ...where,\n message: `asked for ${asked.pathname}, landed on ${landed.hostname}: no session for ${host}. ${LADDER}`,\n }\n }\n\n // One password field that is not a new-password field is a sign-in form.\n // Two, or a new-password one, is a settings page or a sign-up.\n const signInForm =\n (a.passwordFields === 1 && !a.newPasswordField) || a.oneTimeCodeField\n if (\n (away && isSignInPath(landed.pathname)) ||\n (signInForm && !askedForSignIn)\n ) {\n const seen = away\n ? `landed on ${landed.pathname}`\n : 'the page is a sign-in form'\n return {\n kind: 'signin',\n level: 'hard',\n ...where,\n message: `asked for ${asked.pathname}, ${seen}: no session for ${host}. ${LADDER}`,\n }\n }\n\n if (away) {\n return {\n kind: 'redirect',\n level: 'soft',\n ...where,\n message: `asked for ${asked.pathname}, landed on ${landed.pathname}: the site sent the recorder elsewhere, which is what a page behind a login does to a stranger. If that is the page you meant, record it by its own URL. ${LADDER}`,\n }\n }\n return null\n}\n\n/** A take refused at the wall. Carries the verdict for `--json`. */\nexport class WallError extends Error {\n constructor(public verdict: WallVerdict) {\n super(verdict.message)\n this.name = 'WallError'\n }\n}\n\n/**\n * Evidence gathered in the page. A STRING, never a function: a serialized\n * function picks up the bundler's `__name` helper and dies in the page.\n */\nexport const WALL_PROBE = `(() => {\n const shown = (el) => {\n const r = el.getBoundingClientRect()\n if (r.width < 2 || r.height < 2) return false\n const s = getComputedStyle(el)\n return s.visibility !== 'hidden' && s.display !== 'none'\n }\n const pw = [...document.querySelectorAll('input[type=\"password\"]')].filter(shown)\n const otp = [...document.querySelectorAll('input[autocomplete=\"one-time-code\"]')].filter(shown)\n return {\n passwordFields: pw.length,\n newPasswordField: pw.some((el) => (el.getAttribute('autocomplete') || '').includes('new-password')),\n oneTimeCodeField: otp.length > 0,\n }\n})()`\n","/**\n * What a signed-in take SHOWS. Getting past the login is half the problem;\n * the other half is that the account's own data is now in the frame: an\n * email address in the header, a key on a settings page, a card number.\n * Asking nicely does not hold this line (a person asked to sign in with a\n * demo account signs in as themselves, because it is the account they have),\n * so the recorder looks.\n *\n * Two pieces, both run IN the page:\n *\n * - the EXPOSURE scan reads the text that is visible in the viewport after\n * each step and reports the KIND of thing it saw and where. Never the\n * string itself: a report that quotes the secret is a second leak.\n * - a MASK hides a selector before the first frame is captured and keeps it\n * hidden across navigations and re-renders, so the real value is never in\n * a frame, never in the recording, never pushed. `as: 'text'` swaps the\n * words (a product reads better than a redaction); `as: 'blur'` blurs.\n *\n * The matchers are ONE plain-JavaScript source string, evaluated in the page\n * and, by the tests, in Node: a function serialized out of a bundle picks up\n * the bundler's `__name` helper and dies in the page, and two copies of a\n * regex drift.\n */\nimport type { RecordingMeta } from '@vosjs/studio-core'\n\nexport type ExposureKind = 'email' | 'key' | 'card' | 'card-tail'\nexport type Exposure = NonNullable<RecordingMeta['exposures']>[number]\nexport type MaskReport = NonNullable<RecordingMeta['masks']>[number]\n\nexport interface MaskRule {\n selector: string\n /** `blur` (default) blurs the element; `text` swaps its words for `text`. */\n as?: 'blur' | 'text'\n text?: string\n}\n\n/**\n * `kindsIn(text)` → the kinds of sensitive-looking strings in `text`.\n *\n * - email: any address, EXCEPT one on a domain that can reach no one\n * (example.com/.org/.net, and the reserved .test/.example/.invalid/\n * .localhost TLDs), which is what demo data should use.\n * - key: the prefixes of keys people actually paste into dashboards, and a\n * JWT's three base64url parts.\n * - card: 13 to 19 digits that pass the Luhn check, so an order number or a\n * phone number is not a card.\n * - card-tail: a masked number's visible tail (•••• 4242).\n */\nexport const EXPOSURE_MATCHERS_SRC = `(() => {\n const EMAIL = /[A-Za-z0-9._%+-]+@((?:[A-Za-z0-9-]+\\\\.)+[A-Za-z]{2,})/g\n const SAFE_HOST = /(^|\\\\.)example\\\\.(com|org|net)$|\\\\.(test|example|invalid|localhost)$/i\n const KEY = new RegExp([\n '\\\\\\\\b[srp]k_(?:live|test)_[A-Za-z0-9]{8,}',\n '\\\\\\\\bgh[pousr]_[A-Za-z0-9]{20,}',\n '\\\\\\\\bgithub_pat_[A-Za-z0-9_]{20,}',\n '\\\\\\\\bxox[abprs]-[A-Za-z0-9-]{10,}',\n '\\\\\\\\bAKIA[0-9A-Z]{16}\\\\\\\\b',\n '\\\\\\\\bAIza[0-9A-Za-z_-]{30,}',\n '\\\\\\\\bvos_(?:sk|rg|ho)_[A-Za-z0-9]{8,}',\n '\\\\\\\\beyJ[A-Za-z0-9_-]{8,}\\\\\\\\.eyJ[A-Za-z0-9_-]{8,}\\\\\\\\.[A-Za-z0-9_-]{8,}',\n ].join('|'))\n const CARD = /(?:^|[^0-9])((?:[0-9][ -]?){12,18}[0-9])(?![0-9])/g\n const TAIL = /[\\\\u2022\\\\u00b7*xX]{2,}[ -]?[0-9]{4}(?![0-9])/\n const luhn = (digits) => {\n let sum = 0, alt = false\n for (let i = digits.length - 1; i >= 0; i--) {\n let d = digits.charCodeAt(i) - 48\n if (alt) { d *= 2; if (d > 9) d -= 9 }\n sum += d; alt = !alt\n }\n return sum % 10 === 0\n }\n const kindsIn = (text) => {\n const kinds = []\n if (!text || text.length < 6) return kinds\n EMAIL.lastIndex = 0\n for (let m; (m = EMAIL.exec(text)); ) {\n if (!SAFE_HOST.test(m[1])) { kinds.push('email'); break }\n }\n if (KEY.test(text)) kinds.push('key')\n CARD.lastIndex = 0\n for (let m; (m = CARD.exec(text)); ) {\n const digits = m[1].replace(/[ -]/g, '')\n if (digits.length >= 13 && digits.length <= 19 && !/^0+$/.test(digits) && luhn(digits)) { kinds.push('card'); break }\n }\n if (TAIL.test(text)) kinds.push('card-tail')\n return kinds\n }\n return { kindsIn }\n})()`\n\n/**\n * The scan: visible text in the viewport, plus the values of text inputs.\n * Skips anything inside a masked element, password fields, and text the eye\n * cannot see. Reports kind + a selector-ish name for the element + its rect.\n */\nexport const EXPOSURE_PROBE = `(() => {\n const { kindsIn } = ${EXPOSURE_MATCHERS_SRC}\n const vw = innerWidth, vh = innerHeight\n // A selector that reaches THIS element and no other, so it can be pasted\n // into \"mask\" as it is: climb until the path is unique, and say which\n // sibling wherever a tag-and-class alone would match several.\n const segment = (el) => {\n const tid = el.getAttribute('data-testid')\n if (tid) return el.tagName.toLowerCase() + '[data-testid=\"' + tid + '\"]'\n if (el.id && /^[A-Za-z_][\\\\w-]*$/.test(el.id)) return el.tagName.toLowerCase() + '#' + el.id\n const cls = [...el.classList].filter((c) => /^[A-Za-z_-][\\\\w-]*$/.test(c)).slice(0, 2)\n let seg = el.tagName.toLowerCase() + (cls.length ? '.' + cls.join('.') : '')\n const p = el.parentElement\n if (p && [...p.children].filter((c) => c.matches(seg)).length > 1)\n seg += ':nth-child(' + ([...p.children].indexOf(el) + 1) + ')'\n return seg\n }\n const name = (el) => {\n const parts = []\n for (let cur = el; cur && cur !== document.body && cur !== document.documentElement; cur = cur.parentElement) {\n parts.unshift(segment(cur))\n let n = 2\n try { n = document.querySelectorAll(parts.join(' > ')).length } catch {}\n // a bare tag is unique HERE and nowhere else: give it its parent too\n const bare = parts.length === 1 && !/[#.\\\\[]/.test(parts[0])\n if ((n === 1 && !bare) || parts.length >= 5) break\n }\n return parts.join(' > ')\n }\n const seen = (el) => {\n if (el.closest('[data-vos-masked]')) return null\n const r = el.getBoundingClientRect()\n if (r.width < 2 || r.height < 2) return null\n if (r.bottom <= 0 || r.right <= 0 || r.top >= vh || r.left >= vw) return null\n const s = getComputedStyle(el)\n if (s.visibility === 'hidden' || s.display === 'none' || +s.opacity === 0) return null\n return r\n }\n const out = [], keys = new Set()\n const add = (el, text) => {\n const kinds = kindsIn(text)\n if (!kinds.length) return\n const r = seen(el)\n if (!r) return\n const selector = name(el)\n for (const kind of kinds) {\n const k = kind + '|' + selector\n if (keys.has(k) || out.length >= 40) continue\n keys.add(k)\n out.push({ kind, selector, rect: { x: Math.round(r.left), y: Math.round(r.top), w: Math.round(r.width), h: Math.round(r.height) } })\n }\n }\n const walker = document.createTreeWalker(document.body || document.documentElement, NodeFilter.SHOW_TEXT)\n for (let n; (n = walker.nextNode()); ) {\n const el = n.parentElement\n if (!el || /^(SCRIPT|STYLE|NOSCRIPT|TEMPLATE)$/.test(el.tagName)) continue\n add(el, n.nodeValue)\n }\n for (const el of document.querySelectorAll('input, textarea')) {\n if (/^(password|hidden|file|checkbox|radio)$/.test(el.type)) continue\n add(el, el.value)\n }\n const hits = {}\n for (const [sel, n] of Object.entries(window.__vosMaskHits || {})) hits[sel] = n\n return { exposures: out, maskHits: hits }\n})()`\n\n/**\n * The mask, as an init script: it runs at document start on EVERY navigation,\n * so a masked value is hidden before the page's first paint, and a\n * MutationObserver re-applies it after a client-side re-render. A form\n * control is blurred even when `as: 'text'` was asked for: writing into an\n * input would change what the app submits.\n */\nexport function maskInitScript(masks: MaskRule[]): string {\n const rules = masks.map((m) => ({\n selector: m.selector,\n as: m.as === 'text' ? 'text' : 'blur',\n text: m.text ?? '',\n }))\n return `(() => {\n const RULES = ${JSON.stringify(rules)}\n const hits = (window.__vosMaskHits = window.__vosMaskHits || {})\n for (const r of RULES) hits[r.selector] = hits[r.selector] || 0\n const css = RULES.map((r) => r.selector + '[data-vos-masked=\"blur\"]{filter:blur(7px) !important;user-select:none !important}').join('\\\\n')\n const paint = () => {\n const root = document.head || document.documentElement\n if (!root || document.getElementById('__vos-mask-style')) return\n const style = document.createElement('style')\n style.id = '__vos-mask-style'\n style.textContent = css\n root.appendChild(style)\n }\n const apply = () => {\n paint()\n for (const r of RULES) {\n let found\n try { found = document.querySelectorAll(r.selector) } catch { continue }\n if (found.length > hits[r.selector]) hits[r.selector] = found.length\n for (const el of found) {\n const form = /^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName)\n const mode = r.as === 'text' && !form ? 'text' : 'blur'\n if (el.getAttribute('data-vos-masked') !== mode) el.setAttribute('data-vos-masked', mode)\n if (mode !== 'text') continue\n const texts = []\n const w = document.createTreeWalker(el, NodeFilter.SHOW_TEXT)\n for (let n; (n = w.nextNode()); ) texts.push(n)\n if (!texts.length) { if (el.textContent !== r.text) el.textContent = r.text; continue }\n texts.forEach((n, i) => { const want = i === 0 ? r.text : ''; if (n.nodeValue !== want) n.nodeValue = want })\n }\n }\n }\n new MutationObserver(apply).observe(document, { childList: true, subtree: true, characterData: true })\n document.addEventListener('DOMContentLoaded', apply)\n apply()\n})()`\n}\n\n/**\n * Accumulates scans across a take: an exposure is reported ONCE, at the\n * first step it was seen, with how many scans saw it.\n */\nexport class ExposureLog {\n private byKey = new Map<string, Exposure>()\n private maskHits = new Map<string, number>()\n\n constructor(masks: MaskRule[] = []) {\n for (const m of masks) this.maskHits.set(m.selector, 0)\n }\n\n add(\n step: number,\n scan: {\n exposures?: Omit<Exposure, 'step' | 'seen'>[]\n maskHits?: Record<string, number>\n } | null,\n ): void {\n if (!scan) return\n for (const e of scan.exposures ?? []) {\n const key = `${e.kind}|${e.selector}`\n const had = this.byKey.get(key)\n if (had) had.seen += 1\n else this.byKey.set(key, { step, ...e, seen: 1 })\n }\n for (const [sel, n] of Object.entries(scan.maskHits ?? {})) {\n if (n > (this.maskHits.get(sel) ?? 0)) this.maskHits.set(sel, n)\n }\n }\n\n exposures(): Exposure[] {\n return [...this.byKey.values()]\n }\n\n masks(rules: MaskRule[]): MaskReport[] {\n return rules.map((m) => ({\n selector: m.selector,\n as: m.as === 'text' ? 'text' : 'blur',\n hits: this.maskHits.get(m.selector) ?? 0,\n }))\n }\n}\n\nconst KIND_WORDS: Record<ExposureKind, string> = {\n email: 'an email address',\n key: 'something shaped like an API key or token',\n card: 'a card number',\n 'card-tail': \"a card's last digits\",\n}\n\n/** One exposure, in words. `step` -1 is the page as it opened. */\nexport function exposureLine(e: Exposure): string {\n const when = e.step < 0 ? 'as the page opened' : `at step #${e.step}`\n return `${KIND_WORDS[e.kind]} in ${e.selector} (${when}, ${e.rect.x},${e.rect.y} ${e.rect.w}×${e.rect.h})`\n}\n\nexport const EXPOSURE_ADVICE =\n 'The recording shows these. Record from a demo account, or hide them before the camera rolls with \"mask\" in actions.json: { \"selector\": \"…\", \"as\": \"text\", \"text\": \"demo@acme.test\" } swaps the words, \"as\": \"blur\" blurs. Then re-record.'\n","/**\n * `setup`: the steps that run BEFORE the camera rolls. A sign-in form, a\n * cookie banner, the \"choose your editor\" modal, an onboarding tour: things\n * a take must get past and must not show. They run after the first\n * navigation and before `Page.startScreencast`, with no cursor synthesis,\n * no pace accounting and nothing written to `meta.steps`. Then the recorder\n * navigates to `url` again and the take begins where the setup left it.\n *\n * A `type` step's `text` may be `{ \"env\": \"DEMO_PASSWORD\" }`: resolved at run\n * time from the `env` the caller hands in (the shell's for the CLI, the\n * job's own map for a hosted take), never logged, never stored. The\n * guarantee is narrow and real: the secret is never in a file that travels\n * (`actions.json` is committed and pushed with the take; `meta.json` is read\n * by the digest) and never in the footage.\n */\nimport type { RecorderPage } from './driver'\n\nexport type SetupText = string | { env: string }\n\nexport type SetupStep = (\n | { do: 'wait'; ms: number }\n | { do: 'click'; selector: string; ms?: number }\n | { do: 'type'; selector: string; text: SetupText; ms?: number }\n | { do: 'press'; key: string; ms?: number }\n | { do: 'goto'; url: string }\n) & { id?: string }\n\nconst VERBS = new Set(['wait', 'click', 'type', 'press', 'goto'])\n\n/** Structural validation, mirrored on `validateActions` for `steps`. */\nexport function validateSetup(value: unknown): string[] {\n const errors: string[] = []\n if (!Array.isArray(value)) return ['setup must be an array of steps']\n value.forEach((raw, i) => {\n const at = `setup[${i}]`\n if (typeof raw !== 'object' || raw === null) {\n errors.push(`${at}: must be an object`)\n return\n }\n const s = raw as Record<string, unknown>\n if (typeof s.do !== 'string' || !VERBS.has(s.do)) {\n errors.push(`${at}: \"do\" must be one of ${[...VERBS].join(', ')}`)\n return\n }\n if ((s.do === 'click' || s.do === 'type') && typeof s.selector !== 'string')\n errors.push(`${at}: ${s.do} needs a selector`)\n if (s.do === 'wait' && typeof s.ms !== 'number')\n errors.push(`${at}: wait needs ms`)\n if (s.do === 'press' && typeof s.key !== 'string')\n errors.push(`${at}: press needs a key`)\n if (s.do === 'goto' && typeof s.url !== 'string')\n errors.push(`${at}: goto needs a url`)\n if (s.do === 'type') {\n const t = s.text\n const isEnv =\n typeof t === 'object' &&\n t !== null &&\n typeof (t as { env?: unknown }).env === 'string' &&\n Object.keys(t).length === 1\n if (typeof t !== 'string' && !isEnv)\n errors.push(\n `${at}: type needs text, a string or { \"env\": \"NAME\" } read at run time`,\n )\n if (\n typeof t === 'string' &&\n /passw|secret|token/i.test(String(s.selector)) &&\n t.length > 0\n )\n errors.push(\n `${at}: a literal value typed into ${String(s.selector)} would live in actions.json, which is committed and pushed. Use { \"env\": \"NAME\" }`,\n )\n }\n })\n return errors\n}\n\n/** The names a setup reads from its env, so a host can say what it must supply. */\nexport function setupEnvNames(steps: SetupStep[] | undefined): string[] {\n const out = new Set<string>()\n for (const s of steps ?? []) {\n if (s.do === 'type' && typeof s.text === 'object') out.add(s.text.env)\n }\n return [...out]\n}\n\n/** A `{ env }` text resolved, or thrown in words. The value is never returned to a log. */\nexport function resolveSetupText(\n t: SetupText,\n env: Record<string, string | undefined>,\n): { value: string; secret: boolean } {\n if (typeof t === 'string') return { value: t, secret: false }\n const v = env[t.env]\n if (v === undefined || v === '')\n throw new SetupEnvError(\n `setup: the environment variable ${t.env} is not set, and a type step reads it. Export it in the shell that runs vos record; it is never written to a file`,\n )\n return { value: v, secret: true }\n}\n\nexport interface SetupResult {\n ran: number\n /** what the log may say about each step: the verb and the selector, never a typed value */\n lines: string[]\n /** a selector that never appeared; the take does not start */\n failed?: { step: number; do: string; selector?: string }\n}\n\n/**\n * Run the setup on a page, with plain Playwright actions: no humanized\n * pointer, no cursor events, no frames. A selector that never appears\n * fails the setup, because a take that begins at a half-finished sign-in is\n * the wall by another name.\n */\nexport async function runSetup(\n page: RecorderPage,\n steps: SetupStep[],\n opts: {\n sleep: (ms: number) => Promise<void>\n /** Absent means no env at all: a `{ env }` text then fails in words. */\n env?: Record<string, string | undefined>\n },\n): Promise<SetupResult> {\n const env = opts.env ?? {}\n const lines: string[] = []\n for (const [i, step] of steps.entries()) {\n const settle = 'ms' in step && typeof step.ms === 'number' ? step.ms : 150\n try {\n switch (step.do) {\n case 'wait':\n await opts.sleep(step.ms)\n lines.push(`setup #${i} wait ${step.ms}ms`)\n break\n case 'goto':\n await page\n .goto(step.url, { waitUntil: 'networkidle', timeout: 45000 })\n .catch(() => null)\n lines.push(`setup #${i} goto ${step.url}`)\n break\n case 'press':\n await page.keyboard.press(step.key)\n await opts.sleep(settle)\n lines.push(`setup #${i} press ${step.key}`)\n break\n case 'click': {\n const loc = page.locator(step.selector).first()\n await loc.waitFor({ state: 'visible', timeout: 8000 })\n await loc.click()\n await opts.sleep(settle)\n lines.push(`setup #${i} click ${step.selector}`)\n break\n }\n case 'type': {\n const loc = page.locator(step.selector).first()\n await loc.waitFor({ state: 'visible', timeout: 8000 })\n const { value, secret } = resolveSetupText(step.text, env)\n await loc.fill(value)\n await opts.sleep(settle)\n // The log names the field, never what went into it.\n lines.push(\n `setup #${i} type ${secret ? `\\${${(step.text as { env: string }).env}}` : `${value.length} chars`} into ${step.selector}`,\n )\n break\n }\n }\n } catch (e) {\n if (e instanceof SetupEnvError) throw e\n return {\n ran: i,\n lines,\n failed: {\n step: i,\n do: step.do,\n ...('selector' in step ? { selector: step.selector } : {}),\n },\n }\n }\n }\n return { ran: steps.length, lines }\n}\n\n/** A `{ env }` the shell did not provide: a usage error, not a page's. */\nexport class SetupEnvError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'SetupEnvError'\n }\n}\n\n/** A setup step whose selector never appeared: the take does not start. */\nexport class SetupError extends Error {\n constructor(public failed: NonNullable<SetupResult['failed']>) {\n super(\n `setup #${failed.step} ${failed.do}${failed.selector ? ` ${failed.selector}` : ''}: the selector never appeared, so the take would begin at a half-finished setup. Nothing was recorded. Fix the setup step (rehearse with --dry-run), then record.`,\n )\n this.name = 'SetupError'\n }\n}\n\n/** `--header name=value`, repeatable: extra request headers on every request. */\nexport function parseHeaders(\n given: string[] | undefined,\n): Record<string, string> {\n const out: Record<string, string> = {}\n for (const h of given ?? []) {\n const i = h.indexOf('=')\n if (i <= 0) throw new Error(`--header wants name=value, got \"${h}\"`)\n out[h.slice(0, i).trim()] = h.slice(i + 1)\n }\n return out\n}\n","/**\n * The recorder — a browser drives the action script while:\n * - CDP Page.startScreencast collects JPEG frames (epoch-timestamped;\n * hold-last-frame gaps are filled at encode time), and\n * - every dispatched input is logged as a CursorEvent (the synthesized track:\n * exact coords, exact times, fresh element rects — no capture needed).\n *\n * The MECHANISM, host-free (AN4): the browser arrives as a structural driver\n * (a launched Node Playwright browser from the CLI, a connected\n * @cloudflare/playwright browser from the fleet), frames leave through a\n * sink, the platform and the setup env are handed in, and nothing here\n * touches a filesystem or a process. The CLI's `recordTake` wraps this with\n * a take directory; the fleet's consumer wraps it with R2. Both produce the\n * same events, meta and frame index for the same script.\n */\nimport type {\n FrameSink,\n RecorderBrowser,\n RecorderClock,\n RecorderContext,\n} from './driver'\nimport { realSleep } from './driver'\nimport { capReached, cappedLine, clampWait } from './cap'\nimport {\n PRESS_HOLD_MS,\n PRESS_LEAD_MS,\n SCROLL_SETTLE_MS,\n TRAILING_HOLD_MS,\n askedMs,\n deadLine,\n deadTime,\n deadWarns,\n holdLeftMs,\n settleVerdict,\n clockMotion,\n clockTyping,\n paceLine,\n paceReport,\n pointerTravelMs,\n scrollTravelMs,\n settleMs,\n} from './pace'\nimport type { DeadReport, PaceReport, StepPace } from './pace'\nimport type {\n CursorEvent,\n RecordingMeta,\n Rect,\n StepSpan,\n} from '@vosjs/studio-core'\nimport { WALL_PROBE } from './wall'\nimport { EXPOSURE_PROBE, ExposureLog, maskInitScript } from './exposure'\nimport { SetupError, runSetup } from './setup'\nimport type { ActionsFile } from './actions'\nimport type { Arrival, WallVerdict } from './wall'\n\nexport interface FrameRec {\n file: string\n /** ms since t0 */\n tMs: number\n}\n\nexport interface SkippedStep {\n /** index into actions.steps */\n step: number\n do: string\n selector: string\n}\n\n/** A stretch with no screencast frames — the page pixels did not change. */\nexport interface FreezeSpan {\n /** seconds into the take */\n from: number\n to: number\n ms: number\n}\n\nexport interface RecordResult {\n events: CursorEvent[]\n frames: FrameRec[]\n meta: RecordingMeta\n /** The take's pace: what the script asked, what the gestures added, what the page cost. */\n pace: PaceReport\n /** steps whose selector never became visible — the take continued without them. */\n skipped: SkippedStep[]\n /** the initial goto never reached networkidle (recording proceeded anyway). */\n navTimeout: boolean\n /** A wall the caller let through; null when the recorder landed where it was asked. */\n wall: WallVerdict | null\n /** Sensitive-looking text seen in the frame (kind + place, never the string). */\n exposures: NonNullable<RecordingMeta['exposures']>\n /** The script's masks with how many elements each reached; hits 0 hid nothing. */\n masks: NonNullable<RecordingMeta['masks']>\n /**\n * Smoothness telemetry: stretches ≥400ms with no visual change. Frozen\n * footage is the #1 enemy of a smooth product video — either the flow should\n * keep motion in frame (animate, hover a preview, scroll) or the doc should\n * trim/speed through these. freezePct = share of the take that is frozen.\n */\n freezes: FreezeSpan[]\n freezePct: number\n /**\n * The smoothness WARNING: still footage under a parked cursor past the\n * beat it takes to read what changed, per step (`deadTime`). A still page\n * being read is content; this is the part of a hold nobody is reading.\n */\n dead: DeadReport\n /** The take reached --max-duration and stopped there; later steps did not run. */\n capped: boolean\n /** The actions as recorded, `url` resolved: what a take directory writes back. */\n actions: ActionsFile\n}\n\nexport interface RecordOpts {\n /** Stop the capture at this many seconds (the hosted cap). */\n maxDurationSeconds?: number\n /**\n * Path to a Playwright storage state (cookies + origin storage), so the\n * recorder drives a SIGNED-IN product. A demo of anything behind a login\n * needs it, and a sign-in form cannot always be scripted (an emailed code,\n * an SSO hop). Export one from a real browser, or with\n * `context.storageState({ path })`.\n */\n storageState?: string\n /**\n * REHEARSE the flow: every step runs against the real page, in order,\n * because a later selector usually exists only after an earlier click. But\n * nothing is captured (no screencast, no frames), nothing is written, the\n * pointer lands instead of travelling and every pause is cut to a beat, so\n * a script that misses a selector says so in seconds instead of after a\n * full real-time take and its encode. Selector lookups keep their whole\n * timeout, so a miss here is a miss in the take.\n */\n dryRun?: boolean\n /**\n * Called once the first navigation has settled, BEFORE a frame is captured.\n * The caller judges the wall here and only then prepares the take\n * directory, so a take refused at a sign-in never clears the footage a\n * re-record would have replaced. A throw closes the context and propagates.\n */\n onArrival?: (\n arrival: Arrival,\n ) => Promise<WallVerdict | null | void> | WallVerdict | null | void\n /** Extra request headers on every request (`--header name=value`). */\n headers?: Record<string, string>\n /**\n * A ready-made context to record IN, instead of one made from `browser`:\n * `--session <name>`, a persistent profile the person signed in to. The\n * recorder still sizes the viewport and closes it at the end.\n */\n context?: RecorderContext\n /**\n * The environment a setup's `{ env }` texts resolve from: the shell's\n * for the CLI, the job's own map on the fleet. Absent means no env at\n * all, so a setup that reads one fails in words.\n */\n env?: Record<string, string | undefined>\n /** The platform the take is recorded on, for `meta.platform`. */\n platform?: RecordingMeta['platform']\n}\n\n/** The longest any pause lasts in a rehearsal. */\nconst DRY_PAUSE_MS = 120\n\n/** Minimal JPEG SOF parse for real encoded dimensions. */\nexport function jpegDims(buf: Uint8Array): { w: number; h: number } | null {\n const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength)\n let i = 2\n while (i < buf.length - 9) {\n if (buf[i] !== 0xff) return null\n const marker = buf[i + 1]\n const len = view.getUint16(i + 2)\n if (\n marker >= 0xc0 &&\n marker <= 0xcf &&\n marker !== 0xc4 &&\n marker !== 0xc8 &&\n marker !== 0xcc\n ) {\n return { h: view.getUint16(i + 5), w: view.getUint16(i + 7) }\n }\n i += 2 + len\n }\n return null\n}\n\n/** Base64 to bytes without Node's Buffer, so the fleet's isolate runs it too. */\nexport function base64Bytes(data: string): Uint8Array {\n const bin = atob(data)\n const out = new Uint8Array(bin.length)\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i)\n return out\n}\n\nexport async function recordTake(\n browser: RecorderBrowser,\n url: string,\n actions: ActionsFile,\n sink: FrameSink,\n log: (msg: string) => void,\n opts: RecordOpts = {},\n): Promise<RecordResult> {\n const dry = opts.dryRun === true\n const sleep = dry\n ? (ms: number) => realSleep(Math.min(ms, DRY_PAUSE_MS))\n : realSleep\n // A rehearsal is not held to the recording cap: its clock is not the take's.\n const maxSeconds = dry ? Infinity : (opts.maxDurationSeconds ?? Infinity)\n const vw = actions.viewport?.width ?? 1280\n const vh = actions.viewport?.height ?? 720\n const context =\n opts.context ??\n (await browser.newContext({\n viewport: { width: vw, height: vh },\n deviceScaleFactor: 1,\n ...(opts.storageState ? { storageState: opts.storageState } : {}),\n ...(opts.headers && Object.keys(opts.headers).length\n ? { extraHTTPHeaders: opts.headers }\n : {}),\n }))\n if (opts.context && opts.headers && Object.keys(opts.headers).length)\n await context.setExtraHTTPHeaders(opts.headers)\n // Masks go in as an init script, so they are in force at document start on\n // every navigation: the real value never reaches a painted frame.\n const masks = actions.mask ?? []\n if (masks.length) await context.addInitScript(maskInitScript(masks))\n const exposureLog = new ExposureLog(masks)\n const page = await context.newPage()\n // What the frame shows, read after each step. A few ms in the page; a scan\n // that fails (mid-navigation) is skipped, never fatal.\n const scanExposures = async (step: number) => {\n const scan = await page.evaluate(EXPOSURE_PROBE).catch(() => null)\n exposureLog.add(step, scan as Parameters<ExposureLog['add']>[1])\n }\n page.on('console', (m) => {\n if (m.type() === 'error') log(` [page error] ${m.text()}`)\n })\n\n log(`goto ${url}`)\n let navTimeout = false\n let response = await page\n .goto(url, { waitUntil: 'networkidle', timeout: 45000 })\n .catch(() => {\n navTimeout = true\n log(' (networkidle timeout — continuing)')\n return null\n })\n await sleep(800) // hydration settle\n\n // The setup: off camera, before the wall is judged, because a setup that\n // signs in is exactly what makes the wall not there. A selector that never\n // appears fails the take here, in words, before anything is captured.\n const setup = actions.setup ?? []\n if (setup.length) {\n log(`setup: ${setup.length} step(s), off camera`)\n const result = await runSetup(page, setup, {\n sleep: realSleep,\n env: opts.env ?? {},\n })\n for (const line of result.lines) log(` ${line}`)\n if (result.failed) {\n await context.close().catch(() => {})\n throw new SetupError(result.failed)\n }\n // Back to the page the take is OF, signed in and past the banners.\n log(`goto ${url} (after setup)`)\n response = await page\n .goto(url, { waitUntil: 'networkidle', timeout: 45000 })\n .catch(() => null)\n await sleep(800)\n }\n\n // A wall the caller let through (--allow-wall, or a soft one without\n // --strict) rides the take's meta, so a digest made later still says so.\n let wall: WallVerdict | null = null\n if (opts.onArrival) {\n const seen = (await page.evaluate(WALL_PROBE).catch(() => null)) as Pick<\n Arrival,\n 'passwordFields' | 'newPasswordField' | 'oneTimeCodeField'\n > | null\n try {\n wall =\n (await opts.onArrival({\n askedUrl: url,\n landedUrl: page.url(),\n ...(response ? { status: response.status() } : {}),\n passwordFields: seen?.passwordFields ?? 0,\n newPasswordField: seen?.newPasswordField ?? false,\n oneTimeCodeField: seen?.oneTimeCodeField ?? false,\n })) ?? null\n } catch (e) {\n await context.close().catch(() => {})\n throw e\n }\n }\n\n await scanExposures(-1)\n\n const cdp = await context.newCDPSession(page)\n const frames: FrameRec[] = []\n let frameIdx = 0\n let t0 = 0\n // Written from the screencast callback, so the read below does not\n // narrow: a box, not a plain local.\n const first: { dims: { w: number; h: number } | null } = { dims: null }\n // Frames are handed to the sink in order; the chain keeps a slow sink\n // (R2) from reordering them and is awaited before the meta is built.\n let sinkChain: Promise<void> = Promise.resolve()\n if (!dry)\n cdp.on('Page.screencastFrame', (ev) => {\n const tsMs = ev.metadata.timestamp\n ? ev.metadata.timestamp * 1000\n : Date.now()\n const file = `frame-${String(frameIdx++).padStart(5, '0')}.jpg`\n const bytes = base64Bytes(ev.data)\n if (!first.dims) first.dims = jpegDims(bytes)\n frames.push({ file, tMs: Math.max(0, Math.round(tsMs - t0)) })\n sinkChain = sinkChain.then(() => sink.frame(file, bytes)).catch(() => {})\n cdp\n .send('Page.screencastFrameAck', { sessionId: ev.sessionId })\n .catch(() => {})\n })\n\n t0 = Date.now()\n if (!dry)\n await cdp.send('Page.startScreencast', {\n format: 'jpeg',\n quality: 90,\n maxWidth: vw,\n maxHeight: vh,\n everyNthFrame: 1,\n })\n\n const events: CursorEvent[] = []\n const cur = { x: 48, y: 48 }\n const now = () => Date.now() - t0\n const emit = (e: Omit<CursorEvent, 't'>) => events.push({ t: now(), ...e })\n\n await page.mouse.move(cur.x, cur.y)\n emit({ x: cur.x, y: cur.y, type: 'move' })\n\n const clock: RecorderClock = { now: () => Date.now(), sleep }\n // The pointer's travel is driven by the CLOCK (pace.ts): its position is\n // a function of the elapsed time and it ends when the travel's duration\n // has elapsed, so a page that answers each mouse.move slowly costs\n // samples, never seconds. Every sample the page took is a cursor event\n // at its real time, which is what the smoothing and the follow read.\n const moveTo = async (tx: number, ty: number) => {\n const dist = Math.hypot(tx - cur.x, ty - cur.y)\n const from = { ...cur }\n await clockMotion(\n dry ? 0 : pointerTravelMs(dist),\n async (u) => {\n cur.x = from.x + (tx - from.x) * u\n cur.y = from.y + (ty - from.y) * u\n await page.mouse.move(cur.x, cur.y)\n emit({ x: Math.round(cur.x), y: Math.round(cur.y), type: 'move' })\n },\n clock,\n )\n }\n\n // One animation frame in the page: the compositor has produced a frame\n // since the last input, so the screencast's next capture is whole. Fails\n // open (a navigating page has no frame to give).\n const paintFrame = () =>\n page\n .evaluate(\n () => new Promise<void>((r) => requestAnimationFrame(() => r())),\n )\n .catch(() => undefined)\n\n const boxOf = async (selector: string): Promise<Rect | null> => {\n const loc = page.locator(selector).first()\n try {\n await loc.waitFor({ state: 'visible', timeout: 8000 })\n const before = await page\n .evaluate(() => [window.scrollX, window.scrollY])\n .catch(() => [0, 0])\n await loc.scrollIntoViewIfNeeded()\n const after = await page\n .evaluate(() => [window.scrollX, window.scrollY])\n .catch(() => before)\n // A lookup that scrolled the page lets the scroll land; one that\n // did not costs nothing.\n if (before[0] !== after[0] || before[1] !== after[1])\n await sleep(SCROLL_SETTLE_MS)\n const bb = await loc.boundingBox()\n if (!bb) return null\n return { x: bb.x, y: bb.y, w: bb.width, h: bb.height }\n } catch {\n log(` (selector not found, skipping: ${selector})`)\n return null\n }\n }\n\n const clickAt = async (rect: Rect) => {\n await moveTo(rect.x + rect.w / 2, rect.y + rect.h / 2)\n await sleep(PRESS_LEAD_MS)\n emit({\n x: Math.round(cur.x),\n y: Math.round(cur.y),\n type: 'down',\n button: 0,\n rect,\n })\n await page.mouse.down()\n await sleep(PRESS_HOLD_MS)\n await page.mouse.up()\n emit({\n x: Math.round(cur.x),\n y: Math.round(cur.y),\n type: 'up',\n button: 0,\n rect,\n })\n }\n\n /**\n * Wait for the page to settle after a press, reading the screencast:\n * returns ms since the last change (null when nothing changed). A dry\n * run has no frames and settles at once.\n */\n const waitSettled = async (): Promise<number | null> => {\n if (dry) return null\n const pressAt = now()\n const before = frames.length\n for (;;) {\n const t = now()\n const last = frames.length > before ? frames[frames.length - 1] : null\n const sinceChange = last ? t - last.tMs : null\n if (settleVerdict({ sincePress: t - pressAt, sinceChange }) === 'settled')\n return sinceChange\n await sleep(40)\n }\n }\n\n const skipped: SkippedStep[] = []\n // The step timeline: when each step ran, in source seconds — what\n // lets a cut anchored to steps re-time onto a NEW recording of the same\n // script (`vos plan --reuse`). Every step that STARTED is recorded; a\n // skipped selector is marked, never silently absent.\n const stepSpans: StepSpan[] = []\n const paces: StepPace[] = []\n let capped = false\n for (const [stepIdx, step] of actions.steps.entries()) {\n // The cap is checked between steps (a step is one gesture and runs\n // whole); only a wait is cut short, since it is the one step that can\n // sit past the cap on its own.\n if (capReached(now(), maxSeconds)) {\n capped = true\n break\n }\n const stepStart = now()\n // The gesture the recorder adds by design around this step's ask: the\n // pointer's travel, the press, the settle. Everything else the step's\n // wall time holds is the page's (a slow round trip, a scroll landing).\n let gestureMs = 0\n const gesture = async <T>(fn: () => Promise<T>): Promise<T> => {\n const t = now()\n const out = await fn()\n gestureMs += now() - t\n return out\n }\n const skippedBefore = skipped.length\n // A step that changes the page's URL (a click that navigates, the\n // wait that lets the load land) is marked: the planner proposes a\n // transition at the page change.\n const urlBefore = page.url()\n // The selector's box at the gesture: kept on the step so a layer can\n // name what the step touched (`overlays[].pin.step`).\n let stepRect: Rect | null = null\n switch (step.do) {\n case 'wait':\n await sleep(clampWait(step.ms, now(), maxSeconds))\n break\n case 'move':\n await moveTo(step.x, step.y)\n break\n case 'hover': {\n const rect = await boxOf(step.selector)\n stepRect = rect\n if (rect) {\n await gesture(() => moveTo(rect.x + rect.w / 2, rect.y + rect.h / 2))\n log(`hover ${step.selector}`)\n await sleep(step.ms ?? 700) // parked cursor = dwell signal for the planner\n } else\n skipped.push({ step: stepIdx, do: step.do, selector: step.selector })\n break\n }\n case 'click': {\n const rect = await boxOf(step.selector)\n stepRect = rect\n if (rect) {\n await gesture(() => clickAt(rect))\n log(`click ${step.selector}`)\n // The settle is the recorder's (watched on the screencast); the\n // hold is the author's, from the page's last change.\n const sinceChange = await gesture(() => waitSettled())\n await sleep(holdLeftMs(settleMs(step), sinceChange))\n } else\n skipped.push({ step: stepIdx, do: step.do, selector: step.selector })\n break\n }\n case 'type': {\n const rect = await boxOf(step.selector)\n stepRect = rect\n if (rect) {\n // The click is what opens the typing zoom on the field. A step that\n // only finishes earlier typing (a submitting Enter) passes\n // focus:false: the field is already focused and a second click\n // rings a click effect on empty space beside the text.\n if (step.focus !== false) await gesture(() => clickAt(rect))\n // Typing-activity pings (TZ): one per ~350ms while characters land,\n // plus one at completion so the planner's hold starts at the true\n // typing end. When-and-where only — a ping never carries the text.\n // ~0.6s+ of typing earns the zoom (TYPING_MIN_DUR + ping floor), so\n // pace delayMs like a person for a camera moment, not a paste.\n const delay = step.delayMs ?? 40\n const ping = () =>\n emit({\n x: Math.round(rect.x + rect.w / 2),\n y: Math.round(rect.y + rect.h / 2),\n type: 'key',\n rect,\n })\n let lastPing = -Infinity\n await clockTyping(\n [...step.text],\n delay,\n async (ch) => {\n if (now() - lastPing >= 350) {\n lastPing = now()\n ping()\n }\n await page.keyboard.type(ch)\n },\n clock,\n )\n ping()\n log(`type \"${step.text}\" into ${step.selector}`)\n await gesture(() => sleep(settleMs(step)))\n } else\n skipped.push({ step: stepIdx, do: step.do, selector: step.selector })\n break\n }\n case 'scroll': {\n // The scroll travels by the clock, like the pointer: each tick sends\n // the wheel delta the eased position owes, then waits for the page\n // to paint a frame before the next, so every captured frame is a\n // settled composite and the motion is paced by the clock rather than\n // by whatever the browser's own smooth scroll manages to draw.\n const total = Math.abs(step.dy)\n const dir = Math.sign(step.dy)\n let sent = 0\n await gesture(() =>\n clockMotion(\n dry ? 0 : scrollTravelMs(total),\n async (u) => {\n const target = Math.round(total * u)\n const delta = target - sent\n if (delta <= 0) return\n await page.mouse.wheel(0, dir * delta)\n sent = target\n emit({\n x: Math.round(cur.x),\n y: Math.round(cur.y),\n type: 'scroll',\n })\n await paintFrame()\n },\n clock,\n ),\n )\n await gesture(() => sleep(settleMs(step)))\n break\n }\n case 'drag': {\n // Start point: selector center when given, else explicit coords.\n let start: { x: number; y: number } | null = null\n let rect: Rect | undefined\n if (step.selector) {\n const box = await boxOf(step.selector)\n stepRect = box\n if (box) {\n rect = box\n start = { x: box.x + box.w / 2, y: box.y + box.h / 2 }\n } else {\n skipped.push({\n step: stepIdx,\n do: step.do,\n selector: step.selector,\n })\n break\n }\n } else if (typeof step.x === 'number' && typeof step.y === 'number') {\n start = { x: step.x, y: step.y }\n }\n if (!start) break\n await gesture(() => moveTo(start.x, start.y))\n await gesture(() => sleep(PRESS_LEAD_MS))\n emit({\n x: Math.round(cur.x),\n y: Math.round(cur.y),\n type: 'down',\n button: 0,\n rect,\n })\n await page.mouse.down()\n await gesture(() => sleep(PRESS_HOLD_MS))\n // The drag is the ask: `ms` of travel by the clock, however slowly\n // the page answers each move (a slider re-rendering per sample).\n const dur = step.ms ?? 700\n const from = { ...cur }\n await clockMotion(\n dur,\n async (u) => {\n cur.x = from.x + (step.tx - from.x) * u\n cur.y = from.y + (step.ty - from.y) * u\n await page.mouse.move(cur.x, cur.y)\n emit({ x: Math.round(cur.x), y: Math.round(cur.y), type: 'move' })\n },\n clock,\n )\n await gesture(() => sleep(settleMs({ do: 'drag' })))\n await page.mouse.up()\n emit({\n x: Math.round(cur.x),\n y: Math.round(cur.y),\n type: 'up',\n button: 0,\n rect,\n })\n log(\n `drag ${step.selector ?? `${start.x},${start.y}`} → ${step.tx},${step.ty}`,\n )\n await sleep(400)\n break\n }\n }\n const stepInfo = step as { id?: string; selector?: string }\n stepSpans.push({\n step: stepIdx,\n ...(stepInfo.id ? { id: stepInfo.id } : {}),\n do: step.do,\n ...(stepInfo.selector ? { selector: stepInfo.selector } : {}),\n tStart: +(stepStart / 1000).toFixed(3),\n tEnd: +(now() / 1000).toFixed(3),\n ...(skipped.length > skippedBefore ? { skipped: true } : {}),\n ...(page.url() !== urlBefore ? { navigated: true } : {}),\n ...(stepRect\n ? {\n rect: {\n x: +stepRect.x.toFixed(1),\n y: +stepRect.y.toFixed(1),\n w: +stepRect.w.toFixed(1),\n h: +stepRect.h.toFixed(1),\n },\n }\n : {}),\n })\n paces.push({\n step: stepIdx,\n do: step.do,\n askedMs: askedMs(step as Parameters<typeof askedMs>[0]),\n gestureMs,\n wallMs: now() - stepStart,\n })\n // After the pace is taken: the scan's few ms are not the step's.\n await scanExposures(stepIdx)\n }\n if (!capped) await sleep(TRAILING_HOLD_MS) // trailing hold\n if (capped || capReached(now(), maxSeconds)) {\n capped = true\n log(` ${cappedLine(maxSeconds)}`)\n }\n\n const durationMs = capped ? Math.min(now(), maxSeconds * 1000) : now()\n await cdp.send('Page.stopScreencast').catch(() => {})\n await sleep(200)\n const pageTitle = await page.title().catch(() => '')\n await context.close()\n // Every frame the screencast delivered has reached the sink.\n await sinkChain\n\n const meta: RecordingMeta = {\n dpr: 1,\n zoom: 1,\n t0,\n durationMs,\n width: vw,\n height: vh,\n fps: 30,\n hasAudio: false,\n captureWidth: first.dims?.w ?? vw,\n captureHeight: first.dims?.h ?? vh,\n captureSurface: 'tab',\n pageUrl: url,\n pageTitle,\n platform: opts.platform ?? 'linux',\n producer: 'cli',\n steps: stepSpans,\n ...(wall\n ? { wall: { kind: wall.kind, asked: wall.asked, landed: wall.landed } }\n : {}),\n ...(exposureLog.exposures().length\n ? { exposures: exposureLog.exposures() }\n : {}),\n ...(masks.length ? { masks: exposureLog.masks(masks) } : {}),\n }\n\n // Smoothness telemetry: screencast emits only on visual change, so frame\n // gaps = frozen pixels. ≥400ms reads as dead time in a product video.\n const freezes: FreezeSpan[] = []\n for (let i = 1; i < frames.length; i++) {\n const gap = frames[i].tMs - frames[i - 1].tMs\n if (gap >= 400) {\n freezes.push({\n from: +(frames[i - 1].tMs / 1000).toFixed(2),\n to: +(frames[i].tMs / 1000).toFixed(2),\n ms: gap,\n })\n }\n }\n const frozenMs = freezes.reduce((sum, f) => sum + f.ms, 0)\n const freezePct =\n durationMs > 0 ? Math.round((frozenMs / durationMs) * 100) : 0\n // The freeze share is a FACT (a workspace tour is mostly still and fine);\n // the warning is dead time: a hold nobody is reading, named per step.\n const dead = deadTime(stepSpans, frames, events, durationMs)\n if (deadWarns(dead)) log(` WARNING: ${deadLine(dead)}`)\n\n const pace = paceReport(paces)\n if (!dry) log(` ${paceLine(pace)}`)\n return {\n events,\n frames,\n meta,\n pace,\n skipped,\n navTimeout,\n wall,\n exposures: exposureLog.exposures(),\n masks: exposureLog.masks(masks),\n freezes,\n freezePct,\n dead,\n capped,\n actions: { ...actions, url },\n }\n}\n","import type { MaskRule } from './exposure'\nimport type { SetupStep } from './setup'\nimport { validateSetup } from './setup'\n\n/**\n * The action script — the declarative recipe an agent (or human) writes to\n * drive a take. Small on purpose: selectors + a handful of verbs. The recorder\n * executes it with humanized cursor motion and synthesizes the CursorTrack\n * from its own dispatches.\n */\nexport interface ActionsFile {\n /** Page to record. `--url` overrides. */\n url?: string\n /** Recording viewport in CSS px (default 1280x720). */\n viewport?: { width: number; height: number }\n /**\n * Hidden BEFORE the first frame is captured and kept hidden across\n * navigations: what a signed-in account shows that must not ship. `blur`\n * (default) blurs the element; `text` swaps its words for `text`, which is\n * for IDENTIFIERS (an email, a name, an account id), never for product\n * copy or numbers: the video must stay true to the product.\n */\n mask?: MaskRule[]\n /**\n * Run BEFORE the camera rolls, after the first navigation: a sign-in form,\n * a cookie banner, an onboarding tour. Plain actions with no cursor, no\n * frames, no pace and nothing in `meta.steps`; then the recorder opens\n * `url` again and the take begins where the setup left it. A `type`\n * step's text may be `{ env: 'NAME' }`, read at run time and never logged\n * or stored.\n */\n setup?: SetupStep[]\n steps: ActionStep[]\n}\n\nexport type ActionStep = (\n | { do: 'wait'; ms: number }\n | { do: 'hover'; selector: string; ms?: number }\n /** `ms` is the settle after the press (default 150). */\n | { do: 'click'; selector: string; ms?: number }\n /**\n * Type into `selector`. The recorder clicks the field first, which is what\n * opens the typing zoom on it; `focus: false` types into the field as it is\n * already focused, for a keystroke that follows earlier typing (a submitting\n * Enter) rather than starting it — a second click there rings a click effect\n * on empty space beside the text.\n */\n | {\n do: 'type'\n selector: string\n text: string\n delayMs?: number\n focus?: boolean\n /** the settle after the last character (default 150) */\n ms?: number\n }\n /** `ms` is the settle after the scroll lands (default 200). */\n | { do: 'scroll'; dy: number; ms?: number }\n | { do: 'move'; x: number; y: number }\n /**\n * Press-move-release — real edits (drag an element on the stage canvas,\n * slide a range input, move a timeline clip). Start = the selector's center\n * when given, else (x, y); end = (tx, ty); eased over ms (default 700).\n */\n | {\n do: 'drag'\n selector?: string\n x?: number\n y?: number\n tx: number\n ty: number\n ms?: number\n }\n) & {\n /**\n * Optional stable identity: anchors in doc.json name a step by this\n * id (else by index), so a step can move or gain neighbours across script\n * edits without breaking the cut anchored to it. Unique when present.\n */\n id?: string\n /**\n * A beat's caption, two to eight words: `vos deliver` lands it as a\n * lower-third at this step's moment on the cuts that take words, and\n * `vos actions script` leads the beat with it.\n */\n caption?: string\n}\n\nconst VERBS = new Set([\n 'wait',\n 'hover',\n 'click',\n 'type',\n 'scroll',\n 'move',\n 'drag',\n])\n\n/** Structural validation with actionable messages. Returns [] when valid. */\nexport function validateActions(value: unknown): string[] {\n const errors: string[] = []\n if (typeof value !== 'object' || value === null)\n return ['actions file must be a JSON object']\n const obj = value as Record<string, unknown>\n if (obj.url !== undefined && typeof obj.url !== 'string')\n errors.push('url must be a string')\n if (obj.viewport !== undefined) {\n const v: unknown = obj.viewport\n if (typeof v !== 'object' || v === null) {\n errors.push('viewport must be { width, height }')\n } else {\n const vp = v as Record<string, unknown>\n if (typeof vp.width !== 'number' || typeof vp.height !== 'number') {\n errors.push('viewport must be { width, height }')\n }\n }\n }\n if (obj.setup !== undefined) errors.push(...validateSetup(obj.setup))\n if (obj.mask !== undefined) {\n if (!Array.isArray(obj.mask)) {\n errors.push('mask must be an array of { selector, as?, text? }')\n } else {\n obj.mask.forEach((raw, i) => {\n const at = `mask[${i}]`\n const m = (raw ?? {}) as Record<string, unknown>\n if (typeof m.selector !== 'string' || !m.selector.trim())\n errors.push(`${at}: needs a selector`)\n if (m.as !== undefined && m.as !== 'blur' && m.as !== 'text')\n errors.push(`${at}: as must be \"blur\" or \"text\"`)\n if (m.as === 'text' && (typeof m.text !== 'string' || !m.text))\n errors.push(`${at}: as \"text\" needs the text to show instead`)\n if (m.as !== 'text' && m.text !== undefined)\n errors.push(`${at}: text is only read with as \"text\"`)\n })\n }\n }\n if (!Array.isArray(obj.steps) || obj.steps.length === 0) {\n errors.push('steps must be a non-empty array')\n return errors\n }\n const seenIds = new Set<string>()\n obj.steps.forEach((raw, i) => {\n const at = `steps[${i}]`\n if (typeof raw !== 'object' || raw === null) {\n errors.push(`${at}: must be an object`)\n return\n }\n const s = raw as Record<string, unknown>\n if (typeof s.do !== 'string' || !VERBS.has(s.do)) {\n errors.push(`${at}: \"do\" must be one of ${[...VERBS].join(', ')}`)\n return\n }\n if (s.id !== undefined) {\n if (typeof s.id !== 'string' || !s.id.trim()) {\n errors.push(`${at}: id must be a non-empty string`)\n } else if (seenIds.has(s.id)) {\n errors.push(\n `${at}: duplicate id \"${s.id}\" — an anchor could not tell the two steps apart`,\n )\n } else {\n seenIds.add(s.id)\n }\n }\n const needSelector = s.do === 'hover' || s.do === 'click' || s.do === 'type'\n if (needSelector && typeof s.selector !== 'string')\n errors.push(`${at}: ${s.do} needs a selector`)\n if (s.do === 'wait' && typeof s.ms !== 'number')\n errors.push(`${at}: wait needs ms`)\n if (\n s.ms !== undefined &&\n (typeof s.ms !== 'number' || !Number.isFinite(s.ms) || s.ms < 0)\n )\n errors.push(`${at}: ms must be a number of milliseconds ≥ 0`)\n if (s.do === 'type' && typeof s.text !== 'string')\n errors.push(`${at}: type needs text`)\n if (\n s.do === 'type' &&\n s.focus !== undefined &&\n typeof s.focus !== 'boolean'\n )\n errors.push(`${at}: type focus must be true or false`)\n if (s.do === 'scroll' && typeof s.dy !== 'number')\n errors.push(`${at}: scroll needs dy`)\n if (\n s.do === 'move' &&\n (typeof s.x !== 'number' || typeof s.y !== 'number')\n ) {\n errors.push(`${at}: move needs x and y (CSS px)`)\n }\n if (s.do === 'drag') {\n if (typeof s.tx !== 'number' || typeof s.ty !== 'number') {\n errors.push(`${at}: drag needs tx and ty (CSS px)`)\n }\n const hasStart =\n typeof s.selector === 'string' ||\n (typeof s.x === 'number' && typeof s.y === 'number')\n if (!hasStart)\n errors.push(\n `${at}: drag needs a selector OR x and y as the start point`,\n )\n }\n })\n return errors\n}\n","/**\n * Frames → CFR WebM, as a PAGE. The VFR screencast JPEGs are drawn\n * hold-last-frame onto a canvas at a fixed 30fps and encoded with\n * mediabunny (WebCodecs) inside a headless page — no ffmpeg dependency.\n * The CLI serves the take directory to it and reads the bytes back through\n * its own `/save`; the fleet serves the frames through the ingest route and\n * the page PUTs the recording to the same route. One page, two hosts: the\n * URLs and the way out are the host's, the encode is not.\n */\n\nconst MEDIABUNNY_URL = 'https://esm.sh/mediabunny@1.55.7?target=es2022'\n\nexport interface EncodePageOptions {\n /** Where `frames.json` (the index `[{file, tMs}]`) is fetched from. */\n framesIndexUrl: string\n /** Where `meta.json` is fetched from. */\n metaUrl: string\n /** `<framesBaseUrl><file>` is each JPEG. */\n framesBaseUrl: string\n /** The way out: a POST to the CLI's take server, or a PUT to the ingest route. */\n save: { method: 'POST' | 'PUT'; url: string }\n /** Optional `?rt=`-style query the frame fetches must carry (the fleet's token). */\n frameQuery?: string\n}\n\nexport function buildEncodePage(o: EncodePageOptions): string {\n const q = o.frameQuery ? `?${o.frameQuery}` : ''\n return `<!doctype html><html><head><meta charset=\"utf-8\"></head><body>\n<script type=\"module\">\ntry {\n const MB = await import('${MEDIABUNNY_URL}')\n const frames = await (await fetch(${JSON.stringify(o.framesIndexUrl)})).json()\n const meta = await (await fetch(${JSON.stringify(o.metaUrl)})).json()\n const W = meta.captureWidth ?? meta.width, H = meta.captureHeight ?? meta.height\n const FPS = 30\n const durSec = meta.durationMs / 1000\n const cv = document.createElement('canvas')\n cv.width = W; cv.height = H\n const c2 = cv.getContext('2d')\n const output = new MB.Output({ format: new MB.WebMOutputFormat(), target: new MB.BufferTarget() })\n const src = new MB.CanvasSource(cv, { codec: 'vp9', bitrate: 8_000_000 })\n output.addVideoTrack(src, { frameRate: FPS })\n await output.start()\n let fi = -1\n const total = Math.ceil(durSec * FPS)\n for (let i = 0; i < total; i++) {\n const t = i / FPS\n let next = fi\n while (next + 1 < frames.length && frames[next + 1].tMs <= t * 1000) next++\n if (next < 0) next = 0\n if (next !== fi) {\n fi = next\n const blob = await (await fetch(${JSON.stringify(o.framesBaseUrl)} + frames[fi].file + ${JSON.stringify(q)})).blob()\n const bmp = await createImageBitmap(blob)\n c2.drawImage(bmp, 0, 0, W, H)\n bmp.close()\n }\n await src.add(t, 1 / FPS)\n window.__progress = i / total\n }\n await output.finalize()\n const buf = output.target.buffer\n const res = await fetch(${JSON.stringify(o.save.url)}, { method: ${JSON.stringify(o.save.method)}, headers: { 'content-type': 'video/webm' }, body: buf })\n if (!res.ok) throw new Error('save failed: ' + res.status)\n // Two hosts, two completion words: the CLI's take server polls __done,\n // the fleet's page runner polls __renderComplete.\n window.__done = { ok: true, bytes: buf.byteLength }\n window.__renderComplete = { success: true, size: buf.byteLength, uploaded: true }\n} catch (e) {\n window.__error = String(e && e.stack || e)\n window.__renderComplete = { success: false, error: String(e && e.message || e) }\n}\n</script></body></html>`\n}\n","/**\n * The FRESH plan of a take: what `vos record` writes as doc.json the moment\n * a recording lands, and what a hosted take gets on the fleet. One\n * composition, so a take recorded anywhere opens on the same camera: the\n * artifact becomes a document (`projectFromArtifact`), the zooms are\n * planned from the cursor track, the speed spans from the same track. The\n * backdrop is a host's PICK (vosso's house set, the CLI's `--background`),\n * handed in as a wire row; studio-core carries only the mechanism.\n *\n * The CLI's `vos plan` is the fuller verb (reuse, style, motion, a still);\n * this is its fresh branch, byte-identical to it.\n */\nimport {\n DEFAULT_FRAME_STYLE,\n planAutoSpeed,\n planAutoZoom,\n projectFromArtifact,\n withBackdrop,\n} from '@vosjs/studio-core'\nimport type {\n Backdrop,\n ProjectDoc,\n RecordingArtifact,\n} from '@vosjs/studio-core'\n\nexport interface FreshPlanOptions {\n /** The backdrop the document opens on; absent keeps the bare frame. */\n backdrop?: Backdrop | null\n /** A digest's activity bins, when one exists (the speed planner's witness). */\n activity?: readonly number[] | null\n}\n\nexport function planFreshTake(\n artifact: RecordingArtifact,\n recordingName: string,\n opts: FreshPlanOptions = {},\n): ProjectDoc {\n const ingest = opts.backdrop\n ? { frame: withBackdrop(DEFAULT_FRAME_STYLE, opts.backdrop) }\n : {}\n const doc = projectFromArtifact(artifact, recordingName, ingest).doc\n doc.zoom = planAutoZoom(doc.source.cursor, {\n width: doc.source.meta.width,\n height: doc.source.meta.height,\n duration: doc.source.meta.durationMs / 1000,\n style: doc.zoomStyle,\n params: doc.zoomParams,\n })\n doc.speed = planAutoSpeed(doc.source.cursor, {\n durationMs: doc.source.meta.durationMs,\n params: doc.speedParams,\n activity: opts.activity,\n })\n return doc\n}\n"],"mappings":";AAkHO,IAAM,YAAY,CAAC,OACxB,IAAI,QAAc,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;;;AC3GrC,SAAS,WAAW,WAAmB,YAA6B;AACzE,SAAO,aAAa,aAAa;AACnC;AAGO,SAAS,UACd,QACA,WACA,YACQ;AACR,SAAO,KAAK,IAAI,GAAG,KAAK,IAAI,QAAQ,aAAa,MAAO,SAAS,CAAC;AACpE;AAGO,SAAS,kBAAkB,SAAyB;AACzD,MAAI,UAAU,GAAI,QAAO,GAAG,OAAO;AACnC,QAAM,IAAI,KAAK,MAAM,UAAU,EAAE;AACjC,MAAI,IAAI,GAAI,QAAO,GAAG,CAAC;AACvB,QAAM,IAAI,KAAK,MAAM,IAAI,EAAE;AAC3B,QAAM,OAAO,IAAI;AACjB,SAAO,OAAO,GAAG,CAAC,MAAM,IAAI,SAAS,GAAG,CAAC;AAC3C;AAGO,SAAS,WAAW,YAA4B;AACrD,SAAO,cAAc,kBAAkB,UAAU,CAAC,oBAAoB,UAAU;AAClF;;;ACZO,IAAM,oBAAoB;AAC1B,IAAM,iBAAiB;AACvB,IAAM,iBAAiB;AAEvB,IAAM,iBAAiB;AAGvB,IAAM,YAAY;AAAA,EACvB,OAAO;AAAA,EACP,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,MAAM;AACR;AAGO,IAAM,gBAAgB;AACtB,IAAM,gBAAgB;AAEtB,IAAM,mBAAmB;AAWzB,IAAM,mBAAmB;AACzB,IAAM,gBAAgB;AACtB,IAAM,gBAAgB;AAEtB,IAAM,mBAAmB;AAGzB,SAAS,eAAe,IAAoB;AACjD,SAAO,KAAK;AAAA,IACV;AAAA,IACA,KAAK,IAAI,eAAe,KAAK,MAAM,KAAK,IAAI,EAAE,IAAI,gBAAgB,CAAC;AAAA,EACrE;AACF;AAEO,SAAS,gBAAgB,MAAsB;AACpD,SAAO,KAAK;AAAA,IACV;AAAA,IACA,KAAK,IAAI,gBAAgB,KAAK,MAAM,OAAO,iBAAiB,CAAC;AAAA,EAC/D;AACF;AAgBO,IAAM,cAAc;AACpB,IAAM,WAAW;AACjB,IAAM,gBAAgB;AAMtB,SAAS,cACd,GAMA,IAAI,EAAE,UAAU,aAAa,OAAO,UAAU,KAAK,cAAc,GAC7C;AACpB,MAAI,EAAE,cAAc,EAAE,IAAK,QAAO;AAClC,MAAI,EAAE,gBAAgB;AACpB,WAAO,EAAE,cAAc,EAAE,WAAW,YAAY;AAClD,SAAO,EAAE,eAAe,EAAE,QAAQ,YAAY;AAChD;AAMO,SAAS,WAAW,QAAgB,aAAoC;AAC7E,SAAO,KAAK,IAAI,GAAG,UAAU,eAAe,EAAE;AAChD;AAGO,SAAS,SAAS,MAA2C;AAClE,MAAI,OAAO,KAAK,OAAO,YAAY,KAAK,MAAM,EAAG,QAAO,KAAK;AAC7D,QAAM,IAAI,KAAK;AACf,SAAO,UAAU,CAAC,KAAK;AACzB;AAEO,IAAM,iBAAiB,CAAC,MAC7B,IAAI,MAAM,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI,KAAK,IAAI,GAAG,CAAC,IAAI;AAU1D,eAAsB,YACpB,KACA,IACA,OACiB;AACjB,QAAM,QAAQ,MAAM,IAAI;AACxB,MAAI,UAAU;AACd,aAAS;AACP,UAAM,UAAU,MAAM,IAAI,IAAI;AAC9B,QAAI,WAAW,IAAK;AACpB,UAAM,GAAG,eAAe,KAAK,IAAI,GAAG,UAAU,GAAG,CAAC,CAAC;AACnD;AACA,UAAM,OACJ,QAAQ,KAAK,MAAM,MAAM,IAAI,IAAI,SAAS,cAAc,IAAI;AAC9D,UAAM,OAAO,KAAK,IAAI,MAAM,QAAQ,GAAG,IAAI,MAAM,IAAI;AACrD,QAAI,OAAO,EAAG,OAAM,MAAM,MAAM,IAAI;AAAA,EACtC;AACA,QAAM,GAAG,CAAC;AACV,SAAO,UAAU;AACnB;AAQA,eAAsB,YACpB,OACA,OACA,MACA,OACe;AACf,QAAM,QAAQ,MAAM,IAAI;AACxB,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,KAAK,MAAM,CAAC,GAAG,CAAC;AACtB,UAAM,MAAM,SAAS,IAAI,KAAK;AAC9B,UAAM,OAAO,MAAM,MAAM,IAAI;AAC7B,QAAI,OAAO,KAAK,IAAI,MAAM,SAAS,EAAG,OAAM,MAAM,MAAM,IAAI;AAAA,EAC9D;AACF;AAyBO,SAAS,QAAQ,MAKb;AACT,UAAQ,KAAK,IAAI;AAAA,IACf,KAAK;AACH,aAAO,KAAK,MAAM;AAAA;AAAA,IAEpB,KAAK;AACH,aAAO,KAAK,MAAM,UAAU;AAAA,IAC9B,KAAK;AACH,aAAO,KAAK,MAAM;AAAA,IACpB,KAAK;AACH,aAAO,KAAK,MAAM;AAAA,IACpB,KAAK;AACH,cAAQ,KAAK,MAAM,UAAU,MAAM,KAAK,WAAW;AAAA,IACrD;AACE,aAAO;AAAA,EACX;AACF;AAEO,SAAS,WAAW,OAAwC;AACjE,QAAMA,WAAU,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,SAAS,CAAC;AACvD,QAAM,YAAY,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,WAAW,CAAC;AAC3D,QAAM,SAAS,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,QAAQ,CAAC;AACrD,QAAM,aAAa,KAAK,IAAI,GAAG,SAASA,WAAU,SAAS;AAC3D,QAAM,OAAO,MACV,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,OAAO,EAAE,EAC9D,IAAI,CAAC,OAAO;AAAA,IACX,MAAM,EAAE;AAAA,IACR,IAAI,EAAE;AAAA,IACN,SAAS,EAAE;AAAA,IACX,QAAQ,EAAE;AAAA,EACZ,EAAE;AACJ,SAAO;AAAA,IACL,SAAAA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa,SAAS,IAAI,KAAK,MAAO,aAAa,SAAU,GAAG,IAAI;AAAA,IACpE;AAAA,EACF;AACF;AAGO,SAAS,SAAS,GAAuB;AAC9C,QAAM,IAAI,CAAC,OAAe,IAAI,KAAK,KAAM,QAAQ,CAAC,CAAC;AACnD,QAAM,OAAO,EAAE,KAAK,SAChB,WAAW,EAAE,KAAK,IAAI,CAAC,MAAM,IAAI,EAAE,IAAI,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE,OAAO,CAAC,QAAQ,EAAE,KAAK,IAAI,CAAC,KACtG;AACJ,SAAO,0BAA0B,EAAE,EAAE,OAAO,CAAC,wBAAwB,EAAE,EAAE,SAAS,CAAC,kBAAkB,EAAE,EAAE,MAAM,CAAC,KAAK,EAAE,WAAW,cAAc,IAAI;AACtJ;AAeO,IAAM,eAAe,EAAE,MAAM,KAAM,SAAS,IAAI;AAEhD,IAAM,uBAAuB;AAE7B,IAAM,sBAAsB;AAuB5B,SAAS,SACd,OAQA,QACA,QACA,YACY;AACZ,QAAM,MAAkB,CAAC;AACzB,MAAI,KAAK;AACT,MAAI,YAAY;AAChB,aAAW,KAAK,OAAO;AACrB,UAAM,KAAK,EAAE,SAAS;AACtB,UAAM,KAAK,EAAE,OAAO;AACpB,QAAI,EAAE,KAAK,IAAK;AAEhB,UAAM,QAAQ,OAAO;AAAA,MACnB,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,KAAK,MAAM,EAAE,KAAK;AAAA,IAClD;AACA,UAAM,MAAM,QAAQ,MAAM,IAAI;AAE9B,QAAI,OAAO;AACX,eAAW,KAAK,QAAQ;AACtB,UAAI,EAAE,MAAM,OAAO,EAAE,OAAO,GAAI,QAAO,EAAE;AAAA,IAC3C;AACA,UAAM,YAAY,KAAK,MAAM,OAAO,GAAG;AACvC,UAAM,SAAS,KAAK,MAAM,KAAK,IAAI;AAEnC,UAAM,QAAQ,OAAO;AAAA,MACnB,CAAC,OACE,EAAE,SAAS,UAAU,EAAE,SAAS,aAAa,EAAE,IAAI,QAAQ,EAAE,KAAK;AAAA,IACvE;AAEA,UAAM,OACJ,EAAE,aAAa,EAAE,SAAS,IAAI,aAAa,OAAO,aAAa;AACjE,UAAM,SAAS,QAAQ,IAAI,KAAK,IAAI,GAAG,SAAS,IAAI;AACpD,QAAI,SAAS,GAAG;AACd,UAAI,KAAK;AAAA,QACP,MAAM,EAAE;AAAA,QACR,GAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC;AAAA,QAC3B,IAAI,EAAE;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AACD,YAAM;AACN,kBAAY,KAAK,IAAI,WAAW,MAAM;AAAA,IACxC;AAAA,EACF;AACA,SAAO;AAAA,IACL;AAAA,IACA,KAAK,aAAa,IAAI,KAAK,MAAO,KAAK,aAAc,GAAG,IAAI;AAAA,IAC5D,OAAO;AAAA,IACP;AAAA,EACF;AACF;AAGO,SAAS,UAAU,GAAwB;AAChD,SAAO,EAAE,OAAO,uBAAuB,EAAE,aAAa;AACxD;AAGO,SAAS,SAAS,GAAuB;AAC9C,QAAM,IAAI,CAAC,OAAe,IAAI,KAAK,KAAM,QAAQ,CAAC,CAAC;AACnD,MAAI,CAAC,EAAE,MAAM,OAAQ,QAAO;AAC5B,QAAM,QAAQ,EAAE,MAAM;AAAA,IACpB,CAAC,MACC,IAAI,EAAE,IAAI,GAAG,EAAE,KAAK,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,sBAAsB,EAAE,EAAE,MAAM,CAAC;AAAA,EACtG;AACA,SAAO,cAAc,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,GAAG,mEAAmE,MAAM,KAAK,IAAI,CAAC;AAC3H;;;AC/TA,IAAM,YAAY;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGA,IAAM,kBAAkB,oBAAI,IAAI;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,MAAM,KAAyB;AACtC,MAAI;AACF,WAAO,IAAI,IAAI,GAAG;AAAA,EACpB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,IAAM,WAAW,CAAC,MAAc,EAAE,QAAQ,UAAU,EAAE,EAAE,YAAY;AACpE,IAAM,YAAY,CAAC,MAAe,EAAE,SAAS,IAAI,EAAE,QAAQ,QAAQ,EAAE,IAAI;AACzE,IAAM,QAAQ,CAAC,MAAW,GAAG,EAAE,MAAM,GAAG,UAAU,EAAE,QAAQ,CAAC;AAEtD,SAAS,UAAU,MAAuB;AAC/C,QAAM,IAAI,KAAK,YAAY;AAC3B,MAAI,EAAE,WAAW,QAAQ,EAAG,QAAO;AACnC,SAAO,UAAU,KAAK,CAAC,QAAQ,MAAM,OAAO,EAAE,SAAS,IAAI,GAAG,EAAE,CAAC;AACnE;AAEO,SAAS,aAAa,UAA2B;AACtD,QAAM,OAAO,SAAS,YAAY,EAAE,MAAM,GAAG,EAAE,OAAO,OAAO;AAC7D,SAAO,KAAK,KAAK,CAAC,MAAM,gBAAgB,IAAI,CAAC,CAAC;AAChD;AAQO,SAAS,eAAe,OAAY,QAAsB;AAC/D,MAAI,SAAS,MAAM,QAAQ,MAAM,SAAS,OAAO,QAAQ,EAAG,QAAO;AACnE,QAAM,IAAI,UAAU,MAAM,QAAQ,EAAE,YAAY;AAChD,QAAM,IAAI,UAAU,OAAO,QAAQ,EAAE,YAAY;AACjD,MAAI,MAAM,KAAK,MAAM,IAAK,QAAO;AACjC,MAAI,EAAE,WAAW,GAAG,CAAC,GAAG,EAAG,QAAO;AAClC,MAAI,EAAE,SAAS,CAAC,EAAG,QAAO;AAC1B,SAAO;AACT;AAEA,IAAM,SACJ;AAEK,SAAS,YAAY,GAAgC;AAC1D,QAAM,QAAQ,MAAM,EAAE,QAAQ;AAC9B,QAAM,SAAS,MAAM,EAAE,SAAS;AAEhC,MAAI,CAAC,SAAS,CAAC,OAAQ,QAAO;AAC9B,MAAI,CAAC,YAAY,KAAK,MAAM,QAAQ,EAAG,QAAO;AAE9C,QAAM,OAAO,eAAe,OAAO,MAAM;AACzC,QAAM,QAAQ,EAAE,OAAO,MAAM,KAAK,GAAG,QAAQ,MAAM,MAAM,EAAE;AAC3D,QAAM,OAAO,SAAS,MAAM,QAAQ;AAEpC,MAAI,EAAE,WAAW,OAAO,EAAE,WAAW,KAAK;AACxC,WAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,GAAG;AAAA,MACH,SAAS,aAAa,MAAM,QAAQ,cAAc,EAAE,MAAM,oBAAoB,IAAI,KAAK,MAAM;AAAA,IAC/F;AAAA,EACF;AAGA,QAAM,iBACJ,aAAa,MAAM,QAAQ,KAAK,UAAU,MAAM,QAAQ;AAC1D,MAAI,kBAAkB,CAAC,KAAM,QAAO;AAEpC,MAAI,QAAQ,UAAU,OAAO,QAAQ,KAAK,CAAC,UAAU,MAAM,QAAQ,GAAG;AACpE,WAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,GAAG;AAAA,MACH,SAAS,aAAa,MAAM,QAAQ,eAAe,OAAO,QAAQ,oBAAoB,IAAI,KAAK,MAAM;AAAA,IACvG;AAAA,EACF;AAIA,QAAM,aACH,EAAE,mBAAmB,KAAK,CAAC,EAAE,oBAAqB,EAAE;AACvD,MACG,QAAQ,aAAa,OAAO,QAAQ,KACpC,cAAc,CAAC,gBAChB;AACA,UAAM,OAAO,OACT,aAAa,OAAO,QAAQ,KAC5B;AACJ,WAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,GAAG;AAAA,MACH,SAAS,aAAa,MAAM,QAAQ,KAAK,IAAI,oBAAoB,IAAI,KAAK,MAAM;AAAA,IAClF;AAAA,EACF;AAEA,MAAI,MAAM;AACR,WAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,GAAG;AAAA,MACH,SAAS,aAAa,MAAM,QAAQ,eAAe,OAAO,QAAQ,4JAA4J,MAAM;AAAA,IACtO;AAAA,EACF;AACA,SAAO;AACT;AAGO,IAAM,YAAN,cAAwB,MAAM;AAAA,EACnC,YAAmB,SAAsB;AACvC,UAAM,QAAQ,OAAO;AADJ;AAEjB,SAAK,OAAO;AAAA,EACd;AAAA,EAHmB;AAIrB;AAMO,IAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC1JnB,IAAM,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgD9B,IAAM,iBAAiB;AAAA,wBACN,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyEtC,SAAS,eAAe,OAA2B;AACxD,QAAM,QAAQ,MAAM,IAAI,CAAC,OAAO;AAAA,IAC9B,UAAU,EAAE;AAAA,IACZ,IAAI,EAAE,OAAO,SAAS,SAAS;AAAA,IAC/B,MAAM,EAAE,QAAQ;AAAA,EAClB,EAAE;AACF,SAAO;AAAA,kBACS,KAAK,UAAU,KAAK,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmCvC;AAMO,IAAM,cAAN,MAAkB;AAAA,EACf,QAAQ,oBAAI,IAAsB;AAAA,EAClC,WAAW,oBAAI,IAAoB;AAAA,EAE3C,YAAY,QAAoB,CAAC,GAAG;AAClC,eAAW,KAAK,MAAO,MAAK,SAAS,IAAI,EAAE,UAAU,CAAC;AAAA,EACxD;AAAA,EAEA,IACE,MACA,MAIM;AACN,QAAI,CAAC,KAAM;AACX,eAAW,KAAK,KAAK,aAAa,CAAC,GAAG;AACpC,YAAM,MAAM,GAAG,EAAE,IAAI,IAAI,EAAE,QAAQ;AACnC,YAAM,MAAM,KAAK,MAAM,IAAI,GAAG;AAC9B,UAAI,IAAK,KAAI,QAAQ;AAAA,UAChB,MAAK,MAAM,IAAI,KAAK,EAAE,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;AAAA,IAClD;AACA,eAAW,CAAC,KAAK,CAAC,KAAK,OAAO,QAAQ,KAAK,YAAY,CAAC,CAAC,GAAG;AAC1D,UAAI,KAAK,KAAK,SAAS,IAAI,GAAG,KAAK,GAAI,MAAK,SAAS,IAAI,KAAK,CAAC;AAAA,IACjE;AAAA,EACF;AAAA,EAEA,YAAwB;AACtB,WAAO,CAAC,GAAG,KAAK,MAAM,OAAO,CAAC;AAAA,EAChC;AAAA,EAEA,MAAM,OAAiC;AACrC,WAAO,MAAM,IAAI,CAAC,OAAO;AAAA,MACvB,UAAU,EAAE;AAAA,MACZ,IAAI,EAAE,OAAO,SAAS,SAAS;AAAA,MAC/B,MAAM,KAAK,SAAS,IAAI,EAAE,QAAQ,KAAK;AAAA,IACzC,EAAE;AAAA,EACJ;AACF;AAEA,IAAM,aAA2C;AAAA,EAC/C,OAAO;AAAA,EACP,KAAK;AAAA,EACL,MAAM;AAAA,EACN,aAAa;AACf;AAGO,SAAS,aAAa,GAAqB;AAChD,QAAM,OAAO,EAAE,OAAO,IAAI,uBAAuB,YAAY,EAAE,IAAI;AACnE,SAAO,GAAG,WAAW,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,KAAK,IAAI,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAI,EAAE,KAAK,CAAC;AACzG;AAEO,IAAM,kBACX;;;ACrPF,IAAM,QAAQ,oBAAI,IAAI,CAAC,QAAQ,SAAS,QAAQ,SAAS,MAAM,CAAC;AAGzD,SAAS,cAAc,OAA0B;AACtD,QAAM,SAAmB,CAAC;AAC1B,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO,CAAC,iCAAiC;AACpE,QAAM,QAAQ,CAAC,KAAK,MAAM;AACxB,UAAM,KAAK,SAAS,CAAC;AACrB,QAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM;AAC3C,aAAO,KAAK,GAAG,EAAE,qBAAqB;AACtC;AAAA,IACF;AACA,UAAM,IAAI;AACV,QAAI,OAAO,EAAE,OAAO,YAAY,CAAC,MAAM,IAAI,EAAE,EAAE,GAAG;AAChD,aAAO,KAAK,GAAG,EAAE,yBAAyB,CAAC,GAAG,KAAK,EAAE,KAAK,IAAI,CAAC,EAAE;AACjE;AAAA,IACF;AACA,SAAK,EAAE,OAAO,WAAW,EAAE,OAAO,WAAW,OAAO,EAAE,aAAa;AACjE,aAAO,KAAK,GAAG,EAAE,KAAK,EAAE,EAAE,mBAAmB;AAC/C,QAAI,EAAE,OAAO,UAAU,OAAO,EAAE,OAAO;AACrC,aAAO,KAAK,GAAG,EAAE,iBAAiB;AACpC,QAAI,EAAE,OAAO,WAAW,OAAO,EAAE,QAAQ;AACvC,aAAO,KAAK,GAAG,EAAE,qBAAqB;AACxC,QAAI,EAAE,OAAO,UAAU,OAAO,EAAE,QAAQ;AACtC,aAAO,KAAK,GAAG,EAAE,oBAAoB;AACvC,QAAI,EAAE,OAAO,QAAQ;AACnB,YAAM,IAAI,EAAE;AACZ,YAAM,QACJ,OAAO,MAAM,YACb,MAAM,QACN,OAAQ,EAAwB,QAAQ,YACxC,OAAO,KAAK,CAAC,EAAE,WAAW;AAC5B,UAAI,OAAO,MAAM,YAAY,CAAC;AAC5B,eAAO;AAAA,UACL,GAAG,EAAE;AAAA,QACP;AACF,UACE,OAAO,MAAM,YACb,sBAAsB,KAAK,OAAO,EAAE,QAAQ,CAAC,KAC7C,EAAE,SAAS;AAEX,eAAO;AAAA,UACL,GAAG,EAAE,gCAAgC,OAAO,EAAE,QAAQ,CAAC;AAAA,QACzD;AAAA,IACJ;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAGO,SAAS,cAAc,OAA0C;AACtE,QAAM,MAAM,oBAAI,IAAY;AAC5B,aAAW,KAAK,SAAS,CAAC,GAAG;AAC3B,QAAI,EAAE,OAAO,UAAU,OAAO,EAAE,SAAS,SAAU,KAAI,IAAI,EAAE,KAAK,GAAG;AAAA,EACvE;AACA,SAAO,CAAC,GAAG,GAAG;AAChB;AAGO,SAAS,iBACd,GACA,KACoC;AACpC,MAAI,OAAO,MAAM,SAAU,QAAO,EAAE,OAAO,GAAG,QAAQ,MAAM;AAC5D,QAAM,IAAI,IAAI,EAAE,GAAG;AACnB,MAAI,MAAM,UAAa,MAAM;AAC3B,UAAM,IAAI;AAAA,MACR,mCAAmC,EAAE,GAAG;AAAA,IAC1C;AACF,SAAO,EAAE,OAAO,GAAG,QAAQ,KAAK;AAClC;AAgBA,eAAsB,SACpB,MACA,OACA,MAKsB;AACtB,QAAM,MAAM,KAAK,OAAO,CAAC;AACzB,QAAM,QAAkB,CAAC;AACzB,aAAW,CAAC,GAAG,IAAI,KAAK,MAAM,QAAQ,GAAG;AACvC,UAAM,SAAS,QAAQ,QAAQ,OAAO,KAAK,OAAO,WAAW,KAAK,KAAK;AACvE,QAAI;AACF,cAAQ,KAAK,IAAI;AAAA,QACf,KAAK;AACH,gBAAM,KAAK,MAAM,KAAK,EAAE;AACxB,gBAAM,KAAK,UAAU,CAAC,SAAS,KAAK,EAAE,IAAI;AAC1C;AAAA,QACF,KAAK;AACH,gBAAM,KACH,KAAK,KAAK,KAAK,EAAE,WAAW,eAAe,SAAS,KAAM,CAAC,EAC3D,MAAM,MAAM,IAAI;AACnB,gBAAM,KAAK,UAAU,CAAC,SAAS,KAAK,GAAG,EAAE;AACzC;AAAA,QACF,KAAK;AACH,gBAAM,KAAK,SAAS,MAAM,KAAK,GAAG;AAClC,gBAAM,KAAK,MAAM,MAAM;AACvB,gBAAM,KAAK,UAAU,CAAC,UAAU,KAAK,GAAG,EAAE;AAC1C;AAAA,QACF,KAAK,SAAS;AACZ,gBAAM,MAAM,KAAK,QAAQ,KAAK,QAAQ,EAAE,MAAM;AAC9C,gBAAM,IAAI,QAAQ,EAAE,OAAO,WAAW,SAAS,IAAK,CAAC;AACrD,gBAAM,IAAI,MAAM;AAChB,gBAAM,KAAK,MAAM,MAAM;AACvB,gBAAM,KAAK,UAAU,CAAC,UAAU,KAAK,QAAQ,EAAE;AAC/C;AAAA,QACF;AAAA,QACA,KAAK,QAAQ;AACX,gBAAM,MAAM,KAAK,QAAQ,KAAK,QAAQ,EAAE,MAAM;AAC9C,gBAAM,IAAI,QAAQ,EAAE,OAAO,WAAW,SAAS,IAAK,CAAC;AACrD,gBAAM,EAAE,OAAO,OAAO,IAAI,iBAAiB,KAAK,MAAM,GAAG;AACzD,gBAAM,IAAI,KAAK,KAAK;AACpB,gBAAM,KAAK,MAAM,MAAM;AAEvB,gBAAM;AAAA,YACJ,UAAU,CAAC,SAAS,SAAS,MAAO,KAAK,KAAyB,GAAG,MAAM,GAAG,MAAM,MAAM,QAAQ,SAAS,KAAK,QAAQ;AAAA,UAC1H;AACA;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,GAAG;AACV,UAAI,aAAa,cAAe,OAAM;AACtC,aAAO;AAAA,QACL,KAAK;AAAA,QACL;AAAA,QACA,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,IAAI,KAAK;AAAA,UACT,GAAI,cAAc,OAAO,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;AAAA,QAC1D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,KAAK,MAAM,QAAQ,MAAM;AACpC;AAGO,IAAM,gBAAN,cAA4B,MAAM;AAAA,EACvC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAGO,IAAM,aAAN,cAAyB,MAAM;AAAA,EACpC,YAAmB,QAA4C;AAC7D;AAAA,MACE,UAAU,OAAO,IAAI,IAAI,OAAO,EAAE,GAAG,OAAO,WAAW,IAAI,OAAO,QAAQ,KAAK,EAAE;AAAA,IACnF;AAHiB;AAIjB,SAAK,OAAO;AAAA,EACd;AAAA,EALmB;AAMrB;AAGO,SAAS,aACd,OACwB;AACxB,QAAM,MAA8B,CAAC;AACrC,aAAW,KAAK,SAAS,CAAC,GAAG;AAC3B,UAAM,IAAI,EAAE,QAAQ,GAAG;AACvB,QAAI,KAAK,EAAG,OAAM,IAAI,MAAM,mCAAmC,CAAC,GAAG;AACnE,QAAI,EAAE,MAAM,GAAG,CAAC,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC;AAAA,EAC3C;AACA,SAAO;AACT;;;AChDA,IAAM,eAAe;AAGd,SAAS,SAAS,KAAkD;AACzE,QAAM,OAAO,IAAI,SAAS,IAAI,QAAQ,IAAI,YAAY,IAAI,UAAU;AACpE,MAAI,IAAI;AACR,SAAO,IAAI,IAAI,SAAS,GAAG;AACzB,QAAI,IAAI,CAAC,MAAM,IAAM,QAAO;AAC5B,UAAM,SAAS,IAAI,IAAI,CAAC;AACxB,UAAM,MAAM,KAAK,UAAU,IAAI,CAAC;AAChC,QACE,UAAU,OACV,UAAU,OACV,WAAW,OACX,WAAW,OACX,WAAW,KACX;AACA,aAAO,EAAE,GAAG,KAAK,UAAU,IAAI,CAAC,GAAG,GAAG,KAAK,UAAU,IAAI,CAAC,EAAE;AAAA,IAC9D;AACA,SAAK,IAAI;AAAA,EACX;AACA,SAAO;AACT;AAGO,SAAS,YAAY,MAA0B;AACpD,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,MAAM,IAAI,WAAW,IAAI,MAAM;AACrC,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,IAAK,KAAI,CAAC,IAAI,IAAI,WAAW,CAAC;AAC9D,SAAO;AACT;AAEA,eAAsB,WACpB,SACA,KACA,SACA,MACA,KACA,OAAmB,CAAC,GACG;AACvB,QAAM,MAAM,KAAK,WAAW;AAC5B,QAAM,QAAQ,MACV,CAAC,OAAe,UAAU,KAAK,IAAI,IAAI,YAAY,CAAC,IACpD;AAEJ,QAAM,aAAa,MAAM,WAAY,KAAK,sBAAsB;AAChE,QAAM,KAAK,QAAQ,UAAU,SAAS;AACtC,QAAM,KAAK,QAAQ,UAAU,UAAU;AACvC,QAAM,UACJ,KAAK,WACJ,MAAM,QAAQ,WAAW;AAAA,IACxB,UAAU,EAAE,OAAO,IAAI,QAAQ,GAAG;AAAA,IAClC,mBAAmB;AAAA,IACnB,GAAI,KAAK,eAAe,EAAE,cAAc,KAAK,aAAa,IAAI,CAAC;AAAA,IAC/D,GAAI,KAAK,WAAW,OAAO,KAAK,KAAK,OAAO,EAAE,SAC1C,EAAE,kBAAkB,KAAK,QAAQ,IACjC,CAAC;AAAA,EACP,CAAC;AACH,MAAI,KAAK,WAAW,KAAK,WAAW,OAAO,KAAK,KAAK,OAAO,EAAE;AAC5D,UAAM,QAAQ,oBAAoB,KAAK,OAAO;AAGhD,QAAM,QAAQ,QAAQ,QAAQ,CAAC;AAC/B,MAAI,MAAM,OAAQ,OAAM,QAAQ,cAAc,eAAe,KAAK,CAAC;AACnE,QAAM,cAAc,IAAI,YAAY,KAAK;AACzC,QAAM,OAAO,MAAM,QAAQ,QAAQ;AAGnC,QAAM,gBAAgB,OAAO,SAAiB;AAC5C,UAAM,OAAO,MAAM,KAAK,SAAS,cAAc,EAAE,MAAM,MAAM,IAAI;AACjE,gBAAY,IAAI,MAAM,IAAyC;AAAA,EACjE;AACA,OAAK,GAAG,WAAW,CAAC,MAAM;AACxB,QAAI,EAAE,KAAK,MAAM,QAAS,KAAI,mBAAmB,EAAE,KAAK,CAAC,EAAE;AAAA,EAC7D,CAAC;AAED,MAAI,QAAQ,GAAG,EAAE;AACjB,MAAI,aAAa;AACjB,MAAI,WAAW,MAAM,KAClB,KAAK,KAAK,EAAE,WAAW,eAAe,SAAS,KAAM,CAAC,EACtD,MAAM,MAAM;AACX,iBAAa;AACb,QAAI,4CAAuC;AAC3C,WAAO;AAAA,EACT,CAAC;AACH,QAAM,MAAM,GAAG;AAKf,QAAM,QAAQ,QAAQ,SAAS,CAAC;AAChC,MAAI,MAAM,QAAQ;AAChB,QAAI,UAAU,MAAM,MAAM,sBAAsB;AAChD,UAAM,SAAS,MAAM,SAAS,MAAM,OAAO;AAAA,MACzC,OAAO;AAAA,MACP,KAAK,KAAK,OAAO,CAAC;AAAA,IACpB,CAAC;AACD,eAAW,QAAQ,OAAO,MAAO,KAAI,MAAM,IAAI,EAAE;AACjD,QAAI,OAAO,QAAQ;AACjB,YAAM,QAAQ,MAAM,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AACpC,YAAM,IAAI,WAAW,OAAO,MAAM;AAAA,IACpC;AAEA,QAAI,QAAQ,GAAG,gBAAgB;AAC/B,eAAW,MAAM,KACd,KAAK,KAAK,EAAE,WAAW,eAAe,SAAS,KAAM,CAAC,EACtD,MAAM,MAAM,IAAI;AACnB,UAAM,MAAM,GAAG;AAAA,EACjB;AAIA,MAAI,OAA2B;AAC/B,MAAI,KAAK,WAAW;AAClB,UAAM,OAAQ,MAAM,KAAK,SAAS,UAAU,EAAE,MAAM,MAAM,IAAI;AAI9D,QAAI;AACF,aACG,MAAM,KAAK,UAAU;AAAA,QACpB,UAAU;AAAA,QACV,WAAW,KAAK,IAAI;AAAA,QACpB,GAAI,WAAW,EAAE,QAAQ,SAAS,OAAO,EAAE,IAAI,CAAC;AAAA,QAChD,gBAAgB,MAAM,kBAAkB;AAAA,QACxC,kBAAkB,MAAM,oBAAoB;AAAA,QAC5C,kBAAkB,MAAM,oBAAoB;AAAA,MAC9C,CAAC,KAAM;AAAA,IACX,SAAS,GAAG;AACV,YAAM,QAAQ,MAAM,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AACpC,YAAM;AAAA,IACR;AAAA,EACF;AAEA,QAAM,cAAc,EAAE;AAEtB,QAAM,MAAM,MAAM,QAAQ,cAAc,IAAI;AAC5C,QAAM,SAAqB,CAAC;AAC5B,MAAI,WAAW;AACf,MAAI,KAAK;AAGT,QAAM,QAAmD,EAAE,MAAM,KAAK;AAGtE,MAAI,YAA2B,QAAQ,QAAQ;AAC/C,MAAI,CAAC;AACH,QAAI,GAAG,wBAAwB,CAAC,OAAO;AACrC,YAAM,OAAO,GAAG,SAAS,YACrB,GAAG,SAAS,YAAY,MACxB,KAAK,IAAI;AACb,YAAM,OAAO,SAAS,OAAO,UAAU,EAAE,SAAS,GAAG,GAAG,CAAC;AACzD,YAAM,QAAQ,YAAY,GAAG,IAAI;AACjC,UAAI,CAAC,MAAM,KAAM,OAAM,OAAO,SAAS,KAAK;AAC5C,aAAO,KAAK,EAAE,MAAM,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,EAAE,CAAC,EAAE,CAAC;AAC7D,kBAAY,UAAU,KAAK,MAAM,KAAK,MAAM,MAAM,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AACxE,UACG,KAAK,2BAA2B,EAAE,WAAW,GAAG,UAAU,CAAC,EAC3D,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACnB,CAAC;AAEH,OAAK,KAAK,IAAI;AACd,MAAI,CAAC;AACH,UAAM,IAAI,KAAK,wBAAwB;AAAA,MACrC,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,UAAU;AAAA,MACV,WAAW;AAAA,MACX,eAAe;AAAA,IACjB,CAAC;AAEH,QAAM,SAAwB,CAAC;AAC/B,QAAM,MAAM,EAAE,GAAG,IAAI,GAAG,GAAG;AAC3B,QAAM,MAAM,MAAM,KAAK,IAAI,IAAI;AAC/B,QAAM,OAAO,CAAC,MAA8B,OAAO,KAAK,EAAE,GAAG,IAAI,GAAG,GAAG,EAAE,CAAC;AAE1E,QAAM,KAAK,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;AAClC,OAAK,EAAE,GAAG,IAAI,GAAG,GAAG,IAAI,GAAG,MAAM,OAAO,CAAC;AAEzC,QAAM,QAAuB,EAAE,KAAK,MAAM,KAAK,IAAI,GAAG,MAAM;AAM5D,QAAM,SAAS,OAAO,IAAY,OAAe;AAC/C,UAAM,OAAO,KAAK,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,CAAC;AAC9C,UAAM,OAAO,EAAE,GAAG,IAAI;AACtB,UAAM;AAAA,MACJ,MAAM,IAAI,gBAAgB,IAAI;AAAA,MAC9B,OAAO,MAAM;AACX,YAAI,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK;AACjC,YAAI,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK;AACjC,cAAM,KAAK,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;AAClC,aAAK,EAAE,GAAG,KAAK,MAAM,IAAI,CAAC,GAAG,GAAG,KAAK,MAAM,IAAI,CAAC,GAAG,MAAM,OAAO,CAAC;AAAA,MACnE;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAKA,QAAM,aAAa,MACjB,KACG;AAAA,IACC,MAAM,IAAI,QAAc,CAAC,MAAM,sBAAsB,MAAM,EAAE,CAAC,CAAC;AAAA,EACjE,EACC,MAAM,MAAM,MAAS;AAE1B,QAAM,QAAQ,OAAO,aAA2C;AAC9D,UAAM,MAAM,KAAK,QAAQ,QAAQ,EAAE,MAAM;AACzC,QAAI;AACF,YAAM,IAAI,QAAQ,EAAE,OAAO,WAAW,SAAS,IAAK,CAAC;AACrD,YAAM,SAAS,MAAM,KAClB,SAAS,MAAM,CAAC,OAAO,SAAS,OAAO,OAAO,CAAC,EAC/C,MAAM,MAAM,CAAC,GAAG,CAAC,CAAC;AACrB,YAAM,IAAI,uBAAuB;AACjC,YAAM,QAAQ,MAAM,KACjB,SAAS,MAAM,CAAC,OAAO,SAAS,OAAO,OAAO,CAAC,EAC/C,MAAM,MAAM,MAAM;AAGrB,UAAI,OAAO,CAAC,MAAM,MAAM,CAAC,KAAK,OAAO,CAAC,MAAM,MAAM,CAAC;AACjD,cAAM,MAAM,gBAAgB;AAC9B,YAAM,KAAK,MAAM,IAAI,YAAY;AACjC,UAAI,CAAC,GAAI,QAAO;AAChB,aAAO,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,OAAO,GAAG,GAAG,OAAO;AAAA,IACvD,QAAQ;AACN,UAAI,qCAAqC,QAAQ,GAAG;AACpD,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,UAAU,OAAO,SAAe;AACpC,UAAM,OAAO,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,IAAI,CAAC;AACrD,UAAM,MAAM,aAAa;AACzB,SAAK;AAAA,MACH,GAAG,KAAK,MAAM,IAAI,CAAC;AAAA,MACnB,GAAG,KAAK,MAAM,IAAI,CAAC;AAAA,MACnB,MAAM;AAAA,MACN,QAAQ;AAAA,MACR;AAAA,IACF,CAAC;AACD,UAAM,KAAK,MAAM,KAAK;AACtB,UAAM,MAAM,aAAa;AACzB,UAAM,KAAK,MAAM,GAAG;AACpB,SAAK;AAAA,MACH,GAAG,KAAK,MAAM,IAAI,CAAC;AAAA,MACnB,GAAG,KAAK,MAAM,IAAI,CAAC;AAAA,MACnB,MAAM;AAAA,MACN,QAAQ;AAAA,MACR;AAAA,IACF,CAAC;AAAA,EACH;AAOA,QAAM,cAAc,YAAoC;AACtD,QAAI,IAAK,QAAO;AAChB,UAAM,UAAU,IAAI;AACpB,UAAM,SAAS,OAAO;AACtB,eAAS;AACP,YAAM,IAAI,IAAI;AACd,YAAM,OAAO,OAAO,SAAS,SAAS,OAAO,OAAO,SAAS,CAAC,IAAI;AAClE,YAAM,cAAc,OAAO,IAAI,KAAK,MAAM;AAC1C,UAAI,cAAc,EAAE,YAAY,IAAI,SAAS,YAAY,CAAC,MAAM;AAC9D,eAAO;AACT,YAAM,MAAM,EAAE;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,UAAyB,CAAC;AAKhC,QAAM,YAAwB,CAAC;AAC/B,QAAM,QAAoB,CAAC;AAC3B,MAAI,SAAS;AACb,aAAW,CAAC,SAAS,IAAI,KAAK,QAAQ,MAAM,QAAQ,GAAG;AAIrD,QAAI,WAAW,IAAI,GAAG,UAAU,GAAG;AACjC,eAAS;AACT;AAAA,IACF;AACA,UAAM,YAAY,IAAI;AAItB,QAAI,YAAY;AAChB,UAAM,UAAU,OAAU,OAAqC;AAC7D,YAAM,IAAI,IAAI;AACd,YAAM,MAAM,MAAM,GAAG;AACrB,mBAAa,IAAI,IAAI;AACrB,aAAO;AAAA,IACT;AACA,UAAM,gBAAgB,QAAQ;AAI9B,UAAM,YAAY,KAAK,IAAI;AAG3B,QAAI,WAAwB;AAC5B,YAAQ,KAAK,IAAI;AAAA,MACf,KAAK;AACH,cAAM,MAAM,UAAU,KAAK,IAAI,IAAI,GAAG,UAAU,CAAC;AACjD;AAAA,MACF,KAAK;AACH,cAAM,OAAO,KAAK,GAAG,KAAK,CAAC;AAC3B;AAAA,MACF,KAAK,SAAS;AACZ,cAAM,OAAO,MAAM,MAAM,KAAK,QAAQ;AACtC,mBAAW;AACX,YAAI,MAAM;AACR,gBAAM,QAAQ,MAAM,OAAO,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,IAAI,CAAC,CAAC;AACpE,cAAI,SAAS,KAAK,QAAQ,EAAE;AAC5B,gBAAM,MAAM,KAAK,MAAM,GAAG;AAAA,QAC5B;AACE,kBAAQ,KAAK,EAAE,MAAM,SAAS,IAAI,KAAK,IAAI,UAAU,KAAK,SAAS,CAAC;AACtE;AAAA,MACF;AAAA,MACA,KAAK,SAAS;AACZ,cAAM,OAAO,MAAM,MAAM,KAAK,QAAQ;AACtC,mBAAW;AACX,YAAI,MAAM;AACR,gBAAM,QAAQ,MAAM,QAAQ,IAAI,CAAC;AACjC,cAAI,SAAS,KAAK,QAAQ,EAAE;AAG5B,gBAAM,cAAc,MAAM,QAAQ,MAAM,YAAY,CAAC;AACrD,gBAAM,MAAM,WAAW,SAAS,IAAI,GAAG,WAAW,CAAC;AAAA,QACrD;AACE,kBAAQ,KAAK,EAAE,MAAM,SAAS,IAAI,KAAK,IAAI,UAAU,KAAK,SAAS,CAAC;AACtE;AAAA,MACF;AAAA,MACA,KAAK,QAAQ;AACX,cAAM,OAAO,MAAM,MAAM,KAAK,QAAQ;AACtC,mBAAW;AACX,YAAI,MAAM;AAKR,cAAI,KAAK,UAAU,MAAO,OAAM,QAAQ,MAAM,QAAQ,IAAI,CAAC;AAM3D,gBAAM,QAAQ,KAAK,WAAW;AAC9B,gBAAM,OAAO,MACX,KAAK;AAAA,YACH,GAAG,KAAK,MAAM,KAAK,IAAI,KAAK,IAAI,CAAC;AAAA,YACjC,GAAG,KAAK,MAAM,KAAK,IAAI,KAAK,IAAI,CAAC;AAAA,YACjC,MAAM;AAAA,YACN;AAAA,UACF,CAAC;AACH,cAAI,WAAW;AACf,gBAAM;AAAA,YACJ,CAAC,GAAG,KAAK,IAAI;AAAA,YACb;AAAA,YACA,OAAO,OAAO;AACZ,kBAAI,IAAI,IAAI,YAAY,KAAK;AAC3B,2BAAW,IAAI;AACf,qBAAK;AAAA,cACP;AACA,oBAAM,KAAK,SAAS,KAAK,EAAE;AAAA,YAC7B;AAAA,YACA;AAAA,UACF;AACA,eAAK;AACL,cAAI,SAAS,KAAK,IAAI,UAAU,KAAK,QAAQ,EAAE;AAC/C,gBAAM,QAAQ,MAAM,MAAM,SAAS,IAAI,CAAC,CAAC;AAAA,QAC3C;AACE,kBAAQ,KAAK,EAAE,MAAM,SAAS,IAAI,KAAK,IAAI,UAAU,KAAK,SAAS,CAAC;AACtE;AAAA,MACF;AAAA,MACA,KAAK,UAAU;AAMb,cAAM,QAAQ,KAAK,IAAI,KAAK,EAAE;AAC9B,cAAM,MAAM,KAAK,KAAK,KAAK,EAAE;AAC7B,YAAI,OAAO;AACX,cAAM;AAAA,UAAQ,MACZ;AAAA,YACE,MAAM,IAAI,eAAe,KAAK;AAAA,YAC9B,OAAO,MAAM;AACX,oBAAM,SAAS,KAAK,MAAM,QAAQ,CAAC;AACnC,oBAAM,QAAQ,SAAS;AACvB,kBAAI,SAAS,EAAG;AAChB,oBAAM,KAAK,MAAM,MAAM,GAAG,MAAM,KAAK;AACrC,qBAAO;AACP,mBAAK;AAAA,gBACH,GAAG,KAAK,MAAM,IAAI,CAAC;AAAA,gBACnB,GAAG,KAAK,MAAM,IAAI,CAAC;AAAA,gBACnB,MAAM;AAAA,cACR,CAAC;AACD,oBAAM,WAAW;AAAA,YACnB;AAAA,YACA;AAAA,UACF;AAAA,QACF;AACA,cAAM,QAAQ,MAAM,MAAM,SAAS,IAAI,CAAC,CAAC;AACzC;AAAA,MACF;AAAA,MACA,KAAK,QAAQ;AAEX,YAAI,QAAyC;AAC7C,YAAI;AACJ,YAAI,KAAK,UAAU;AACjB,gBAAM,MAAM,MAAM,MAAM,KAAK,QAAQ;AACrC,qBAAW;AACX,cAAI,KAAK;AACP,mBAAO;AACP,oBAAQ,EAAE,GAAG,IAAI,IAAI,IAAI,IAAI,GAAG,GAAG,IAAI,IAAI,IAAI,IAAI,EAAE;AAAA,UACvD,OAAO;AACL,oBAAQ,KAAK;AAAA,cACX,MAAM;AAAA,cACN,IAAI,KAAK;AAAA,cACT,UAAU,KAAK;AAAA,YACjB,CAAC;AACD;AAAA,UACF;AAAA,QACF,WAAW,OAAO,KAAK,MAAM,YAAY,OAAO,KAAK,MAAM,UAAU;AACnE,kBAAQ,EAAE,GAAG,KAAK,GAAG,GAAG,KAAK,EAAE;AAAA,QACjC;AACA,YAAI,CAAC,MAAO;AACZ,cAAM,QAAQ,MAAM,OAAO,MAAM,GAAG,MAAM,CAAC,CAAC;AAC5C,cAAM,QAAQ,MAAM,MAAM,aAAa,CAAC;AACxC,aAAK;AAAA,UACH,GAAG,KAAK,MAAM,IAAI,CAAC;AAAA,UACnB,GAAG,KAAK,MAAM,IAAI,CAAC;AAAA,UACnB,MAAM;AAAA,UACN,QAAQ;AAAA,UACR;AAAA,QACF,CAAC;AACD,cAAM,KAAK,MAAM,KAAK;AACtB,cAAM,QAAQ,MAAM,MAAM,aAAa,CAAC;AAGxC,cAAM,MAAM,KAAK,MAAM;AACvB,cAAM,OAAO,EAAE,GAAG,IAAI;AACtB,cAAM;AAAA,UACJ;AAAA,UACA,OAAO,MAAM;AACX,gBAAI,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK;AACtC,gBAAI,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK;AACtC,kBAAM,KAAK,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;AAClC,iBAAK,EAAE,GAAG,KAAK,MAAM,IAAI,CAAC,GAAG,GAAG,KAAK,MAAM,IAAI,CAAC,GAAG,MAAM,OAAO,CAAC;AAAA,UACnE;AAAA,UACA;AAAA,QACF;AACA,cAAM,QAAQ,MAAM,MAAM,SAAS,EAAE,IAAI,OAAO,CAAC,CAAC,CAAC;AACnD,cAAM,KAAK,MAAM,GAAG;AACpB,aAAK;AAAA,UACH,GAAG,KAAK,MAAM,IAAI,CAAC;AAAA,UACnB,GAAG,KAAK,MAAM,IAAI,CAAC;AAAA,UACnB,MAAM;AAAA,UACN,QAAQ;AAAA,UACR;AAAA,QACF,CAAC;AACD;AAAA,UACE,QAAQ,KAAK,YAAY,GAAG,MAAM,CAAC,IAAI,MAAM,CAAC,EAAE,WAAM,KAAK,EAAE,IAAI,KAAK,EAAE;AAAA,QAC1E;AACA,cAAM,MAAM,GAAG;AACf;AAAA,MACF;AAAA,IACF;AACA,UAAM,WAAW;AACjB,cAAU,KAAK;AAAA,MACb,MAAM;AAAA,MACN,GAAI,SAAS,KAAK,EAAE,IAAI,SAAS,GAAG,IAAI,CAAC;AAAA,MACzC,IAAI,KAAK;AAAA,MACT,GAAI,SAAS,WAAW,EAAE,UAAU,SAAS,SAAS,IAAI,CAAC;AAAA,MAC3D,QAAQ,EAAE,YAAY,KAAM,QAAQ,CAAC;AAAA,MACrC,MAAM,EAAE,IAAI,IAAI,KAAM,QAAQ,CAAC;AAAA,MAC/B,GAAI,QAAQ,SAAS,gBAAgB,EAAE,SAAS,KAAK,IAAI,CAAC;AAAA,MAC1D,GAAI,KAAK,IAAI,MAAM,YAAY,EAAE,WAAW,KAAK,IAAI,CAAC;AAAA,MACtD,GAAI,WACA;AAAA,QACE,MAAM;AAAA,UACJ,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC;AAAA,UACxB,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC;AAAA,UACxB,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC;AAAA,UACxB,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC;AAAA,QAC1B;AAAA,MACF,IACA,CAAC;AAAA,IACP,CAAC;AACD,UAAM,KAAK;AAAA,MACT,MAAM;AAAA,MACN,IAAI,KAAK;AAAA,MACT,SAAS,QAAQ,IAAqC;AAAA,MACtD;AAAA,MACA,QAAQ,IAAI,IAAI;AAAA,IAClB,CAAC;AAED,UAAM,cAAc,OAAO;AAAA,EAC7B;AACA,MAAI,CAAC,OAAQ,OAAM,MAAM,gBAAgB;AACzC,MAAI,UAAU,WAAW,IAAI,GAAG,UAAU,GAAG;AAC3C,aAAS;AACT,QAAI,MAAM,WAAW,UAAU,CAAC,EAAE;AAAA,EACpC;AAEA,QAAM,aAAa,SAAS,KAAK,IAAI,IAAI,GAAG,aAAa,GAAI,IAAI,IAAI;AACrE,QAAM,IAAI,KAAK,qBAAqB,EAAE,MAAM,MAAM;AAAA,EAAC,CAAC;AACpD,QAAM,MAAM,GAAG;AACf,QAAM,YAAY,MAAM,KAAK,MAAM,EAAE,MAAM,MAAM,EAAE;AACnD,QAAM,QAAQ,MAAM;AAEpB,QAAM;AAEN,QAAM,OAAsB;AAAA,IAC1B,KAAK;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,KAAK;AAAA,IACL,UAAU;AAAA,IACV,cAAc,MAAM,MAAM,KAAK;AAAA,IAC/B,eAAe,MAAM,MAAM,KAAK;AAAA,IAChC,gBAAgB;AAAA,IAChB,SAAS;AAAA,IACT;AAAA,IACA,UAAU,KAAK,YAAY;AAAA,IAC3B,UAAU;AAAA,IACV,OAAO;AAAA,IACP,GAAI,OACA,EAAE,MAAM,EAAE,MAAM,KAAK,MAAM,OAAO,KAAK,OAAO,QAAQ,KAAK,OAAO,EAAE,IACpE,CAAC;AAAA,IACL,GAAI,YAAY,UAAU,EAAE,SACxB,EAAE,WAAW,YAAY,UAAU,EAAE,IACrC,CAAC;AAAA,IACL,GAAI,MAAM,SAAS,EAAE,OAAO,YAAY,MAAM,KAAK,EAAE,IAAI,CAAC;AAAA,EAC5D;AAIA,QAAM,UAAwB,CAAC;AAC/B,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,MAAM,OAAO,CAAC,EAAE,MAAM,OAAO,IAAI,CAAC,EAAE;AAC1C,QAAI,OAAO,KAAK;AACd,cAAQ,KAAK;AAAA,QACX,MAAM,EAAE,OAAO,IAAI,CAAC,EAAE,MAAM,KAAM,QAAQ,CAAC;AAAA,QAC3C,IAAI,EAAE,OAAO,CAAC,EAAE,MAAM,KAAM,QAAQ,CAAC;AAAA,QACrC,IAAI;AAAA,MACN,CAAC;AAAA,IACH;AAAA,EACF;AACA,QAAM,WAAW,QAAQ,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,IAAI,CAAC;AACzD,QAAM,YACJ,aAAa,IAAI,KAAK,MAAO,WAAW,aAAc,GAAG,IAAI;AAG/D,QAAM,OAAO,SAAS,WAAW,QAAQ,QAAQ,UAAU;AAC3D,MAAI,UAAU,IAAI,EAAG,KAAI,eAAe,SAAS,IAAI,CAAC,EAAE;AAExD,QAAM,OAAO,WAAW,KAAK;AAC7B,MAAI,CAAC,IAAK,KAAI,MAAM,SAAS,IAAI,CAAC,EAAE;AACpC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,YAAY,UAAU;AAAA,IACjC,OAAO,YAAY,MAAM,KAAK;AAAA,IAC9B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,EAAE,GAAG,SAAS,IAAI;AAAA,EAC7B;AACF;;;ACrpBA,IAAMC,SAAQ,oBAAI,IAAI;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGM,SAAS,gBAAgB,OAA0B;AACxD,QAAM,SAAmB,CAAC;AAC1B,MAAI,OAAO,UAAU,YAAY,UAAU;AACzC,WAAO,CAAC,oCAAoC;AAC9C,QAAM,MAAM;AACZ,MAAI,IAAI,QAAQ,UAAa,OAAO,IAAI,QAAQ;AAC9C,WAAO,KAAK,sBAAsB;AACpC,MAAI,IAAI,aAAa,QAAW;AAC9B,UAAM,IAAa,IAAI;AACvB,QAAI,OAAO,MAAM,YAAY,MAAM,MAAM;AACvC,aAAO,KAAK,oCAAoC;AAAA,IAClD,OAAO;AACL,YAAM,KAAK;AACX,UAAI,OAAO,GAAG,UAAU,YAAY,OAAO,GAAG,WAAW,UAAU;AACjE,eAAO,KAAK,oCAAoC;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AACA,MAAI,IAAI,UAAU,OAAW,QAAO,KAAK,GAAG,cAAc,IAAI,KAAK,CAAC;AACpE,MAAI,IAAI,SAAS,QAAW;AAC1B,QAAI,CAAC,MAAM,QAAQ,IAAI,IAAI,GAAG;AAC5B,aAAO,KAAK,mDAAmD;AAAA,IACjE,OAAO;AACL,UAAI,KAAK,QAAQ,CAAC,KAAK,MAAM;AAC3B,cAAM,KAAK,QAAQ,CAAC;AACpB,cAAM,IAAK,OAAO,CAAC;AACnB,YAAI,OAAO,EAAE,aAAa,YAAY,CAAC,EAAE,SAAS,KAAK;AACrD,iBAAO,KAAK,GAAG,EAAE,oBAAoB;AACvC,YAAI,EAAE,OAAO,UAAa,EAAE,OAAO,UAAU,EAAE,OAAO;AACpD,iBAAO,KAAK,GAAG,EAAE,+BAA+B;AAClD,YAAI,EAAE,OAAO,WAAW,OAAO,EAAE,SAAS,YAAY,CAAC,EAAE;AACvD,iBAAO,KAAK,GAAG,EAAE,4CAA4C;AAC/D,YAAI,EAAE,OAAO,UAAU,EAAE,SAAS;AAChC,iBAAO,KAAK,GAAG,EAAE,oCAAoC;AAAA,MACzD,CAAC;AAAA,IACH;AAAA,EACF;AACA,MAAI,CAAC,MAAM,QAAQ,IAAI,KAAK,KAAK,IAAI,MAAM,WAAW,GAAG;AACvD,WAAO,KAAK,iCAAiC;AAC7C,WAAO;AAAA,EACT;AACA,QAAM,UAAU,oBAAI,IAAY;AAChC,MAAI,MAAM,QAAQ,CAAC,KAAK,MAAM;AAC5B,UAAM,KAAK,SAAS,CAAC;AACrB,QAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM;AAC3C,aAAO,KAAK,GAAG,EAAE,qBAAqB;AACtC;AAAA,IACF;AACA,UAAM,IAAI;AACV,QAAI,OAAO,EAAE,OAAO,YAAY,CAACA,OAAM,IAAI,EAAE,EAAE,GAAG;AAChD,aAAO,KAAK,GAAG,EAAE,yBAAyB,CAAC,GAAGA,MAAK,EAAE,KAAK,IAAI,CAAC,EAAE;AACjE;AAAA,IACF;AACA,QAAI,EAAE,OAAO,QAAW;AACtB,UAAI,OAAO,EAAE,OAAO,YAAY,CAAC,EAAE,GAAG,KAAK,GAAG;AAC5C,eAAO,KAAK,GAAG,EAAE,iCAAiC;AAAA,MACpD,WAAW,QAAQ,IAAI,EAAE,EAAE,GAAG;AAC5B,eAAO;AAAA,UACL,GAAG,EAAE,mBAAmB,EAAE,EAAE;AAAA,QAC9B;AAAA,MACF,OAAO;AACL,gBAAQ,IAAI,EAAE,EAAE;AAAA,MAClB;AAAA,IACF;AACA,UAAM,eAAe,EAAE,OAAO,WAAW,EAAE,OAAO,WAAW,EAAE,OAAO;AACtE,QAAI,gBAAgB,OAAO,EAAE,aAAa;AACxC,aAAO,KAAK,GAAG,EAAE,KAAK,EAAE,EAAE,mBAAmB;AAC/C,QAAI,EAAE,OAAO,UAAU,OAAO,EAAE,OAAO;AACrC,aAAO,KAAK,GAAG,EAAE,iBAAiB;AACpC,QACE,EAAE,OAAO,WACR,OAAO,EAAE,OAAO,YAAY,CAAC,OAAO,SAAS,EAAE,EAAE,KAAK,EAAE,KAAK;AAE9D,aAAO,KAAK,GAAG,EAAE,gDAA2C;AAC9D,QAAI,EAAE,OAAO,UAAU,OAAO,EAAE,SAAS;AACvC,aAAO,KAAK,GAAG,EAAE,mBAAmB;AACtC,QACE,EAAE,OAAO,UACT,EAAE,UAAU,UACZ,OAAO,EAAE,UAAU;AAEnB,aAAO,KAAK,GAAG,EAAE,oCAAoC;AACvD,QAAI,EAAE,OAAO,YAAY,OAAO,EAAE,OAAO;AACvC,aAAO,KAAK,GAAG,EAAE,mBAAmB;AACtC,QACE,EAAE,OAAO,WACR,OAAO,EAAE,MAAM,YAAY,OAAO,EAAE,MAAM,WAC3C;AACA,aAAO,KAAK,GAAG,EAAE,+BAA+B;AAAA,IAClD;AACA,QAAI,EAAE,OAAO,QAAQ;AACnB,UAAI,OAAO,EAAE,OAAO,YAAY,OAAO,EAAE,OAAO,UAAU;AACxD,eAAO,KAAK,GAAG,EAAE,iCAAiC;AAAA,MACpD;AACA,YAAM,WACJ,OAAO,EAAE,aAAa,YACrB,OAAO,EAAE,MAAM,YAAY,OAAO,EAAE,MAAM;AAC7C,UAAI,CAAC;AACH,eAAO;AAAA,UACL,GAAG,EAAE;AAAA,QACP;AAAA,IACJ;AAAA,EACF,CAAC;AACD,SAAO;AACT;;;ACjMA,IAAM,iBAAiB;AAehB,SAAS,gBAAgB,GAA8B;AAC5D,QAAM,IAAI,EAAE,aAAa,IAAI,EAAE,UAAU,KAAK;AAC9C,SAAO;AAAA;AAAA;AAAA,6BAGoB,cAAc;AAAA,sCACL,KAAK,UAAU,EAAE,cAAc,CAAC;AAAA,oCAClC,KAAK,UAAU,EAAE,OAAO,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wCAoBrB,KAAK,UAAU,EAAE,aAAa,CAAC,wBAAwB,KAAK,UAAU,CAAC,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4BAUpF,KAAK,UAAU,EAAE,KAAK,GAAG,CAAC,eAAe,KAAK,UAAU,EAAE,KAAK,MAAM,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWlG;;;AC7DA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAcA,SAAS,cACd,UACA,eACA,OAAyB,CAAC,GACd;AACZ,QAAM,SAAS,KAAK,WAChB,EAAE,OAAO,aAAa,qBAAqB,KAAK,QAAQ,EAAE,IAC1D,CAAC;AACL,QAAM,MAAM,oBAAoB,UAAU,eAAe,MAAM,EAAE;AACjE,MAAI,OAAO,aAAa,IAAI,OAAO,QAAQ;AAAA,IACzC,OAAO,IAAI,OAAO,KAAK;AAAA,IACvB,QAAQ,IAAI,OAAO,KAAK;AAAA,IACxB,UAAU,IAAI,OAAO,KAAK,aAAa;AAAA,IACvC,OAAO,IAAI;AAAA,IACX,QAAQ,IAAI;AAAA,EACd,CAAC;AACD,MAAI,QAAQ,cAAc,IAAI,OAAO,QAAQ;AAAA,IAC3C,YAAY,IAAI,OAAO,KAAK;AAAA,IAC5B,QAAQ,IAAI;AAAA,IACZ,UAAU,KAAK;AAAA,EACjB,CAAC;AACD,SAAO;AACT;","names":["askedMs","VERBS"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vosjs/render-core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "The render harness for deterministic vos renders: timeline-sharded chunk planning and stream-copy chunk concat. Pixels stay in the browser; this package owns the orchestration math and the Node-side media plumbing.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "vosso",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"sideEffects": false,
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"mediabunny": "^1.55.7",
|
|
35
|
-
"@vosjs/studio-core": "^0.
|
|
35
|
+
"@vosjs/studio-core": "^0.31.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"tsup": "^8.5.0",
|