@qwertybit/pr-preview 0.1.1 → 0.1.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/README.md +1 -1
- package/dist/cli/index.js +46 -7
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -239,4 +239,4 @@ This is an open-core project:
|
|
|
239
239
|
hosted clips, team reviews & sharing, coming soon.</sub>
|
|
240
240
|
</p>
|
|
241
241
|
|
|
242
|
-
<p align="center"><sub>Built by <a href="https://
|
|
242
|
+
<p align="center"><sub>Built by <a href="https://qwertybit.com">QwertyBit</a> · Free & Open Source · MIT</sub></p>
|
package/dist/cli/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
|
+
import { readFileSync } from "fs";
|
|
4
5
|
import { Command } from "commander";
|
|
5
6
|
|
|
6
7
|
// src/cli/commands/init.ts
|
|
@@ -1868,7 +1869,12 @@ async function detectBase(repoRoot, override) {
|
|
|
1868
1869
|
if (sha && sha !== current) return { sha, source: "merge-base", ref };
|
|
1869
1870
|
if (sha && sha === current) {
|
|
1870
1871
|
throw new GitError(
|
|
1871
|
-
`
|
|
1872
|
+
`Your branch has no commits beyond ${ref}, so there's no "before" to compare against.
|
|
1873
|
+
|
|
1874
|
+
\u2022 If your changes aren't committed yet, commit them \u2014 then ${ref} becomes the "before"
|
|
1875
|
+
and your commit the "after".
|
|
1876
|
+
\u2022 Or switch to your PR branch, or pass --base <ref> to choose a different base.
|
|
1877
|
+
\u2022 Or skip the comparison and record a single clip: pr-preview run --single`
|
|
1872
1878
|
);
|
|
1873
1879
|
}
|
|
1874
1880
|
}
|
|
@@ -2029,9 +2035,17 @@ async function runCommand(repoRoot, opts) {
|
|
|
2029
2035
|
if (passes === 1) return runSingleClip(repoRoot, config, outDir, opts);
|
|
2030
2036
|
if (opts.url) return runWithExternalApp(repoRoot, config, outDir, opts);
|
|
2031
2037
|
try {
|
|
2038
|
+
const currentBranch = await tryGit(repoRoot, ["symbolic-ref", "--quiet", "--short", "HEAD"]);
|
|
2039
|
+
if (!currentBranch) {
|
|
2040
|
+
throw new GitError(
|
|
2041
|
+
"You're not on a branch (detached HEAD).\n\n`pr-preview run` records the BEFORE on your PR's base branch and the AFTER on your\ncurrent branch \u2014 so it needs you to be on a branch. Check yours out first:\n git switch <your-branch>\n\n\u2026or skip the comparison and just record the current app:\n pr-preview run --single"
|
|
2042
|
+
);
|
|
2043
|
+
}
|
|
2032
2044
|
const base = await detectBase(repoRoot, opts.base ?? config.baseBranch);
|
|
2033
|
-
const currentBranch = await tryGit(repoRoot, ["rev-parse", "--abbrev-ref", "HEAD"]) ?? "HEAD";
|
|
2034
2045
|
log.info(`PR base: ${pc3.bold(base.ref)} (${base.sha.slice(0, 10)}, via ${base.source})`);
|
|
2046
|
+
if (await tryGit(repoRoot, ["status", "--porcelain"])) {
|
|
2047
|
+
log.info("Heads up: the AFTER clip captures your working tree, including uncommitted changes.");
|
|
2048
|
+
}
|
|
2035
2049
|
const worktree = await createBaseWorktree(repoRoot, base.sha);
|
|
2036
2050
|
if (!opts.keepWorktree) registerCleanup(() => worktree.remove());
|
|
2037
2051
|
log.success(`Base worktree at ${path14.relative(repoRoot, worktree.dir)}`);
|
|
@@ -2223,16 +2237,41 @@ async function saveJourneyFile(repoRoot, name, config, startUrl, steps, baseRef)
|
|
|
2223
2237
|
}
|
|
2224
2238
|
|
|
2225
2239
|
// src/cli/index.ts
|
|
2240
|
+
var version = "0.0.0";
|
|
2241
|
+
try {
|
|
2242
|
+
version = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8")).version;
|
|
2243
|
+
} catch {
|
|
2244
|
+
}
|
|
2226
2245
|
var program = new Command();
|
|
2227
|
-
program.name("pr-preview").description("Record a
|
|
2246
|
+
program.name("pr-preview").description("Record a UI journey once \u2014 get before/after video clips for your pull request.").version(version);
|
|
2228
2247
|
program.command("init").description("Scaffold pr-preview.config.js and .gitignore entries").action(() => wrap(() => initCommand(process.cwd())));
|
|
2229
|
-
program.command("record").description("Record a journey on the current branch (no
|
|
2230
|
-
program.command("run").description("Record on the PR base, then produce before.
|
|
2248
|
+
program.command("record").description("Record a journey to a file on the current branch (no video, no git)").option("-o, --out <file>", "journey output path", ".pr-preview/journey.json").action((opts) => wrap(() => recordCommand(process.cwd(), opts)));
|
|
2249
|
+
program.command("run").description("Record on the PR base and your branch, then produce before.mp4 and after.mp4").option("-b, --base <ref>", "override PR base detection (e.g. origin/main)").option("--keep-worktree", "keep the base worktree around for faster re-runs").option(
|
|
2231
2250
|
"-u, --url <url>",
|
|
2232
|
-
"
|
|
2251
|
+
"record against an app already running at <url> \u2014 local (http://localhost:3000) or a remote/staging URL \u2014 instead of starting a dev server"
|
|
2233
2252
|
).option(
|
|
2234
2253
|
"-s, --single",
|
|
2235
|
-
"record
|
|
2254
|
+
"record one standalone clip of the current app, skipping the before/after comparison"
|
|
2255
|
+
).addHelpText(
|
|
2256
|
+
"after",
|
|
2257
|
+
`
|
|
2258
|
+
Every option can also be set in pr-preview.config.js, so a configured project runs with
|
|
2259
|
+
just \`pr-preview run\` (handy for CI and AI agents). Flags override the config file.
|
|
2260
|
+
|
|
2261
|
+
Examples:
|
|
2262
|
+
$ pr-preview run before/after: PR base vs your current branch
|
|
2263
|
+
$ pr-preview run --single one clip of the current app, no comparison
|
|
2264
|
+
$ pr-preview run --url http://localhost:3000 use an app you're already running (local)
|
|
2265
|
+
$ pr-preview run --url https://staging.example.com \u2026or a remote / deployed app
|
|
2266
|
+
$ pr-preview run --base origin/develop compare against a specific base branch
|
|
2267
|
+
|
|
2268
|
+
Notes:
|
|
2269
|
+
\u2022 --single skips git/the base worktree entirely and saves a single video \u2014 great for a demo,
|
|
2270
|
+
a bug repro, or a how-to.
|
|
2271
|
+
\u2022 --url accepts any reachable address \u2014 a local dev server or a remote/staging deployment.
|
|
2272
|
+
In --url mode you switch your app to the PR branch and restart it yourself between passes.
|
|
2273
|
+
\u2022 Without --url, a before/after run needs a git repo and to be on a branch with commits
|
|
2274
|
+
beyond its base (it records the base in a throwaway worktree, your branch in place).`
|
|
2236
2275
|
).action((opts) => wrap(() => runCommand(process.cwd(), opts)));
|
|
2237
2276
|
async function wrap(fn) {
|
|
2238
2277
|
try {
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cli/index.ts","../../src/cli/commands/init.ts","../../src/cli/ui/logger.ts","../../src/cli/commands/record.ts","../../src/config/load.ts","../../src/config/schema.ts","../../src/session/bootstrap.ts","../../src/server/ports.ts","../../src/server/devServer.ts","../../src/server/harnessServer.ts","../../src/ipc/protocol.ts","../../src/ipc/bus.ts","../../src/browser/launch.ts","../../src/browser/headerStrip.ts","../../src/browser/recorderInject.ts","../../src/session/session.ts","../../src/recorder/steps.ts","../../src/recorder/types.ts","../../src/capture/screencast.ts","../../src/capture/region.ts","../../src/encode/media.ts","../../src/capture/frames.ts","../../src/encode/gif.ts","../../src/encode/ffmpeg.ts","../../src/encode/label.ts","../../src/browser/frame.ts","../../src/cli/cleanup.ts","../../src/recorder/journey.ts","../../src/cli/commands/run.ts","../../src/git/exec.ts","../../src/git/base.ts","../../src/git/worktree.ts","../../src/server/pkgManager.ts","../../src/cli/util/openPath.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { initCommand } from \"./commands/init.js\";\nimport { recordCommand } from \"./commands/record.js\";\nimport { runCommand } from \"./commands/run.js\";\nimport { log } from \"./ui/logger.js\";\nimport { ConfigError } from \"../config/load.js\";\nimport { GitError } from \"../git/exec.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"pr-preview\")\n .description(\"Record a user journey once, get before/after PR GIFs of your UI.\")\n .version(\"0.1.0\");\n\nprogram\n .command(\"init\")\n .description(\"Scaffold pr-preview.config.js and .gitignore entries\")\n .action(() => wrap(() => initCommand(process.cwd())));\n\nprogram\n .command(\"record\")\n .description(\"Record a journey on the current branch (no GIFs)\")\n .option(\"-o, --out <file>\", \"journey output path\", \".pr-preview/journey.json\")\n .action((opts) => wrap(() => recordCommand(process.cwd(), opts)));\n\nprogram\n .command(\"run\")\n .description(\"Record on the PR base, then produce before.gif and after.gif\")\n .option(\"-b, --base <ref>\", \"override PR base detection\")\n .option(\"--keep-worktree\", \"keep the base worktree around for faster re-runs\")\n .option(\n \"-u, --url <url>\",\n \"use your own already-running app at <url> instead of starting a dev server \" +\n \"(records BEFORE, you switch branches + restart, records AFTER)\",\n )\n .option(\n \"-s, --single\",\n \"record a single standalone clip of the current app (no before/after comparison)\",\n )\n .action((opts) => wrap(() => runCommand(process.cwd(), opts)));\n\nasync function wrap(fn: () => Promise<void>): Promise<void> {\n try {\n await fn();\n process.exit(0);\n } catch (err) {\n if (err instanceof ConfigError || err instanceof GitError) {\n log.error(err.message);\n } else {\n log.error(err instanceof Error ? (err.stack ?? err.message) : String(err));\n }\n process.exit(1);\n }\n}\n\nprogram.parse();\n","import { existsSync } from \"node:fs\";\nimport { readFile, writeFile, appendFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { log } from \"../ui/logger.js\";\n\nconst CONFIG_TEMPLATE = `/** @type {import('pr-preview').Config} */\nexport default {\n // ── App under preview ────────────────────────────────────────────────\n // Command that starts your dev server. $PORT is set for you.\n devCommand: \"npm run dev\",\n // Where the app answers once ready. {port} is replaced with the allocated port.\n url: \"http://localhost:{port}\",\n // Frontend directory relative to the repo root (monorepos).\n cwd: \".\",\n // How long to wait for the dev server (ms).\n readyTimeout: 60000,\n\n // ── Run options (set here once instead of passing CLI flags every time) ─\n // Use an app YOU already run instead of a managed dev server. Set this for\n // apps that need real env/backends.\n // externalUrl: \"http://localhost:3000\",\n // Override PR-base (\"before\") detection, e.g. \"origin/main\".\n // baseBranch: undefined,\n // Reuse the base worktree across runs (skips reinstall).\n // keepWorktree: false,\n // 2 (default) = before/after comparison; 1 = a single standalone clip.\n // (run --single forces 1.)\n passes: 2,\n\n // ── Output ───────────────────────────────────────────────────────────\n output: \".pr-preview/output\",\n format: \"mp4\", // \"mp4\" | \"gif\" | \"both\"\n // Start-of-pass reset choice default: true offers \"reset to a clean app\",\n // false offers \"keep my session\". The nudge only shows when there's state.\n resetStorage: true,\n viewport: { width: 1920, height: 1080 },\n\n // ── Permissions ──────────────────────────────────────────────────────\n // Grant browser permissions so apps that ask for them work (others stay\n // denied silently — no native prompt blocks the run).\n // permissions: [\"geolocation\", \"clipboard-read\", \"clipboard-write\"],\n // geolocation: { latitude: 51.5074, longitude: -0.1278 }, // fixed = deterministic\n};\n`;\n\nconst GITIGNORE_BLOCK = `\n# pr-preview (worktrees, recordings, output)\n.pr-preview/\n`;\n\nexport async function initCommand(root: string): Promise<void> {\n const configPath = path.join(root, \"pr-preview.config.js\");\n if (\n existsSync(configPath) ||\n existsSync(path.join(root, \"pr-preview.config.ts\")) ||\n existsSync(path.join(root, \"pr-preview.config.json\"))\n ) {\n log.warn(\"A pr-preview config already exists — leaving it untouched.\");\n } else {\n await writeFile(configPath, CONFIG_TEMPLATE);\n log.success(`Created ${path.basename(configPath)}`);\n }\n\n const gitignorePath = path.join(root, \".gitignore\");\n const current = existsSync(gitignorePath) ? await readFile(gitignorePath, \"utf8\") : \"\";\n if (!current.includes(\".pr-preview/\")) {\n await appendFile(gitignorePath, GITIGNORE_BLOCK);\n log.success(\"Added .pr-preview/ to .gitignore\");\n }\n\n log.info(\"Edit the config to match your dev command, then run `pr-preview run` on a PR branch.\");\n}\n","import pc from \"picocolors\";\nimport ora, { type Ora } from \"ora\";\n\nexport const log = {\n info(msg: string): void {\n console.log(`${pc.cyan(\"●\")} ${msg}`);\n },\n success(msg: string): void {\n console.log(`${pc.green(\"✔\")} ${msg}`);\n },\n warn(msg: string): void {\n console.log(`${pc.yellow(\"▲\")} ${msg}`);\n },\n error(msg: string): void {\n console.error(`${pc.red(\"✖\")} ${msg}`);\n },\n step(msg: string): void {\n console.log(pc.dim(` ${msg}`));\n },\n spinner(text: string): Ora {\n return ora({ text, color: \"cyan\" }).start();\n },\n};\n","import path from \"node:path\";\nimport pc from \"picocolors\";\nimport { loadConfig } from \"../../config/load.js\";\nimport { bootstrap } from \"../../session/bootstrap.js\";\nimport { saveJourney } from \"../../recorder/journey.js\";\nimport { log } from \"../ui/logger.js\";\nimport { runCleanups } from \"../cleanup.js\";\n\nexport interface RecordOptions {\n out?: string;\n}\n\n/** Record a journey on the current branch only — no GIFs, no git. */\nexport async function recordCommand(repoRoot: string, opts: RecordOptions): Promise<void> {\n const { config } = await loadConfig(repoRoot);\n const journeyFile = path.resolve(repoRoot, opts.out ?? \".pr-preview/journey.json\");\n\n try {\n const boot = await bootstrap(repoRoot, config, \"record\");\n await boot.startApp(\"after\", repoRoot);\n await boot.openBrowser(\"after\");\n\n log.info(`Harness open at ${pc.bold(boot.harness.url)}`);\n log.step(\"Click Record in the sidebar, perform the journey, then Confirm.\");\n\n const steps = await boot.session.recordUntilConfirmed();\n if (steps.length === 0) {\n log.warn(\"No steps recorded — nothing saved.\");\n return;\n }\n\n await saveJourney(journeyFile, {\n version: 1,\n createdAt: new Date().toISOString(),\n viewport: { ...config.viewport, deviceScaleFactor: 2 },\n startUrl: boot.session.startUrl,\n steps,\n });\n log.success(`Saved ${steps.length} steps to ${path.relative(repoRoot, journeyFile)}`);\n } finally {\n await runCleanups();\n }\n}\n","import { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { createJiti } from \"jiti\";\nimport { configSchema, type Config } from \"./schema.js\";\n\nconst CONFIG_FILES = [\n \"pr-preview.config.ts\",\n \"pr-preview.config.mts\",\n \"pr-preview.config.js\",\n \"pr-preview.config.mjs\",\n \"pr-preview.config.json\",\n];\n\nexport class ConfigError extends Error {}\n\n/** Find and validate pr-preview.config.* in the given repo root. */\nexport async function loadConfig(root: string): Promise<{ config: Config; file: string }> {\n const file = CONFIG_FILES.map((f) => path.join(root, f)).find(existsSync);\n if (!file) {\n throw new ConfigError(\n `No pr-preview config found in ${root}. Run \\`pr-preview init\\` to create one.`,\n );\n }\n\n let raw: unknown;\n if (file.endsWith(\".json\")) {\n raw = JSON.parse(await readFile(file, \"utf8\"));\n } else {\n const jiti = createJiti(import.meta.url, { interopDefault: true });\n raw = await jiti.import(file, { default: true });\n }\n\n const parsed = configSchema.safeParse(raw);\n if (!parsed.success) {\n const issues = parsed.error.issues\n .map((i) => ` ${i.path.join(\".\") || \"(root)\"}: ${i.message}`)\n .join(\"\\n\");\n throw new ConfigError(`Invalid config in ${path.basename(file)}:\\n${issues}`);\n }\n return { config: parsed.data, file };\n}\n","import { z } from \"zod\";\n\nexport const configSchema = z.object({\n /** Command that starts the project's dev server, e.g. \"npm run dev\". */\n devCommand: z.string().min(1),\n /** URL the app is reachable on once ready. Supports {port} templating. */\n url: z.string().min(1),\n /** Working directory of the frontend relative to the repo root (monorepos). */\n cwd: z.string().default(\".\"),\n /** ms to wait for the dev server to answer. */\n readyTimeout: z.number().int().positive().default(60_000),\n /** Override PR base detection (the \"before\" ref). e.g. \"origin/main\". */\n baseBranch: z.string().optional(),\n /**\n * Run against an app YOU already have running, instead of letting pr-preview\n * start a dev server in a worktree. Set this (e.g. \"http://localhost:3000\")\n * for apps that need real env/backends. Equivalent to `run --url`; the CLI\n * flag overrides it. When set, devCommand/url are ignored for `run`.\n */\n externalUrl: z.string().optional(),\n /** Reuse the base worktree across runs (skips reinstall). `--keep-worktree`. */\n keepWorktree: z.boolean().default(false),\n /** Dependency install behavior in the base worktree. */\n install: z.enum([\"auto\", \"always\", \"never\"]).default(\"auto\"),\n /** Output directory for the clips, relative to repo root. */\n output: z.string().default(\".pr-preview/output\"),\n /**\n * Output format. \"mp4\" (default) is small + HQ and uploads straight into a\n * GitHub PR description; needs ffmpeg on PATH (falls back to gif if\n * missing). \"both\" produces the two side by side.\n */\n format: z.enum([\"mp4\", \"gif\", \"both\"]).default(\"mp4\"),\n /**\n * How many recordings the session does: 2 (default) = before/after on the\n * base branch and your branch; 1 = a single standalone clip of the current\n * app (no comparison, no base worktree). `run --single` forces 1.\n */\n passes: z.union([z.literal(1), z.literal(2)]).default(2),\n gif: z\n .object({\n width: z.number().int().positive().default(900),\n // 24fps keeps the synthetic cursor's motion fluid in the clip.\n fps: z.number().positive().max(60).default(24),\n quality: z.enum([\"high\", \"max\"]).default(\"high\"),\n maxColors: z.number().int().min(2).max(256).default(256),\n })\n .default({}),\n /**\n * Start-of-pass reset choice default: true (default) offers \"reset\" (clear\n * cookies + localStorage + sessionStorage and reload) before recording;\n * false offers \"keep my session\". The nudge only appears when there's state\n * to reset.\n */\n resetStorage: z.boolean().default(true),\n /** Logical app resolution — Full HD by default. The harness scales the\n * iframe down to fit the window, keeping this resolution and ratio. */\n viewport: z\n .object({\n width: z.number().int().positive().default(1920),\n height: z.number().int().positive().default(1080),\n })\n .default({}),\n /** Strip X-Frame-Options / frame-ancestors so any app loads in the iframe. */\n headerStrip: z.boolean().default(true),\n /**\n * Browser permissions to GRANT up front (Playwright names) so a native\n * prompt never blocks the run. Defaults to allow-all (the broadly-supported\n * set); unsupported names for your Chrome are skipped gracefully. Set your\n * own narrower list to override, or [] to deny everything.\n */\n permissions: z\n .array(z.string())\n .default([\n \"geolocation\",\n \"notifications\",\n \"camera\",\n \"microphone\",\n \"clipboard-read\",\n \"clipboard-write\",\n \"midi\",\n \"background-sync\",\n \"accelerometer\",\n \"gyroscope\",\n \"magnetometer\",\n \"payment-handler\",\n \"storage-access\",\n ]),\n /**\n * Fixed geolocation for the session (implies the \"geolocation\" permission),\n * so location-based apps render real, deterministic results in both clips.\n */\n geolocation: z\n .object({\n latitude: z.number(),\n longitude: z.number(),\n accuracy: z.number().nonnegative().optional(),\n })\n .optional(),\n});\n\nexport type Config = z.infer<typeof configSchema>;\nexport type ConfigInput = z.input<typeof configSchema>;\n\n/** Replace {port} in the configured url. */\nexport function resolveUrl(config: Config, port: number): string {\n return config.url.replace(\"{port}\", String(port));\n}\n","import path from \"node:path\";\nimport type { Config } from \"../config/schema.js\";\nimport { resolveUrl } from \"../config/schema.js\";\nimport { allocatePorts, type PortPlan } from \"../server/ports.js\";\nimport { startDevServer, type DevServerHandle } from \"../server/devServer.js\";\nimport { startHarnessServer, type HarnessServer } from \"../server/harnessServer.js\";\nimport { launchSession, type BrowserSession } from \"../browser/launch.js\";\nimport { Session } from \"./session.js\";\nimport { registerCleanup } from \"../cli/cleanup.js\";\nimport { log } from \"../cli/ui/logger.js\";\n\nexport interface Bootstrapped {\n ports: PortPlan;\n harness: HarnessServer;\n session: Session;\n appUrl(which: \"before\" | \"after\"): string;\n startApp(which: \"before\" | \"after\", cwd: string): Promise<DevServerHandle>;\n /** Launch headed Chrome on the harness (call after the first app is up). */\n openBrowser(which: \"before\" | \"after\"): Promise<BrowserSession>;\n /** Point the open harness iframe at the other app. */\n switchApp(which: \"before\" | \"after\"): Promise<void>;\n}\n\n/**\n * Wire up everything a harness session needs: ports, harness server, bus and\n * session state machine. Dev servers and the browser start on demand so\n * `run` can sequence before/after.\n */\nexport async function bootstrap(\n repoRoot: string,\n config: Config,\n mode: \"record\" | \"run\",\n opts: { fixedUrl?: string } = {},\n): Promise<Bootstrapped> {\n const ports = await allocatePorts();\n // `fixedUrl` = the user runs the app themselves; both passes use that one\n // URL (they switch branches + restart between them). No dev server, no ports.\n const appUrl = (which: \"before\" | \"after\") =>\n opts.fixedUrl ?? resolveUrl(config, which === \"before\" ? ports.beforeApp : ports.afterApp);\n\n const initial = mode === \"run\" ? \"before\" : \"after\";\n const harness = await startHarnessServer({\n port: ports.harness,\n appUrl: appUrl(initial),\n mode,\n viewport: config.viewport,\n });\n registerCleanup(() => harness.close());\n\n const session = new Session(harness.bus, config, mode, appUrl(initial));\n let browser: BrowserSession | null = null;\n\n return {\n ports,\n harness,\n session,\n appUrl,\n\n async startApp(which, cwd) {\n const port = which === \"before\" ? ports.beforeApp : ports.afterApp;\n const url = appUrl(which);\n const spinner = log.spinner(`Starting ${which} dev server (${config.devCommand}) …`);\n try {\n const handle = await startDevServer({\n command: config.devCommand,\n cwd: path.join(cwd, config.cwd),\n port,\n url,\n readyTimeout: config.readyTimeout,\n logFile: path.join(repoRoot, \".pr-preview\", `dev-${which}.log`),\n });\n registerCleanup(() => handle.stop());\n spinner.succeed(`${which} app ready at ${url}`);\n return handle;\n } catch (err) {\n spinner.fail(`${which} dev server failed`);\n throw err;\n }\n },\n\n async openBrowser(which) {\n browser = await launchSession({\n harnessUrl: harness.url,\n targetOrigins: [new URL(appUrl(\"before\")).origin, new URL(appUrl(\"after\")).origin],\n appViewport: config.viewport,\n headerStrip: config.headerStrip,\n onRawEvent: session.handleRawEvent,\n permissions: config.permissions,\n geolocation: config.geolocation,\n });\n registerCleanup(() => browser!.close());\n session.attachPage(browser.page, appUrl(which));\n return browser;\n },\n\n async switchApp(which) {\n if (!browser) throw new Error(\"Browser not open\");\n harness.setAppUrl(appUrl(which));\n session.attachPage(browser.page, appUrl(which));\n // Harness refetches runtime.json on load → iframe points at the new app.\n await browser.page.reload({ waitUntil: \"domcontentloaded\" });\n },\n };\n}\n","import getPort, { portNumbers } from \"get-port\";\n\nexport interface PortPlan {\n harness: number;\n beforeApp: number;\n afterApp: number;\n}\n\n/** Allocate the ports a run needs up front so dev commands can be templated. */\nexport async function allocatePorts(): Promise<PortPlan> {\n // Test hook: PR_PREVIEW_PORTS=\"harness,before,after\" pins all three.\n const pinned = process.env.PR_PREVIEW_PORTS?.split(\",\").map(Number);\n if (pinned?.length === 3 && pinned.every((p) => Number.isInteger(p) && p > 0)) {\n return { harness: pinned[0]!, beforeApp: pinned[1]!, afterApp: pinned[2]! };\n }\n const harness = await getPort({ port: portNumbers(4310, 4400) });\n const beforeApp = await getPort({ port: portNumbers(4401, 4500), exclude: [harness] });\n const afterApp = await getPort({\n port: portNumbers(4501, 4600),\n exclude: [harness, beforeApp],\n });\n return { harness, beforeApp, afterApp };\n}\n","import { spawn, type ChildProcess } from \"node:child_process\";\nimport { appendFileSync, mkdirSync } from \"node:fs\";\nimport path from \"node:path\";\nimport treeKill from \"tree-kill\";\n\nexport interface DevServerOptions {\n command: string; // e.g. \"npm run dev\"\n cwd: string;\n port: number; // exposed as $PORT to the command\n url: string; // polled for readiness\n readyTimeout: number;\n logFile?: string;\n}\n\nexport class DevServerError extends Error {}\n\nexport interface DevServerHandle {\n url: string;\n stop(): Promise<void>;\n}\n\n/** Spawn the project dev server and wait until `url` answers. */\nexport async function startDevServer(opts: DevServerOptions): Promise<DevServerHandle> {\n if (opts.logFile) mkdirSync(path.dirname(opts.logFile), { recursive: true });\n\n const child = spawn(opts.command, {\n cwd: opts.cwd,\n shell: true,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n env: { ...process.env, PORT: String(opts.port), BROWSER: \"none\", FORCE_COLOR: \"0\" },\n });\n\n let logTail = \"\";\n const onChunk = (chunk: Buffer) => {\n logTail = (logTail + chunk.toString()).slice(-4000);\n if (opts.logFile) appendFileSync(opts.logFile, chunk);\n };\n child.stdout.on(\"data\", onChunk);\n child.stderr.on(\"data\", onChunk);\n\n let exited = false;\n let exitCode: number | null = null;\n child.on(\"exit\", (code) => {\n exited = true;\n exitCode = code;\n });\n\n const deadline = Date.now() + opts.readyTimeout;\n while (Date.now() < deadline) {\n if (exited) {\n throw new DevServerError(\n `Dev server exited early (code ${exitCode}). Last output:\\n${logTail}`,\n );\n }\n try {\n const res = await fetch(opts.url, { redirect: \"manual\" });\n if (res.status < 500) {\n return { url: opts.url, stop: () => killTree(child) };\n }\n } catch {\n /* not up yet */\n }\n await sleep(400);\n }\n\n await killTree(child);\n throw new DevServerError(\n `Dev server did not answer at ${opts.url} within ${opts.readyTimeout}ms. Last output:\\n${logTail}`,\n );\n}\n\nfunction killTree(child: ChildProcess): Promise<void> {\n return new Promise((resolve) => {\n if (child.pid == null || child.exitCode !== null) return resolve();\n treeKill(child.pid, \"SIGTERM\", () => {\n // escalate if still alive shortly after\n setTimeout(() => {\n if (child.exitCode === null && child.pid != null) {\n treeKill(child.pid, \"SIGKILL\", () => resolve());\n } else {\n resolve();\n }\n }, 1500);\n });\n });\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n","import http from \"node:http\";\nimport { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { WebSocketServer } from \"ws\";\nimport { Bus } from \"../ipc/bus.js\";\n\nconst MIME: Record<string, string> = {\n \".html\": \"text/html; charset=utf-8\",\n \".js\": \"text/javascript; charset=utf-8\",\n \".css\": \"text/css; charset=utf-8\",\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n \".json\": \"application/json\",\n \".map\": \"application/json\",\n};\n\nexport interface HarnessServerOptions {\n port: number;\n /** URL of the target app the iframe should load. */\n appUrl: string;\n mode: \"record\" | \"run\";\n /** Fixed logical size of the app iframe (letterboxed in the stage). */\n viewport: { width: number; height: number };\n}\n\nexport interface HarnessServer {\n url: string;\n bus: Bus;\n setAppUrl(url: string): void;\n close(): Promise<void>;\n}\n\n/** Locate the built harness SPA (dist/harness next to the CLI bundle). */\nfunction harnessDistDir(): string {\n const here = path.dirname(fileURLToPath(import.meta.url));\n // dist/cli/index.js → dist/harness ; src fallback for tests/dev\n const candidates = [\n path.resolve(here, \"../harness\"),\n path.resolve(here, \"../../dist/harness\"),\n ];\n const found = candidates.find((c) => existsSync(path.join(c, \"index.html\")));\n if (!found) {\n throw new Error(\n `Harness UI build not found (looked in: ${candidates.join(\", \")}). Run \\`npm run build\\` first.`,\n );\n }\n return found;\n}\n\n/** Serve the sidebar SPA, a /runtime.json config endpoint and the /ws bus. */\nexport async function startHarnessServer(opts: HarnessServerOptions): Promise<HarnessServer> {\n const dist = harnessDistDir();\n let appUrl = opts.appUrl;\n\n const server = http.createServer(async (req, res) => {\n const urlPath = (req.url ?? \"/\").split(\"?\")[0]!;\n if (urlPath === \"/runtime.json\") {\n res.writeHead(200, { \"content-type\": \"application/json\", \"cache-control\": \"no-store\" });\n res.end(JSON.stringify({ appUrl, mode: opts.mode, viewport: opts.viewport }));\n return;\n }\n const rel = urlPath === \"/\" ? \"index.html\" : urlPath.slice(1);\n const file = path.join(dist, path.normalize(rel));\n if (!file.startsWith(dist) || !existsSync(file)) {\n // SPA fallback\n res.writeHead(200, { \"content-type\": MIME[\".html\"]! });\n res.end(await readFile(path.join(dist, \"index.html\")));\n return;\n }\n res.writeHead(200, {\n \"content-type\": MIME[path.extname(file)] ?? \"application/octet-stream\",\n \"cache-control\": \"no-store\",\n });\n res.end(await readFile(file));\n });\n\n const wss = new WebSocketServer({ server, path: \"/ws\" });\n const bus = new Bus();\n bus.attach(wss);\n\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(opts.port, \"127.0.0.1\", resolve);\n });\n\n return {\n url: `http://127.0.0.1:${opts.port}`,\n bus,\n setAppUrl(url: string) {\n appUrl = url;\n },\n close: () =>\n new Promise((resolve) => {\n wss.close();\n for (const ws of wss.clients) ws.terminate();\n server.close(() => resolve());\n }),\n };\n}\n","/**\n * Shared message types between the Node CLI process (hub) and the\n * harness sidebar SPA (view). Pure types + JSON — imported by both builds.\n *\n * Flow of events:\n * iframe recorder --exposeBinding--> Node --WS--> sidebar\n * sidebar --WS--> Node --Playwright--> browser\n */\n\nimport type { Step } from \"../recorder/types.js\";\n\nexport type Phase = \"idle\" | \"recording\" | \"encoding\" | \"done\";\n\n/** Which pass the session is on, with branch labels for the tab strip. */\nexport interface PassInfo {\n pass: \"before\" | \"after\";\n /** Branch/ref labels; absent in single-pass `record` mode (tabs hidden). */\n branches?: { before: string; after: string };\n /** Which GIFs are already finished (ticks the tabs). */\n done: { before: boolean; after: boolean };\n /** Whether this pass's clip starts from a cleared app (true) or keeps the\n * signed-in state captured when recording began (false). User-toggleable. */\n resetStorage: boolean;\n /** Recordings planned this session: 2 = before/after wizard, 1 = a single\n * standalone clip. Undefined in the `record` command (no clips). Drives how\n * the harness wizard/tabs render. */\n passes?: 1 | 2;\n}\n\n/** Node → sidebar */\nexport type ServerMessage =\n | {\n type: \"HELLO\";\n phase: Phase;\n steps: StepSummary[];\n appUrl: string;\n mode: \"record\" | \"run\";\n passInfo: PassInfo;\n }\n | { type: \"PASS_CHANGED\"; passInfo: PassInfo }\n | { type: \"PHASE_CHANGED\"; phase: Phase; detail?: string }\n | { type: \"STEP_ADDED\"; step: StepSummary }\n | { type: \"STEP_UPDATED\"; step: StepSummary }\n | { type: \"STEP_REMOVED\"; stepId: string }\n | { type: \"STEPS_RESET\"; steps: StepSummary[] }\n | { type: \"ENCODE_PROGRESS\"; which: \"before\" | \"after\"; stage: string; done: number; total: number }\n /** A hands-off prompt (e.g. \"switch your app to the PR branch, then Continue\"\n * in --url mode). The user acts in the app, then hits Continue. */\n | { type: \"MANUAL_PAUSE\"; stepId: string | null; label: string; kind: \"generic\" }\n | { type: \"GIF_READY\"; which: \"before\" | \"after\"; path: string }\n | { type: \"DONE\"; outputs: { before?: string; after?: string } }\n /** Blocking nudge at the start of a pass: reset the app or keep the session,\n * before recording. `defaultReset` seeds the highlighted choice. */\n | { type: \"RESET_PROMPT\"; pass: \"before\" | \"after\"; defaultReset: boolean }\n | { type: \"ERROR\"; message: string };\n\n/** Sidebar → Node */\nexport type ClientMessage =\n | { type: \"START_RECORD\" }\n | { type: \"STOP_RECORD\" }\n | { type: \"CONFIRM\" }\n | { type: \"DELETE_STEP\"; stepId: string }\n | { type: \"RERECORD_FROM\"; stepId: string }\n | { type: \"CONTINUE\" }\n | { type: \"LOAD_BEFORE_STEPS\" }\n /** Answer to RESET_PROMPT: start the pass fresh (true) or keep the session. */\n | { type: \"RESET_CHOICE\"; reset: boolean }\n /** Manually reload the app inside the iframe (refresh button). */\n | { type: \"RELOAD_IFRAME\" }\n | { type: \"ABORT\" };\n\n/** What the sidebar needs to render a step row. */\nexport interface StepSummary {\n id: string;\n type: Step[\"type\"];\n label: string;\n thumbnail?: string; // small data-URL png\n}\n\nexport function serialize(msg: ServerMessage | ClientMessage): string {\n return JSON.stringify(msg);\n}\n\nexport function parseClientMessage(raw: string): ClientMessage | null {\n try {\n const msg = JSON.parse(raw);\n if (typeof msg === \"object\" && msg !== null && typeof msg.type === \"string\") {\n return msg as ClientMessage;\n }\n } catch {\n /* malformed frame — drop */\n }\n return null;\n}\n\nexport function parseServerMessage(raw: string): ServerMessage | null {\n try {\n const msg = JSON.parse(raw);\n if (typeof msg === \"object\" && msg !== null && typeof msg.type === \"string\") {\n return msg as ServerMessage;\n }\n } catch {\n /* malformed frame — drop */\n }\n return null;\n}\n","import type { WebSocketServer, WebSocket } from \"ws\";\nimport {\n serialize,\n parseClientMessage,\n type ClientMessage,\n type ServerMessage,\n} from \"./protocol.js\";\n\ntype Handler = (msg: ClientMessage) => void;\n\n/**\n * Typed pub/sub over the sidebar WebSocket. The Node process is the single\n * source of truth; the sidebar is a view that reconnects freely.\n */\nexport class Bus {\n private sockets = new Set<WebSocket>();\n private handlers = new Set<Handler>();\n /** Replayed to late-joining sidebars so they can render current state. */\n private helloFactory: (() => ServerMessage) | null = null;\n\n attach(wss: WebSocketServer): void {\n wss.on(\"connection\", (ws) => {\n this.sockets.add(ws);\n if (this.helloFactory) ws.send(serialize(this.helloFactory()));\n ws.on(\"message\", (data) => {\n const msg = parseClientMessage(data.toString());\n if (msg) for (const h of this.handlers) h(msg);\n });\n ws.on(\"close\", () => this.sockets.delete(ws));\n ws.on(\"error\", () => this.sockets.delete(ws));\n });\n }\n\n onHello(factory: () => ServerMessage): void {\n this.helloFactory = factory;\n }\n\n send(msg: ServerMessage): void {\n const data = serialize(msg);\n for (const ws of this.sockets) {\n if (ws.readyState === ws.OPEN) ws.send(data);\n }\n }\n\n onMessage(handler: Handler): () => void {\n this.handlers.add(handler);\n return () => this.handlers.delete(handler);\n }\n\n /** Resolve once a message of the given type arrives. */\n waitFor<T extends ClientMessage[\"type\"]>(type: T): Promise<Extract<ClientMessage, { type: T }>> {\n return new Promise((resolve) => {\n const off = this.onMessage((msg) => {\n if (msg.type === type) {\n off();\n resolve(msg as Extract<ClientMessage, { type: T }>);\n }\n });\n });\n }\n\n /** Resolve with whichever of the given message types arrives first. */\n waitForAny<T extends ClientMessage[\"type\"]>(...types: T[]): Promise<Extract<ClientMessage, { type: T }>> {\n return new Promise((resolve) => {\n const off = this.onMessage((msg) => {\n if ((types as string[]).includes(msg.type)) {\n off();\n resolve(msg as Extract<ClientMessage, { type: T }>);\n }\n });\n });\n }\n}\n","import { chromium, type Browser, type BrowserContext, type Page } from \"playwright\";\nimport { installHeaderStrip } from \"./headerStrip.js\";\nimport { installRecorderBinding, type RawEventHandler } from \"./recorderInject.js\";\n\n/** Harness chrome around the app iframe — must match harness/src CSS. */\nexport const SIDEBAR_WIDTH = 320;\nexport const BANNER_HEIGHT = 44 + 36; // banner + tab strip\n\nexport interface SessionOptions {\n harnessUrl: string;\n /** All origins the app iframe may live on across the run (before + after). */\n targetOrigins: string[];\n appViewport: { width: number; height: number };\n headerStrip: boolean;\n onRawEvent: RawEventHandler;\n /** Permissions to grant up front (e.g. \"geolocation\", \"clipboard-read\"). */\n permissions?: string[];\n /** Fixed geolocation; implies the \"geolocation\" permission. */\n geolocation?: { latitude: number; longitude: number; accuracy?: number };\n}\n\nexport interface BrowserSession {\n browser: Browser;\n context: BrowserContext;\n page: Page;\n close(): Promise<void>;\n}\n\n/**\n * Local/Private Network Access features to disable so the harness (127.0.0.1)\n * can embed an app on localhost:PORT and that app can make its own localhost\n * requests (HMR sockets, RSC/data fetches). Without this Chrome blocks them and\n * the page renders but never hydrates → nothing is clickable.\n */\nconst LNA_DISABLE = [\n \"LocalNetworkAccessChecks\",\n \"BlockInsecurePrivateNetworkRequests\",\n \"PrivateNetworkAccessSendPreflights\",\n \"PrivateNetworkAccessForNavigations\",\n];\n\nlet cachedBaseDisabledFeatures: string | null = null;\n\n/**\n * Discover Playwright's own default `--disable-features` value (probed once per\n * process). Chrome honors only the LAST `--disable-features`, so to add ours\n * without clobbering Playwright's we must concatenate onto theirs.\n */\nasync function playwrightDisabledFeatures(): Promise<string> {\n if (cachedBaseDisabledFeatures !== null) return cachedBaseDisabledFeatures;\n cachedBaseDisabledFeatures = \"\";\n try {\n const probe = await chromium.launchServer({ headless: true });\n const arg = probe.process().spawnargs.find((a) => a.startsWith(\"--disable-features=\"));\n cachedBaseDisabledFeatures = arg ? arg.slice(\"--disable-features=\".length) : \"\";\n await probe.close();\n } catch {\n /* probe failed — we'll skip the LNA flag rather than clobber theirs */\n }\n return cachedBaseDisabledFeatures;\n}\n\n/**\n * Grant permissions across all origins. Tries the whole list at once; if Chrome\n * rejects an unknown name, falls back to granting each individually so the\n * supported ones still apply (and unsupported ones are simply skipped).\n */\nasync function grantPermissionsResilient(context: BrowserContext, perms: string[]): Promise<void> {\n if (perms.length === 0) return;\n try {\n await context.grantPermissions(perms);\n } catch {\n // grantPermissions REPLACES the granted set, so accumulate the valid names\n // and re-grant the growing set — skipping any this Chrome doesn't know.\n const good: string[] = [];\n for (const p of perms) {\n try {\n await context.grantPermissions([...good, p]);\n good.push(p);\n } catch {\n /* unsupported name — skip it, keep the rest */\n }\n }\n }\n}\n\n/**\n * Launch headed Chrome on the harness page. The window is freely resizable\n * (viewport: null = real window size); the harness letterboxes the app\n * iframe at its exact configured size so resizing never distorts it. The\n * initial window is sized to fit the iframe + sidebar + banner.\n */\nexport async function launchSession(opts: SessionOptions): Promise<BrowserSession> {\n const headless = process.env.PR_PREVIEW_HEADLESS === \"1\"; // CI/tests only\n const windowChrome = headless ? 0 : 88; // headed Chrome UI (tabs + URL bar)\n\n // Disable Local/Private Network Access checks, MERGED into Playwright's own\n // --disable-features so we don't re-enable the features it deliberately turns\n // off. If the probe failed, skip rather than clobber.\n const baseDisabled = await playwrightDisabledFeatures();\n const args = [\n \"--hide-crash-restore-bubble\",\n // Auto-accept camera/mic prompts with a fake device so getUserMedia\n // never blocks the run.\n \"--use-fake-ui-for-media-stream\",\n \"--use-fake-device-for-media-stream\",\n // Reduce the automation fingerprint (hides navigator.webdriver) so OAuth\n // providers like Google are less likely to refuse sign-in with \"this\n // browser may not be secure\". Not a guarantee — they're aggressive.\n \"--disable-blink-features=AutomationControlled\",\n `--window-size=${opts.appViewport.width + SIDEBAR_WIDTH},${\n opts.appViewport.height + BANNER_HEIGHT + windowChrome\n }`,\n ];\n if (baseDisabled) args.push(`--disable-features=${[baseDisabled, ...LNA_DISABLE].join(\",\")}`);\n\n const browser = await chromium.launch({\n headless,\n args,\n // Drop the \"controlled by automation\" flag — another OAuth-detection signal.\n ignoreDefaultArgs: [\"--enable-automation\"],\n });\n\n const context = await browser.newContext({\n viewport: null, // follow the real window — user can resize freely\n });\n\n // Grant permissions so a native prompt never blocks the run. Granted on the\n // context (all origins) and resiliently — names unsupported by this Chrome\n // are skipped instead of failing the whole launch.\n const permissions = new Set(opts.permissions ?? []);\n if (opts.geolocation) permissions.add(\"geolocation\");\n await grantPermissionsResilient(context, [...permissions]);\n\n // A position so geolocation resolves (no prompt, no hang) — the configured\n // one, or a neutral default when location is allowed but unset.\n const geolocation = opts.geolocation ?? (permissions.has(\"geolocation\") ? { latitude: 0, longitude: 0 } : undefined);\n if (geolocation) await context.setGeolocation(geolocation).catch(() => {});\n\n if (opts.headerStrip) await installHeaderStrip(context, opts.targetOrigins);\n\n const page = await context.newPage();\n\n // Native dialogs (alert/confirm/prompt) would otherwise be auto-dismissed by\n // Playwright — breaking flows that expect \"OK\"/\"Confirm\". Accept them (with\n // the prompt's default value) so the journey proceeds, in both record and\n // replay. Pages without a dialog listener just never trigger this.\n context.on(\"dialog\", (dialog) => {\n void dialog.accept(dialog.type() === \"prompt\" ? dialog.defaultValue() : undefined).catch(() => {});\n });\n\n await installRecorderBinding(page, opts.targetOrigins, opts.onRawEvent);\n await page.goto(opts.harnessUrl, { waitUntil: \"domcontentloaded\" });\n\n return {\n browser,\n context,\n page,\n close: async () => {\n await context.close().catch(() => {});\n await browser.close().catch(() => {});\n },\n };\n}\n","import type { BrowserContext } from \"playwright\";\n\n/**\n * Strip ONLY the frame-busting response headers from the target app so it\n * loads inside the harness iframe. Everything else passes through untouched —\n * the browser still talks to the dev server directly (cookies, redirects and\n * HMR websockets are unaffected).\n */\nexport async function installHeaderStrip(\n context: BrowserContext,\n targetOrigins: string[],\n): Promise<void> {\n await context.route(\n (url) => targetOrigins.includes(url.origin),\n async (route) => {\n // Only documents/frames can be frame-busted; let subresources flow.\n const type = route.request().resourceType();\n if (type !== \"document\") return route.continue();\n\n const response = await route.fetch();\n const headers = { ...response.headers() };\n delete headers[\"x-frame-options\"];\n\n const csp = headers[\"content-security-policy\"];\n if (csp) {\n // Surgically remove only the frame-ancestors directive.\n const stripped = csp\n .split(\";\")\n .filter((d) => !/^\\s*frame-ancestors/i.test(d))\n .join(\";\")\n .trim();\n if (stripped) headers[\"content-security-policy\"] = stripped;\n else delete headers[\"content-security-policy\"];\n }\n\n await route.fulfill({ response, headers });\n },\n );\n}\n","import { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { Page } from \"playwright\";\nimport type { RawEvent } from \"../recorder/types.js\";\n\nexport type RawEventHandler = (event: RawEvent) => void;\n\nlet recorderSource: string | null = null;\n\n/** Load the built in-page recorder IIFE (dist/inpage/recorder.global.js). */\nasync function loadRecorderSource(): Promise<string> {\n if (recorderSource) return recorderSource;\n const here = path.dirname(fileURLToPath(import.meta.url));\n const candidates = [\n path.resolve(here, \"../inpage/recorder.global.js\"), // dist/cli → dist/inpage\n path.resolve(here, \"../../dist/inpage/recorder.global.js\"), // running from src (tests)\n path.resolve(here, \"../../../dist/inpage/recorder.global.js\"),\n ];\n const found = candidates.find(existsSync);\n if (!found) {\n throw new Error(\n `In-page recorder bundle not found (looked in: ${candidates.join(\", \")}). Run \\`npm run build\\` first.`,\n );\n }\n recorderSource = await readFile(found, \"utf8\");\n return recorderSource;\n}\n\n/**\n * The cross-origin trick that makes the whole architecture work:\n * exposeBinding is available in ALL frames (CDP-level), so the recorder\n * inside the cross-origin app iframe can call __prPreviewEmit and reach Node\n * — no proxy, no postMessage gymnastics.\n */\nexport async function installRecorderBinding(\n page: Page,\n targetOrigins: string[],\n onEvent: RawEventHandler,\n): Promise<void> {\n await page.exposeBinding(\"__prPreviewEmit\", (source, json: string) => {\n // Accept events only from the target app's frames.\n try {\n if (!targetOrigins.includes(new URL(source.frame.url()).origin)) return;\n } catch {\n return;\n }\n try {\n onEvent(JSON.parse(json) as RawEvent);\n } catch {\n /* malformed event — drop */\n }\n });\n\n // Runs in every frame (including ones created by future navigations/HMR\n // reloads); the script self-gates to iframes only.\n await page.addInitScript({ content: await loadRecorderSource() });\n}\n","import sharp from \"sharp\";\nimport type { Page } from \"playwright\";\nimport type { Bus } from \"../ipc/bus.js\";\nimport type { PassInfo, Phase, StepSummary } from \"../ipc/protocol.js\";\nimport { StepBuilder } from \"../recorder/steps.js\";\nimport { describeStep, type RawEvent, type Step } from \"../recorder/types.js\";\nimport { startScreencast, type ScreencastHandle, type CapturedFrame } from \"../capture/screencast.js\";\nimport { cssRectToFramePixels } from \"../capture/region.js\";\nimport { encodeMedia, type MediaResult } from \"../encode/media.js\";\nimport { getAppFrame, getAppFrameRect } from \"../browser/frame.js\";\nimport type { Config } from \"../config/schema.js\";\n\n/**\n * One interactive harness session. The clip IS the live recording: while the\n * user performs the journey we screencast the page (with a synthetic cursor\n * that follows the real mouse), then encode those frames directly — no replay,\n * so nothing can drift. Steps are recorded purely as a sidebar outline (with\n * thumbnails + re-record-from-a-point).\n */\nexport class Session {\n readonly builder = new StepBuilder();\n /** Path the journey starts on — captured when recording begins. */\n startUrl = \"/\";\n private phase: Phase = \"idle\";\n private page: Page | null = null;\n private thumbnailQueue = Promise.resolve();\n /** Full screencast kept alive during recording — its frames ARE the clip,\n * and its latest frame drives the sidebar thumbnails. */\n private recordingFeed: ScreencastHandle | null = null;\n /** frames.length at the moment each step was recorded — lets a re-record\n * truncate the footage at that step. */\n private frameMark = new Map<string, number>();\n private passInfo: PassInfo = {\n pass: \"before\",\n done: { before: false, after: false },\n resetStorage: true,\n };\n /** Single-recording session — the clip's caption drops the BEFORE/AFTER pill. */\n private singleClip = false;\n /** The confirmed BEFORE journey, offered as a starting point for AFTER. */\n private beforeJourney: { steps: Step[]; startUrl: string } | null = null;\n\n constructor(\n private readonly bus: Bus,\n private readonly config: Config,\n private readonly mode: \"record\" | \"run\",\n private appUrl: string,\n ) {\n this.passInfo.resetStorage = config.resetStorage;\n\n bus.onHello(() => ({\n type: \"HELLO\",\n phase: this.phase,\n steps: this.builder.getSteps().map(toSummary),\n appUrl: this.appUrl,\n mode: this.mode,\n passInfo: this.passInfo,\n }));\n\n // Manual refresh button in the iframe corner.\n bus.onMessage((msg) => {\n if (msg.type === \"RELOAD_IFRAME\") void this.reloadAppFrame();\n });\n\n this.builder.onChange((change) => {\n switch (change.kind) {\n case \"added\":\n // Mark where in the live footage this step landed (re-record + trim).\n if (this.recordingFeed) this.frameMark.set(change.step.id, this.recordingFeed.frameCount());\n bus.send({ type: \"STEP_ADDED\", step: toSummary(change.step) });\n this.scheduleThumbnail(change.step);\n break;\n case \"updated\":\n // Advance the mark as the step changes (a fill coalesces keystrokes),\n // so the end-trim keeps the WHOLE action — not just its first frame.\n if (this.recordingFeed) this.frameMark.set(change.step.id, this.recordingFeed.frameCount());\n bus.send({ type: \"STEP_UPDATED\", step: toSummary(change.step) });\n break;\n case \"removed\":\n bus.send({ type: \"STEP_REMOVED\", stepId: change.stepId });\n break;\n case \"reset\":\n bus.send({ type: \"STEPS_RESET\", steps: this.builder.getSteps().map(toSummary) });\n break;\n }\n });\n }\n\n attachPage(page: Page, appUrl: string): void {\n this.page = page;\n this.appUrl = appUrl;\n }\n\n get targetOrigin(): string {\n return new URL(this.appUrl).origin;\n }\n\n setPhase(phase: Phase, detail?: string): void {\n this.phase = phase;\n this.bus.send({ type: \"PHASE_CHANGED\", phase, detail });\n // The recording feed captures only while recording: start/resume on enter,\n // pause on leave (so STOP_RECORD / record-more gaps are cut from the clip).\n if (phase === \"recording\") void this.onEnterRecording();\n else void this.onLeaveRecording();\n }\n\n /** Set branch labels for the tab strip (run mode). */\n setBranches(before: string, after: string): void {\n this.passInfo = { ...this.passInfo, branches: { before, after } };\n this.bus.send({ type: \"PASS_CHANGED\", passInfo: this.passInfo });\n }\n\n /** Declare the session plan (1 = single clip, 2 = before/after) so the\n * harness wizard/tabs render the right shape. */\n setPasses(passes: 1 | 2): void {\n this.singleClip = passes === 1;\n this.passInfo = { ...this.passInfo, passes };\n this.bus.send({ type: \"PASS_CHANGED\", passInfo: this.passInfo });\n }\n\n /**\n * Blocking nudge at the start of a pass, BEFORE recording: clear the app\n * (network/cookies/storage) and reload, or keep the current session. Shown\n * only when there's actually state to reset.\n */\n async promptResetChoice(): Promise<void> {\n if (!(await this.hasResettableState())) return;\n this.bus.send({\n type: \"RESET_PROMPT\",\n pass: this.passInfo.pass,\n defaultReset: this.config.resetStorage,\n });\n const { reset } = await this.bus.waitFor(\"RESET_CHOICE\");\n this.passInfo = { ...this.passInfo, resetStorage: reset };\n this.bus.send({ type: \"PASS_CHANGED\", passInfo: this.passInfo });\n if (reset) await this.resetIframe();\n }\n\n /** Is there any cookie / localStorage / sessionStorage worth resetting? */\n private async hasResettableState(): Promise<boolean> {\n const page = this.page;\n if (!page) return false;\n try {\n const cookies = await page.context().cookies();\n if (cookies.length > 0) return true;\n const frame = await getAppFrame(page, this.targetOrigin, 5_000);\n return await frame.evaluate(() => localStorage.length > 0 || sessionStorage.length > 0);\n } catch {\n return false;\n }\n }\n\n /** Clear cookies + localStorage + sessionStorage and reload the app. */\n private async resetIframe(): Promise<void> {\n const page = this.page;\n if (!page) return;\n await page.context().clearCookies().catch(() => {});\n try {\n const frame = await getAppFrame(page, this.targetOrigin, 5_000);\n await frame.evaluate(() => {\n localStorage.clear();\n sessionStorage.clear();\n });\n } catch {\n /* frame not up — the reload starts clean anyway */\n }\n await page.reload({ waitUntil: \"domcontentloaded\" });\n await getAppFrame(page, this.targetOrigin)\n .then((f) => f.waitForLoadState(\"domcontentloaded\").catch(() => {}))\n .catch(() => {});\n }\n\n /** Switch the session to the other pass; AFTER starts with a clean slate. */\n switchPass(pass: \"before\" | \"after\"): void {\n this.passInfo = { ...this.passInfo, pass };\n if (pass === \"after\") {\n // Keep the confirmed BEFORE journey around as an optional template.\n this.beforeJourney = { steps: this.builder.getSteps(), startUrl: this.startUrl };\n this.builder.setSteps([]);\n this.frameMark.clear();\n this.startUrl = \"/\";\n this.passInfo = { ...this.passInfo, resetStorage: this.config.resetStorage };\n }\n this.bus.send({ type: \"PASS_CHANGED\", passInfo: this.passInfo });\n }\n\n markGifDone(which: \"before\" | \"after\"): void {\n this.passInfo = { ...this.passInfo, done: { ...this.passInfo.done, [which]: true } };\n this.bus.send({ type: \"PASS_CHANGED\", passInfo: this.passInfo });\n }\n\n /** Raw events from the in-page recorder land here (via exposeBinding). */\n handleRawEvent = (event: RawEvent): void => {\n if (this.phase === \"recording\") this.builder.handle(event);\n };\n\n /** Reload the current page inside the app iframe (cross-origin safe via CDP). */\n private async reloadAppFrame(): Promise<void> {\n const page = this.page;\n if (!page) return;\n try {\n const frame = await getAppFrame(page, this.targetOrigin, 3_000);\n await frame.evaluate(() => location.reload());\n } catch {\n /* frame not up — nothing to reload */\n }\n }\n\n /**\n * Drive the interactive record/edit loop until the user confirms.\n * Resolves with the confirmed steps.\n */\n async recordUntilConfirmed(): Promise<Step[]> {\n return new Promise<Step[]>((resolve, reject) => {\n const off = this.bus.onMessage((msg) => {\n switch (msg.type) {\n case \"START_RECORD\":\n // A fresh recording defines the journey's start page.\n if (this.builder.getSteps().length === 0) {\n void this.currentAppPath().then((p) => (this.startUrl = p));\n }\n this.setPhase(\"recording\");\n break;\n case \"STOP_RECORD\":\n this.setPhase(\"idle\");\n break;\n case \"DELETE_STEP\":\n this.builder.removeStep(msg.stepId);\n break;\n case \"RERECORD_FROM\":\n // Drop the step + everything after (and the footage from that\n // point), then keep recording from there.\n this.truncateLiveFrames(msg.stepId);\n this.builder.truncateFrom(msg.stepId);\n this.setPhase(\"recording\", \"Re-recording — perform the journey from this point\");\n break;\n case \"LOAD_BEFORE_STEPS\":\n // AFTER pass convenience: start the outline from the BEFORE journey.\n if (this.beforeJourney) {\n this.builder.setSteps(this.beforeJourney.steps);\n this.startUrl = this.beforeJourney.startUrl;\n }\n break;\n case \"CONFIRM\":\n this.setPhase(\"idle\");\n off();\n resolve(this.builder.getSteps());\n break;\n case \"ABORT\":\n off();\n reject(new Error(\"Aborted from the sidebar\"));\n break;\n }\n });\n });\n }\n\n private async currentAppPath(): Promise<string> {\n const page = this.page;\n if (!page) return \"/\";\n try {\n const frame = await getAppFrame(page, this.targetOrigin, 5_000);\n return await frame.evaluate(() => location.pathname + location.search);\n } catch {\n return \"/\";\n }\n }\n\n /** Enter recording: start the full screencast (or resume it) + tell the\n * in-page cursor to follow the real mouse so it lands in the footage. */\n private async onEnterRecording(): Promise<void> {\n await this.setIframeRecording(true);\n if (this.recordingFeed) {\n this.recordingFeed.resume();\n return;\n }\n const page = this.page;\n if (!page) return;\n try {\n this.recordingFeed = await startScreencast(page, { quality: 80 });\n } catch {\n this.recordingFeed = null;\n }\n }\n\n /** Leave recording: pause the feed (keep its frames). */\n private async onLeaveRecording(): Promise<void> {\n this.recordingFeed?.pause();\n await this.setIframeRecording(false);\n }\n\n /** Fully stop the recording feed and return its frames (the clip). */\n private async takeRecordingFrames(): Promise<CapturedFrame[]> {\n const feed = this.recordingFeed;\n this.recordingFeed = null;\n if (!feed) return [];\n return feed.stop();\n }\n\n /** Re-record: drop the footage recorded from `stepId` onwards. */\n truncateLiveFrames(stepId: string): void {\n const mark = this.frameMark.get(stepId);\n if (mark != null) this.recordingFeed?.truncate(mark);\n for (const [id, n] of this.frameMark) if (mark != null && n >= mark) this.frameMark.delete(id);\n }\n\n /** Tell the in-page recorder to make the synthetic cursor follow the mouse. */\n private async setIframeRecording(on: boolean): Promise<void> {\n const page = this.page;\n if (!page) return;\n try {\n const frame = await getAppFrame(page, this.targetOrigin, 2_000);\n await frame.evaluate((v) => {\n window.__prPreviewRecording = v;\n }, on);\n } catch {\n /* frame not up yet */\n }\n }\n\n /**\n * Trim dead time at the ends of the footage: keep a small lead-in before the\n * first interaction and a short tail after the last, so \"getting ready\" and\n * \"reaching for Confirm\" time isn't baked into the clip.\n */\n private trimToActivity(frames: CapturedFrame[]): CapturedFrame[] {\n const marks = [...this.frameMark.values()]\n .filter((n) => n >= 0 && n < frames.length)\n .sort((a, b) => a - b);\n if (marks.length === 0) return frames;\n // Small lead-in; a generous tail so the result of the last action (e.g. the\n // text you just typed rendering) is never clipped — only the dead \"reaching\n // for Confirm\" time after it is trimmed.\n const HEAD = 12; // ~0.5s at 24fps\n const TAIL = 40; // ~1.6s at 24fps\n const head = Math.max(0, marks[0]! - HEAD);\n const tail = Math.min(frames.length, marks[marks.length - 1]! + TAIL);\n return frames.slice(head, tail);\n }\n\n /**\n * Encode the recorded footage into the clip — cropped to the iframe, with the\n * caption/watermark overlay. No replay, no state re-creation.\n */\n async encodeClip(\n which: \"before\" | \"after\",\n outBase: string,\n label?: { branch: string; baseBranch: string; timestamp: string },\n ): Promise<MediaResult> {\n const page = this.page;\n if (!page) throw new Error(\"No browser page attached\");\n await this.setIframeRecording(false);\n const frames = this.trimToActivity(await this.takeRecordingFrames());\n if (frames.length === 0) throw new Error(\"No footage captured for the clip\");\n\n const cropCss = await getAppFrameRect(page);\n const pageCssSize = await page.evaluate(() => ({\n width: window.innerWidth,\n height: window.innerHeight,\n }));\n\n this.setPhase(\"encoding\", `Encoding ${which} clip`);\n const result = await encodeMedia(frames, {\n cropCss,\n pageCssSize,\n width: this.config.gif.width,\n fps: this.config.gif.fps,\n quality: this.config.gif.quality,\n maxColors: this.config.gif.maxColors,\n format: this.config.format,\n outBase,\n label: label ? { pass: this.singleClip ? \"single\" : which, ...label } : undefined,\n onProgress: (stage, done, total) =>\n this.bus.send({ type: \"ENCODE_PROGRESS\", which, stage, done, total }),\n });\n this.markGifDone(which);\n this.bus.send({ type: \"GIF_READY\", which, path: result.paths[0]! });\n return result;\n }\n\n /**\n * Crop the recording feed's latest frame to the iframe for the sidebar step\n * row. No discrete screenshot → no flicker on recorded actions.\n */\n private scheduleThumbnail(step: Step): void {\n const page = this.page;\n if (!page) return;\n this.thumbnailQueue = this.thumbnailQueue.then(async () => {\n try {\n const frame = this.recordingFeed?.latest();\n if (!frame) return; // no frame yet — skip rather than flash a capture\n const meta = await sharp(frame).metadata();\n if (!meta.width || !meta.height) return;\n const rect = await getAppFrameRect(page);\n const pageCss = await page.evaluate(() => ({\n width: window.innerWidth,\n height: window.innerHeight,\n }));\n const crop = cssRectToFramePixels(rect, { width: meta.width, height: meta.height }, pageCss);\n if (crop.width < 2 || crop.height < 2) return;\n const small = await sharp(frame)\n .extract({ left: crop.left, top: crop.top, width: crop.width, height: crop.height })\n .resize(160)\n .png()\n .toBuffer();\n this.builder.setThumbnail(step.id, `data:image/png;base64,${small.toString(\"base64\")}`);\n } catch {\n /* transient (reload/race) — the next step retries */\n }\n });\n }\n}\n\nfunction toSummary(step: Step): StepSummary {\n return {\n id: step.id,\n type: step.type,\n label: describeStep(step),\n thumbnail: step.thumbnail,\n };\n}\n","import type { RawEvent, Step } from \"./types.js\";\n\n/**\n * Normalizes the raw in-page event stream into discrete steps:\n * - consecutive `input` events on the same element coalesce into one `fill`\n * - a click followed by navigation within 500ms gets `causesNavigation`\n * - SPA `navigate` events right after a click are folded into the click\n */\nexport class StepBuilder {\n private steps: Step[] = [];\n private counter = 0;\n private pendingFill: Step | null = null;\n private listeners = new Set<(change: StepChange) => void>();\n\n onChange(listener: (change: StepChange) => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private notify(change: StepChange): void {\n for (const l of this.listeners) l(change);\n }\n\n private nextId(): string {\n return `stp_${String(++this.counter).padStart(2, \"0\")}`;\n }\n\n getSteps(): Step[] {\n this.flushFill();\n return [...this.steps];\n }\n\n /** Replace all steps (journey reuse / re-record truncation). */\n setSteps(steps: Step[]): void {\n this.pendingFill = null;\n this.steps = [...steps];\n this.counter = steps.length;\n this.notify({ kind: \"reset\" });\n }\n\n removeStep(id: string): void {\n const i = this.steps.findIndex((s) => s.id === id);\n if (i >= 0) {\n this.steps.splice(i, 1);\n this.notify({ kind: \"removed\", stepId: id });\n }\n }\n\n /** Drop the given step and everything after it (re-record from there). */\n truncateFrom(id: string): Step[] {\n this.flushFill();\n const i = this.steps.findIndex((s) => s.id === id);\n if (i >= 0) {\n this.steps = this.steps.slice(0, i);\n this.notify({ kind: \"reset\" });\n }\n return [...this.steps];\n }\n\n setThumbnail(id: string, thumbnail: string): Step | undefined {\n const step = this.steps.find((s) => s.id === id);\n if (step) {\n step.thumbnail = thumbnail;\n this.notify({ kind: \"updated\", step });\n }\n return step;\n }\n\n handle(event: RawEvent): void {\n switch (event.kind) {\n case \"input\": {\n const sameField =\n this.pendingFill &&\n selectorsOverlap(this.pendingFill.selectors ?? [], event.selectors);\n if (sameField && this.pendingFill) {\n this.pendingFill.value = event.value;\n this.pendingFill.timestamp = event.ts;\n this.notify({ kind: \"updated\", step: this.pendingFill });\n } else {\n this.flushFill();\n // A click that merely focused this field is redundant — replaying\n // the fill clicks the field anyway. Absorb it.\n const prev = this.steps[this.steps.length - 1];\n if (\n prev?.type === \"click\" &&\n selectorsOverlap(prev.selectors ?? [], event.selectors) &&\n event.ts - prev.timestamp < 3_000\n ) {\n this.steps.pop();\n this.notify({ kind: \"removed\", stepId: prev.id });\n }\n this.pendingFill = {\n id: this.nextId(),\n type: \"fill\",\n selectors: event.selectors,\n value: event.value,\n coordinates: { xNorm: event.xNorm, yNorm: event.yNorm },\n frameUrl: event.frameUrl,\n timestamp: event.ts,\n };\n this.steps.push(this.pendingFill);\n this.notify({ kind: \"added\", step: this.pendingFill });\n }\n break;\n }\n\n case \"click\": {\n this.flushFill();\n const step: Step = {\n id: this.nextId(),\n type: \"click\",\n selectors: event.selectors,\n coordinates: { xNorm: event.xNorm, yNorm: event.yNorm },\n frameUrl: event.frameUrl,\n label: event.text,\n timestamp: event.ts,\n };\n this.steps.push(step);\n this.notify({ kind: \"added\", step });\n break;\n }\n\n case \"select\": {\n this.flushFill();\n const step: Step = {\n id: this.nextId(),\n type: \"select\",\n selectors: event.selectors,\n value: event.value,\n coordinates: { xNorm: event.xNorm, yNorm: event.yNorm },\n frameUrl: event.frameUrl,\n timestamp: event.ts,\n };\n this.steps.push(step);\n this.notify({ kind: \"added\", step });\n break;\n }\n\n case \"key\": {\n // Enter inside a pending fill: flush the fill first, then the press.\n this.flushFill();\n const step: Step = {\n id: this.nextId(),\n type: \"press\",\n key: event.key,\n selectors: event.selectors,\n frameUrl: event.frameUrl,\n timestamp: event.ts,\n };\n this.steps.push(step);\n this.notify({ kind: \"added\", step });\n break;\n }\n\n case \"scroll\": {\n this.flushFill();\n // Collapse consecutive scrolls on the same target into the last one.\n const prev = this.steps[this.steps.length - 1];\n if (prev?.type === \"scroll\" && prev.scrollTarget === event.target) {\n prev.scroll = { xNorm: event.xNorm, yNorm: event.yNorm };\n prev.timestamp = event.ts;\n this.notify({ kind: \"updated\", step: prev });\n } else {\n const step: Step = {\n id: this.nextId(),\n type: \"scroll\",\n scrollTarget: event.target,\n scroll: { xNorm: event.xNorm, yNorm: event.yNorm },\n frameUrl: event.frameUrl,\n timestamp: event.ts,\n };\n this.steps.push(step);\n this.notify({ kind: \"added\", step });\n }\n break;\n }\n\n case \"navigate\": {\n this.flushFill();\n const prev = this.steps[this.steps.length - 1];\n // A click that triggered this navigation replays itself — just annotate.\n if (\n prev &&\n (prev.type === \"click\" || prev.type === \"press\") &&\n event.ts - prev.timestamp < 800\n ) {\n prev.causesNavigation = true;\n this.notify({ kind: \"updated\", step: prev });\n } else if (prev?.type === \"navigate\") {\n prev.url = event.url;\n prev.timestamp = event.ts;\n this.notify({ kind: \"updated\", step: prev });\n } else {\n const step: Step = {\n id: this.nextId(),\n type: \"navigate\",\n url: event.url,\n frameUrl: event.frameUrl,\n timestamp: event.ts,\n };\n this.steps.push(step);\n this.notify({ kind: \"added\", step });\n }\n break;\n }\n }\n }\n\n private flushFill(): void {\n this.pendingFill = null;\n }\n}\n\nexport type StepChange =\n | { kind: \"added\"; step: Step }\n | { kind: \"updated\"; step: Step }\n | { kind: \"removed\"; stepId: string }\n | { kind: \"reset\" };\n\n/** Two selector chains refer to the same element if any selector matches. */\nfunction selectorsOverlap(a: string[], b: string[]): boolean {\n return a.some((s) => b.includes(s));\n}\n","/**\n * Core data shapes: raw in-page events, normalized steps, and the journey file.\n * No Node imports — these types are shared with the in-page recorder bundle.\n */\n\n/** Raw event emitted by the in-page recorder via the exposed binding. */\nexport type RawEvent =\n | {\n kind: \"click\";\n selectors: string[];\n xNorm: number;\n yNorm: number;\n text?: string;\n frameUrl: string;\n ts: number;\n }\n | {\n kind: \"input\";\n selectors: string[];\n value: string;\n xNorm: number;\n yNorm: number;\n frameUrl: string;\n ts: number;\n }\n | {\n kind: \"key\";\n key: string;\n selectors: string[];\n frameUrl: string;\n ts: number;\n }\n | {\n kind: \"scroll\";\n target: string; // \"window\" or a selector\n xNorm: number; // scrollLeft / scrollWidth\n yNorm: number; // scrollTop / scrollHeight\n frameUrl: string;\n ts: number;\n }\n | {\n kind: \"select\";\n selectors: string[];\n value: string;\n xNorm: number;\n yNorm: number;\n frameUrl: string;\n ts: number;\n }\n | {\n kind: \"navigate\";\n url: string;\n frameUrl: string;\n ts: number;\n };\n\nexport type StepType = \"click\" | \"fill\" | \"select\" | \"press\" | \"scroll\" | \"navigate\" | \"wait\";\n\nexport interface Step {\n id: string;\n type: StepType;\n /** Ordered best→worst; every entry resolved uniquely at record time. */\n selectors?: string[];\n /** Viewport-normalized coordinates — the absolute fallback. */\n coordinates?: { xNorm: number; yNorm: number };\n /** Path (origin-relative) of the app at the time of the step. */\n frameUrl?: string;\n /** fill */\n value?: string;\n /** press */\n key?: string;\n /** scroll */\n scrollTarget?: string;\n scroll?: { xNorm: number; yNorm: number };\n /** navigate */\n url?: string;\n causesNavigation?: boolean;\n /** Human label for the sidebar row. */\n label?: string;\n /** ms offset from recording start. */\n timestamp: number;\n /** Small data-URL png for the sidebar; stripped when persisted. */\n thumbnail?: string;\n}\n\nexport interface Journey {\n version: 1;\n createdAt: string;\n baseRef?: string;\n viewport: { width: number; height: number; deviceScaleFactor: number };\n startUrl: string;\n steps: Step[];\n}\n\n/** Human-readable one-liner for a step (sidebar rows, logs). */\nexport function describeStep(step: Step): string {\n switch (step.type) {\n case \"click\":\n return `Click ${step.label ?? firstSelector(step)}`;\n case \"fill\":\n return `Type \"${truncate(step.value ?? \"\", 24)}\" into ${firstSelector(step)}`;\n case \"select\":\n return `Select \"${truncate(step.value ?? \"\", 24)}\" in ${firstSelector(step)}`;\n case \"press\":\n return `Press ${step.key}`;\n case \"scroll\":\n return `Scroll ${step.scrollTarget === \"window\" ? \"page\" : step.scrollTarget ?? \"page\"}`;\n case \"navigate\":\n return `Go to ${step.url}`;\n case \"wait\":\n return \"Wait\";\n }\n}\n\nfunction firstSelector(step: Step): string {\n return step.selectors?.[0] ?? \"element\";\n}\n\nfunction truncate(s: string, n: number): string {\n return s.length > n ? s.slice(0, n - 1) + \"…\" : s;\n}\n","import type { CDPSession, Page } from \"playwright\";\n\nexport interface CapturedFrame {\n /** JPEG bytes straight from the screencast. */\n data: Buffer;\n /** ms since capture start, with paused windows removed. */\n t: number;\n}\n\nexport interface ScreencastHandle {\n /** Drop incoming frames and compress the timeline (manual pauses). */\n pause(): void;\n resume(): void;\n stop(): Promise<CapturedFrame[]>;\n /** Frames captured so far (live) — used to mark where each step landed. */\n frameCount(): number;\n /** Drop frames after index `n` (re-record from a step in live capture). */\n truncate(n: number): void;\n /** Most recent frame's bytes — drives recording-time thumbnails. */\n latest(): Buffer | null;\n}\n\n/**\n * Capture frames via CDP Page.startScreencast — real video-rate frames with\n * timestamps, far smoother than interval screenshots for cursor motion.\n * Frames cover the whole page; cropping to the iframe happens at encode time.\n *\n * pause()/resume() cut manual-pause windows (user typing a password, fixing a\n * drifted step) out of the GIF entirely instead of baking in dead footage.\n */\nexport async function startScreencast(\n page: Page,\n opts: { quality?: number; everyNthFrame?: number } = {},\n): Promise<ScreencastHandle> {\n const cdp: CDPSession = await page.context().newCDPSession(page);\n const frames: CapturedFrame[] = [];\n let t0: number | null = null;\n let paused = false;\n let pauseBeganAbs: number | null = null;\n let removed = 0; // total ms cut from the timeline\n let lastAbs = 0;\n\n const onFrame = async (event: {\n data: string;\n sessionId: number;\n metadata: { timestamp?: number };\n }) => {\n const abs = (event.metadata.timestamp ?? Date.now() / 1000) * 1000;\n lastAbs = abs;\n if (t0 === null) t0 = abs;\n if (!paused) {\n frames.push({ data: Buffer.from(event.data, \"base64\"), t: abs - t0 - removed });\n }\n // Must ack every frame (even dropped ones) or Chrome stops sending.\n await cdp.send(\"Page.screencastFrameAck\", { sessionId: event.sessionId }).catch(() => {});\n };\n cdp.on(\"Page.screencastFrame\", onFrame);\n\n await cdp.send(\"Page.startScreencast\", {\n format: \"jpeg\",\n quality: opts.quality ?? 90,\n everyNthFrame: opts.everyNthFrame ?? 1,\n });\n\n return {\n frameCount() {\n return frames.length;\n },\n truncate(n: number) {\n if (n >= 0 && n < frames.length) frames.length = n;\n },\n latest() {\n return frames.length ? (frames[frames.length - 1]!.data) : null;\n },\n pause() {\n if (paused) return;\n paused = true;\n pauseBeganAbs = lastAbs;\n },\n resume() {\n if (!paused) return;\n paused = false;\n if (pauseBeganAbs !== null) removed += Math.max(0, lastAbs - pauseBeganAbs);\n pauseBeganAbs = null;\n },\n async stop() {\n await cdp.send(\"Page.stopScreencast\").catch(() => {});\n cdp.off(\"Page.screencastFrame\", onFrame);\n await cdp.detach().catch(() => {});\n return frames;\n },\n };\n}\n","export interface CropRect {\n left: number;\n top: number;\n width: number;\n height: number;\n}\n\n/**\n * Translate the iframe's CSS-pixel bounding box into pixel coordinates of an\n * actual screencast frame (which may be at devicePixelRatio scale).\n */\nexport function cssRectToFramePixels(\n rect: { x: number; y: number; width: number; height: number },\n frameSize: { width: number; height: number },\n pageCssSize: { width: number; height: number },\n): CropRect {\n const scaleX = frameSize.width / pageCssSize.width;\n const scaleY = frameSize.height / pageCssSize.height;\n const left = Math.max(0, Math.round(rect.x * scaleX));\n const top = Math.max(0, Math.round(rect.y * scaleY));\n return {\n left,\n top,\n width: Math.min(Math.round(rect.width * scaleX), frameSize.width - left),\n height: Math.min(Math.round(rect.height * scaleY), frameSize.height - top),\n };\n}\n","import sharp from \"sharp\";\nimport type { CapturedFrame } from \"../capture/screencast.js\";\nimport { resampleToFps } from \"../capture/frames.js\";\nimport { cssRectToFramePixels } from \"../capture/region.js\";\nimport { buildOverlays, encodeGif, type EncodeOptions } from \"./gif.js\";\nimport { encodeMp4, ffmpegAvailable } from \"./ffmpeg.js\";\n\nexport type MediaFormat = \"mp4\" | \"gif\" | \"both\";\n\nexport interface MediaResult {\n /** Produced files (mp4 first when both). */\n paths: string[];\n frameCount: number;\n /** Set when mp4 was requested but ffmpeg is missing. */\n fellBackToGif: boolean;\n}\n\n/**\n * Encode captured frames into the configured format(s).\n * `outBase` is the extension-less output path (e.g. \".pr-preview/output/before\").\n */\nexport async function encodeMedia(\n rawFrames: CapturedFrame[],\n opts: Omit<EncodeOptions, \"outFile\"> & { format: MediaFormat; outBase: string },\n): Promise<MediaResult> {\n if (rawFrames.length === 0) throw new Error(\"No frames captured — nothing to encode\");\n\n const wantsMp4 = opts.format === \"mp4\" || opts.format === \"both\";\n const haveFfmpeg = await ffmpegAvailable();\n const doMp4 = wantsMp4 && haveFfmpeg;\n const doGif = opts.format === \"gif\" || opts.format === \"both\" || (wantsMp4 && !haveFfmpeg);\n\n const paths: string[] = [];\n let frameCount = 0;\n\n if (doMp4) {\n const frames = resampleToFps(rawFrames, opts.fps);\n frameCount = frames.length;\n\n const meta = await sharp(frames[0]!.data).metadata();\n const crop = cssRectToFramePixels(\n opts.cropCss,\n { width: meta.width!, height: meta.height! },\n opts.pageCssSize,\n );\n // MP4 keeps full capture resolution up to 2x the configured width —\n // video compresses well, and PR viewers can fullscreen it.\n const outWidth = Math.min(opts.width * 2, crop.width);\n const outHeight = Math.round((crop.height / crop.width) * outWidth);\n const overlays = await buildOverlays(opts.label, outWidth, outHeight);\n\n const pngs: Buffer[] = [];\n const cache = new Map<Buffer, Buffer>();\n for (const f of frames) {\n let png = cache.get(f.data);\n if (!png) {\n let pipe = sharp(f.data).extract(crop).resize(outWidth, outHeight);\n if (overlays.length) pipe = pipe.composite(overlays);\n png = await pipe.png().toBuffer();\n cache.set(f.data, png);\n }\n pngs.push(png);\n opts.onProgress?.(\"Rendering frames\", pngs.length, frames.length);\n }\n const file = `${opts.outBase}.mp4`;\n opts.onProgress?.(\"Encoding MP4\", frames.length, frames.length);\n await encodeMp4(pngs, opts.fps, file);\n paths.push(file);\n }\n\n if (doGif) {\n const result = await encodeGif(rawFrames, { ...opts, outFile: `${opts.outBase}.gif` });\n paths.push(result.path);\n frameCount = result.frameCount;\n }\n\n return { paths, frameCount, fellBackToGif: wantsMp4 && !haveFfmpeg };\n}\n","import type { CapturedFrame } from \"./screencast.js\";\n\n/**\n * Resample variable-rate screencast frames onto a fixed-fps timeline by\n * nearest preceding frame. Consecutive duplicates are kept (GIF needs the\n * delay anyway) but identical buffers are reused by reference, so later\n * stages can cheap-compare with ===.\n */\nexport function resampleToFps(frames: CapturedFrame[], fps: number): CapturedFrame[] {\n if (frames.length === 0) return [];\n const interval = 1000 / fps;\n const duration = frames[frames.length - 1]!.t;\n const out: CapturedFrame[] = [];\n let src = 0;\n for (let t = 0; t <= duration; t += interval) {\n while (src + 1 < frames.length && frames[src + 1]!.t <= t) src++;\n out.push({ data: frames[src]!.data, t });\n }\n return out;\n}\n","import { mkdir, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport sharp from \"sharp\";\n// @ts-expect-error gifenc ships no types\nimport * as gifencNs from \"gifenc\";\n// Dual-package interop: Node ESM sees CJS (functions hang off .default),\n// bundler-resolved ESM has real named exports PLUS a default that is just\n// the encoder — so pick whichever shape actually carries the API.\nconst gifencLib: any =\n typeof (gifencNs as any).quantize === \"function\" ? gifencNs : (gifencNs as any).default;\nconst { GIFEncoder, quantize, applyPalette } = gifencLib;\nimport type { CapturedFrame } from \"../capture/screencast.js\";\nimport { resampleToFps } from \"../capture/frames.js\";\nimport { cssRectToFramePixels } from \"../capture/region.js\";\nimport { encodeWithFfmpeg, ffmpegAvailable } from \"./ffmpeg.js\";\nimport { renderLabel, renderWatermark, type LabelSpec } from \"./label.js\";\n\nexport interface EncodeOptions {\n /** Iframe bounding box in CSS px (from getAppFrameRect). */\n cropCss: { x: number; y: number; width: number; height: number };\n /** Page viewport in CSS px (to derive the screencast pixel scale). */\n pageCssSize: { width: number; height: number };\n width: number;\n fps: number;\n quality: \"high\" | \"max\";\n maxColors: number;\n outFile: string;\n /** Burned-in branch/timestamp caption (omit videoWidth — set per output). */\n label?: Omit<LabelSpec, \"videoWidth\">;\n /** Encoding progress, for a UI bar. `stage` is a short human label. */\n onProgress?: (stage: string, done: number, total: number) => void;\n}\n\nexport interface Overlay {\n input: Buffer;\n top: number;\n left: number;\n}\n\n/**\n * Build the burned-in overlays: the branch/timestamp caption (bottom-left)\n * and the pr-preview.com watermark (bottom-right). The watermark is always\n * present; the caption only when label info is provided.\n */\nexport async function buildOverlays(\n label: Omit<LabelSpec, \"videoWidth\"> | undefined,\n outWidth: number,\n outHeight: number,\n): Promise<Overlay[]> {\n const inset = Math.round(outWidth * 0.018);\n const overlays: Overlay[] = [];\n\n if (label) {\n const png = await renderLabel({ ...label, videoWidth: outWidth });\n const m = await sharp(png).metadata();\n overlays.push({ input: png, top: outHeight - (m.height ?? 0) - inset, left: inset });\n }\n\n const wm = await renderWatermark(outWidth);\n const wmMeta = await sharp(wm).metadata();\n overlays.push({\n input: wm,\n top: outHeight - (wmMeta.height ?? 0) - inset,\n left: outWidth - (wmMeta.width ?? 0) - inset,\n });\n\n return overlays;\n}\n\n/**\n * frames (JPEG, full page) → crop to iframe → resize → global palette →\n * GIF. With quality:\"max\" and ffmpeg on PATH, defer to ffmpeg's\n * palettegen/paletteuse for the best dithering.\n */\nexport async function encodeGif(\n rawFrames: CapturedFrame[],\n opts: EncodeOptions,\n): Promise<{ path: string; frameCount: number }> {\n if (rawFrames.length === 0) throw new Error(\"No frames captured — nothing to encode\");\n\n const frames = resampleToFps(rawFrames, opts.fps);\n const meta = await sharp(frames[0]!.data).metadata();\n const crop = cssRectToFramePixels(\n opts.cropCss,\n { width: meta.width!, height: meta.height! },\n opts.pageCssSize,\n );\n\n const outWidth = Math.min(opts.width, crop.width);\n const outHeight = Math.round((crop.height / crop.width) * outWidth);\n const overlays = await buildOverlays(opts.label, outWidth, outHeight);\n\n // Decode each unique source buffer once (resampling repeats buffers).\n const rgbaCache = new Map<Buffer, Uint8ClampedArray>();\n const decode = async (jpeg: Buffer): Promise<Uint8ClampedArray> => {\n const hit = rgbaCache.get(jpeg);\n if (hit) return hit;\n let pipe = sharp(jpeg).extract(crop).resize(outWidth, outHeight);\n if (overlays.length) pipe = pipe.composite(overlays);\n const { data } = await pipe.ensureAlpha().raw().toBuffer({ resolveWithObject: true });\n const rgba = new Uint8ClampedArray(data.buffer, data.byteOffset, data.byteLength);\n rgbaCache.set(jpeg, rgba);\n return rgba;\n };\n\n if (opts.quality === \"max\" && (await ffmpegAvailable())) {\n const pngs: Buffer[] = [];\n for (const f of frames) {\n let pipe = sharp(f.data).extract(crop).resize(outWidth, outHeight);\n if (overlays.length) pipe = pipe.composite(overlays);\n pngs.push(await pipe.png().toBuffer());\n opts.onProgress?.(\"Rendering frames\", pngs.length, frames.length);\n }\n opts.onProgress?.(\"Encoding GIF\", frames.length, frames.length);\n await encodeWithFfmpeg(pngs, opts.fps, opts.outFile);\n return { path: opts.outFile, frameCount: frames.length };\n }\n\n // Global palette sampled across the run — less flicker than per-frame.\n const sampleCount = Math.min(10, frames.length);\n const sampleStride = Math.max(1, Math.floor(frames.length / sampleCount));\n const samples: Uint8ClampedArray[] = [];\n for (let i = 0; i < frames.length; i += sampleStride) {\n samples.push(await decode(frames[i]!.data));\n }\n const combined = new Uint8ClampedArray(samples.reduce((n, s) => n + s.length, 0));\n let offset = 0;\n for (const s of samples) {\n combined.set(s, offset);\n offset += s.length;\n }\n const palette = quantize(combined, opts.maxColors);\n\n const gif = GIFEncoder();\n const delay = 1000 / opts.fps;\n let first = true;\n let done = 0;\n for (const frame of frames) {\n const rgba = await decode(frame.data);\n const indexed = applyPalette(rgba, palette);\n gif.writeFrame(indexed, outWidth, outHeight, {\n palette: first ? palette : undefined,\n first,\n delay,\n repeat: first ? 0 : undefined, // loop forever\n });\n first = false;\n opts.onProgress?.(\"Encoding GIF\", ++done, frames.length);\n }\n gif.finish();\n\n await mkdir(path.dirname(opts.outFile), { recursive: true });\n await writeFile(opts.outFile, gif.bytes());\n return { path: opts.outFile, frameCount: frames.length };\n}\n","import { execFile, spawn } from \"node:child_process\";\nimport { mkdtemp, rm, writeFile, mkdir } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\n\nlet available: boolean | null = null;\n\nexport function ffmpegAvailable(): Promise<boolean> {\n if (available !== null) return Promise.resolve(available);\n return new Promise((resolve) => {\n execFile(\"ffmpeg\", [\"-version\"], (err) => {\n available = !err;\n resolve(available);\n });\n });\n}\n\n/** Write frames into a temp dir, hand the pattern to fn, clean up after. */\nasync function withFrameDir<T>(\n pngFrames: Buffer[],\n fn: (pattern: string) => Promise<T>,\n): Promise<T> {\n const dir = await mkdtemp(path.join(tmpdir(), \"pr-preview-frames-\"));\n try {\n await Promise.all(\n pngFrames.map((buf, i) =>\n writeFile(path.join(dir, `frame${String(i).padStart(5, \"0\")}.png`), buf),\n ),\n );\n return await fn(path.join(dir, \"frame%05d.png\"));\n } finally {\n await rm(dir, { recursive: true, force: true });\n }\n}\n\n/**\n * Max-quality GIF via ffmpeg's two-pass palettegen/paletteuse with\n * Bayer dithering — noticeably better gradients than pure-JS quantization.\n */\nexport async function encodeWithFfmpeg(\n pngFrames: Buffer[],\n fps: number,\n outFile: string,\n): Promise<void> {\n await withFrameDir(pngFrames, async (pattern) => {\n await mkdir(path.dirname(outFile), { recursive: true });\n const filter =\n `[0:v]fps=${fps},split[a][b];` +\n `[a]palettegen=stats_mode=diff[p];` +\n `[b][p]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle`;\n await run(\"ffmpeg\", [\"-y\", \"-framerate\", String(fps), \"-i\", pattern, \"-filter_complex\", filter, outFile]);\n });\n}\n\n/**\n * Small + HQ H.264 MP4 — uploads directly into a GitHub PR description.\n * yuv420p + faststart for universal playback; dimensions forced even\n * (H.264 requirement).\n */\nexport async function encodeMp4(\n pngFrames: Buffer[],\n fps: number,\n outFile: string,\n): Promise<void> {\n await withFrameDir(pngFrames, async (pattern) => {\n await mkdir(path.dirname(outFile), { recursive: true });\n await run(\"ffmpeg\", [\n \"-y\",\n \"-framerate\",\n String(fps),\n \"-i\",\n pattern,\n \"-c:v\",\n \"libx264\",\n \"-preset\",\n \"slow\",\n \"-crf\",\n \"19\",\n \"-pix_fmt\",\n \"yuv420p\",\n \"-vf\",\n \"scale=trunc(iw/2)*2:trunc(ih/2)*2\",\n \"-movflags\",\n \"+faststart\",\n outFile,\n ]);\n });\n}\n\nfunction run(cmd: string, args: string[]): Promise<void> {\n return new Promise((resolve, reject) => {\n const child = spawn(cmd, args, { stdio: [\"ignore\", \"ignore\", \"pipe\"] });\n let err = \"\";\n child.stderr.on(\"data\", (c) => (err = (err + c.toString()).slice(-2000)));\n child.on(\"error\", reject);\n child.on(\"exit\", (code) =>\n code === 0 ? resolve() : reject(new Error(`${cmd} failed (${code}):\\n${err}`)),\n );\n });\n}\n","import { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { fileURLToPath } from \"node:url\";\nimport sharp from \"sharp\";\n\nexport interface LabelSpec {\n pass: \"before\" | \"after\" | \"single\";\n /** Branch this clip shows. */\n branch: string;\n /** The PR base / main branch. */\n baseBranch: string;\n /** Pre-formatted timestamp, e.g. \"2026-06-08 10:21\". */\n timestamp: string;\n /** Output clip width in px — the caption scales to it. */\n videoWidth: number;\n}\n\nconst BLURPLE = \"#635BFF\";\nconst GREEN = \"#2DA44E\";\nconst INK = \"#1F2328\";\nconst MUTED = \"#57606A\";\n\n/** Locate the bundled Inter font (dist/assets at runtime, repo assets in tests). */\nfunction fontFile(): string {\n const here = path.dirname(fileURLToPath(import.meta.url));\n const candidates = [\n path.resolve(here, \"../../assets/fonts/Inter.ttf\"), // dist/encode → dist/../assets? n/a\n path.resolve(here, \"../assets/fonts/Inter.ttf\"), // dist/encode → dist/assets\n path.resolve(here, \"../../../assets/fonts/Inter.ttf\"), // src/encode → repo/assets\n ];\n return candidates.find(existsSync) ?? candidates[candidates.length - 1]!;\n}\n\n/**\n * Silence fontconfig's \"Cannot load default config\" stderr noise by pointing\n * it at a generated minimal config that also exposes the bundled font dir.\n * Runs once, lazily, before the first text render.\n */\nlet fontconfigReady = false;\nfunction ensureFontconfig(): void {\n if (fontconfigReady || process.env.FONTCONFIG_FILE) {\n fontconfigReady = true;\n return;\n }\n try {\n const dir = path.join(tmpdir(), \"pr-preview-fontconfig\");\n mkdirSync(dir, { recursive: true });\n const conf = path.join(dir, \"fonts.conf\");\n writeFileSync(\n conf,\n `<?xml version=\"1.0\"?>\n<!DOCTYPE fontconfig SYSTEM \"fonts.dtd\">\n<fontconfig>\n <dir>${path.dirname(fontFile())}</dir>\n <cachedir>${path.join(dir, \"cache\")}</cachedir>\n</fontconfig>\n`,\n );\n process.env.FONTCONFIG_FILE = conf;\n } catch {\n /* the explicit fontfile still works without this — just noisier */\n }\n fontconfigReady = true;\n}\n\nfunction escapeMarkup(s: string): string {\n return s\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\n}\n\n/**\n * Render a polished caption pill (PNG with alpha) naming the branch, the base\n * it's compared against, and the timestamp — composited onto every output\n * frame so the clip is self-describing.\n */\nexport async function renderLabel(spec: LabelSpec): Promise<Buffer> {\n ensureFontconfig();\n\n const accent = spec.pass === \"after\" ? GREEN : BLURPLE;\n const branch = escapeMarkup(spec.branch);\n const base = escapeMarkup(spec.baseBranch);\n const ts = escapeMarkup(spec.timestamp);\n\n // One Pango-markup run: colored dot + (tag), bold branch, muted context.\n // Single clips drop the BEFORE/AFTER pill and the base comparison.\n const tagSpan =\n spec.pass === \"single\"\n ? `<span foreground=\"${accent}\" weight=\"700\">●</span>`\n : `<span foreground=\"${accent}\" weight=\"700\">● ${spec.pass === \"before\" ? \"BEFORE\" : \"AFTER\"}</span>`;\n const context =\n spec.pass === \"single\"\n ? `${ts}`\n : spec.pass === \"before\"\n ? `base branch · ${ts}`\n : `vs base ${base} · ${ts}`;\n\n const markup =\n `${tagSpan}` +\n ` <span foreground=\"${INK}\" weight=\"700\">${branch}</span>` +\n ` <span foreground=\"${MUTED}\">${context}</span>`;\n\n const pt = Math.round(Math.min(28, Math.max(15, spec.videoWidth * 0.0145)));\n const text = await sharp({\n text: { text: markup, fontfile: fontFile(), font: `sans ${pt}`, rgba: true, dpi: 72 },\n })\n .png()\n .toBuffer();\n const tm = await sharp(text).metadata();\n const tw = tm.width ?? 200;\n const th = tm.height ?? pt;\n\n const padX = Math.round(pt * 0.85);\n const padY = Math.round(pt * 0.6);\n const w = tw + padX * 2;\n const h = th + padY * 2;\n const r = Math.round(h / 2);\n\n // Rounded-rect background (shapes only — always renders, no font needed).\n const bg = Buffer.from(\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${w}\" height=\"${h}\">\n <rect x=\"0.5\" y=\"0.5\" width=\"${w - 1}\" height=\"${h - 1}\" rx=\"${r}\" ry=\"${r}\"\n fill=\"#FFFFFF\" fill-opacity=\"0.94\" stroke=\"#D0D7DE\" stroke-opacity=\"0.9\"/>\n </svg>`,\n );\n\n return sharp(bg)\n .composite([{ input: text, top: padY, left: padX }])\n .png()\n .toBuffer();\n}\n\n/** The pr-preview mark (clapperboard + </>) as an SVG string, for rasterizing. */\nconst LOGO_SVG = `<svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" xmlns=\"http://www.w3.org/2000/svg\">\n <defs><linearGradient id=\"g\" x1=\"6\" y1=\"4\" x2=\"58\" y2=\"60\" gradientUnits=\"userSpaceOnUse\">\n <stop stop-color=\"#635BFF\"/><stop offset=\"1\" stop-color=\"#4B45C6\"/></linearGradient></defs>\n <rect x=\"2\" y=\"2\" width=\"60\" height=\"60\" rx=\"15\" fill=\"url(#g)\"/>\n <g transform=\"rotate(-9 32 19)\"><rect x=\"13\" y=\"13.5\" width=\"38\" height=\"9.5\" rx=\"2.4\" fill=\"#fff\"/>\n <path d=\"M19 13.5 24 23M27 13.5 32 23M35 13.5 40 23M43 13.5 48 23\" stroke=\"#4B45C6\" stroke-width=\"2.3\" stroke-linecap=\"round\"/></g>\n <rect x=\"13\" y=\"26\" width=\"38\" height=\"24\" rx=\"4.5\" fill=\"#fff\"/>\n <g stroke=\"#4B45C6\" stroke-width=\"2.7\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\">\n <path d=\"M23 32 17.5 38 23 44\"/><path d=\"M41 32 46.5 38 41 44\"/><path d=\"M35.5 30.5 28.5 45.5\" stroke=\"#635BFF\"/></g>\n</svg>`;\n\n/**\n * A small bottom-right watermark: the pr-preview mark + the project URL,\n * burned into every clip.\n */\nexport async function renderWatermark(videoWidth: number): Promise<Buffer> {\n ensureFontconfig();\n const pt = Math.round(Math.min(22, Math.max(12, videoWidth * 0.0115)));\n const logoPx = Math.round(pt * 1.7);\n\n const logo = await sharp(Buffer.from(LOGO_SVG)).resize(logoPx, logoPx).png().toBuffer();\n const text = await sharp({\n text: {\n text: `<span foreground=\"${INK}\" weight=\"600\">pr-preview</span><span foreground=\"${MUTED}\">.com</span>`,\n fontfile: fontFile(),\n font: `sans ${pt}`,\n rgba: true,\n dpi: 72,\n },\n })\n .png()\n .toBuffer();\n const tm = await sharp(text).metadata();\n const tw = tm.width ?? 120;\n const th = tm.height ?? pt;\n\n const gap = Math.round(pt * 0.5);\n const padX = Math.round(pt * 0.75);\n const padY = Math.round(pt * 0.5);\n const h = Math.max(logoPx, th) + padY * 2;\n const w = padX * 2 + logoPx + gap + tw;\n const r = Math.round(h / 2);\n\n const bg = Buffer.from(\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${w}\" height=\"${h}\">\n <rect x=\"0.5\" y=\"0.5\" width=\"${w - 1}\" height=\"${h - 1}\" rx=\"${r}\" ry=\"${r}\"\n fill=\"#FFFFFF\" fill-opacity=\"0.94\" stroke=\"#D0D7DE\" stroke-opacity=\"0.9\"/>\n </svg>`,\n );\n\n return sharp(bg)\n .composite([\n { input: logo, top: Math.round((h - logoPx) / 2), left: padX },\n { input: text, top: Math.round((h - th) / 2), left: padX + logoPx + gap },\n ])\n .png()\n .toBuffer();\n}\n","import type { Frame, Page } from \"playwright\";\n\n/**\n * Resolve the target app's Frame inside the harness page. Frames are\n * recreated on full reloads (HMR, navigation) so always re-resolve rather\n * than caching the handle.\n */\nexport async function getAppFrame(\n page: Page,\n targetOrigin: string,\n timeoutMs = 15_000,\n): Promise<Frame> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n const frame = page.frames().find((f) => {\n try {\n return new URL(f.url()).origin === targetOrigin;\n } catch {\n return false;\n }\n });\n if (frame && !frame.isDetached()) return frame;\n await page.waitForTimeout(200);\n }\n throw new Error(`App iframe (${targetOrigin}) did not appear within ${timeoutMs}ms`);\n}\n\n/** Bounding box of the iframe element in page coordinates (CSS px). */\nexport async function getAppFrameRect(\n page: Page,\n): Promise<{ x: number; y: number; width: number; height: number }> {\n const box = await page.locator(\"#app-frame\").boundingBox();\n if (!box) throw new Error(\"App iframe element (#app-frame) not found in harness page\");\n return box;\n}\n","/**\n * Signal-safe teardown: register cleanups (dev servers, worktrees, browser)\n * and run them on normal exit, Ctrl-C, or crash — never leak a worktree.\n */\n\ntype Cleanup = () => Promise<void> | void;\n\nconst cleanups: Cleanup[] = [];\nlet installed = false;\nlet running = false;\n\nexport function registerCleanup(fn: Cleanup): () => void {\n install();\n cleanups.push(fn);\n return () => {\n const i = cleanups.indexOf(fn);\n if (i >= 0) cleanups.splice(i, 1);\n };\n}\n\nexport async function runCleanups(): Promise<void> {\n if (running) return;\n running = true;\n // LIFO: tear down in reverse acquisition order.\n for (const fn of [...cleanups].reverse()) {\n try {\n await fn();\n } catch {\n /* best effort */\n }\n }\n cleanups.length = 0;\n running = false;\n}\n\nfunction install(): void {\n if (installed) return;\n installed = true;\n for (const signal of [\"SIGINT\", \"SIGTERM\"] as const) {\n process.on(signal, () => {\n void runCleanups().then(() => process.exit(130));\n });\n }\n process.on(\"uncaughtException\", (err) => {\n console.error(err);\n void runCleanups().then(() => process.exit(1));\n });\n}\n","import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { z } from \"zod\";\nimport type { Journey, Step } from \"./types.js\";\n\nconst stepSchema: z.ZodType<Step> = z.object({\n id: z.string(),\n type: z.enum([\"click\", \"fill\", \"select\", \"press\", \"scroll\", \"navigate\", \"wait\"]),\n selectors: z.array(z.string()).optional(),\n coordinates: z.object({ xNorm: z.number(), yNorm: z.number() }).optional(),\n frameUrl: z.string().optional(),\n value: z.string().optional(),\n key: z.string().optional(),\n scrollTarget: z.string().optional(),\n scroll: z.object({ xNorm: z.number(), yNorm: z.number() }).optional(),\n url: z.string().optional(),\n causesNavigation: z.boolean().optional(),\n label: z.string().optional(),\n timestamp: z.number(),\n thumbnail: z.string().optional(),\n});\n\nconst journeySchema: z.ZodType<Journey> = z.object({\n version: z.literal(1),\n createdAt: z.string(),\n baseRef: z.string().optional(),\n viewport: z.object({\n width: z.number(),\n height: z.number(),\n deviceScaleFactor: z.number(),\n }),\n startUrl: z.string(),\n steps: z.array(stepSchema),\n});\n\nexport async function loadJourney(file: string): Promise<Journey> {\n const raw = JSON.parse(await readFile(file, \"utf8\"));\n const parsed = journeySchema.safeParse(raw);\n if (!parsed.success) {\n throw new Error(`Invalid journey file ${file}: ${parsed.error.issues[0]?.message}`);\n }\n return parsed.data;\n}\n\n/** Persist a journey — sidebar thumbnails never hit disk. */\nexport async function saveJourney(file: string, journey: Journey): Promise<void> {\n const clean: Journey = {\n ...journey,\n steps: journey.steps.map((s) => {\n const { thumbnail: _thumbnail, ...rest } = s;\n return rest;\n }),\n };\n await mkdir(path.dirname(file), { recursive: true });\n await writeFile(file, JSON.stringify(clean, null, 2) + \"\\n\");\n}\n","import { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport pc from \"picocolors\";\nimport { loadConfig, ConfigError } from \"../../config/load.js\";\nimport { configSchema, type Config } from \"../../config/schema.js\";\nimport { detectBase } from \"../../git/base.js\";\nimport { tryGit } from \"../../git/exec.js\";\nimport { createBaseWorktree } from \"../../git/worktree.js\";\nimport { detectPackageManager, installDependencies } from \"../../server/pkgManager.js\";\nimport { bootstrap } from \"../../session/bootstrap.js\";\nimport type { Session } from \"../../session/session.js\";\nimport { saveJourney } from \"../../recorder/journey.js\";\nimport type { Step } from \"../../recorder/types.js\";\nimport { log } from \"../ui/logger.js\";\nimport { revealFiles } from \"../util/openPath.js\";\nimport { registerCleanup, runCleanups } from \"../cleanup.js\";\n\n/** Encode the recorded footage for one pass into its clip. */\nfunction captureClip(\n session: Session,\n which: \"before\" | \"after\",\n outBase: string,\n label: { branch: string; baseBranch: string; timestamp: string },\n) {\n return session.encodeClip(which, outBase, label);\n}\n\n/** Short, human \"YYYY-MM-DD HH:MM\" for the burned-in caption. */\nfunction stamp(): string {\n const d = new Date();\n const p = (n: number) => String(n).padStart(2, \"0\");\n return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;\n}\n\n/** Trim noisy ref prefixes for the on-clip label (origin/main → main). */\nfunction shortRef(ref: string): string {\n return ref.replace(/^origin\\//, \"\");\n}\n\n/** Filesystem-safe slug for a branch name (feature/demo → feature-demo). */\nfunction slug(ref: string): string {\n return shortRef(ref).replace(/[^a-zA-Z0-9._-]+/g, \"-\").replace(/^-+|-+$/g, \"\");\n}\n\n/** Compact timestamp for filenames, e.g. 20260608-1108. */\nfunction fileStamp(): string {\n const d = new Date();\n const p = (n: number) => String(n).padStart(2, \"0\");\n return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}`;\n}\n\nexport interface RunOptions {\n base?: string;\n keepWorktree?: boolean;\n /** Use an already-running app at this URL instead of a managed dev server. */\n url?: string;\n /** Record a single standalone clip (no before/after). Forces passes = 1. */\n single?: boolean;\n}\n\n/** Current branch name (slugged), or a fallback when not in a git repo. */\nasync function branchName(repoRoot: string, fallback: string): Promise<string> {\n const ref = await tryGit(repoRoot, [\"rev-parse\", \"--abbrev-ref\", \"HEAD\"]);\n return ref ? shortRef(ref) : fallback;\n}\n\n/**\n * The product flow — each pass is recorded on its own version, because the\n * before and after UIs may share nothing:\n *\n * BEFORE (base worktree): user records → Confirm → replay+capture → before.gif\n * AFTER (working tree): user records again (or loads the BEFORE steps as a\n * starting point) → Save → replay+capture → after.gif\n */\nexport async function runCommand(repoRoot: string, opts: RunOptions): Promise<void> {\n let config: Config;\n const urlFlag = opts.url; // may also come from config.externalUrl below\n try {\n config = (await loadConfig(repoRoot)).config;\n } catch (err) {\n // In --url mode the dev command/url are unused, so a config file is optional.\n if (urlFlag && err instanceof ConfigError) {\n config = configSchema.parse({ devCommand: \"external\", url: urlFlag });\n } else {\n throw err;\n }\n }\n\n // CLI flags override config; otherwise fall back to config defaults so a\n // project can be set up once and run with just `pr-preview run` (handy for\n // CI and scripting).\n opts = {\n url: opts.url ?? config.externalUrl,\n base: opts.base ?? config.baseBranch,\n keepWorktree: opts.keepWorktree ?? config.keepWorktree,\n single: opts.single,\n };\n const outDir = path.resolve(repoRoot, config.output);\n const passes: 1 | 2 = opts.single ? 1 : config.passes;\n\n if (passes === 1) return runSingleClip(repoRoot, config, outDir, opts);\n if (opts.url) return runWithExternalApp(repoRoot, config, outDir, opts);\n\n try {\n // ---- 1. base worktree ---------------------------------------------------\n const base = await detectBase(repoRoot, opts.base ?? config.baseBranch);\n const currentBranch =\n (await tryGit(repoRoot, [\"rev-parse\", \"--abbrev-ref\", \"HEAD\"])) ?? \"HEAD\";\n log.info(`PR base: ${pc.bold(base.ref)} (${base.sha.slice(0, 10)}, via ${base.source})`);\n\n const worktree = await createBaseWorktree(repoRoot, base.sha);\n if (!opts.keepWorktree) registerCleanup(() => worktree.remove());\n log.success(`Base worktree at ${path.relative(repoRoot, worktree.dir)}`);\n\n if (config.install !== \"never\") {\n const appDir = path.join(worktree.dir, config.cwd);\n const pm = detectPackageManager(appDir);\n const hasModules = existsSync(path.join(appDir, \"node_modules\"));\n if (config.install === \"always\" || !hasModules) {\n const spinner = log.spinner(`Installing dependencies in worktree (${pm}) …`);\n try {\n await installDependencies(appDir, pm);\n spinner.succeed(\"Worktree dependencies installed\");\n } catch (err) {\n spinner.fail(\"Install failed\");\n throw err;\n }\n }\n }\n\n // ---- 2. BEFORE pass ------------------------------------------------------\n const boot = await bootstrap(repoRoot, config, \"run\");\n boot.session.setPasses(2);\n boot.session.setBranches(base.ref, currentBranch);\n\n const beforeServer = await boot.startApp(\"before\", worktree.dir);\n await boot.openBrowser(\"before\");\n log.info(`Harness open at ${pc.bold(boot.harness.url)}`);\n\n log.step(`Record the journey on BEFORE (${base.ref}), then Confirm in the sidebar.`);\n\n await boot.session.promptResetChoice(); // reset/keep the app, then record\n const beforeSteps: Step[] = await boot.session.recordUntilConfirmed();\n if (beforeSteps.length === 0) throw new Error(\"No steps recorded for BEFORE — aborting.\");\n await saveJourneyFile(repoRoot, \"journey-before.json\", config, boot.session.startUrl, beforeSteps, base.sha);\n\n const timestamp = stamp();\n const fstamp = fileStamp();\n const baseLabel = shortRef(base.ref);\n const headLabel = shortRef(currentBranch);\n\n log.info(\"Capturing BEFORE …\");\n const before = await captureClip(boot.session,\n \"before\",\n path.join(outDir, `before-${slug(base.ref)}-${fstamp}`),\n { branch: baseLabel, baseBranch: baseLabel, timestamp },\n );\n if (before.fellBackToGif) {\n log.warn(\"ffmpeg not found — produced a GIF instead of MP4 (brew install ffmpeg).\");\n }\n log.success(`${before.paths.map((p) => path.basename(p)).join(\" + \")} (${before.frameCount} frames)`);\n\n // ---- 3. AFTER pass --------------------------------------------------------\n await beforeServer.stop();\n await boot.startApp(\"after\", repoRoot);\n boot.session.switchPass(\"after\"); // stores BEFORE steps as an optional template\n await boot.switchApp(\"after\");\n boot.session.setPhase(\"idle\"); // back to interactive — AFTER recording starts\n\n log.step(\n `Now record the journey on AFTER (${currentBranch}) — the UI may differ. ` +\n `Record fresh or load the BEFORE steps, then Save.`,\n );\n await boot.session.promptResetChoice(); // reset/keep the app, then record\n const afterSteps: Step[] = await boot.session.recordUntilConfirmed();\n if (afterSteps.length === 0) throw new Error(\"No steps recorded for AFTER — aborting.\");\n await saveJourneyFile(repoRoot, \"journey-after.json\", config, boot.session.startUrl, afterSteps);\n\n log.info(\"Capturing AFTER …\");\n const after = await captureClip(boot.session,\n \"after\",\n path.join(outDir, `after-${slug(currentBranch)}-${fstamp}`),\n { branch: headLabel, baseBranch: baseLabel, timestamp },\n );\n log.success(`${after.paths.map((p) => path.basename(p)).join(\" + \")} (${after.frameCount} frames)`);\n\n boot.session.setPhase(\"done\");\n boot.harness.bus.send({\n type: \"DONE\",\n outputs: { before: before.paths[0], after: after.paths[0] },\n });\n\n console.log();\n log.success(pc.bold(\"Done — drag these into your PR description:\"));\n for (const p of [...before.paths, ...after.paths]) {\n log.step(path.relative(repoRoot, p));\n }\n // Reveal the produced clips in the file manager (selected).\n revealFiles([...before.paths, ...after.paths], outDir);\n } finally {\n await runCleanups();\n }\n}\n\n/**\n * Single-recording flow (`--single` / `passes: 1`): record ONE journey on the\n * current app and save one clip — no before/after, no base worktree. Works with\n * a managed dev server (current working tree) or your own running app (`--url`).\n */\nasync function runSingleClip(\n repoRoot: string,\n config: Config,\n outDir: string,\n opts: RunOptions,\n): Promise<void> {\n try {\n const boot = opts.url\n ? await bootstrap(repoRoot, config, \"run\", { fixedUrl: opts.url })\n : await bootstrap(repoRoot, config, \"run\");\n boot.session.setPasses(1); // single-clip wizard (no before/after tabs)\n const branch = await branchName(repoRoot, \"app\");\n\n let server;\n if (opts.url) {\n log.info(`Using your running app at ${pc.bold(opts.url)} (single clip).`);\n } else {\n // No base worktree — just run the current project's dev server.\n server = await boot.startApp(\"before\", repoRoot);\n }\n await boot.openBrowser(\"before\");\n log.info(`Harness open at ${pc.bold(boot.harness.url)}`);\n\n log.step(`Record the journey, then Confirm to save the clip.`);\n\n await boot.session.promptResetChoice();\n const steps: Step[] = await boot.session.recordUntilConfirmed();\n if (steps.length === 0) throw new Error(\"No steps recorded — aborting.\");\n await saveJourneyFile(repoRoot, \"journey.json\", config, boot.session.startUrl, steps);\n\n const timestamp = stamp();\n log.info(\"Capturing the clip …\");\n const clip = await captureClip(\n boot.session,\n \"before\",\n path.join(outDir, `${slug(branch)}-${fileStamp()}`),\n { branch, baseBranch: branch, timestamp },\n );\n if (clip.fellBackToGif) {\n log.warn(\"ffmpeg not found — produced a GIF instead of MP4 (brew install ffmpeg).\");\n }\n await server?.stop();\n\n boot.session.setPhase(\"done\");\n boot.harness.bus.send({ type: \"DONE\", outputs: { before: clip.paths[0] } });\n console.log();\n log.success(pc.bold(\"Done — your clip:\"));\n for (const p of clip.paths) log.step(path.relative(repoRoot, p));\n revealFiles(clip.paths, outDir);\n } finally {\n await runCleanups();\n }\n}\n\n/**\n * `--url` flow: the user runs the app themselves. We record BEFORE on their\n * running app, pause while they switch branches + restart it on the same URL,\n * then record AFTER. No git worktree, no managed dev server.\n */\nasync function runWithExternalApp(\n repoRoot: string,\n config: Config,\n outDir: string,\n opts: RunOptions,\n): Promise<void> {\n log.info(`Using your running app at ${pc.bold(opts.url!)} (no dev server managed).`);\n try {\n const boot = await bootstrap(repoRoot, config, \"run\", { fixedUrl: opts.url });\n boot.session.setPasses(2);\n const beforeBranch = await branchName(repoRoot, \"before\");\n boot.session.setBranches(beforeBranch, \"your PR branch\");\n await boot.openBrowser(\"before\");\n log.info(`Harness open at ${pc.bold(boot.harness.url)}`);\n\n log.step(`Record the journey on your app (currently ${beforeBranch}), then Confirm.`);\n await boot.session.promptResetChoice(); // reset/keep the app, then record\n const beforeSteps: Step[] = await boot.session.recordUntilConfirmed();\n if (beforeSteps.length === 0) throw new Error(\"No steps recorded for BEFORE — aborting.\");\n\n const timestamp = stamp();\n const fstamp = fileStamp();\n await saveJourneyFile(repoRoot, \"journey-before.json\", config, boot.session.startUrl, beforeSteps);\n\n log.info(\"Capturing BEFORE …\");\n const before = await captureClip(boot.session,\n \"before\",\n path.join(outDir, `before-${slug(beforeBranch)}-${fstamp}`),\n { branch: beforeBranch, baseBranch: beforeBranch, timestamp },\n );\n log.success(`${before.paths.map((p) => path.basename(p)).join(\" + \")} (${before.frameCount} frames)`);\n\n // Hand off: the user switches their app to the PR branch and restarts it.\n log.info(pc.bold(\"→ Switch your app to the PR branch and restart it on the same URL, then Continue in the harness.\"));\n boot.harness.bus.send({\n type: \"MANUAL_PAUSE\",\n stepId: null,\n kind: \"generic\",\n label: `Switch your app to your PR branch and restart it on ${opts.url}, then click Continue.`,\n });\n await boot.harness.bus.waitFor(\"CONTINUE\");\n\n const afterBranch = await branchName(repoRoot, \"after\");\n boot.session.switchPass(\"after\"); // stashes BEFORE steps as a template\n boot.session.setBranches(beforeBranch, afterBranch);\n await boot.switchApp(\"after\"); // reload the same URL → now the PR-branch app\n boot.session.setPhase(\"idle\");\n\n log.step(`Now record the journey on AFTER (${afterBranch}) — record fresh or load the BEFORE steps, then Save.`);\n await boot.session.promptResetChoice(); // reset/keep the app, then record\n const afterSteps: Step[] = await boot.session.recordUntilConfirmed();\n if (afterSteps.length === 0) throw new Error(\"No steps recorded for AFTER — aborting.\");\n await saveJourneyFile(repoRoot, \"journey-after.json\", config, boot.session.startUrl, afterSteps);\n\n log.info(\"Capturing AFTER …\");\n const after = await captureClip(boot.session,\n \"after\",\n path.join(outDir, `after-${slug(afterBranch)}-${fstamp}`),\n { branch: afterBranch, baseBranch: beforeBranch, timestamp },\n );\n log.success(`${after.paths.map((p) => path.basename(p)).join(\" + \")} (${after.frameCount} frames)`);\n\n boot.session.setPhase(\"done\");\n boot.harness.bus.send({ type: \"DONE\", outputs: { before: before.paths[0], after: after.paths[0] } });\n console.log();\n log.success(pc.bold(\"Done — drag these into your PR description:\"));\n for (const p of [...before.paths, ...after.paths]) log.step(path.relative(repoRoot, p));\n revealFiles([...before.paths, ...after.paths], outDir);\n } finally {\n await runCleanups();\n }\n}\n\nasync function saveJourneyFile(\n repoRoot: string,\n name: string,\n config: { viewport: { width: number; height: number } },\n startUrl: string,\n steps: Step[],\n baseRef?: string,\n): Promise<void> {\n await saveJourney(path.join(repoRoot, \".pr-preview\", name), {\n version: 1,\n createdAt: new Date().toISOString(),\n baseRef,\n viewport: { ...config.viewport, deviceScaleFactor: 2 },\n startUrl,\n steps,\n });\n}\n","import { execFile } from \"node:child_process\";\n\nexport class GitError extends Error {}\n\n/** Run a git command in dir and return trimmed stdout; throws GitError on failure. */\nexport function git(dir: string, args: string[]): Promise<string> {\n return new Promise((resolve, reject) => {\n execFile(\"git\", args, { cwd: dir, maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {\n if (err) reject(new GitError(`git ${args.join(\" \")} failed: ${stderr || err.message}`));\n else resolve(stdout.trim());\n });\n });\n}\n\n/** Like git() but null on failure — for probes. */\nexport async function tryGit(dir: string, args: string[]): Promise<string | null> {\n try {\n return await git(dir, args);\n } catch {\n return null;\n }\n}\n\n/** Run `gh` and return trimmed stdout, or null if unavailable/failed. */\nexport function tryGh(dir: string, args: string[]): Promise<string | null> {\n return new Promise((resolve) => {\n execFile(\"gh\", args, { cwd: dir }, (err, stdout) => {\n resolve(err ? null : stdout.trim());\n });\n });\n}\n","import { git, tryGit, tryGh, GitError } from \"./exec.js\";\n\nexport interface BaseInfo {\n /** Commit sha the \"before\" worktree is created at (merge-base). */\n sha: string;\n /** Where the base came from, for logging. */\n source: \"gh-pr\" | \"config\" | \"merge-base\";\n ref: string;\n}\n\n/**\n * Determine the PR base commit:\n * 1. explicit config override\n * 2. `gh pr view` if the branch has an open PR\n * 3. merge-base with origin/HEAD → main → master → develop\n */\nexport async function detectBase(repoRoot: string, override?: string): Promise<BaseInfo> {\n // Friendly pre-flight: a git repo with no commits / no HEAD can't be diffed.\n if (!(await tryGit(repoRoot, [\"rev-parse\", \"--is-inside-work-tree\"]))) {\n throw new GitError(\n `Not inside a git repository (${repoRoot}). pr-preview run needs a git repo with a ` +\n `committed base branch — cd into your project and try again.`,\n );\n }\n if (!(await tryGit(repoRoot, [\"rev-parse\", \"--verify\", \"HEAD\"]))) {\n throw new GitError(\n `This git repository has no commits yet, so there is no base to compare against. ` +\n `Commit your work and run pr-preview on a feature branch (or pass --base <ref>).`,\n );\n }\n\n if (override) {\n const sha = await git(repoRoot, [\"merge-base\", \"HEAD\", override]);\n return { sha, source: \"config\", ref: override };\n }\n\n const ghBase = await tryGh(repoRoot, [\"pr\", \"view\", \"--json\", \"baseRefName\", \"-q\", \".baseRefName\"]);\n if (ghBase) {\n const ref = (await tryGit(repoRoot, [\"rev-parse\", \"--verify\", `origin/${ghBase}`]))\n ? `origin/${ghBase}`\n : ghBase;\n const sha = await tryGit(repoRoot, [\"merge-base\", \"HEAD\", ref]);\n if (sha) return { sha, source: \"gh-pr\", ref };\n }\n\n const candidates: string[] = [];\n const originHead = await tryGit(repoRoot, [\"symbolic-ref\", \"refs/remotes/origin/HEAD\"]);\n if (originHead) candidates.push(originHead.replace(\"refs/remotes/\", \"\"));\n candidates.push(\"origin/main\", \"origin/master\", \"main\", \"master\", \"develop\");\n\n const current = await git(repoRoot, [\"rev-parse\", \"HEAD\"]);\n for (const ref of candidates) {\n if (!(await tryGit(repoRoot, [\"rev-parse\", \"--verify\", `${ref}^{commit}`]))) continue;\n const sha = await tryGit(repoRoot, [\"merge-base\", \"HEAD\", ref]);\n // A base equal to HEAD means we're ON the default branch — keep looking.\n if (sha && sha !== current) return { sha, source: \"merge-base\", ref };\n if (sha && sha === current) {\n throw new GitError(\n `Current branch has no commits beyond ${ref} — there is no \"before\" to compare. ` +\n `Switch to your PR branch first, or pass --base <ref>.`,\n );\n }\n }\n throw new GitError(\n \"Could not detect the PR base. Pass --base <ref> or set baseBranch in pr-preview.config.\",\n );\n}\n","import { existsSync } from \"node:fs\";\nimport { rm } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { git, tryGit, GitError } from \"./exec.js\";\n\nconst WORKTREE_DIR = \".pr-preview/worktrees\";\n\n/**\n * git crashing with a signal (SIGBUS/SIGSEGV → \"died of signal\", exit 138)\n * while reading objects almost always means the repo's object files are\n * iCloud-evicted \"dataless\" placeholders (repo lives in ~/Documents). Turn\n * the raw crash into an actionable message.\n */\nfunction explainGitCrash(err: unknown, repoRoot: string): never {\n const msg = err instanceof Error ? err.message : String(err);\n if (/died of signal|signal 1[01]\\b|SIGBUS|SIGSEGV/i.test(msg)) {\n const inICloud = /\\/(Documents|Desktop)\\//.test(repoRoot);\n throw new GitError(\n `git crashed creating the base worktree (it died of a signal). This usually means ` +\n `the repository's git objects are iCloud-evicted \"dataless\" placeholders` +\n (inICloud ? \" — and this repo is under ~/Documents, which iCloud syncs.\\n\\n\" : \".\\n\\n\") +\n `Fixes:\\n` +\n ` • Move the repo out of iCloud (e.g. ~/dev), or disable Desktop & Documents sync, then retry.\\n` +\n ` • Or force-download the objects now: find .git -type f -exec cat {} + >/dev/null\\n` +\n `\\nOriginal error: ${msg}`,\n );\n }\n throw err;\n}\n\nexport interface WorktreeHandle {\n dir: string;\n remove(): Promise<void>;\n}\n\n/** Create a detached worktree at the given sha for the \"before\" app. */\nexport async function createBaseWorktree(repoRoot: string, sha: string): Promise<WorktreeHandle> {\n const dir = path.join(repoRoot, WORKTREE_DIR, `base-${sha.slice(0, 12)}`);\n\n // Reuse an intact worktree from a previous run (skips reinstall).\n if (existsSync(dir) && (await tryGit(dir, [\"rev-parse\", \"HEAD\"])) === sha) {\n return { dir, remove: () => removeWorktree(repoRoot, dir) };\n }\n\n await pruneStaleWorktrees(repoRoot);\n if (existsSync(dir)) await rm(dir, { recursive: true, force: true });\n try {\n await git(repoRoot, [\"worktree\", \"add\", \"--detach\", dir, sha]);\n } catch (err) {\n // A half-created worktree leaves a registration behind — clean it up.\n await removeWorktree(repoRoot, dir).catch(() => {});\n explainGitCrash(err, repoRoot);\n }\n return { dir, remove: () => removeWorktree(repoRoot, dir) };\n}\n\nexport async function removeWorktree(repoRoot: string, dir: string): Promise<void> {\n await tryGit(repoRoot, [\"worktree\", \"remove\", \"--force\", dir]);\n if (existsSync(dir)) await rm(dir, { recursive: true, force: true });\n await tryGit(repoRoot, [\"worktree\", \"prune\"]);\n}\n\nexport async function pruneStaleWorktrees(repoRoot: string): Promise<void> {\n await tryGit(repoRoot, [\"worktree\", \"prune\"]);\n}\n","import { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { spawn } from \"node:child_process\";\n\nexport type PackageManager = \"pnpm\" | \"yarn\" | \"bun\" | \"npm\";\n\nexport function detectPackageManager(dir: string): PackageManager {\n if (existsSync(path.join(dir, \"pnpm-lock.yaml\"))) return \"pnpm\";\n if (existsSync(path.join(dir, \"yarn.lock\"))) return \"yarn\";\n if (existsSync(path.join(dir, \"bun.lockb\")) || existsSync(path.join(dir, \"bun.lock\"))) {\n return \"bun\";\n }\n return \"npm\";\n}\n\n/** Run `<pm> install` in dir; resolves on exit 0, rejects otherwise. */\nexport function installDependencies(dir: string, pm: PackageManager): Promise<void> {\n return new Promise((resolve, reject) => {\n const child = spawn(pm, [\"install\"], {\n cwd: dir,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n env: process.env,\n });\n let tail = \"\";\n const keepTail = (chunk: Buffer) => {\n tail = (tail + chunk.toString()).slice(-2000);\n };\n child.stdout.on(\"data\", keepTail);\n child.stderr.on(\"data\", keepTail);\n child.on(\"error\", reject);\n child.on(\"exit\", (code) => {\n if (code === 0) resolve();\n else reject(new Error(`${pm} install failed (exit ${code}):\\n${tail}`));\n });\n });\n}\n","import { spawn } from \"node:child_process\";\n\n/**\n * Reveal a path in the OS file manager (Finder / Explorer / default handler).\n * Best-effort and non-blocking — never throws, and stays out of the way in\n * headless/CI environments where there's no desktop to open.\n */\nexport function openPath(target: string): void {\n if (process.env.PR_PREVIEW_HEADLESS === \"1\" || process.env.CI) return;\n\n const [cmd, args] =\n process.platform === \"darwin\"\n ? ([\"open\", [target]] as const)\n : process.platform === \"win32\"\n ? ([\"cmd\", [\"/c\", \"start\", \"\", target]] as const)\n : ([\"xdg-open\", [target]] as const);\n\n try {\n const child = spawn(cmd, [...args], { detached: true, stdio: \"ignore\" });\n child.on(\"error\", () => {});\n child.unref();\n } catch {\n /* opening the folder is a nicety, never a failure */\n }\n}\n\n/**\n * Reveal the produced files in the file manager. On macOS this opens the\n * folder with the files selected; elsewhere it just opens the folder.\n */\nexport function revealFiles(files: string[], dir: string): void {\n if (process.env.PR_PREVIEW_HEADLESS === \"1\" || process.env.CI) return;\n if (process.platform === \"darwin\" && files.length > 0) {\n try {\n const child = spawn(\"open\", [\"-R\", ...files], { detached: true, stdio: \"ignore\" });\n child.on(\"error\", () => openPath(dir));\n child.unref();\n return;\n } catch {\n /* fall through to opening the folder */\n }\n }\n openPath(dir);\n}\n"],"mappings":"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","names":["path","pc","existsSync","readFile","path","path","existsSync","readFile","path","path","readFile","existsSync","path","path","existsSync","readFile","readFile","existsSync","path","fileURLToPath","sharp","sharp","mkdir","writeFile","path","sharp","spawn","writeFile","path","existsSync","mkdirSync","path","tmpdir","fileURLToPath","quantize","sharp","mkdir","path","writeFile","sharp","sharp","path","readFile","writeFile","mkdir","path","z","mkdir","path","writeFile","path","pc","existsSync","path","pc","execFile","existsSync","rm","path","path","existsSync","rm","existsSync","path","spawn","spawn","path","pc","existsSync"]}
|
|
1
|
+
{"version":3,"sources":["../../src/cli/index.ts","../../src/cli/commands/init.ts","../../src/cli/ui/logger.ts","../../src/cli/commands/record.ts","../../src/config/load.ts","../../src/config/schema.ts","../../src/session/bootstrap.ts","../../src/server/ports.ts","../../src/server/devServer.ts","../../src/server/harnessServer.ts","../../src/ipc/protocol.ts","../../src/ipc/bus.ts","../../src/browser/launch.ts","../../src/browser/headerStrip.ts","../../src/browser/recorderInject.ts","../../src/session/session.ts","../../src/recorder/steps.ts","../../src/recorder/types.ts","../../src/capture/screencast.ts","../../src/capture/region.ts","../../src/encode/media.ts","../../src/capture/frames.ts","../../src/encode/gif.ts","../../src/encode/ffmpeg.ts","../../src/encode/label.ts","../../src/browser/frame.ts","../../src/cli/cleanup.ts","../../src/recorder/journey.ts","../../src/cli/commands/run.ts","../../src/git/exec.ts","../../src/git/base.ts","../../src/git/worktree.ts","../../src/server/pkgManager.ts","../../src/cli/util/openPath.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { Command } from \"commander\";\nimport { initCommand } from \"./commands/init.js\";\nimport { recordCommand } from \"./commands/record.js\";\nimport { runCommand } from \"./commands/run.js\";\nimport { log } from \"./ui/logger.js\";\nimport { ConfigError } from \"../config/load.js\";\nimport { GitError } from \"../git/exec.js\";\n\n// Keep `--version` in sync with package.json automatically.\nlet version = \"0.0.0\";\ntry {\n version = (JSON.parse(readFileSync(new URL(\"../../package.json\", import.meta.url), \"utf8\")) as { version: string }).version;\n} catch {\n /* fall back to 0.0.0 if package.json can't be read */\n}\n\nconst program = new Command();\n\nprogram\n .name(\"pr-preview\")\n .description(\"Record a UI journey once — get before/after video clips for your pull request.\")\n .version(version);\n\nprogram\n .command(\"init\")\n .description(\"Scaffold pr-preview.config.js and .gitignore entries\")\n .action(() => wrap(() => initCommand(process.cwd())));\n\nprogram\n .command(\"record\")\n .description(\"Record a journey to a file on the current branch (no video, no git)\")\n .option(\"-o, --out <file>\", \"journey output path\", \".pr-preview/journey.json\")\n .action((opts) => wrap(() => recordCommand(process.cwd(), opts)));\n\nprogram\n .command(\"run\")\n .description(\"Record on the PR base and your branch, then produce before.mp4 and after.mp4\")\n .option(\"-b, --base <ref>\", \"override PR base detection (e.g. origin/main)\")\n .option(\"--keep-worktree\", \"keep the base worktree around for faster re-runs\")\n .option(\n \"-u, --url <url>\",\n \"record against an app already running at <url> — local (http://localhost:3000) or a \" +\n \"remote/staging URL — instead of starting a dev server\",\n )\n .option(\n \"-s, --single\",\n \"record one standalone clip of the current app, skipping the before/after comparison\",\n )\n .addHelpText(\n \"after\",\n `\nEvery option can also be set in pr-preview.config.js, so a configured project runs with\njust \\`pr-preview run\\` (handy for CI and AI agents). Flags override the config file.\n\nExamples:\n $ pr-preview run before/after: PR base vs your current branch\n $ pr-preview run --single one clip of the current app, no comparison\n $ pr-preview run --url http://localhost:3000 use an app you're already running (local)\n $ pr-preview run --url https://staging.example.com …or a remote / deployed app\n $ pr-preview run --base origin/develop compare against a specific base branch\n\nNotes:\n • --single skips git/the base worktree entirely and saves a single video — great for a demo,\n a bug repro, or a how-to.\n • --url accepts any reachable address — a local dev server or a remote/staging deployment.\n In --url mode you switch your app to the PR branch and restart it yourself between passes.\n • Without --url, a before/after run needs a git repo and to be on a branch with commits\n beyond its base (it records the base in a throwaway worktree, your branch in place).`,\n )\n .action((opts) => wrap(() => runCommand(process.cwd(), opts)));\n\nasync function wrap(fn: () => Promise<void>): Promise<void> {\n try {\n await fn();\n process.exit(0);\n } catch (err) {\n if (err instanceof ConfigError || err instanceof GitError) {\n log.error(err.message);\n } else {\n log.error(err instanceof Error ? (err.stack ?? err.message) : String(err));\n }\n process.exit(1);\n }\n}\n\nprogram.parse();\n","import { existsSync } from \"node:fs\";\nimport { readFile, writeFile, appendFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { log } from \"../ui/logger.js\";\n\nconst CONFIG_TEMPLATE = `/** @type {import('pr-preview').Config} */\nexport default {\n // ── App under preview ────────────────────────────────────────────────\n // Command that starts your dev server. $PORT is set for you.\n devCommand: \"npm run dev\",\n // Where the app answers once ready. {port} is replaced with the allocated port.\n url: \"http://localhost:{port}\",\n // Frontend directory relative to the repo root (monorepos).\n cwd: \".\",\n // How long to wait for the dev server (ms).\n readyTimeout: 60000,\n\n // ── Run options (set here once instead of passing CLI flags every time) ─\n // Use an app YOU already run instead of a managed dev server. Set this for\n // apps that need real env/backends.\n // externalUrl: \"http://localhost:3000\",\n // Override PR-base (\"before\") detection, e.g. \"origin/main\".\n // baseBranch: undefined,\n // Reuse the base worktree across runs (skips reinstall).\n // keepWorktree: false,\n // 2 (default) = before/after comparison; 1 = a single standalone clip.\n // (run --single forces 1.)\n passes: 2,\n\n // ── Output ───────────────────────────────────────────────────────────\n output: \".pr-preview/output\",\n format: \"mp4\", // \"mp4\" | \"gif\" | \"both\"\n // Start-of-pass reset choice default: true offers \"reset to a clean app\",\n // false offers \"keep my session\". The nudge only shows when there's state.\n resetStorage: true,\n viewport: { width: 1920, height: 1080 },\n\n // ── Permissions ──────────────────────────────────────────────────────\n // Grant browser permissions so apps that ask for them work (others stay\n // denied silently — no native prompt blocks the run).\n // permissions: [\"geolocation\", \"clipboard-read\", \"clipboard-write\"],\n // geolocation: { latitude: 51.5074, longitude: -0.1278 }, // fixed = deterministic\n};\n`;\n\nconst GITIGNORE_BLOCK = `\n# pr-preview (worktrees, recordings, output)\n.pr-preview/\n`;\n\nexport async function initCommand(root: string): Promise<void> {\n const configPath = path.join(root, \"pr-preview.config.js\");\n if (\n existsSync(configPath) ||\n existsSync(path.join(root, \"pr-preview.config.ts\")) ||\n existsSync(path.join(root, \"pr-preview.config.json\"))\n ) {\n log.warn(\"A pr-preview config already exists — leaving it untouched.\");\n } else {\n await writeFile(configPath, CONFIG_TEMPLATE);\n log.success(`Created ${path.basename(configPath)}`);\n }\n\n const gitignorePath = path.join(root, \".gitignore\");\n const current = existsSync(gitignorePath) ? await readFile(gitignorePath, \"utf8\") : \"\";\n if (!current.includes(\".pr-preview/\")) {\n await appendFile(gitignorePath, GITIGNORE_BLOCK);\n log.success(\"Added .pr-preview/ to .gitignore\");\n }\n\n log.info(\"Edit the config to match your dev command, then run `pr-preview run` on a PR branch.\");\n}\n","import pc from \"picocolors\";\nimport ora, { type Ora } from \"ora\";\n\nexport const log = {\n info(msg: string): void {\n console.log(`${pc.cyan(\"●\")} ${msg}`);\n },\n success(msg: string): void {\n console.log(`${pc.green(\"✔\")} ${msg}`);\n },\n warn(msg: string): void {\n console.log(`${pc.yellow(\"▲\")} ${msg}`);\n },\n error(msg: string): void {\n console.error(`${pc.red(\"✖\")} ${msg}`);\n },\n step(msg: string): void {\n console.log(pc.dim(` ${msg}`));\n },\n spinner(text: string): Ora {\n return ora({ text, color: \"cyan\" }).start();\n },\n};\n","import path from \"node:path\";\nimport pc from \"picocolors\";\nimport { loadConfig } from \"../../config/load.js\";\nimport { bootstrap } from \"../../session/bootstrap.js\";\nimport { saveJourney } from \"../../recorder/journey.js\";\nimport { log } from \"../ui/logger.js\";\nimport { runCleanups } from \"../cleanup.js\";\n\nexport interface RecordOptions {\n out?: string;\n}\n\n/** Record a journey on the current branch only — no GIFs, no git. */\nexport async function recordCommand(repoRoot: string, opts: RecordOptions): Promise<void> {\n const { config } = await loadConfig(repoRoot);\n const journeyFile = path.resolve(repoRoot, opts.out ?? \".pr-preview/journey.json\");\n\n try {\n const boot = await bootstrap(repoRoot, config, \"record\");\n await boot.startApp(\"after\", repoRoot);\n await boot.openBrowser(\"after\");\n\n log.info(`Harness open at ${pc.bold(boot.harness.url)}`);\n log.step(\"Click Record in the sidebar, perform the journey, then Confirm.\");\n\n const steps = await boot.session.recordUntilConfirmed();\n if (steps.length === 0) {\n log.warn(\"No steps recorded — nothing saved.\");\n return;\n }\n\n await saveJourney(journeyFile, {\n version: 1,\n createdAt: new Date().toISOString(),\n viewport: { ...config.viewport, deviceScaleFactor: 2 },\n startUrl: boot.session.startUrl,\n steps,\n });\n log.success(`Saved ${steps.length} steps to ${path.relative(repoRoot, journeyFile)}`);\n } finally {\n await runCleanups();\n }\n}\n","import { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { createJiti } from \"jiti\";\nimport { configSchema, type Config } from \"./schema.js\";\n\nconst CONFIG_FILES = [\n \"pr-preview.config.ts\",\n \"pr-preview.config.mts\",\n \"pr-preview.config.js\",\n \"pr-preview.config.mjs\",\n \"pr-preview.config.json\",\n];\n\nexport class ConfigError extends Error {}\n\n/** Find and validate pr-preview.config.* in the given repo root. */\nexport async function loadConfig(root: string): Promise<{ config: Config; file: string }> {\n const file = CONFIG_FILES.map((f) => path.join(root, f)).find(existsSync);\n if (!file) {\n throw new ConfigError(\n `No pr-preview config found in ${root}. Run \\`pr-preview init\\` to create one.`,\n );\n }\n\n let raw: unknown;\n if (file.endsWith(\".json\")) {\n raw = JSON.parse(await readFile(file, \"utf8\"));\n } else {\n const jiti = createJiti(import.meta.url, { interopDefault: true });\n raw = await jiti.import(file, { default: true });\n }\n\n const parsed = configSchema.safeParse(raw);\n if (!parsed.success) {\n const issues = parsed.error.issues\n .map((i) => ` ${i.path.join(\".\") || \"(root)\"}: ${i.message}`)\n .join(\"\\n\");\n throw new ConfigError(`Invalid config in ${path.basename(file)}:\\n${issues}`);\n }\n return { config: parsed.data, file };\n}\n","import { z } from \"zod\";\n\nexport const configSchema = z.object({\n /** Command that starts the project's dev server, e.g. \"npm run dev\". */\n devCommand: z.string().min(1),\n /** URL the app is reachable on once ready. Supports {port} templating. */\n url: z.string().min(1),\n /** Working directory of the frontend relative to the repo root (monorepos). */\n cwd: z.string().default(\".\"),\n /** ms to wait for the dev server to answer. */\n readyTimeout: z.number().int().positive().default(60_000),\n /** Override PR base detection (the \"before\" ref). e.g. \"origin/main\". */\n baseBranch: z.string().optional(),\n /**\n * Run against an app YOU already have running, instead of letting pr-preview\n * start a dev server in a worktree. Set this (e.g. \"http://localhost:3000\")\n * for apps that need real env/backends. Equivalent to `run --url`; the CLI\n * flag overrides it. When set, devCommand/url are ignored for `run`.\n */\n externalUrl: z.string().optional(),\n /** Reuse the base worktree across runs (skips reinstall). `--keep-worktree`. */\n keepWorktree: z.boolean().default(false),\n /** Dependency install behavior in the base worktree. */\n install: z.enum([\"auto\", \"always\", \"never\"]).default(\"auto\"),\n /** Output directory for the clips, relative to repo root. */\n output: z.string().default(\".pr-preview/output\"),\n /**\n * Output format. \"mp4\" (default) is small + HQ and uploads straight into a\n * GitHub PR description; needs ffmpeg on PATH (falls back to gif if\n * missing). \"both\" produces the two side by side.\n */\n format: z.enum([\"mp4\", \"gif\", \"both\"]).default(\"mp4\"),\n /**\n * How many recordings the session does: 2 (default) = before/after on the\n * base branch and your branch; 1 = a single standalone clip of the current\n * app (no comparison, no base worktree). `run --single` forces 1.\n */\n passes: z.union([z.literal(1), z.literal(2)]).default(2),\n gif: z\n .object({\n width: z.number().int().positive().default(900),\n // 24fps keeps the synthetic cursor's motion fluid in the clip.\n fps: z.number().positive().max(60).default(24),\n quality: z.enum([\"high\", \"max\"]).default(\"high\"),\n maxColors: z.number().int().min(2).max(256).default(256),\n })\n .default({}),\n /**\n * Start-of-pass reset choice default: true (default) offers \"reset\" (clear\n * cookies + localStorage + sessionStorage and reload) before recording;\n * false offers \"keep my session\". The nudge only appears when there's state\n * to reset.\n */\n resetStorage: z.boolean().default(true),\n /** Logical app resolution — Full HD by default. The harness scales the\n * iframe down to fit the window, keeping this resolution and ratio. */\n viewport: z\n .object({\n width: z.number().int().positive().default(1920),\n height: z.number().int().positive().default(1080),\n })\n .default({}),\n /** Strip X-Frame-Options / frame-ancestors so any app loads in the iframe. */\n headerStrip: z.boolean().default(true),\n /**\n * Browser permissions to GRANT up front (Playwright names) so a native\n * prompt never blocks the run. Defaults to allow-all (the broadly-supported\n * set); unsupported names for your Chrome are skipped gracefully. Set your\n * own narrower list to override, or [] to deny everything.\n */\n permissions: z\n .array(z.string())\n .default([\n \"geolocation\",\n \"notifications\",\n \"camera\",\n \"microphone\",\n \"clipboard-read\",\n \"clipboard-write\",\n \"midi\",\n \"background-sync\",\n \"accelerometer\",\n \"gyroscope\",\n \"magnetometer\",\n \"payment-handler\",\n \"storage-access\",\n ]),\n /**\n * Fixed geolocation for the session (implies the \"geolocation\" permission),\n * so location-based apps render real, deterministic results in both clips.\n */\n geolocation: z\n .object({\n latitude: z.number(),\n longitude: z.number(),\n accuracy: z.number().nonnegative().optional(),\n })\n .optional(),\n});\n\nexport type Config = z.infer<typeof configSchema>;\nexport type ConfigInput = z.input<typeof configSchema>;\n\n/** Replace {port} in the configured url. */\nexport function resolveUrl(config: Config, port: number): string {\n return config.url.replace(\"{port}\", String(port));\n}\n","import path from \"node:path\";\nimport type { Config } from \"../config/schema.js\";\nimport { resolveUrl } from \"../config/schema.js\";\nimport { allocatePorts, type PortPlan } from \"../server/ports.js\";\nimport { startDevServer, type DevServerHandle } from \"../server/devServer.js\";\nimport { startHarnessServer, type HarnessServer } from \"../server/harnessServer.js\";\nimport { launchSession, type BrowserSession } from \"../browser/launch.js\";\nimport { Session } from \"./session.js\";\nimport { registerCleanup } from \"../cli/cleanup.js\";\nimport { log } from \"../cli/ui/logger.js\";\n\nexport interface Bootstrapped {\n ports: PortPlan;\n harness: HarnessServer;\n session: Session;\n appUrl(which: \"before\" | \"after\"): string;\n startApp(which: \"before\" | \"after\", cwd: string): Promise<DevServerHandle>;\n /** Launch headed Chrome on the harness (call after the first app is up). */\n openBrowser(which: \"before\" | \"after\"): Promise<BrowserSession>;\n /** Point the open harness iframe at the other app. */\n switchApp(which: \"before\" | \"after\"): Promise<void>;\n}\n\n/**\n * Wire up everything a harness session needs: ports, harness server, bus and\n * session state machine. Dev servers and the browser start on demand so\n * `run` can sequence before/after.\n */\nexport async function bootstrap(\n repoRoot: string,\n config: Config,\n mode: \"record\" | \"run\",\n opts: { fixedUrl?: string } = {},\n): Promise<Bootstrapped> {\n const ports = await allocatePorts();\n // `fixedUrl` = the user runs the app themselves; both passes use that one\n // URL (they switch branches + restart between them). No dev server, no ports.\n const appUrl = (which: \"before\" | \"after\") =>\n opts.fixedUrl ?? resolveUrl(config, which === \"before\" ? ports.beforeApp : ports.afterApp);\n\n const initial = mode === \"run\" ? \"before\" : \"after\";\n const harness = await startHarnessServer({\n port: ports.harness,\n appUrl: appUrl(initial),\n mode,\n viewport: config.viewport,\n });\n registerCleanup(() => harness.close());\n\n const session = new Session(harness.bus, config, mode, appUrl(initial));\n let browser: BrowserSession | null = null;\n\n return {\n ports,\n harness,\n session,\n appUrl,\n\n async startApp(which, cwd) {\n const port = which === \"before\" ? ports.beforeApp : ports.afterApp;\n const url = appUrl(which);\n const spinner = log.spinner(`Starting ${which} dev server (${config.devCommand}) …`);\n try {\n const handle = await startDevServer({\n command: config.devCommand,\n cwd: path.join(cwd, config.cwd),\n port,\n url,\n readyTimeout: config.readyTimeout,\n logFile: path.join(repoRoot, \".pr-preview\", `dev-${which}.log`),\n });\n registerCleanup(() => handle.stop());\n spinner.succeed(`${which} app ready at ${url}`);\n return handle;\n } catch (err) {\n spinner.fail(`${which} dev server failed`);\n throw err;\n }\n },\n\n async openBrowser(which) {\n browser = await launchSession({\n harnessUrl: harness.url,\n targetOrigins: [new URL(appUrl(\"before\")).origin, new URL(appUrl(\"after\")).origin],\n appViewport: config.viewport,\n headerStrip: config.headerStrip,\n onRawEvent: session.handleRawEvent,\n permissions: config.permissions,\n geolocation: config.geolocation,\n });\n registerCleanup(() => browser!.close());\n session.attachPage(browser.page, appUrl(which));\n return browser;\n },\n\n async switchApp(which) {\n if (!browser) throw new Error(\"Browser not open\");\n harness.setAppUrl(appUrl(which));\n session.attachPage(browser.page, appUrl(which));\n // Harness refetches runtime.json on load → iframe points at the new app.\n await browser.page.reload({ waitUntil: \"domcontentloaded\" });\n },\n };\n}\n","import getPort, { portNumbers } from \"get-port\";\n\nexport interface PortPlan {\n harness: number;\n beforeApp: number;\n afterApp: number;\n}\n\n/** Allocate the ports a run needs up front so dev commands can be templated. */\nexport async function allocatePorts(): Promise<PortPlan> {\n // Test hook: PR_PREVIEW_PORTS=\"harness,before,after\" pins all three.\n const pinned = process.env.PR_PREVIEW_PORTS?.split(\",\").map(Number);\n if (pinned?.length === 3 && pinned.every((p) => Number.isInteger(p) && p > 0)) {\n return { harness: pinned[0]!, beforeApp: pinned[1]!, afterApp: pinned[2]! };\n }\n const harness = await getPort({ port: portNumbers(4310, 4400) });\n const beforeApp = await getPort({ port: portNumbers(4401, 4500), exclude: [harness] });\n const afterApp = await getPort({\n port: portNumbers(4501, 4600),\n exclude: [harness, beforeApp],\n });\n return { harness, beforeApp, afterApp };\n}\n","import { spawn, type ChildProcess } from \"node:child_process\";\nimport { appendFileSync, mkdirSync } from \"node:fs\";\nimport path from \"node:path\";\nimport treeKill from \"tree-kill\";\n\nexport interface DevServerOptions {\n command: string; // e.g. \"npm run dev\"\n cwd: string;\n port: number; // exposed as $PORT to the command\n url: string; // polled for readiness\n readyTimeout: number;\n logFile?: string;\n}\n\nexport class DevServerError extends Error {}\n\nexport interface DevServerHandle {\n url: string;\n stop(): Promise<void>;\n}\n\n/** Spawn the project dev server and wait until `url` answers. */\nexport async function startDevServer(opts: DevServerOptions): Promise<DevServerHandle> {\n if (opts.logFile) mkdirSync(path.dirname(opts.logFile), { recursive: true });\n\n const child = spawn(opts.command, {\n cwd: opts.cwd,\n shell: true,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n env: { ...process.env, PORT: String(opts.port), BROWSER: \"none\", FORCE_COLOR: \"0\" },\n });\n\n let logTail = \"\";\n const onChunk = (chunk: Buffer) => {\n logTail = (logTail + chunk.toString()).slice(-4000);\n if (opts.logFile) appendFileSync(opts.logFile, chunk);\n };\n child.stdout.on(\"data\", onChunk);\n child.stderr.on(\"data\", onChunk);\n\n let exited = false;\n let exitCode: number | null = null;\n child.on(\"exit\", (code) => {\n exited = true;\n exitCode = code;\n });\n\n const deadline = Date.now() + opts.readyTimeout;\n while (Date.now() < deadline) {\n if (exited) {\n throw new DevServerError(\n `Dev server exited early (code ${exitCode}). Last output:\\n${logTail}`,\n );\n }\n try {\n const res = await fetch(opts.url, { redirect: \"manual\" });\n if (res.status < 500) {\n return { url: opts.url, stop: () => killTree(child) };\n }\n } catch {\n /* not up yet */\n }\n await sleep(400);\n }\n\n await killTree(child);\n throw new DevServerError(\n `Dev server did not answer at ${opts.url} within ${opts.readyTimeout}ms. Last output:\\n${logTail}`,\n );\n}\n\nfunction killTree(child: ChildProcess): Promise<void> {\n return new Promise((resolve) => {\n if (child.pid == null || child.exitCode !== null) return resolve();\n treeKill(child.pid, \"SIGTERM\", () => {\n // escalate if still alive shortly after\n setTimeout(() => {\n if (child.exitCode === null && child.pid != null) {\n treeKill(child.pid, \"SIGKILL\", () => resolve());\n } else {\n resolve();\n }\n }, 1500);\n });\n });\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n","import http from \"node:http\";\nimport { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { WebSocketServer } from \"ws\";\nimport { Bus } from \"../ipc/bus.js\";\n\nconst MIME: Record<string, string> = {\n \".html\": \"text/html; charset=utf-8\",\n \".js\": \"text/javascript; charset=utf-8\",\n \".css\": \"text/css; charset=utf-8\",\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n \".json\": \"application/json\",\n \".map\": \"application/json\",\n};\n\nexport interface HarnessServerOptions {\n port: number;\n /** URL of the target app the iframe should load. */\n appUrl: string;\n mode: \"record\" | \"run\";\n /** Fixed logical size of the app iframe (letterboxed in the stage). */\n viewport: { width: number; height: number };\n}\n\nexport interface HarnessServer {\n url: string;\n bus: Bus;\n setAppUrl(url: string): void;\n close(): Promise<void>;\n}\n\n/** Locate the built harness SPA (dist/harness next to the CLI bundle). */\nfunction harnessDistDir(): string {\n const here = path.dirname(fileURLToPath(import.meta.url));\n // dist/cli/index.js → dist/harness ; src fallback for tests/dev\n const candidates = [\n path.resolve(here, \"../harness\"),\n path.resolve(here, \"../../dist/harness\"),\n ];\n const found = candidates.find((c) => existsSync(path.join(c, \"index.html\")));\n if (!found) {\n throw new Error(\n `Harness UI build not found (looked in: ${candidates.join(\", \")}). Run \\`npm run build\\` first.`,\n );\n }\n return found;\n}\n\n/** Serve the sidebar SPA, a /runtime.json config endpoint and the /ws bus. */\nexport async function startHarnessServer(opts: HarnessServerOptions): Promise<HarnessServer> {\n const dist = harnessDistDir();\n let appUrl = opts.appUrl;\n\n const server = http.createServer(async (req, res) => {\n const urlPath = (req.url ?? \"/\").split(\"?\")[0]!;\n if (urlPath === \"/runtime.json\") {\n res.writeHead(200, { \"content-type\": \"application/json\", \"cache-control\": \"no-store\" });\n res.end(JSON.stringify({ appUrl, mode: opts.mode, viewport: opts.viewport }));\n return;\n }\n const rel = urlPath === \"/\" ? \"index.html\" : urlPath.slice(1);\n const file = path.join(dist, path.normalize(rel));\n if (!file.startsWith(dist) || !existsSync(file)) {\n // SPA fallback\n res.writeHead(200, { \"content-type\": MIME[\".html\"]! });\n res.end(await readFile(path.join(dist, \"index.html\")));\n return;\n }\n res.writeHead(200, {\n \"content-type\": MIME[path.extname(file)] ?? \"application/octet-stream\",\n \"cache-control\": \"no-store\",\n });\n res.end(await readFile(file));\n });\n\n const wss = new WebSocketServer({ server, path: \"/ws\" });\n const bus = new Bus();\n bus.attach(wss);\n\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(opts.port, \"127.0.0.1\", resolve);\n });\n\n return {\n url: `http://127.0.0.1:${opts.port}`,\n bus,\n setAppUrl(url: string) {\n appUrl = url;\n },\n close: () =>\n new Promise((resolve) => {\n wss.close();\n for (const ws of wss.clients) ws.terminate();\n server.close(() => resolve());\n }),\n };\n}\n","/**\n * Shared message types between the Node CLI process (hub) and the\n * harness sidebar SPA (view). Pure types + JSON — imported by both builds.\n *\n * Flow of events:\n * iframe recorder --exposeBinding--> Node --WS--> sidebar\n * sidebar --WS--> Node --Playwright--> browser\n */\n\nimport type { Step } from \"../recorder/types.js\";\n\nexport type Phase = \"idle\" | \"recording\" | \"encoding\" | \"done\";\n\n/** Which pass the session is on, with branch labels for the tab strip. */\nexport interface PassInfo {\n pass: \"before\" | \"after\";\n /** Branch/ref labels; absent in single-pass `record` mode (tabs hidden). */\n branches?: { before: string; after: string };\n /** Which GIFs are already finished (ticks the tabs). */\n done: { before: boolean; after: boolean };\n /** Whether this pass's clip starts from a cleared app (true) or keeps the\n * signed-in state captured when recording began (false). User-toggleable. */\n resetStorage: boolean;\n /** Recordings planned this session: 2 = before/after wizard, 1 = a single\n * standalone clip. Undefined in the `record` command (no clips). Drives how\n * the harness wizard/tabs render. */\n passes?: 1 | 2;\n}\n\n/** Node → sidebar */\nexport type ServerMessage =\n | {\n type: \"HELLO\";\n phase: Phase;\n steps: StepSummary[];\n appUrl: string;\n mode: \"record\" | \"run\";\n passInfo: PassInfo;\n }\n | { type: \"PASS_CHANGED\"; passInfo: PassInfo }\n | { type: \"PHASE_CHANGED\"; phase: Phase; detail?: string }\n | { type: \"STEP_ADDED\"; step: StepSummary }\n | { type: \"STEP_UPDATED\"; step: StepSummary }\n | { type: \"STEP_REMOVED\"; stepId: string }\n | { type: \"STEPS_RESET\"; steps: StepSummary[] }\n | { type: \"ENCODE_PROGRESS\"; which: \"before\" | \"after\"; stage: string; done: number; total: number }\n /** A hands-off prompt (e.g. \"switch your app to the PR branch, then Continue\"\n * in --url mode). The user acts in the app, then hits Continue. */\n | { type: \"MANUAL_PAUSE\"; stepId: string | null; label: string; kind: \"generic\" }\n | { type: \"GIF_READY\"; which: \"before\" | \"after\"; path: string }\n | { type: \"DONE\"; outputs: { before?: string; after?: string } }\n /** Blocking nudge at the start of a pass: reset the app or keep the session,\n * before recording. `defaultReset` seeds the highlighted choice. */\n | { type: \"RESET_PROMPT\"; pass: \"before\" | \"after\"; defaultReset: boolean }\n | { type: \"ERROR\"; message: string };\n\n/** Sidebar → Node */\nexport type ClientMessage =\n | { type: \"START_RECORD\" }\n | { type: \"STOP_RECORD\" }\n | { type: \"CONFIRM\" }\n | { type: \"DELETE_STEP\"; stepId: string }\n | { type: \"RERECORD_FROM\"; stepId: string }\n | { type: \"CONTINUE\" }\n | { type: \"LOAD_BEFORE_STEPS\" }\n /** Answer to RESET_PROMPT: start the pass fresh (true) or keep the session. */\n | { type: \"RESET_CHOICE\"; reset: boolean }\n /** Manually reload the app inside the iframe (refresh button). */\n | { type: \"RELOAD_IFRAME\" }\n | { type: \"ABORT\" };\n\n/** What the sidebar needs to render a step row. */\nexport interface StepSummary {\n id: string;\n type: Step[\"type\"];\n label: string;\n thumbnail?: string; // small data-URL png\n}\n\nexport function serialize(msg: ServerMessage | ClientMessage): string {\n return JSON.stringify(msg);\n}\n\nexport function parseClientMessage(raw: string): ClientMessage | null {\n try {\n const msg = JSON.parse(raw);\n if (typeof msg === \"object\" && msg !== null && typeof msg.type === \"string\") {\n return msg as ClientMessage;\n }\n } catch {\n /* malformed frame — drop */\n }\n return null;\n}\n\nexport function parseServerMessage(raw: string): ServerMessage | null {\n try {\n const msg = JSON.parse(raw);\n if (typeof msg === \"object\" && msg !== null && typeof msg.type === \"string\") {\n return msg as ServerMessage;\n }\n } catch {\n /* malformed frame — drop */\n }\n return null;\n}\n","import type { WebSocketServer, WebSocket } from \"ws\";\nimport {\n serialize,\n parseClientMessage,\n type ClientMessage,\n type ServerMessage,\n} from \"./protocol.js\";\n\ntype Handler = (msg: ClientMessage) => void;\n\n/**\n * Typed pub/sub over the sidebar WebSocket. The Node process is the single\n * source of truth; the sidebar is a view that reconnects freely.\n */\nexport class Bus {\n private sockets = new Set<WebSocket>();\n private handlers = new Set<Handler>();\n /** Replayed to late-joining sidebars so they can render current state. */\n private helloFactory: (() => ServerMessage) | null = null;\n\n attach(wss: WebSocketServer): void {\n wss.on(\"connection\", (ws) => {\n this.sockets.add(ws);\n if (this.helloFactory) ws.send(serialize(this.helloFactory()));\n ws.on(\"message\", (data) => {\n const msg = parseClientMessage(data.toString());\n if (msg) for (const h of this.handlers) h(msg);\n });\n ws.on(\"close\", () => this.sockets.delete(ws));\n ws.on(\"error\", () => this.sockets.delete(ws));\n });\n }\n\n onHello(factory: () => ServerMessage): void {\n this.helloFactory = factory;\n }\n\n send(msg: ServerMessage): void {\n const data = serialize(msg);\n for (const ws of this.sockets) {\n if (ws.readyState === ws.OPEN) ws.send(data);\n }\n }\n\n onMessage(handler: Handler): () => void {\n this.handlers.add(handler);\n return () => this.handlers.delete(handler);\n }\n\n /** Resolve once a message of the given type arrives. */\n waitFor<T extends ClientMessage[\"type\"]>(type: T): Promise<Extract<ClientMessage, { type: T }>> {\n return new Promise((resolve) => {\n const off = this.onMessage((msg) => {\n if (msg.type === type) {\n off();\n resolve(msg as Extract<ClientMessage, { type: T }>);\n }\n });\n });\n }\n\n /** Resolve with whichever of the given message types arrives first. */\n waitForAny<T extends ClientMessage[\"type\"]>(...types: T[]): Promise<Extract<ClientMessage, { type: T }>> {\n return new Promise((resolve) => {\n const off = this.onMessage((msg) => {\n if ((types as string[]).includes(msg.type)) {\n off();\n resolve(msg as Extract<ClientMessage, { type: T }>);\n }\n });\n });\n }\n}\n","import { chromium, type Browser, type BrowserContext, type Page } from \"playwright\";\nimport { installHeaderStrip } from \"./headerStrip.js\";\nimport { installRecorderBinding, type RawEventHandler } from \"./recorderInject.js\";\n\n/** Harness chrome around the app iframe — must match harness/src CSS. */\nexport const SIDEBAR_WIDTH = 320;\nexport const BANNER_HEIGHT = 44 + 36; // banner + tab strip\n\nexport interface SessionOptions {\n harnessUrl: string;\n /** All origins the app iframe may live on across the run (before + after). */\n targetOrigins: string[];\n appViewport: { width: number; height: number };\n headerStrip: boolean;\n onRawEvent: RawEventHandler;\n /** Permissions to grant up front (e.g. \"geolocation\", \"clipboard-read\"). */\n permissions?: string[];\n /** Fixed geolocation; implies the \"geolocation\" permission. */\n geolocation?: { latitude: number; longitude: number; accuracy?: number };\n}\n\nexport interface BrowserSession {\n browser: Browser;\n context: BrowserContext;\n page: Page;\n close(): Promise<void>;\n}\n\n/**\n * Local/Private Network Access features to disable so the harness (127.0.0.1)\n * can embed an app on localhost:PORT and that app can make its own localhost\n * requests (HMR sockets, RSC/data fetches). Without this Chrome blocks them and\n * the page renders but never hydrates → nothing is clickable.\n */\nconst LNA_DISABLE = [\n \"LocalNetworkAccessChecks\",\n \"BlockInsecurePrivateNetworkRequests\",\n \"PrivateNetworkAccessSendPreflights\",\n \"PrivateNetworkAccessForNavigations\",\n];\n\nlet cachedBaseDisabledFeatures: string | null = null;\n\n/**\n * Discover Playwright's own default `--disable-features` value (probed once per\n * process). Chrome honors only the LAST `--disable-features`, so to add ours\n * without clobbering Playwright's we must concatenate onto theirs.\n */\nasync function playwrightDisabledFeatures(): Promise<string> {\n if (cachedBaseDisabledFeatures !== null) return cachedBaseDisabledFeatures;\n cachedBaseDisabledFeatures = \"\";\n try {\n const probe = await chromium.launchServer({ headless: true });\n const arg = probe.process().spawnargs.find((a) => a.startsWith(\"--disable-features=\"));\n cachedBaseDisabledFeatures = arg ? arg.slice(\"--disable-features=\".length) : \"\";\n await probe.close();\n } catch {\n /* probe failed — we'll skip the LNA flag rather than clobber theirs */\n }\n return cachedBaseDisabledFeatures;\n}\n\n/**\n * Grant permissions across all origins. Tries the whole list at once; if Chrome\n * rejects an unknown name, falls back to granting each individually so the\n * supported ones still apply (and unsupported ones are simply skipped).\n */\nasync function grantPermissionsResilient(context: BrowserContext, perms: string[]): Promise<void> {\n if (perms.length === 0) return;\n try {\n await context.grantPermissions(perms);\n } catch {\n // grantPermissions REPLACES the granted set, so accumulate the valid names\n // and re-grant the growing set — skipping any this Chrome doesn't know.\n const good: string[] = [];\n for (const p of perms) {\n try {\n await context.grantPermissions([...good, p]);\n good.push(p);\n } catch {\n /* unsupported name — skip it, keep the rest */\n }\n }\n }\n}\n\n/**\n * Launch headed Chrome on the harness page. The window is freely resizable\n * (viewport: null = real window size); the harness letterboxes the app\n * iframe at its exact configured size so resizing never distorts it. The\n * initial window is sized to fit the iframe + sidebar + banner.\n */\nexport async function launchSession(opts: SessionOptions): Promise<BrowserSession> {\n const headless = process.env.PR_PREVIEW_HEADLESS === \"1\"; // CI/tests only\n const windowChrome = headless ? 0 : 88; // headed Chrome UI (tabs + URL bar)\n\n // Disable Local/Private Network Access checks, MERGED into Playwright's own\n // --disable-features so we don't re-enable the features it deliberately turns\n // off. If the probe failed, skip rather than clobber.\n const baseDisabled = await playwrightDisabledFeatures();\n const args = [\n \"--hide-crash-restore-bubble\",\n // Auto-accept camera/mic prompts with a fake device so getUserMedia\n // never blocks the run.\n \"--use-fake-ui-for-media-stream\",\n \"--use-fake-device-for-media-stream\",\n // Reduce the automation fingerprint (hides navigator.webdriver) so OAuth\n // providers like Google are less likely to refuse sign-in with \"this\n // browser may not be secure\". Not a guarantee — they're aggressive.\n \"--disable-blink-features=AutomationControlled\",\n `--window-size=${opts.appViewport.width + SIDEBAR_WIDTH},${\n opts.appViewport.height + BANNER_HEIGHT + windowChrome\n }`,\n ];\n if (baseDisabled) args.push(`--disable-features=${[baseDisabled, ...LNA_DISABLE].join(\",\")}`);\n\n const browser = await chromium.launch({\n headless,\n args,\n // Drop the \"controlled by automation\" flag — another OAuth-detection signal.\n ignoreDefaultArgs: [\"--enable-automation\"],\n });\n\n const context = await browser.newContext({\n viewport: null, // follow the real window — user can resize freely\n });\n\n // Grant permissions so a native prompt never blocks the run. Granted on the\n // context (all origins) and resiliently — names unsupported by this Chrome\n // are skipped instead of failing the whole launch.\n const permissions = new Set(opts.permissions ?? []);\n if (opts.geolocation) permissions.add(\"geolocation\");\n await grantPermissionsResilient(context, [...permissions]);\n\n // A position so geolocation resolves (no prompt, no hang) — the configured\n // one, or a neutral default when location is allowed but unset.\n const geolocation = opts.geolocation ?? (permissions.has(\"geolocation\") ? { latitude: 0, longitude: 0 } : undefined);\n if (geolocation) await context.setGeolocation(geolocation).catch(() => {});\n\n if (opts.headerStrip) await installHeaderStrip(context, opts.targetOrigins);\n\n const page = await context.newPage();\n\n // Native dialogs (alert/confirm/prompt) would otherwise be auto-dismissed by\n // Playwright — breaking flows that expect \"OK\"/\"Confirm\". Accept them (with\n // the prompt's default value) so the journey proceeds, in both record and\n // replay. Pages without a dialog listener just never trigger this.\n context.on(\"dialog\", (dialog) => {\n void dialog.accept(dialog.type() === \"prompt\" ? dialog.defaultValue() : undefined).catch(() => {});\n });\n\n await installRecorderBinding(page, opts.targetOrigins, opts.onRawEvent);\n await page.goto(opts.harnessUrl, { waitUntil: \"domcontentloaded\" });\n\n return {\n browser,\n context,\n page,\n close: async () => {\n await context.close().catch(() => {});\n await browser.close().catch(() => {});\n },\n };\n}\n","import type { BrowserContext } from \"playwright\";\n\n/**\n * Strip ONLY the frame-busting response headers from the target app so it\n * loads inside the harness iframe. Everything else passes through untouched —\n * the browser still talks to the dev server directly (cookies, redirects and\n * HMR websockets are unaffected).\n */\nexport async function installHeaderStrip(\n context: BrowserContext,\n targetOrigins: string[],\n): Promise<void> {\n await context.route(\n (url) => targetOrigins.includes(url.origin),\n async (route) => {\n // Only documents/frames can be frame-busted; let subresources flow.\n const type = route.request().resourceType();\n if (type !== \"document\") return route.continue();\n\n const response = await route.fetch();\n const headers = { ...response.headers() };\n delete headers[\"x-frame-options\"];\n\n const csp = headers[\"content-security-policy\"];\n if (csp) {\n // Surgically remove only the frame-ancestors directive.\n const stripped = csp\n .split(\";\")\n .filter((d) => !/^\\s*frame-ancestors/i.test(d))\n .join(\";\")\n .trim();\n if (stripped) headers[\"content-security-policy\"] = stripped;\n else delete headers[\"content-security-policy\"];\n }\n\n await route.fulfill({ response, headers });\n },\n );\n}\n","import { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { Page } from \"playwright\";\nimport type { RawEvent } from \"../recorder/types.js\";\n\nexport type RawEventHandler = (event: RawEvent) => void;\n\nlet recorderSource: string | null = null;\n\n/** Load the built in-page recorder IIFE (dist/inpage/recorder.global.js). */\nasync function loadRecorderSource(): Promise<string> {\n if (recorderSource) return recorderSource;\n const here = path.dirname(fileURLToPath(import.meta.url));\n const candidates = [\n path.resolve(here, \"../inpage/recorder.global.js\"), // dist/cli → dist/inpage\n path.resolve(here, \"../../dist/inpage/recorder.global.js\"), // running from src (tests)\n path.resolve(here, \"../../../dist/inpage/recorder.global.js\"),\n ];\n const found = candidates.find(existsSync);\n if (!found) {\n throw new Error(\n `In-page recorder bundle not found (looked in: ${candidates.join(\", \")}). Run \\`npm run build\\` first.`,\n );\n }\n recorderSource = await readFile(found, \"utf8\");\n return recorderSource;\n}\n\n/**\n * The cross-origin trick that makes the whole architecture work:\n * exposeBinding is available in ALL frames (CDP-level), so the recorder\n * inside the cross-origin app iframe can call __prPreviewEmit and reach Node\n * — no proxy, no postMessage gymnastics.\n */\nexport async function installRecorderBinding(\n page: Page,\n targetOrigins: string[],\n onEvent: RawEventHandler,\n): Promise<void> {\n await page.exposeBinding(\"__prPreviewEmit\", (source, json: string) => {\n // Accept events only from the target app's frames.\n try {\n if (!targetOrigins.includes(new URL(source.frame.url()).origin)) return;\n } catch {\n return;\n }\n try {\n onEvent(JSON.parse(json) as RawEvent);\n } catch {\n /* malformed event — drop */\n }\n });\n\n // Runs in every frame (including ones created by future navigations/HMR\n // reloads); the script self-gates to iframes only.\n await page.addInitScript({ content: await loadRecorderSource() });\n}\n","import sharp from \"sharp\";\nimport type { Page } from \"playwright\";\nimport type { Bus } from \"../ipc/bus.js\";\nimport type { PassInfo, Phase, StepSummary } from \"../ipc/protocol.js\";\nimport { StepBuilder } from \"../recorder/steps.js\";\nimport { describeStep, type RawEvent, type Step } from \"../recorder/types.js\";\nimport { startScreencast, type ScreencastHandle, type CapturedFrame } from \"../capture/screencast.js\";\nimport { cssRectToFramePixels } from \"../capture/region.js\";\nimport { encodeMedia, type MediaResult } from \"../encode/media.js\";\nimport { getAppFrame, getAppFrameRect } from \"../browser/frame.js\";\nimport type { Config } from \"../config/schema.js\";\n\n/**\n * One interactive harness session. The clip IS the live recording: while the\n * user performs the journey we screencast the page (with a synthetic cursor\n * that follows the real mouse), then encode those frames directly — no replay,\n * so nothing can drift. Steps are recorded purely as a sidebar outline (with\n * thumbnails + re-record-from-a-point).\n */\nexport class Session {\n readonly builder = new StepBuilder();\n /** Path the journey starts on — captured when recording begins. */\n startUrl = \"/\";\n private phase: Phase = \"idle\";\n private page: Page | null = null;\n private thumbnailQueue = Promise.resolve();\n /** Full screencast kept alive during recording — its frames ARE the clip,\n * and its latest frame drives the sidebar thumbnails. */\n private recordingFeed: ScreencastHandle | null = null;\n /** frames.length at the moment each step was recorded — lets a re-record\n * truncate the footage at that step. */\n private frameMark = new Map<string, number>();\n private passInfo: PassInfo = {\n pass: \"before\",\n done: { before: false, after: false },\n resetStorage: true,\n };\n /** Single-recording session — the clip's caption drops the BEFORE/AFTER pill. */\n private singleClip = false;\n /** The confirmed BEFORE journey, offered as a starting point for AFTER. */\n private beforeJourney: { steps: Step[]; startUrl: string } | null = null;\n\n constructor(\n private readonly bus: Bus,\n private readonly config: Config,\n private readonly mode: \"record\" | \"run\",\n private appUrl: string,\n ) {\n this.passInfo.resetStorage = config.resetStorage;\n\n bus.onHello(() => ({\n type: \"HELLO\",\n phase: this.phase,\n steps: this.builder.getSteps().map(toSummary),\n appUrl: this.appUrl,\n mode: this.mode,\n passInfo: this.passInfo,\n }));\n\n // Manual refresh button in the iframe corner.\n bus.onMessage((msg) => {\n if (msg.type === \"RELOAD_IFRAME\") void this.reloadAppFrame();\n });\n\n this.builder.onChange((change) => {\n switch (change.kind) {\n case \"added\":\n // Mark where in the live footage this step landed (re-record + trim).\n if (this.recordingFeed) this.frameMark.set(change.step.id, this.recordingFeed.frameCount());\n bus.send({ type: \"STEP_ADDED\", step: toSummary(change.step) });\n this.scheduleThumbnail(change.step);\n break;\n case \"updated\":\n // Advance the mark as the step changes (a fill coalesces keystrokes),\n // so the end-trim keeps the WHOLE action — not just its first frame.\n if (this.recordingFeed) this.frameMark.set(change.step.id, this.recordingFeed.frameCount());\n bus.send({ type: \"STEP_UPDATED\", step: toSummary(change.step) });\n break;\n case \"removed\":\n bus.send({ type: \"STEP_REMOVED\", stepId: change.stepId });\n break;\n case \"reset\":\n bus.send({ type: \"STEPS_RESET\", steps: this.builder.getSteps().map(toSummary) });\n break;\n }\n });\n }\n\n attachPage(page: Page, appUrl: string): void {\n this.page = page;\n this.appUrl = appUrl;\n }\n\n get targetOrigin(): string {\n return new URL(this.appUrl).origin;\n }\n\n setPhase(phase: Phase, detail?: string): void {\n this.phase = phase;\n this.bus.send({ type: \"PHASE_CHANGED\", phase, detail });\n // The recording feed captures only while recording: start/resume on enter,\n // pause on leave (so STOP_RECORD / record-more gaps are cut from the clip).\n if (phase === \"recording\") void this.onEnterRecording();\n else void this.onLeaveRecording();\n }\n\n /** Set branch labels for the tab strip (run mode). */\n setBranches(before: string, after: string): void {\n this.passInfo = { ...this.passInfo, branches: { before, after } };\n this.bus.send({ type: \"PASS_CHANGED\", passInfo: this.passInfo });\n }\n\n /** Declare the session plan (1 = single clip, 2 = before/after) so the\n * harness wizard/tabs render the right shape. */\n setPasses(passes: 1 | 2): void {\n this.singleClip = passes === 1;\n this.passInfo = { ...this.passInfo, passes };\n this.bus.send({ type: \"PASS_CHANGED\", passInfo: this.passInfo });\n }\n\n /**\n * Blocking nudge at the start of a pass, BEFORE recording: clear the app\n * (network/cookies/storage) and reload, or keep the current session. Shown\n * only when there's actually state to reset.\n */\n async promptResetChoice(): Promise<void> {\n if (!(await this.hasResettableState())) return;\n this.bus.send({\n type: \"RESET_PROMPT\",\n pass: this.passInfo.pass,\n defaultReset: this.config.resetStorage,\n });\n const { reset } = await this.bus.waitFor(\"RESET_CHOICE\");\n this.passInfo = { ...this.passInfo, resetStorage: reset };\n this.bus.send({ type: \"PASS_CHANGED\", passInfo: this.passInfo });\n if (reset) await this.resetIframe();\n }\n\n /** Is there any cookie / localStorage / sessionStorage worth resetting? */\n private async hasResettableState(): Promise<boolean> {\n const page = this.page;\n if (!page) return false;\n try {\n const cookies = await page.context().cookies();\n if (cookies.length > 0) return true;\n const frame = await getAppFrame(page, this.targetOrigin, 5_000);\n return await frame.evaluate(() => localStorage.length > 0 || sessionStorage.length > 0);\n } catch {\n return false;\n }\n }\n\n /** Clear cookies + localStorage + sessionStorage and reload the app. */\n private async resetIframe(): Promise<void> {\n const page = this.page;\n if (!page) return;\n await page.context().clearCookies().catch(() => {});\n try {\n const frame = await getAppFrame(page, this.targetOrigin, 5_000);\n await frame.evaluate(() => {\n localStorage.clear();\n sessionStorage.clear();\n });\n } catch {\n /* frame not up — the reload starts clean anyway */\n }\n await page.reload({ waitUntil: \"domcontentloaded\" });\n await getAppFrame(page, this.targetOrigin)\n .then((f) => f.waitForLoadState(\"domcontentloaded\").catch(() => {}))\n .catch(() => {});\n }\n\n /** Switch the session to the other pass; AFTER starts with a clean slate. */\n switchPass(pass: \"before\" | \"after\"): void {\n this.passInfo = { ...this.passInfo, pass };\n if (pass === \"after\") {\n // Keep the confirmed BEFORE journey around as an optional template.\n this.beforeJourney = { steps: this.builder.getSteps(), startUrl: this.startUrl };\n this.builder.setSteps([]);\n this.frameMark.clear();\n this.startUrl = \"/\";\n this.passInfo = { ...this.passInfo, resetStorage: this.config.resetStorage };\n }\n this.bus.send({ type: \"PASS_CHANGED\", passInfo: this.passInfo });\n }\n\n markGifDone(which: \"before\" | \"after\"): void {\n this.passInfo = { ...this.passInfo, done: { ...this.passInfo.done, [which]: true } };\n this.bus.send({ type: \"PASS_CHANGED\", passInfo: this.passInfo });\n }\n\n /** Raw events from the in-page recorder land here (via exposeBinding). */\n handleRawEvent = (event: RawEvent): void => {\n if (this.phase === \"recording\") this.builder.handle(event);\n };\n\n /** Reload the current page inside the app iframe (cross-origin safe via CDP). */\n private async reloadAppFrame(): Promise<void> {\n const page = this.page;\n if (!page) return;\n try {\n const frame = await getAppFrame(page, this.targetOrigin, 3_000);\n await frame.evaluate(() => location.reload());\n } catch {\n /* frame not up — nothing to reload */\n }\n }\n\n /**\n * Drive the interactive record/edit loop until the user confirms.\n * Resolves with the confirmed steps.\n */\n async recordUntilConfirmed(): Promise<Step[]> {\n return new Promise<Step[]>((resolve, reject) => {\n const off = this.bus.onMessage((msg) => {\n switch (msg.type) {\n case \"START_RECORD\":\n // A fresh recording defines the journey's start page.\n if (this.builder.getSteps().length === 0) {\n void this.currentAppPath().then((p) => (this.startUrl = p));\n }\n this.setPhase(\"recording\");\n break;\n case \"STOP_RECORD\":\n this.setPhase(\"idle\");\n break;\n case \"DELETE_STEP\":\n this.builder.removeStep(msg.stepId);\n break;\n case \"RERECORD_FROM\":\n // Drop the step + everything after (and the footage from that\n // point), then keep recording from there.\n this.truncateLiveFrames(msg.stepId);\n this.builder.truncateFrom(msg.stepId);\n this.setPhase(\"recording\", \"Re-recording — perform the journey from this point\");\n break;\n case \"LOAD_BEFORE_STEPS\":\n // AFTER pass convenience: start the outline from the BEFORE journey.\n if (this.beforeJourney) {\n this.builder.setSteps(this.beforeJourney.steps);\n this.startUrl = this.beforeJourney.startUrl;\n }\n break;\n case \"CONFIRM\":\n this.setPhase(\"idle\");\n off();\n resolve(this.builder.getSteps());\n break;\n case \"ABORT\":\n off();\n reject(new Error(\"Aborted from the sidebar\"));\n break;\n }\n });\n });\n }\n\n private async currentAppPath(): Promise<string> {\n const page = this.page;\n if (!page) return \"/\";\n try {\n const frame = await getAppFrame(page, this.targetOrigin, 5_000);\n return await frame.evaluate(() => location.pathname + location.search);\n } catch {\n return \"/\";\n }\n }\n\n /** Enter recording: start the full screencast (or resume it) + tell the\n * in-page cursor to follow the real mouse so it lands in the footage. */\n private async onEnterRecording(): Promise<void> {\n await this.setIframeRecording(true);\n if (this.recordingFeed) {\n this.recordingFeed.resume();\n return;\n }\n const page = this.page;\n if (!page) return;\n try {\n this.recordingFeed = await startScreencast(page, { quality: 80 });\n } catch {\n this.recordingFeed = null;\n }\n }\n\n /** Leave recording: pause the feed (keep its frames). */\n private async onLeaveRecording(): Promise<void> {\n this.recordingFeed?.pause();\n await this.setIframeRecording(false);\n }\n\n /** Fully stop the recording feed and return its frames (the clip). */\n private async takeRecordingFrames(): Promise<CapturedFrame[]> {\n const feed = this.recordingFeed;\n this.recordingFeed = null;\n if (!feed) return [];\n return feed.stop();\n }\n\n /** Re-record: drop the footage recorded from `stepId` onwards. */\n truncateLiveFrames(stepId: string): void {\n const mark = this.frameMark.get(stepId);\n if (mark != null) this.recordingFeed?.truncate(mark);\n for (const [id, n] of this.frameMark) if (mark != null && n >= mark) this.frameMark.delete(id);\n }\n\n /** Tell the in-page recorder to make the synthetic cursor follow the mouse. */\n private async setIframeRecording(on: boolean): Promise<void> {\n const page = this.page;\n if (!page) return;\n try {\n const frame = await getAppFrame(page, this.targetOrigin, 2_000);\n await frame.evaluate((v) => {\n window.__prPreviewRecording = v;\n }, on);\n } catch {\n /* frame not up yet */\n }\n }\n\n /**\n * Trim dead time at the ends of the footage: keep a small lead-in before the\n * first interaction and a short tail after the last, so \"getting ready\" and\n * \"reaching for Confirm\" time isn't baked into the clip.\n */\n private trimToActivity(frames: CapturedFrame[]): CapturedFrame[] {\n const marks = [...this.frameMark.values()]\n .filter((n) => n >= 0 && n < frames.length)\n .sort((a, b) => a - b);\n if (marks.length === 0) return frames;\n // Small lead-in; a generous tail so the result of the last action (e.g. the\n // text you just typed rendering) is never clipped — only the dead \"reaching\n // for Confirm\" time after it is trimmed.\n const HEAD = 12; // ~0.5s at 24fps\n const TAIL = 40; // ~1.6s at 24fps\n const head = Math.max(0, marks[0]! - HEAD);\n const tail = Math.min(frames.length, marks[marks.length - 1]! + TAIL);\n return frames.slice(head, tail);\n }\n\n /**\n * Encode the recorded footage into the clip — cropped to the iframe, with the\n * caption/watermark overlay. No replay, no state re-creation.\n */\n async encodeClip(\n which: \"before\" | \"after\",\n outBase: string,\n label?: { branch: string; baseBranch: string; timestamp: string },\n ): Promise<MediaResult> {\n const page = this.page;\n if (!page) throw new Error(\"No browser page attached\");\n await this.setIframeRecording(false);\n const frames = this.trimToActivity(await this.takeRecordingFrames());\n if (frames.length === 0) throw new Error(\"No footage captured for the clip\");\n\n const cropCss = await getAppFrameRect(page);\n const pageCssSize = await page.evaluate(() => ({\n width: window.innerWidth,\n height: window.innerHeight,\n }));\n\n this.setPhase(\"encoding\", `Encoding ${which} clip`);\n const result = await encodeMedia(frames, {\n cropCss,\n pageCssSize,\n width: this.config.gif.width,\n fps: this.config.gif.fps,\n quality: this.config.gif.quality,\n maxColors: this.config.gif.maxColors,\n format: this.config.format,\n outBase,\n label: label ? { pass: this.singleClip ? \"single\" : which, ...label } : undefined,\n onProgress: (stage, done, total) =>\n this.bus.send({ type: \"ENCODE_PROGRESS\", which, stage, done, total }),\n });\n this.markGifDone(which);\n this.bus.send({ type: \"GIF_READY\", which, path: result.paths[0]! });\n return result;\n }\n\n /**\n * Crop the recording feed's latest frame to the iframe for the sidebar step\n * row. No discrete screenshot → no flicker on recorded actions.\n */\n private scheduleThumbnail(step: Step): void {\n const page = this.page;\n if (!page) return;\n this.thumbnailQueue = this.thumbnailQueue.then(async () => {\n try {\n const frame = this.recordingFeed?.latest();\n if (!frame) return; // no frame yet — skip rather than flash a capture\n const meta = await sharp(frame).metadata();\n if (!meta.width || !meta.height) return;\n const rect = await getAppFrameRect(page);\n const pageCss = await page.evaluate(() => ({\n width: window.innerWidth,\n height: window.innerHeight,\n }));\n const crop = cssRectToFramePixels(rect, { width: meta.width, height: meta.height }, pageCss);\n if (crop.width < 2 || crop.height < 2) return;\n const small = await sharp(frame)\n .extract({ left: crop.left, top: crop.top, width: crop.width, height: crop.height })\n .resize(160)\n .png()\n .toBuffer();\n this.builder.setThumbnail(step.id, `data:image/png;base64,${small.toString(\"base64\")}`);\n } catch {\n /* transient (reload/race) — the next step retries */\n }\n });\n }\n}\n\nfunction toSummary(step: Step): StepSummary {\n return {\n id: step.id,\n type: step.type,\n label: describeStep(step),\n thumbnail: step.thumbnail,\n };\n}\n","import type { RawEvent, Step } from \"./types.js\";\n\n/**\n * Normalizes the raw in-page event stream into discrete steps:\n * - consecutive `input` events on the same element coalesce into one `fill`\n * - a click followed by navigation within 500ms gets `causesNavigation`\n * - SPA `navigate` events right after a click are folded into the click\n */\nexport class StepBuilder {\n private steps: Step[] = [];\n private counter = 0;\n private pendingFill: Step | null = null;\n private listeners = new Set<(change: StepChange) => void>();\n\n onChange(listener: (change: StepChange) => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private notify(change: StepChange): void {\n for (const l of this.listeners) l(change);\n }\n\n private nextId(): string {\n return `stp_${String(++this.counter).padStart(2, \"0\")}`;\n }\n\n getSteps(): Step[] {\n this.flushFill();\n return [...this.steps];\n }\n\n /** Replace all steps (journey reuse / re-record truncation). */\n setSteps(steps: Step[]): void {\n this.pendingFill = null;\n this.steps = [...steps];\n this.counter = steps.length;\n this.notify({ kind: \"reset\" });\n }\n\n removeStep(id: string): void {\n const i = this.steps.findIndex((s) => s.id === id);\n if (i >= 0) {\n this.steps.splice(i, 1);\n this.notify({ kind: \"removed\", stepId: id });\n }\n }\n\n /** Drop the given step and everything after it (re-record from there). */\n truncateFrom(id: string): Step[] {\n this.flushFill();\n const i = this.steps.findIndex((s) => s.id === id);\n if (i >= 0) {\n this.steps = this.steps.slice(0, i);\n this.notify({ kind: \"reset\" });\n }\n return [...this.steps];\n }\n\n setThumbnail(id: string, thumbnail: string): Step | undefined {\n const step = this.steps.find((s) => s.id === id);\n if (step) {\n step.thumbnail = thumbnail;\n this.notify({ kind: \"updated\", step });\n }\n return step;\n }\n\n handle(event: RawEvent): void {\n switch (event.kind) {\n case \"input\": {\n const sameField =\n this.pendingFill &&\n selectorsOverlap(this.pendingFill.selectors ?? [], event.selectors);\n if (sameField && this.pendingFill) {\n this.pendingFill.value = event.value;\n this.pendingFill.timestamp = event.ts;\n this.notify({ kind: \"updated\", step: this.pendingFill });\n } else {\n this.flushFill();\n // A click that merely focused this field is redundant — replaying\n // the fill clicks the field anyway. Absorb it.\n const prev = this.steps[this.steps.length - 1];\n if (\n prev?.type === \"click\" &&\n selectorsOverlap(prev.selectors ?? [], event.selectors) &&\n event.ts - prev.timestamp < 3_000\n ) {\n this.steps.pop();\n this.notify({ kind: \"removed\", stepId: prev.id });\n }\n this.pendingFill = {\n id: this.nextId(),\n type: \"fill\",\n selectors: event.selectors,\n value: event.value,\n coordinates: { xNorm: event.xNorm, yNorm: event.yNorm },\n frameUrl: event.frameUrl,\n timestamp: event.ts,\n };\n this.steps.push(this.pendingFill);\n this.notify({ kind: \"added\", step: this.pendingFill });\n }\n break;\n }\n\n case \"click\": {\n this.flushFill();\n const step: Step = {\n id: this.nextId(),\n type: \"click\",\n selectors: event.selectors,\n coordinates: { xNorm: event.xNorm, yNorm: event.yNorm },\n frameUrl: event.frameUrl,\n label: event.text,\n timestamp: event.ts,\n };\n this.steps.push(step);\n this.notify({ kind: \"added\", step });\n break;\n }\n\n case \"select\": {\n this.flushFill();\n const step: Step = {\n id: this.nextId(),\n type: \"select\",\n selectors: event.selectors,\n value: event.value,\n coordinates: { xNorm: event.xNorm, yNorm: event.yNorm },\n frameUrl: event.frameUrl,\n timestamp: event.ts,\n };\n this.steps.push(step);\n this.notify({ kind: \"added\", step });\n break;\n }\n\n case \"key\": {\n // Enter inside a pending fill: flush the fill first, then the press.\n this.flushFill();\n const step: Step = {\n id: this.nextId(),\n type: \"press\",\n key: event.key,\n selectors: event.selectors,\n frameUrl: event.frameUrl,\n timestamp: event.ts,\n };\n this.steps.push(step);\n this.notify({ kind: \"added\", step });\n break;\n }\n\n case \"scroll\": {\n this.flushFill();\n // Collapse consecutive scrolls on the same target into the last one.\n const prev = this.steps[this.steps.length - 1];\n if (prev?.type === \"scroll\" && prev.scrollTarget === event.target) {\n prev.scroll = { xNorm: event.xNorm, yNorm: event.yNorm };\n prev.timestamp = event.ts;\n this.notify({ kind: \"updated\", step: prev });\n } else {\n const step: Step = {\n id: this.nextId(),\n type: \"scroll\",\n scrollTarget: event.target,\n scroll: { xNorm: event.xNorm, yNorm: event.yNorm },\n frameUrl: event.frameUrl,\n timestamp: event.ts,\n };\n this.steps.push(step);\n this.notify({ kind: \"added\", step });\n }\n break;\n }\n\n case \"navigate\": {\n this.flushFill();\n const prev = this.steps[this.steps.length - 1];\n // A click that triggered this navigation replays itself — just annotate.\n if (\n prev &&\n (prev.type === \"click\" || prev.type === \"press\") &&\n event.ts - prev.timestamp < 800\n ) {\n prev.causesNavigation = true;\n this.notify({ kind: \"updated\", step: prev });\n } else if (prev?.type === \"navigate\") {\n prev.url = event.url;\n prev.timestamp = event.ts;\n this.notify({ kind: \"updated\", step: prev });\n } else {\n const step: Step = {\n id: this.nextId(),\n type: \"navigate\",\n url: event.url,\n frameUrl: event.frameUrl,\n timestamp: event.ts,\n };\n this.steps.push(step);\n this.notify({ kind: \"added\", step });\n }\n break;\n }\n }\n }\n\n private flushFill(): void {\n this.pendingFill = null;\n }\n}\n\nexport type StepChange =\n | { kind: \"added\"; step: Step }\n | { kind: \"updated\"; step: Step }\n | { kind: \"removed\"; stepId: string }\n | { kind: \"reset\" };\n\n/** Two selector chains refer to the same element if any selector matches. */\nfunction selectorsOverlap(a: string[], b: string[]): boolean {\n return a.some((s) => b.includes(s));\n}\n","/**\n * Core data shapes: raw in-page events, normalized steps, and the journey file.\n * No Node imports — these types are shared with the in-page recorder bundle.\n */\n\n/** Raw event emitted by the in-page recorder via the exposed binding. */\nexport type RawEvent =\n | {\n kind: \"click\";\n selectors: string[];\n xNorm: number;\n yNorm: number;\n text?: string;\n frameUrl: string;\n ts: number;\n }\n | {\n kind: \"input\";\n selectors: string[];\n value: string;\n xNorm: number;\n yNorm: number;\n frameUrl: string;\n ts: number;\n }\n | {\n kind: \"key\";\n key: string;\n selectors: string[];\n frameUrl: string;\n ts: number;\n }\n | {\n kind: \"scroll\";\n target: string; // \"window\" or a selector\n xNorm: number; // scrollLeft / scrollWidth\n yNorm: number; // scrollTop / scrollHeight\n frameUrl: string;\n ts: number;\n }\n | {\n kind: \"select\";\n selectors: string[];\n value: string;\n xNorm: number;\n yNorm: number;\n frameUrl: string;\n ts: number;\n }\n | {\n kind: \"navigate\";\n url: string;\n frameUrl: string;\n ts: number;\n };\n\nexport type StepType = \"click\" | \"fill\" | \"select\" | \"press\" | \"scroll\" | \"navigate\" | \"wait\";\n\nexport interface Step {\n id: string;\n type: StepType;\n /** Ordered best→worst; every entry resolved uniquely at record time. */\n selectors?: string[];\n /** Viewport-normalized coordinates — the absolute fallback. */\n coordinates?: { xNorm: number; yNorm: number };\n /** Path (origin-relative) of the app at the time of the step. */\n frameUrl?: string;\n /** fill */\n value?: string;\n /** press */\n key?: string;\n /** scroll */\n scrollTarget?: string;\n scroll?: { xNorm: number; yNorm: number };\n /** navigate */\n url?: string;\n causesNavigation?: boolean;\n /** Human label for the sidebar row. */\n label?: string;\n /** ms offset from recording start. */\n timestamp: number;\n /** Small data-URL png for the sidebar; stripped when persisted. */\n thumbnail?: string;\n}\n\nexport interface Journey {\n version: 1;\n createdAt: string;\n baseRef?: string;\n viewport: { width: number; height: number; deviceScaleFactor: number };\n startUrl: string;\n steps: Step[];\n}\n\n/** Human-readable one-liner for a step (sidebar rows, logs). */\nexport function describeStep(step: Step): string {\n switch (step.type) {\n case \"click\":\n return `Click ${step.label ?? firstSelector(step)}`;\n case \"fill\":\n return `Type \"${truncate(step.value ?? \"\", 24)}\" into ${firstSelector(step)}`;\n case \"select\":\n return `Select \"${truncate(step.value ?? \"\", 24)}\" in ${firstSelector(step)}`;\n case \"press\":\n return `Press ${step.key}`;\n case \"scroll\":\n return `Scroll ${step.scrollTarget === \"window\" ? \"page\" : step.scrollTarget ?? \"page\"}`;\n case \"navigate\":\n return `Go to ${step.url}`;\n case \"wait\":\n return \"Wait\";\n }\n}\n\nfunction firstSelector(step: Step): string {\n return step.selectors?.[0] ?? \"element\";\n}\n\nfunction truncate(s: string, n: number): string {\n return s.length > n ? s.slice(0, n - 1) + \"…\" : s;\n}\n","import type { CDPSession, Page } from \"playwright\";\n\nexport interface CapturedFrame {\n /** JPEG bytes straight from the screencast. */\n data: Buffer;\n /** ms since capture start, with paused windows removed. */\n t: number;\n}\n\nexport interface ScreencastHandle {\n /** Drop incoming frames and compress the timeline (manual pauses). */\n pause(): void;\n resume(): void;\n stop(): Promise<CapturedFrame[]>;\n /** Frames captured so far (live) — used to mark where each step landed. */\n frameCount(): number;\n /** Drop frames after index `n` (re-record from a step in live capture). */\n truncate(n: number): void;\n /** Most recent frame's bytes — drives recording-time thumbnails. */\n latest(): Buffer | null;\n}\n\n/**\n * Capture frames via CDP Page.startScreencast — real video-rate frames with\n * timestamps, far smoother than interval screenshots for cursor motion.\n * Frames cover the whole page; cropping to the iframe happens at encode time.\n *\n * pause()/resume() cut manual-pause windows (user typing a password, fixing a\n * drifted step) out of the GIF entirely instead of baking in dead footage.\n */\nexport async function startScreencast(\n page: Page,\n opts: { quality?: number; everyNthFrame?: number } = {},\n): Promise<ScreencastHandle> {\n const cdp: CDPSession = await page.context().newCDPSession(page);\n const frames: CapturedFrame[] = [];\n let t0: number | null = null;\n let paused = false;\n let pauseBeganAbs: number | null = null;\n let removed = 0; // total ms cut from the timeline\n let lastAbs = 0;\n\n const onFrame = async (event: {\n data: string;\n sessionId: number;\n metadata: { timestamp?: number };\n }) => {\n const abs = (event.metadata.timestamp ?? Date.now() / 1000) * 1000;\n lastAbs = abs;\n if (t0 === null) t0 = abs;\n if (!paused) {\n frames.push({ data: Buffer.from(event.data, \"base64\"), t: abs - t0 - removed });\n }\n // Must ack every frame (even dropped ones) or Chrome stops sending.\n await cdp.send(\"Page.screencastFrameAck\", { sessionId: event.sessionId }).catch(() => {});\n };\n cdp.on(\"Page.screencastFrame\", onFrame);\n\n await cdp.send(\"Page.startScreencast\", {\n format: \"jpeg\",\n quality: opts.quality ?? 90,\n everyNthFrame: opts.everyNthFrame ?? 1,\n });\n\n return {\n frameCount() {\n return frames.length;\n },\n truncate(n: number) {\n if (n >= 0 && n < frames.length) frames.length = n;\n },\n latest() {\n return frames.length ? (frames[frames.length - 1]!.data) : null;\n },\n pause() {\n if (paused) return;\n paused = true;\n pauseBeganAbs = lastAbs;\n },\n resume() {\n if (!paused) return;\n paused = false;\n if (pauseBeganAbs !== null) removed += Math.max(0, lastAbs - pauseBeganAbs);\n pauseBeganAbs = null;\n },\n async stop() {\n await cdp.send(\"Page.stopScreencast\").catch(() => {});\n cdp.off(\"Page.screencastFrame\", onFrame);\n await cdp.detach().catch(() => {});\n return frames;\n },\n };\n}\n","export interface CropRect {\n left: number;\n top: number;\n width: number;\n height: number;\n}\n\n/**\n * Translate the iframe's CSS-pixel bounding box into pixel coordinates of an\n * actual screencast frame (which may be at devicePixelRatio scale).\n */\nexport function cssRectToFramePixels(\n rect: { x: number; y: number; width: number; height: number },\n frameSize: { width: number; height: number },\n pageCssSize: { width: number; height: number },\n): CropRect {\n const scaleX = frameSize.width / pageCssSize.width;\n const scaleY = frameSize.height / pageCssSize.height;\n const left = Math.max(0, Math.round(rect.x * scaleX));\n const top = Math.max(0, Math.round(rect.y * scaleY));\n return {\n left,\n top,\n width: Math.min(Math.round(rect.width * scaleX), frameSize.width - left),\n height: Math.min(Math.round(rect.height * scaleY), frameSize.height - top),\n };\n}\n","import sharp from \"sharp\";\nimport type { CapturedFrame } from \"../capture/screencast.js\";\nimport { resampleToFps } from \"../capture/frames.js\";\nimport { cssRectToFramePixels } from \"../capture/region.js\";\nimport { buildOverlays, encodeGif, type EncodeOptions } from \"./gif.js\";\nimport { encodeMp4, ffmpegAvailable } from \"./ffmpeg.js\";\n\nexport type MediaFormat = \"mp4\" | \"gif\" | \"both\";\n\nexport interface MediaResult {\n /** Produced files (mp4 first when both). */\n paths: string[];\n frameCount: number;\n /** Set when mp4 was requested but ffmpeg is missing. */\n fellBackToGif: boolean;\n}\n\n/**\n * Encode captured frames into the configured format(s).\n * `outBase` is the extension-less output path (e.g. \".pr-preview/output/before\").\n */\nexport async function encodeMedia(\n rawFrames: CapturedFrame[],\n opts: Omit<EncodeOptions, \"outFile\"> & { format: MediaFormat; outBase: string },\n): Promise<MediaResult> {\n if (rawFrames.length === 0) throw new Error(\"No frames captured — nothing to encode\");\n\n const wantsMp4 = opts.format === \"mp4\" || opts.format === \"both\";\n const haveFfmpeg = await ffmpegAvailable();\n const doMp4 = wantsMp4 && haveFfmpeg;\n const doGif = opts.format === \"gif\" || opts.format === \"both\" || (wantsMp4 && !haveFfmpeg);\n\n const paths: string[] = [];\n let frameCount = 0;\n\n if (doMp4) {\n const frames = resampleToFps(rawFrames, opts.fps);\n frameCount = frames.length;\n\n const meta = await sharp(frames[0]!.data).metadata();\n const crop = cssRectToFramePixels(\n opts.cropCss,\n { width: meta.width!, height: meta.height! },\n opts.pageCssSize,\n );\n // MP4 keeps full capture resolution up to 2x the configured width —\n // video compresses well, and PR viewers can fullscreen it.\n const outWidth = Math.min(opts.width * 2, crop.width);\n const outHeight = Math.round((crop.height / crop.width) * outWidth);\n const overlays = await buildOverlays(opts.label, outWidth, outHeight);\n\n const pngs: Buffer[] = [];\n const cache = new Map<Buffer, Buffer>();\n for (const f of frames) {\n let png = cache.get(f.data);\n if (!png) {\n let pipe = sharp(f.data).extract(crop).resize(outWidth, outHeight);\n if (overlays.length) pipe = pipe.composite(overlays);\n png = await pipe.png().toBuffer();\n cache.set(f.data, png);\n }\n pngs.push(png);\n opts.onProgress?.(\"Rendering frames\", pngs.length, frames.length);\n }\n const file = `${opts.outBase}.mp4`;\n opts.onProgress?.(\"Encoding MP4\", frames.length, frames.length);\n await encodeMp4(pngs, opts.fps, file);\n paths.push(file);\n }\n\n if (doGif) {\n const result = await encodeGif(rawFrames, { ...opts, outFile: `${opts.outBase}.gif` });\n paths.push(result.path);\n frameCount = result.frameCount;\n }\n\n return { paths, frameCount, fellBackToGif: wantsMp4 && !haveFfmpeg };\n}\n","import type { CapturedFrame } from \"./screencast.js\";\n\n/**\n * Resample variable-rate screencast frames onto a fixed-fps timeline by\n * nearest preceding frame. Consecutive duplicates are kept (GIF needs the\n * delay anyway) but identical buffers are reused by reference, so later\n * stages can cheap-compare with ===.\n */\nexport function resampleToFps(frames: CapturedFrame[], fps: number): CapturedFrame[] {\n if (frames.length === 0) return [];\n const interval = 1000 / fps;\n const duration = frames[frames.length - 1]!.t;\n const out: CapturedFrame[] = [];\n let src = 0;\n for (let t = 0; t <= duration; t += interval) {\n while (src + 1 < frames.length && frames[src + 1]!.t <= t) src++;\n out.push({ data: frames[src]!.data, t });\n }\n return out;\n}\n","import { mkdir, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport sharp from \"sharp\";\n// @ts-expect-error gifenc ships no types\nimport * as gifencNs from \"gifenc\";\n// Dual-package interop: Node ESM sees CJS (functions hang off .default),\n// bundler-resolved ESM has real named exports PLUS a default that is just\n// the encoder — so pick whichever shape actually carries the API.\nconst gifencLib: any =\n typeof (gifencNs as any).quantize === \"function\" ? gifencNs : (gifencNs as any).default;\nconst { GIFEncoder, quantize, applyPalette } = gifencLib;\nimport type { CapturedFrame } from \"../capture/screencast.js\";\nimport { resampleToFps } from \"../capture/frames.js\";\nimport { cssRectToFramePixels } from \"../capture/region.js\";\nimport { encodeWithFfmpeg, ffmpegAvailable } from \"./ffmpeg.js\";\nimport { renderLabel, renderWatermark, type LabelSpec } from \"./label.js\";\n\nexport interface EncodeOptions {\n /** Iframe bounding box in CSS px (from getAppFrameRect). */\n cropCss: { x: number; y: number; width: number; height: number };\n /** Page viewport in CSS px (to derive the screencast pixel scale). */\n pageCssSize: { width: number; height: number };\n width: number;\n fps: number;\n quality: \"high\" | \"max\";\n maxColors: number;\n outFile: string;\n /** Burned-in branch/timestamp caption (omit videoWidth — set per output). */\n label?: Omit<LabelSpec, \"videoWidth\">;\n /** Encoding progress, for a UI bar. `stage` is a short human label. */\n onProgress?: (stage: string, done: number, total: number) => void;\n}\n\nexport interface Overlay {\n input: Buffer;\n top: number;\n left: number;\n}\n\n/**\n * Build the burned-in overlays: the branch/timestamp caption (bottom-left)\n * and the pr-preview.com watermark (bottom-right). The watermark is always\n * present; the caption only when label info is provided.\n */\nexport async function buildOverlays(\n label: Omit<LabelSpec, \"videoWidth\"> | undefined,\n outWidth: number,\n outHeight: number,\n): Promise<Overlay[]> {\n const inset = Math.round(outWidth * 0.018);\n const overlays: Overlay[] = [];\n\n if (label) {\n const png = await renderLabel({ ...label, videoWidth: outWidth });\n const m = await sharp(png).metadata();\n overlays.push({ input: png, top: outHeight - (m.height ?? 0) - inset, left: inset });\n }\n\n const wm = await renderWatermark(outWidth);\n const wmMeta = await sharp(wm).metadata();\n overlays.push({\n input: wm,\n top: outHeight - (wmMeta.height ?? 0) - inset,\n left: outWidth - (wmMeta.width ?? 0) - inset,\n });\n\n return overlays;\n}\n\n/**\n * frames (JPEG, full page) → crop to iframe → resize → global palette →\n * GIF. With quality:\"max\" and ffmpeg on PATH, defer to ffmpeg's\n * palettegen/paletteuse for the best dithering.\n */\nexport async function encodeGif(\n rawFrames: CapturedFrame[],\n opts: EncodeOptions,\n): Promise<{ path: string; frameCount: number }> {\n if (rawFrames.length === 0) throw new Error(\"No frames captured — nothing to encode\");\n\n const frames = resampleToFps(rawFrames, opts.fps);\n const meta = await sharp(frames[0]!.data).metadata();\n const crop = cssRectToFramePixels(\n opts.cropCss,\n { width: meta.width!, height: meta.height! },\n opts.pageCssSize,\n );\n\n const outWidth = Math.min(opts.width, crop.width);\n const outHeight = Math.round((crop.height / crop.width) * outWidth);\n const overlays = await buildOverlays(opts.label, outWidth, outHeight);\n\n // Decode each unique source buffer once (resampling repeats buffers).\n const rgbaCache = new Map<Buffer, Uint8ClampedArray>();\n const decode = async (jpeg: Buffer): Promise<Uint8ClampedArray> => {\n const hit = rgbaCache.get(jpeg);\n if (hit) return hit;\n let pipe = sharp(jpeg).extract(crop).resize(outWidth, outHeight);\n if (overlays.length) pipe = pipe.composite(overlays);\n const { data } = await pipe.ensureAlpha().raw().toBuffer({ resolveWithObject: true });\n const rgba = new Uint8ClampedArray(data.buffer, data.byteOffset, data.byteLength);\n rgbaCache.set(jpeg, rgba);\n return rgba;\n };\n\n if (opts.quality === \"max\" && (await ffmpegAvailable())) {\n const pngs: Buffer[] = [];\n for (const f of frames) {\n let pipe = sharp(f.data).extract(crop).resize(outWidth, outHeight);\n if (overlays.length) pipe = pipe.composite(overlays);\n pngs.push(await pipe.png().toBuffer());\n opts.onProgress?.(\"Rendering frames\", pngs.length, frames.length);\n }\n opts.onProgress?.(\"Encoding GIF\", frames.length, frames.length);\n await encodeWithFfmpeg(pngs, opts.fps, opts.outFile);\n return { path: opts.outFile, frameCount: frames.length };\n }\n\n // Global palette sampled across the run — less flicker than per-frame.\n const sampleCount = Math.min(10, frames.length);\n const sampleStride = Math.max(1, Math.floor(frames.length / sampleCount));\n const samples: Uint8ClampedArray[] = [];\n for (let i = 0; i < frames.length; i += sampleStride) {\n samples.push(await decode(frames[i]!.data));\n }\n const combined = new Uint8ClampedArray(samples.reduce((n, s) => n + s.length, 0));\n let offset = 0;\n for (const s of samples) {\n combined.set(s, offset);\n offset += s.length;\n }\n const palette = quantize(combined, opts.maxColors);\n\n const gif = GIFEncoder();\n const delay = 1000 / opts.fps;\n let first = true;\n let done = 0;\n for (const frame of frames) {\n const rgba = await decode(frame.data);\n const indexed = applyPalette(rgba, palette);\n gif.writeFrame(indexed, outWidth, outHeight, {\n palette: first ? palette : undefined,\n first,\n delay,\n repeat: first ? 0 : undefined, // loop forever\n });\n first = false;\n opts.onProgress?.(\"Encoding GIF\", ++done, frames.length);\n }\n gif.finish();\n\n await mkdir(path.dirname(opts.outFile), { recursive: true });\n await writeFile(opts.outFile, gif.bytes());\n return { path: opts.outFile, frameCount: frames.length };\n}\n","import { execFile, spawn } from \"node:child_process\";\nimport { mkdtemp, rm, writeFile, mkdir } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\n\nlet available: boolean | null = null;\n\nexport function ffmpegAvailable(): Promise<boolean> {\n if (available !== null) return Promise.resolve(available);\n return new Promise((resolve) => {\n execFile(\"ffmpeg\", [\"-version\"], (err) => {\n available = !err;\n resolve(available);\n });\n });\n}\n\n/** Write frames into a temp dir, hand the pattern to fn, clean up after. */\nasync function withFrameDir<T>(\n pngFrames: Buffer[],\n fn: (pattern: string) => Promise<T>,\n): Promise<T> {\n const dir = await mkdtemp(path.join(tmpdir(), \"pr-preview-frames-\"));\n try {\n await Promise.all(\n pngFrames.map((buf, i) =>\n writeFile(path.join(dir, `frame${String(i).padStart(5, \"0\")}.png`), buf),\n ),\n );\n return await fn(path.join(dir, \"frame%05d.png\"));\n } finally {\n await rm(dir, { recursive: true, force: true });\n }\n}\n\n/**\n * Max-quality GIF via ffmpeg's two-pass palettegen/paletteuse with\n * Bayer dithering — noticeably better gradients than pure-JS quantization.\n */\nexport async function encodeWithFfmpeg(\n pngFrames: Buffer[],\n fps: number,\n outFile: string,\n): Promise<void> {\n await withFrameDir(pngFrames, async (pattern) => {\n await mkdir(path.dirname(outFile), { recursive: true });\n const filter =\n `[0:v]fps=${fps},split[a][b];` +\n `[a]palettegen=stats_mode=diff[p];` +\n `[b][p]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle`;\n await run(\"ffmpeg\", [\"-y\", \"-framerate\", String(fps), \"-i\", pattern, \"-filter_complex\", filter, outFile]);\n });\n}\n\n/**\n * Small + HQ H.264 MP4 — uploads directly into a GitHub PR description.\n * yuv420p + faststart for universal playback; dimensions forced even\n * (H.264 requirement).\n */\nexport async function encodeMp4(\n pngFrames: Buffer[],\n fps: number,\n outFile: string,\n): Promise<void> {\n await withFrameDir(pngFrames, async (pattern) => {\n await mkdir(path.dirname(outFile), { recursive: true });\n await run(\"ffmpeg\", [\n \"-y\",\n \"-framerate\",\n String(fps),\n \"-i\",\n pattern,\n \"-c:v\",\n \"libx264\",\n \"-preset\",\n \"slow\",\n \"-crf\",\n \"19\",\n \"-pix_fmt\",\n \"yuv420p\",\n \"-vf\",\n \"scale=trunc(iw/2)*2:trunc(ih/2)*2\",\n \"-movflags\",\n \"+faststart\",\n outFile,\n ]);\n });\n}\n\nfunction run(cmd: string, args: string[]): Promise<void> {\n return new Promise((resolve, reject) => {\n const child = spawn(cmd, args, { stdio: [\"ignore\", \"ignore\", \"pipe\"] });\n let err = \"\";\n child.stderr.on(\"data\", (c) => (err = (err + c.toString()).slice(-2000)));\n child.on(\"error\", reject);\n child.on(\"exit\", (code) =>\n code === 0 ? resolve() : reject(new Error(`${cmd} failed (${code}):\\n${err}`)),\n );\n });\n}\n","import { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { fileURLToPath } from \"node:url\";\nimport sharp from \"sharp\";\n\nexport interface LabelSpec {\n pass: \"before\" | \"after\" | \"single\";\n /** Branch this clip shows. */\n branch: string;\n /** The PR base / main branch. */\n baseBranch: string;\n /** Pre-formatted timestamp, e.g. \"2026-06-08 10:21\". */\n timestamp: string;\n /** Output clip width in px — the caption scales to it. */\n videoWidth: number;\n}\n\nconst BLURPLE = \"#635BFF\";\nconst GREEN = \"#2DA44E\";\nconst INK = \"#1F2328\";\nconst MUTED = \"#57606A\";\n\n/** Locate the bundled Inter font (dist/assets at runtime, repo assets in tests). */\nfunction fontFile(): string {\n const here = path.dirname(fileURLToPath(import.meta.url));\n const candidates = [\n path.resolve(here, \"../../assets/fonts/Inter.ttf\"), // dist/encode → dist/../assets? n/a\n path.resolve(here, \"../assets/fonts/Inter.ttf\"), // dist/encode → dist/assets\n path.resolve(here, \"../../../assets/fonts/Inter.ttf\"), // src/encode → repo/assets\n ];\n return candidates.find(existsSync) ?? candidates[candidates.length - 1]!;\n}\n\n/**\n * Silence fontconfig's \"Cannot load default config\" stderr noise by pointing\n * it at a generated minimal config that also exposes the bundled font dir.\n * Runs once, lazily, before the first text render.\n */\nlet fontconfigReady = false;\nfunction ensureFontconfig(): void {\n if (fontconfigReady || process.env.FONTCONFIG_FILE) {\n fontconfigReady = true;\n return;\n }\n try {\n const dir = path.join(tmpdir(), \"pr-preview-fontconfig\");\n mkdirSync(dir, { recursive: true });\n const conf = path.join(dir, \"fonts.conf\");\n writeFileSync(\n conf,\n `<?xml version=\"1.0\"?>\n<!DOCTYPE fontconfig SYSTEM \"fonts.dtd\">\n<fontconfig>\n <dir>${path.dirname(fontFile())}</dir>\n <cachedir>${path.join(dir, \"cache\")}</cachedir>\n</fontconfig>\n`,\n );\n process.env.FONTCONFIG_FILE = conf;\n } catch {\n /* the explicit fontfile still works without this — just noisier */\n }\n fontconfigReady = true;\n}\n\nfunction escapeMarkup(s: string): string {\n return s\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\n}\n\n/**\n * Render a polished caption pill (PNG with alpha) naming the branch, the base\n * it's compared against, and the timestamp — composited onto every output\n * frame so the clip is self-describing.\n */\nexport async function renderLabel(spec: LabelSpec): Promise<Buffer> {\n ensureFontconfig();\n\n const accent = spec.pass === \"after\" ? GREEN : BLURPLE;\n const branch = escapeMarkup(spec.branch);\n const base = escapeMarkup(spec.baseBranch);\n const ts = escapeMarkup(spec.timestamp);\n\n // One Pango-markup run: colored dot + (tag), bold branch, muted context.\n // Single clips drop the BEFORE/AFTER pill and the base comparison.\n const tagSpan =\n spec.pass === \"single\"\n ? `<span foreground=\"${accent}\" weight=\"700\">●</span>`\n : `<span foreground=\"${accent}\" weight=\"700\">● ${spec.pass === \"before\" ? \"BEFORE\" : \"AFTER\"}</span>`;\n const context =\n spec.pass === \"single\"\n ? `${ts}`\n : spec.pass === \"before\"\n ? `base branch · ${ts}`\n : `vs base ${base} · ${ts}`;\n\n const markup =\n `${tagSpan}` +\n ` <span foreground=\"${INK}\" weight=\"700\">${branch}</span>` +\n ` <span foreground=\"${MUTED}\">${context}</span>`;\n\n const pt = Math.round(Math.min(28, Math.max(15, spec.videoWidth * 0.0145)));\n const text = await sharp({\n text: { text: markup, fontfile: fontFile(), font: `sans ${pt}`, rgba: true, dpi: 72 },\n })\n .png()\n .toBuffer();\n const tm = await sharp(text).metadata();\n const tw = tm.width ?? 200;\n const th = tm.height ?? pt;\n\n const padX = Math.round(pt * 0.85);\n const padY = Math.round(pt * 0.6);\n const w = tw + padX * 2;\n const h = th + padY * 2;\n const r = Math.round(h / 2);\n\n // Rounded-rect background (shapes only — always renders, no font needed).\n const bg = Buffer.from(\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${w}\" height=\"${h}\">\n <rect x=\"0.5\" y=\"0.5\" width=\"${w - 1}\" height=\"${h - 1}\" rx=\"${r}\" ry=\"${r}\"\n fill=\"#FFFFFF\" fill-opacity=\"0.94\" stroke=\"#D0D7DE\" stroke-opacity=\"0.9\"/>\n </svg>`,\n );\n\n return sharp(bg)\n .composite([{ input: text, top: padY, left: padX }])\n .png()\n .toBuffer();\n}\n\n/** The pr-preview mark (clapperboard + </>) as an SVG string, for rasterizing. */\nconst LOGO_SVG = `<svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" xmlns=\"http://www.w3.org/2000/svg\">\n <defs><linearGradient id=\"g\" x1=\"6\" y1=\"4\" x2=\"58\" y2=\"60\" gradientUnits=\"userSpaceOnUse\">\n <stop stop-color=\"#635BFF\"/><stop offset=\"1\" stop-color=\"#4B45C6\"/></linearGradient></defs>\n <rect x=\"2\" y=\"2\" width=\"60\" height=\"60\" rx=\"15\" fill=\"url(#g)\"/>\n <g transform=\"rotate(-9 32 19)\"><rect x=\"13\" y=\"13.5\" width=\"38\" height=\"9.5\" rx=\"2.4\" fill=\"#fff\"/>\n <path d=\"M19 13.5 24 23M27 13.5 32 23M35 13.5 40 23M43 13.5 48 23\" stroke=\"#4B45C6\" stroke-width=\"2.3\" stroke-linecap=\"round\"/></g>\n <rect x=\"13\" y=\"26\" width=\"38\" height=\"24\" rx=\"4.5\" fill=\"#fff\"/>\n <g stroke=\"#4B45C6\" stroke-width=\"2.7\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\">\n <path d=\"M23 32 17.5 38 23 44\"/><path d=\"M41 32 46.5 38 41 44\"/><path d=\"M35.5 30.5 28.5 45.5\" stroke=\"#635BFF\"/></g>\n</svg>`;\n\n/**\n * A small bottom-right watermark: the pr-preview mark + the project URL,\n * burned into every clip.\n */\nexport async function renderWatermark(videoWidth: number): Promise<Buffer> {\n ensureFontconfig();\n const pt = Math.round(Math.min(22, Math.max(12, videoWidth * 0.0115)));\n const logoPx = Math.round(pt * 1.7);\n\n const logo = await sharp(Buffer.from(LOGO_SVG)).resize(logoPx, logoPx).png().toBuffer();\n const text = await sharp({\n text: {\n text: `<span foreground=\"${INK}\" weight=\"600\">pr-preview</span><span foreground=\"${MUTED}\">.com</span>`,\n fontfile: fontFile(),\n font: `sans ${pt}`,\n rgba: true,\n dpi: 72,\n },\n })\n .png()\n .toBuffer();\n const tm = await sharp(text).metadata();\n const tw = tm.width ?? 120;\n const th = tm.height ?? pt;\n\n const gap = Math.round(pt * 0.5);\n const padX = Math.round(pt * 0.75);\n const padY = Math.round(pt * 0.5);\n const h = Math.max(logoPx, th) + padY * 2;\n const w = padX * 2 + logoPx + gap + tw;\n const r = Math.round(h / 2);\n\n const bg = Buffer.from(\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${w}\" height=\"${h}\">\n <rect x=\"0.5\" y=\"0.5\" width=\"${w - 1}\" height=\"${h - 1}\" rx=\"${r}\" ry=\"${r}\"\n fill=\"#FFFFFF\" fill-opacity=\"0.94\" stroke=\"#D0D7DE\" stroke-opacity=\"0.9\"/>\n </svg>`,\n );\n\n return sharp(bg)\n .composite([\n { input: logo, top: Math.round((h - logoPx) / 2), left: padX },\n { input: text, top: Math.round((h - th) / 2), left: padX + logoPx + gap },\n ])\n .png()\n .toBuffer();\n}\n","import type { Frame, Page } from \"playwright\";\n\n/**\n * Resolve the target app's Frame inside the harness page. Frames are\n * recreated on full reloads (HMR, navigation) so always re-resolve rather\n * than caching the handle.\n */\nexport async function getAppFrame(\n page: Page,\n targetOrigin: string,\n timeoutMs = 15_000,\n): Promise<Frame> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n const frame = page.frames().find((f) => {\n try {\n return new URL(f.url()).origin === targetOrigin;\n } catch {\n return false;\n }\n });\n if (frame && !frame.isDetached()) return frame;\n await page.waitForTimeout(200);\n }\n throw new Error(`App iframe (${targetOrigin}) did not appear within ${timeoutMs}ms`);\n}\n\n/** Bounding box of the iframe element in page coordinates (CSS px). */\nexport async function getAppFrameRect(\n page: Page,\n): Promise<{ x: number; y: number; width: number; height: number }> {\n const box = await page.locator(\"#app-frame\").boundingBox();\n if (!box) throw new Error(\"App iframe element (#app-frame) not found in harness page\");\n return box;\n}\n","/**\n * Signal-safe teardown: register cleanups (dev servers, worktrees, browser)\n * and run them on normal exit, Ctrl-C, or crash — never leak a worktree.\n */\n\ntype Cleanup = () => Promise<void> | void;\n\nconst cleanups: Cleanup[] = [];\nlet installed = false;\nlet running = false;\n\nexport function registerCleanup(fn: Cleanup): () => void {\n install();\n cleanups.push(fn);\n return () => {\n const i = cleanups.indexOf(fn);\n if (i >= 0) cleanups.splice(i, 1);\n };\n}\n\nexport async function runCleanups(): Promise<void> {\n if (running) return;\n running = true;\n // LIFO: tear down in reverse acquisition order.\n for (const fn of [...cleanups].reverse()) {\n try {\n await fn();\n } catch {\n /* best effort */\n }\n }\n cleanups.length = 0;\n running = false;\n}\n\nfunction install(): void {\n if (installed) return;\n installed = true;\n for (const signal of [\"SIGINT\", \"SIGTERM\"] as const) {\n process.on(signal, () => {\n void runCleanups().then(() => process.exit(130));\n });\n }\n process.on(\"uncaughtException\", (err) => {\n console.error(err);\n void runCleanups().then(() => process.exit(1));\n });\n}\n","import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { z } from \"zod\";\nimport type { Journey, Step } from \"./types.js\";\n\nconst stepSchema: z.ZodType<Step> = z.object({\n id: z.string(),\n type: z.enum([\"click\", \"fill\", \"select\", \"press\", \"scroll\", \"navigate\", \"wait\"]),\n selectors: z.array(z.string()).optional(),\n coordinates: z.object({ xNorm: z.number(), yNorm: z.number() }).optional(),\n frameUrl: z.string().optional(),\n value: z.string().optional(),\n key: z.string().optional(),\n scrollTarget: z.string().optional(),\n scroll: z.object({ xNorm: z.number(), yNorm: z.number() }).optional(),\n url: z.string().optional(),\n causesNavigation: z.boolean().optional(),\n label: z.string().optional(),\n timestamp: z.number(),\n thumbnail: z.string().optional(),\n});\n\nconst journeySchema: z.ZodType<Journey> = z.object({\n version: z.literal(1),\n createdAt: z.string(),\n baseRef: z.string().optional(),\n viewport: z.object({\n width: z.number(),\n height: z.number(),\n deviceScaleFactor: z.number(),\n }),\n startUrl: z.string(),\n steps: z.array(stepSchema),\n});\n\nexport async function loadJourney(file: string): Promise<Journey> {\n const raw = JSON.parse(await readFile(file, \"utf8\"));\n const parsed = journeySchema.safeParse(raw);\n if (!parsed.success) {\n throw new Error(`Invalid journey file ${file}: ${parsed.error.issues[0]?.message}`);\n }\n return parsed.data;\n}\n\n/** Persist a journey — sidebar thumbnails never hit disk. */\nexport async function saveJourney(file: string, journey: Journey): Promise<void> {\n const clean: Journey = {\n ...journey,\n steps: journey.steps.map((s) => {\n const { thumbnail: _thumbnail, ...rest } = s;\n return rest;\n }),\n };\n await mkdir(path.dirname(file), { recursive: true });\n await writeFile(file, JSON.stringify(clean, null, 2) + \"\\n\");\n}\n","import { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport pc from \"picocolors\";\nimport { loadConfig, ConfigError } from \"../../config/load.js\";\nimport { configSchema, type Config } from \"../../config/schema.js\";\nimport { detectBase } from \"../../git/base.js\";\nimport { tryGit, GitError } from \"../../git/exec.js\";\nimport { createBaseWorktree } from \"../../git/worktree.js\";\nimport { detectPackageManager, installDependencies } from \"../../server/pkgManager.js\";\nimport { bootstrap } from \"../../session/bootstrap.js\";\nimport type { Session } from \"../../session/session.js\";\nimport { saveJourney } from \"../../recorder/journey.js\";\nimport type { Step } from \"../../recorder/types.js\";\nimport { log } from \"../ui/logger.js\";\nimport { revealFiles } from \"../util/openPath.js\";\nimport { registerCleanup, runCleanups } from \"../cleanup.js\";\n\n/** Encode the recorded footage for one pass into its clip. */\nfunction captureClip(\n session: Session,\n which: \"before\" | \"after\",\n outBase: string,\n label: { branch: string; baseBranch: string; timestamp: string },\n) {\n return session.encodeClip(which, outBase, label);\n}\n\n/** Short, human \"YYYY-MM-DD HH:MM\" for the burned-in caption. */\nfunction stamp(): string {\n const d = new Date();\n const p = (n: number) => String(n).padStart(2, \"0\");\n return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;\n}\n\n/** Trim noisy ref prefixes for the on-clip label (origin/main → main). */\nfunction shortRef(ref: string): string {\n return ref.replace(/^origin\\//, \"\");\n}\n\n/** Filesystem-safe slug for a branch name (feature/demo → feature-demo). */\nfunction slug(ref: string): string {\n return shortRef(ref).replace(/[^a-zA-Z0-9._-]+/g, \"-\").replace(/^-+|-+$/g, \"\");\n}\n\n/** Compact timestamp for filenames, e.g. 20260608-1108. */\nfunction fileStamp(): string {\n const d = new Date();\n const p = (n: number) => String(n).padStart(2, \"0\");\n return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}`;\n}\n\nexport interface RunOptions {\n base?: string;\n keepWorktree?: boolean;\n /** Use an already-running app at this URL instead of a managed dev server. */\n url?: string;\n /** Record a single standalone clip (no before/after). Forces passes = 1. */\n single?: boolean;\n}\n\n/** Current branch name (slugged), or a fallback when not in a git repo. */\nasync function branchName(repoRoot: string, fallback: string): Promise<string> {\n const ref = await tryGit(repoRoot, [\"rev-parse\", \"--abbrev-ref\", \"HEAD\"]);\n return ref ? shortRef(ref) : fallback;\n}\n\n/**\n * The product flow — each pass is recorded on its own version, because the\n * before and after UIs may share nothing:\n *\n * BEFORE (base worktree): user records → Confirm → replay+capture → before.gif\n * AFTER (working tree): user records again (or loads the BEFORE steps as a\n * starting point) → Save → replay+capture → after.gif\n */\nexport async function runCommand(repoRoot: string, opts: RunOptions): Promise<void> {\n let config: Config;\n const urlFlag = opts.url; // may also come from config.externalUrl below\n try {\n config = (await loadConfig(repoRoot)).config;\n } catch (err) {\n // In --url mode the dev command/url are unused, so a config file is optional.\n if (urlFlag && err instanceof ConfigError) {\n config = configSchema.parse({ devCommand: \"external\", url: urlFlag });\n } else {\n throw err;\n }\n }\n\n // CLI flags override config; otherwise fall back to config defaults so a\n // project can be set up once and run with just `pr-preview run` (handy for\n // CI and scripting).\n opts = {\n url: opts.url ?? config.externalUrl,\n base: opts.base ?? config.baseBranch,\n keepWorktree: opts.keepWorktree ?? config.keepWorktree,\n single: opts.single,\n };\n const outDir = path.resolve(repoRoot, config.output);\n const passes: 1 | 2 = opts.single ? 1 : config.passes;\n\n if (passes === 1) return runSingleClip(repoRoot, config, outDir, opts);\n if (opts.url) return runWithExternalApp(repoRoot, config, outDir, opts);\n\n try {\n // ---- 1. base worktree ---------------------------------------------------\n // Pre-flight: a before/after run needs a named branch to compare against\n // its base. (detectBase covers \"no repo\" / \"no commits\" / \"no base\".)\n const currentBranch = await tryGit(repoRoot, [\"symbolic-ref\", \"--quiet\", \"--short\", \"HEAD\"]);\n if (!currentBranch) {\n throw new GitError(\n \"You're not on a branch (detached HEAD).\\n\\n\" +\n \"`pr-preview run` records the BEFORE on your PR's base branch and the AFTER on your\\n\" +\n \"current branch — so it needs you to be on a branch. Check yours out first:\\n\" +\n \" git switch <your-branch>\\n\\n\" +\n \"…or skip the comparison and just record the current app:\\n\" +\n \" pr-preview run --single\",\n );\n }\n\n const base = await detectBase(repoRoot, opts.base ?? config.baseBranch);\n log.info(`PR base: ${pc.bold(base.ref)} (${base.sha.slice(0, 10)}, via ${base.source})`);\n\n // The AFTER pass records your current working tree, so flag uncommitted work.\n if (await tryGit(repoRoot, [\"status\", \"--porcelain\"])) {\n log.info(\"Heads up: the AFTER clip captures your working tree, including uncommitted changes.\");\n }\n\n const worktree = await createBaseWorktree(repoRoot, base.sha);\n if (!opts.keepWorktree) registerCleanup(() => worktree.remove());\n log.success(`Base worktree at ${path.relative(repoRoot, worktree.dir)}`);\n\n if (config.install !== \"never\") {\n const appDir = path.join(worktree.dir, config.cwd);\n const pm = detectPackageManager(appDir);\n const hasModules = existsSync(path.join(appDir, \"node_modules\"));\n if (config.install === \"always\" || !hasModules) {\n const spinner = log.spinner(`Installing dependencies in worktree (${pm}) …`);\n try {\n await installDependencies(appDir, pm);\n spinner.succeed(\"Worktree dependencies installed\");\n } catch (err) {\n spinner.fail(\"Install failed\");\n throw err;\n }\n }\n }\n\n // ---- 2. BEFORE pass ------------------------------------------------------\n const boot = await bootstrap(repoRoot, config, \"run\");\n boot.session.setPasses(2);\n boot.session.setBranches(base.ref, currentBranch);\n\n const beforeServer = await boot.startApp(\"before\", worktree.dir);\n await boot.openBrowser(\"before\");\n log.info(`Harness open at ${pc.bold(boot.harness.url)}`);\n\n log.step(`Record the journey on BEFORE (${base.ref}), then Confirm in the sidebar.`);\n\n await boot.session.promptResetChoice(); // reset/keep the app, then record\n const beforeSteps: Step[] = await boot.session.recordUntilConfirmed();\n if (beforeSteps.length === 0) throw new Error(\"No steps recorded for BEFORE — aborting.\");\n await saveJourneyFile(repoRoot, \"journey-before.json\", config, boot.session.startUrl, beforeSteps, base.sha);\n\n const timestamp = stamp();\n const fstamp = fileStamp();\n const baseLabel = shortRef(base.ref);\n const headLabel = shortRef(currentBranch);\n\n log.info(\"Capturing BEFORE …\");\n const before = await captureClip(boot.session,\n \"before\",\n path.join(outDir, `before-${slug(base.ref)}-${fstamp}`),\n { branch: baseLabel, baseBranch: baseLabel, timestamp },\n );\n if (before.fellBackToGif) {\n log.warn(\"ffmpeg not found — produced a GIF instead of MP4 (brew install ffmpeg).\");\n }\n log.success(`${before.paths.map((p) => path.basename(p)).join(\" + \")} (${before.frameCount} frames)`);\n\n // ---- 3. AFTER pass --------------------------------------------------------\n await beforeServer.stop();\n await boot.startApp(\"after\", repoRoot);\n boot.session.switchPass(\"after\"); // stores BEFORE steps as an optional template\n await boot.switchApp(\"after\");\n boot.session.setPhase(\"idle\"); // back to interactive — AFTER recording starts\n\n log.step(\n `Now record the journey on AFTER (${currentBranch}) — the UI may differ. ` +\n `Record fresh or load the BEFORE steps, then Save.`,\n );\n await boot.session.promptResetChoice(); // reset/keep the app, then record\n const afterSteps: Step[] = await boot.session.recordUntilConfirmed();\n if (afterSteps.length === 0) throw new Error(\"No steps recorded for AFTER — aborting.\");\n await saveJourneyFile(repoRoot, \"journey-after.json\", config, boot.session.startUrl, afterSteps);\n\n log.info(\"Capturing AFTER …\");\n const after = await captureClip(boot.session,\n \"after\",\n path.join(outDir, `after-${slug(currentBranch)}-${fstamp}`),\n { branch: headLabel, baseBranch: baseLabel, timestamp },\n );\n log.success(`${after.paths.map((p) => path.basename(p)).join(\" + \")} (${after.frameCount} frames)`);\n\n boot.session.setPhase(\"done\");\n boot.harness.bus.send({\n type: \"DONE\",\n outputs: { before: before.paths[0], after: after.paths[0] },\n });\n\n console.log();\n log.success(pc.bold(\"Done — drag these into your PR description:\"));\n for (const p of [...before.paths, ...after.paths]) {\n log.step(path.relative(repoRoot, p));\n }\n // Reveal the produced clips in the file manager (selected).\n revealFiles([...before.paths, ...after.paths], outDir);\n } finally {\n await runCleanups();\n }\n}\n\n/**\n * Single-recording flow (`--single` / `passes: 1`): record ONE journey on the\n * current app and save one clip — no before/after, no base worktree. Works with\n * a managed dev server (current working tree) or your own running app (`--url`).\n */\nasync function runSingleClip(\n repoRoot: string,\n config: Config,\n outDir: string,\n opts: RunOptions,\n): Promise<void> {\n try {\n const boot = opts.url\n ? await bootstrap(repoRoot, config, \"run\", { fixedUrl: opts.url })\n : await bootstrap(repoRoot, config, \"run\");\n boot.session.setPasses(1); // single-clip wizard (no before/after tabs)\n const branch = await branchName(repoRoot, \"app\");\n\n let server;\n if (opts.url) {\n log.info(`Using your running app at ${pc.bold(opts.url)} (single clip).`);\n } else {\n // No base worktree — just run the current project's dev server.\n server = await boot.startApp(\"before\", repoRoot);\n }\n await boot.openBrowser(\"before\");\n log.info(`Harness open at ${pc.bold(boot.harness.url)}`);\n\n log.step(`Record the journey, then Confirm to save the clip.`);\n\n await boot.session.promptResetChoice();\n const steps: Step[] = await boot.session.recordUntilConfirmed();\n if (steps.length === 0) throw new Error(\"No steps recorded — aborting.\");\n await saveJourneyFile(repoRoot, \"journey.json\", config, boot.session.startUrl, steps);\n\n const timestamp = stamp();\n log.info(\"Capturing the clip …\");\n const clip = await captureClip(\n boot.session,\n \"before\",\n path.join(outDir, `${slug(branch)}-${fileStamp()}`),\n { branch, baseBranch: branch, timestamp },\n );\n if (clip.fellBackToGif) {\n log.warn(\"ffmpeg not found — produced a GIF instead of MP4 (brew install ffmpeg).\");\n }\n await server?.stop();\n\n boot.session.setPhase(\"done\");\n boot.harness.bus.send({ type: \"DONE\", outputs: { before: clip.paths[0] } });\n console.log();\n log.success(pc.bold(\"Done — your clip:\"));\n for (const p of clip.paths) log.step(path.relative(repoRoot, p));\n revealFiles(clip.paths, outDir);\n } finally {\n await runCleanups();\n }\n}\n\n/**\n * `--url` flow: the user runs the app themselves. We record BEFORE on their\n * running app, pause while they switch branches + restart it on the same URL,\n * then record AFTER. No git worktree, no managed dev server.\n */\nasync function runWithExternalApp(\n repoRoot: string,\n config: Config,\n outDir: string,\n opts: RunOptions,\n): Promise<void> {\n log.info(`Using your running app at ${pc.bold(opts.url!)} (no dev server managed).`);\n try {\n const boot = await bootstrap(repoRoot, config, \"run\", { fixedUrl: opts.url });\n boot.session.setPasses(2);\n const beforeBranch = await branchName(repoRoot, \"before\");\n boot.session.setBranches(beforeBranch, \"your PR branch\");\n await boot.openBrowser(\"before\");\n log.info(`Harness open at ${pc.bold(boot.harness.url)}`);\n\n log.step(`Record the journey on your app (currently ${beforeBranch}), then Confirm.`);\n await boot.session.promptResetChoice(); // reset/keep the app, then record\n const beforeSteps: Step[] = await boot.session.recordUntilConfirmed();\n if (beforeSteps.length === 0) throw new Error(\"No steps recorded for BEFORE — aborting.\");\n\n const timestamp = stamp();\n const fstamp = fileStamp();\n await saveJourneyFile(repoRoot, \"journey-before.json\", config, boot.session.startUrl, beforeSteps);\n\n log.info(\"Capturing BEFORE …\");\n const before = await captureClip(boot.session,\n \"before\",\n path.join(outDir, `before-${slug(beforeBranch)}-${fstamp}`),\n { branch: beforeBranch, baseBranch: beforeBranch, timestamp },\n );\n log.success(`${before.paths.map((p) => path.basename(p)).join(\" + \")} (${before.frameCount} frames)`);\n\n // Hand off: the user switches their app to the PR branch and restarts it.\n log.info(pc.bold(\"→ Switch your app to the PR branch and restart it on the same URL, then Continue in the harness.\"));\n boot.harness.bus.send({\n type: \"MANUAL_PAUSE\",\n stepId: null,\n kind: \"generic\",\n label: `Switch your app to your PR branch and restart it on ${opts.url}, then click Continue.`,\n });\n await boot.harness.bus.waitFor(\"CONTINUE\");\n\n const afterBranch = await branchName(repoRoot, \"after\");\n boot.session.switchPass(\"after\"); // stashes BEFORE steps as a template\n boot.session.setBranches(beforeBranch, afterBranch);\n await boot.switchApp(\"after\"); // reload the same URL → now the PR-branch app\n boot.session.setPhase(\"idle\");\n\n log.step(`Now record the journey on AFTER (${afterBranch}) — record fresh or load the BEFORE steps, then Save.`);\n await boot.session.promptResetChoice(); // reset/keep the app, then record\n const afterSteps: Step[] = await boot.session.recordUntilConfirmed();\n if (afterSteps.length === 0) throw new Error(\"No steps recorded for AFTER — aborting.\");\n await saveJourneyFile(repoRoot, \"journey-after.json\", config, boot.session.startUrl, afterSteps);\n\n log.info(\"Capturing AFTER …\");\n const after = await captureClip(boot.session,\n \"after\",\n path.join(outDir, `after-${slug(afterBranch)}-${fstamp}`),\n { branch: afterBranch, baseBranch: beforeBranch, timestamp },\n );\n log.success(`${after.paths.map((p) => path.basename(p)).join(\" + \")} (${after.frameCount} frames)`);\n\n boot.session.setPhase(\"done\");\n boot.harness.bus.send({ type: \"DONE\", outputs: { before: before.paths[0], after: after.paths[0] } });\n console.log();\n log.success(pc.bold(\"Done — drag these into your PR description:\"));\n for (const p of [...before.paths, ...after.paths]) log.step(path.relative(repoRoot, p));\n revealFiles([...before.paths, ...after.paths], outDir);\n } finally {\n await runCleanups();\n }\n}\n\nasync function saveJourneyFile(\n repoRoot: string,\n name: string,\n config: { viewport: { width: number; height: number } },\n startUrl: string,\n steps: Step[],\n baseRef?: string,\n): Promise<void> {\n await saveJourney(path.join(repoRoot, \".pr-preview\", name), {\n version: 1,\n createdAt: new Date().toISOString(),\n baseRef,\n viewport: { ...config.viewport, deviceScaleFactor: 2 },\n startUrl,\n steps,\n });\n}\n","import { execFile } from \"node:child_process\";\n\nexport class GitError extends Error {}\n\n/** Run a git command in dir and return trimmed stdout; throws GitError on failure. */\nexport function git(dir: string, args: string[]): Promise<string> {\n return new Promise((resolve, reject) => {\n execFile(\"git\", args, { cwd: dir, maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {\n if (err) reject(new GitError(`git ${args.join(\" \")} failed: ${stderr || err.message}`));\n else resolve(stdout.trim());\n });\n });\n}\n\n/** Like git() but null on failure — for probes. */\nexport async function tryGit(dir: string, args: string[]): Promise<string | null> {\n try {\n return await git(dir, args);\n } catch {\n return null;\n }\n}\n\n/** Run `gh` and return trimmed stdout, or null if unavailable/failed. */\nexport function tryGh(dir: string, args: string[]): Promise<string | null> {\n return new Promise((resolve) => {\n execFile(\"gh\", args, { cwd: dir }, (err, stdout) => {\n resolve(err ? null : stdout.trim());\n });\n });\n}\n","import { git, tryGit, tryGh, GitError } from \"./exec.js\";\n\nexport interface BaseInfo {\n /** Commit sha the \"before\" worktree is created at (merge-base). */\n sha: string;\n /** Where the base came from, for logging. */\n source: \"gh-pr\" | \"config\" | \"merge-base\";\n ref: string;\n}\n\n/**\n * Determine the PR base commit:\n * 1. explicit config override\n * 2. `gh pr view` if the branch has an open PR\n * 3. merge-base with origin/HEAD → main → master → develop\n */\nexport async function detectBase(repoRoot: string, override?: string): Promise<BaseInfo> {\n // Friendly pre-flight: a git repo with no commits / no HEAD can't be diffed.\n if (!(await tryGit(repoRoot, [\"rev-parse\", \"--is-inside-work-tree\"]))) {\n throw new GitError(\n `Not inside a git repository (${repoRoot}). pr-preview run needs a git repo with a ` +\n `committed base branch — cd into your project and try again.`,\n );\n }\n if (!(await tryGit(repoRoot, [\"rev-parse\", \"--verify\", \"HEAD\"]))) {\n throw new GitError(\n `This git repository has no commits yet, so there is no base to compare against. ` +\n `Commit your work and run pr-preview on a feature branch (or pass --base <ref>).`,\n );\n }\n\n if (override) {\n const sha = await git(repoRoot, [\"merge-base\", \"HEAD\", override]);\n return { sha, source: \"config\", ref: override };\n }\n\n const ghBase = await tryGh(repoRoot, [\"pr\", \"view\", \"--json\", \"baseRefName\", \"-q\", \".baseRefName\"]);\n if (ghBase) {\n const ref = (await tryGit(repoRoot, [\"rev-parse\", \"--verify\", `origin/${ghBase}`]))\n ? `origin/${ghBase}`\n : ghBase;\n const sha = await tryGit(repoRoot, [\"merge-base\", \"HEAD\", ref]);\n if (sha) return { sha, source: \"gh-pr\", ref };\n }\n\n const candidates: string[] = [];\n const originHead = await tryGit(repoRoot, [\"symbolic-ref\", \"refs/remotes/origin/HEAD\"]);\n if (originHead) candidates.push(originHead.replace(\"refs/remotes/\", \"\"));\n candidates.push(\"origin/main\", \"origin/master\", \"main\", \"master\", \"develop\");\n\n const current = await git(repoRoot, [\"rev-parse\", \"HEAD\"]);\n for (const ref of candidates) {\n if (!(await tryGit(repoRoot, [\"rev-parse\", \"--verify\", `${ref}^{commit}`]))) continue;\n const sha = await tryGit(repoRoot, [\"merge-base\", \"HEAD\", ref]);\n // A base equal to HEAD means we're ON the default branch — keep looking.\n if (sha && sha !== current) return { sha, source: \"merge-base\", ref };\n if (sha && sha === current) {\n throw new GitError(\n `Your branch has no commits beyond ${ref}, so there's no \"before\" to compare against.\\n\\n` +\n ` • If your changes aren't committed yet, commit them — then ${ref} becomes the \"before\"\\n` +\n ` and your commit the \"after\".\\n` +\n ` • Or switch to your PR branch, or pass --base <ref> to choose a different base.\\n` +\n ` • Or skip the comparison and record a single clip: pr-preview run --single`,\n );\n }\n }\n throw new GitError(\n \"Could not detect the PR base. Pass --base <ref> or set baseBranch in pr-preview.config.\",\n );\n}\n","import { existsSync } from \"node:fs\";\nimport { rm } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { git, tryGit, GitError } from \"./exec.js\";\n\nconst WORKTREE_DIR = \".pr-preview/worktrees\";\n\n/**\n * git crashing with a signal (SIGBUS/SIGSEGV → \"died of signal\", exit 138)\n * while reading objects almost always means the repo's object files are\n * iCloud-evicted \"dataless\" placeholders (repo lives in ~/Documents). Turn\n * the raw crash into an actionable message.\n */\nfunction explainGitCrash(err: unknown, repoRoot: string): never {\n const msg = err instanceof Error ? err.message : String(err);\n if (/died of signal|signal 1[01]\\b|SIGBUS|SIGSEGV/i.test(msg)) {\n const inICloud = /\\/(Documents|Desktop)\\//.test(repoRoot);\n throw new GitError(\n `git crashed creating the base worktree (it died of a signal). This usually means ` +\n `the repository's git objects are iCloud-evicted \"dataless\" placeholders` +\n (inICloud ? \" — and this repo is under ~/Documents, which iCloud syncs.\\n\\n\" : \".\\n\\n\") +\n `Fixes:\\n` +\n ` • Move the repo out of iCloud (e.g. ~/dev), or disable Desktop & Documents sync, then retry.\\n` +\n ` • Or force-download the objects now: find .git -type f -exec cat {} + >/dev/null\\n` +\n `\\nOriginal error: ${msg}`,\n );\n }\n throw err;\n}\n\nexport interface WorktreeHandle {\n dir: string;\n remove(): Promise<void>;\n}\n\n/** Create a detached worktree at the given sha for the \"before\" app. */\nexport async function createBaseWorktree(repoRoot: string, sha: string): Promise<WorktreeHandle> {\n const dir = path.join(repoRoot, WORKTREE_DIR, `base-${sha.slice(0, 12)}`);\n\n // Reuse an intact worktree from a previous run (skips reinstall).\n if (existsSync(dir) && (await tryGit(dir, [\"rev-parse\", \"HEAD\"])) === sha) {\n return { dir, remove: () => removeWorktree(repoRoot, dir) };\n }\n\n await pruneStaleWorktrees(repoRoot);\n if (existsSync(dir)) await rm(dir, { recursive: true, force: true });\n try {\n await git(repoRoot, [\"worktree\", \"add\", \"--detach\", dir, sha]);\n } catch (err) {\n // A half-created worktree leaves a registration behind — clean it up.\n await removeWorktree(repoRoot, dir).catch(() => {});\n explainGitCrash(err, repoRoot);\n }\n return { dir, remove: () => removeWorktree(repoRoot, dir) };\n}\n\nexport async function removeWorktree(repoRoot: string, dir: string): Promise<void> {\n await tryGit(repoRoot, [\"worktree\", \"remove\", \"--force\", dir]);\n if (existsSync(dir)) await rm(dir, { recursive: true, force: true });\n await tryGit(repoRoot, [\"worktree\", \"prune\"]);\n}\n\nexport async function pruneStaleWorktrees(repoRoot: string): Promise<void> {\n await tryGit(repoRoot, [\"worktree\", \"prune\"]);\n}\n","import { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { spawn } from \"node:child_process\";\n\nexport type PackageManager = \"pnpm\" | \"yarn\" | \"bun\" | \"npm\";\n\nexport function detectPackageManager(dir: string): PackageManager {\n if (existsSync(path.join(dir, \"pnpm-lock.yaml\"))) return \"pnpm\";\n if (existsSync(path.join(dir, \"yarn.lock\"))) return \"yarn\";\n if (existsSync(path.join(dir, \"bun.lockb\")) || existsSync(path.join(dir, \"bun.lock\"))) {\n return \"bun\";\n }\n return \"npm\";\n}\n\n/** Run `<pm> install` in dir; resolves on exit 0, rejects otherwise. */\nexport function installDependencies(dir: string, pm: PackageManager): Promise<void> {\n return new Promise((resolve, reject) => {\n const child = spawn(pm, [\"install\"], {\n cwd: dir,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n env: process.env,\n });\n let tail = \"\";\n const keepTail = (chunk: Buffer) => {\n tail = (tail + chunk.toString()).slice(-2000);\n };\n child.stdout.on(\"data\", keepTail);\n child.stderr.on(\"data\", keepTail);\n child.on(\"error\", reject);\n child.on(\"exit\", (code) => {\n if (code === 0) resolve();\n else reject(new Error(`${pm} install failed (exit ${code}):\\n${tail}`));\n });\n });\n}\n","import { spawn } from \"node:child_process\";\n\n/**\n * Reveal a path in the OS file manager (Finder / Explorer / default handler).\n * Best-effort and non-blocking — never throws, and stays out of the way in\n * headless/CI environments where there's no desktop to open.\n */\nexport function openPath(target: string): void {\n if (process.env.PR_PREVIEW_HEADLESS === \"1\" || process.env.CI) return;\n\n const [cmd, args] =\n process.platform === \"darwin\"\n ? ([\"open\", [target]] as const)\n : process.platform === \"win32\"\n ? ([\"cmd\", [\"/c\", \"start\", \"\", target]] as const)\n : ([\"xdg-open\", [target]] as const);\n\n try {\n const child = spawn(cmd, [...args], { detached: true, stdio: \"ignore\" });\n child.on(\"error\", () => {});\n child.unref();\n } catch {\n /* opening the folder is a nicety, never a failure */\n }\n}\n\n/**\n * Reveal the produced files in the file manager. On macOS this opens the\n * folder with the files selected; elsewhere it just opens the folder.\n */\nexport function revealFiles(files: string[], dir: string): void {\n if (process.env.PR_PREVIEW_HEADLESS === \"1\" || process.env.CI) return;\n if (process.platform === \"darwin\" && files.length > 0) {\n try {\n const child = spawn(\"open\", [\"-R\", ...files], { detached: true, stdio: \"ignore\" });\n child.on(\"error\", () => openPath(dir));\n child.unref();\n return;\n } catch {\n /* fall through to opening the folder */\n }\n }\n openPath(dir);\n}\n"],"mappings":"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","names":["path","pc","existsSync","readFile","path","path","existsSync","readFile","path","path","readFile","existsSync","path","path","existsSync","readFile","readFile","existsSync","path","fileURLToPath","sharp","sharp","mkdir","writeFile","path","sharp","spawn","writeFile","path","existsSync","mkdirSync","path","tmpdir","fileURLToPath","quantize","sharp","mkdir","path","writeFile","sharp","sharp","path","readFile","writeFile","mkdir","path","z","mkdir","path","writeFile","path","pc","existsSync","path","pc","execFile","existsSync","rm","path","path","existsSync","rm","existsSync","path","spawn","spawn","path","pc","existsSync"]}
|
package/package.json
CHANGED