@qwertybit/pr-preview 0.1.2 → 0.1.4

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.
@@ -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 { 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, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\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"]}
1
+ {"version":3,"sources":["../../src/cli/index.ts","../../src/cli/commands/init.ts","../../src/cli/ui/logger.ts","../../src/mcp/server.ts","../../src/mcp/recorder.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/mcp/motion.ts","../../src/git/exec.ts","../../src/git/base.ts","../../src/git/worktree.ts","../../src/server/pkgManager.ts","../../src/mcp/publish.ts","../../src/mcp/detect.ts","../../src/cli/commands/mcp.ts","../../src/cli/commands/record.ts","../../src/recorder/journey.ts","../../src/cli/commands/run.ts","../../src/cli/util/openPath.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { Command } from \"commander\";\nimport { initCommand } from \"./commands/init.js\";\nimport { mcpCommand } from \"./commands/mcp.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\nprogram\n .command(\"mcp\")\n .description(\n \"Run the Model Context Protocol server (stdio) so Claude Code can drive a recording from a prompt\",\n )\n .action(() => wrap(() => mcpCommand(process.cwd())));\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, mkdir } 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\n/**\n * Register the MCP server in .mcp.json so Claude Code can drive recordings from\n * a prompt. Merges into an existing file rather than clobbering other servers.\n */\nasync function writeMcpConfig(root: string): Promise<void> {\n const mcpPath = path.join(root, \".mcp.json\");\n const entry = { command: \"npx\", args: [\"pr-preview\", \"mcp\"] };\n\n let doc: { mcpServers?: Record<string, unknown> } = {};\n if (existsSync(mcpPath)) {\n try {\n doc = JSON.parse(await readFile(mcpPath, \"utf8\")) as typeof doc;\n } catch {\n log.warn(\".mcp.json exists but isn't valid JSON — leaving it untouched.\");\n return;\n }\n }\n doc.mcpServers ??= {};\n if (doc.mcpServers[\"pr-preview\"]) {\n const overwrite = await log.confirm(\n \".mcp.json already has a pr-preview server. Overwrite it?\",\n );\n if (!overwrite) {\n log.warn(\"Left the existing pr-preview MCP server untouched.\");\n return;\n }\n }\n doc.mcpServers[\"pr-preview\"] = entry;\n await writeFile(mcpPath, JSON.stringify(doc, null, 2) + \"\\n\");\n log.success(`${existsSync(mcpPath) ? \"Updated\" : \"Created\"} .mcp.json (pr-preview MCP server for Claude Code)`);\n}\n\nconst RECORD_SKILL = `---\nname: record\ndescription: Record a video of a user journey through a web app, automatically, using PR Preview (@qwertybit/pr-preview). Claude drives the app itself and produces an MP4 — no human clicking. Usage: /record [url] [journey in plain English]. Use when the user wants to record, film, or capture a walkthrough or before/after video of an app for a pull request or demo.\n---\n\n# /record — record an app journey automatically with PR Preview\n\nYou record a real video walkthrough of a web app by **driving it yourself** through the\nPR Preview MCP server — not by asking the user to click. PR Preview opens its harness (a\nvisible Chrome window with the app in an iframe and a step sidebar); you perform the\njourney through its tools and it captures a clean, PR-ready MP4.\n\n## Arguments\n\\`/record [url] [journey]\\`\n- **url** (optional): the app to record, e.g. \\`http://localhost:3000\\` or \\`https://staging.example.com\\`. The first URL-looking token is the URL; the rest is the journey.\n- **journey** (optional): what to do, in plain English (e.g. \"add 3 books to the cart, then go to checkout\").\n\n## Preconditions\nThe PR Preview MCP tools must be connected: \\`start_recording\\`, \\`snapshot\\`, \\`act\\`,\n\\`next_pass\\`, \\`finish_recording\\`, \\`open_pr\\`, \\`detect_localhost\\`.\n- If they are NOT available, tell the user to install and connect it:\n \\`npm i -D @qwertybit/pr-preview\\` → \\`npx pr-preview init\\` (writes \\`.mcp.json\\`) → reload Claude Code.\n Stop until it's connected.\n\n## Steps\n1. **Resolve the URL.** If a URL was given, use it. Otherwise call \\`detect_localhost\\`; if apps\n are running, ask the user which to record; if none are, ask for a local, staging, or\n production URL. **Never guess.**\n2. **Start.** Call \\`start_recording\\` with \\`{ url }\\`. A Chrome window opens with the harness —\n the app runs in the iframe and the sidebar records each step. Do NOT click \"Start recording\";\n \\`start_recording\\` already began it. (For a base-vs-branch PR comparison, use\n \\`{ mode: \"before-after\" }\\` with a managed dev server instead.)\n3. **Drive the journey.** Read the returned accessibility snapshot. For each step in the journey,\n call \\`act\\` (\\`click\\` / \\`fill\\` / \\`press\\` / \\`hover\\` / \\`navigate\\` / \\`scroll\\`), targeting elements\n by their \\`[ref=…]\\` handle. Take a fresh snapshot after any step that changes the page. Perform\n the journey faithfully — **you drive it; never ask the user to click.**\n4. **Finish.** Call \\`finish_recording\\` and report the output MP4 path.\n5. **PR (optional).** If the user asked, call \\`open_pr\\` with the produced file(s).\n\n## Important\n- This is agent-driven but a REAL capture of the real app — nothing is synthesized.\n- Do NOT use the \\`pr-preview run\\` CLI for this — that is the manual, human-in-the-loop flow that\n waits for a person to click. Always drive via the MCP tools above so recording is automatic.\n`;\n\n/**\n * Install the /record slash command so users can run \"/record <url> <journey>\"\n * right after init. Non-destructive: skips if a skill already exists there.\n */\nasync function writeRecordSkill(root: string): Promise<void> {\n const skillDir = path.join(root, \".claude\", \"skills\", \"record\");\n const skillPath = path.join(skillDir, \"SKILL.md\");\n if (existsSync(skillPath)) {\n const overwrite = await log.confirm(\n \".claude/skills/record/SKILL.md already exists. Overwrite it?\",\n );\n if (!overwrite) {\n log.warn(\"Left the existing /record skill untouched.\");\n return;\n }\n }\n await mkdir(skillDir, { recursive: true });\n await writeFile(skillPath, RECORD_SKILL);\n log.success(\"Installed the /record slash command (.claude/skills/record/SKILL.md)\");\n}\n\nexport async function initCommand(root: string): Promise<void> {\n const configPath = path.join(root, \"pr-preview.config.js\");\n const existingConfig = [\".js\", \".ts\", \".json\"]\n .map((ext) => path.join(root, `pr-preview.config${ext}`))\n .find(existsSync);\n if (existingConfig) {\n const overwrite = await log.confirm(\n `A pr-preview config already exists (${path.basename(existingConfig)}). Overwrite it?`,\n );\n if (overwrite) {\n await writeFile(existingConfig, CONFIG_TEMPLATE);\n log.success(`Overwrote ${path.basename(existingConfig)}`);\n } else {\n log.warn(\"Left the existing pr-preview config untouched.\");\n }\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 await writeMcpConfig(root);\n await writeRecordSkill(root);\n\n log.info(\"Edit the config to match your dev command, then run `pr-preview run` on a PR branch.\");\n log.info(\"Or ask Claude Code to record a flow for you (e.g. “record my add-to-cart flow”),\");\n log.info(\"or run the /record slash command: `/record localhost:3000 add a book, then checkout`.\");\n}\n","import pc from \"picocolors\";\nimport ora, { type Ora } from \"ora\";\nimport { createInterface } from \"node:readline/promises\";\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 * Ask a yes/no question. Defaults to \"no\". In a non-interactive shell\n * (no TTY, e.g. CI) it resolves to `false` without blocking.\n */\n async confirm(question: string): Promise<boolean> {\n if (!process.stdin.isTTY) return false;\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n try {\n const answer = await rl.question(`${pc.yellow(\"?\")} ${question} ${pc.dim(\"(y/N)\")} `);\n return /^y(es)?$/i.test(answer.trim());\n } finally {\n rl.close();\n }\n },\n};\n","import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { z } from \"zod\";\nimport path from \"node:path\";\nimport { PreviewRecorder, type ActInput } from \"./recorder.js\";\nimport { openPr } from \"./publish.js\";\nimport { detectLocalApps, type LocalApp } from \"./detect.js\";\nimport { loadConfig } from \"../config/load.js\";\n\n/** Message that pushes the agent to confirm a URL with the user instead of guessing. */\nfunction urlAsk(found: LocalApp[], hasConfig: boolean): string {\n const lines = [\"No URL was specified — I won't guess which app to record.\"];\n if (found.length) {\n lines.push(\"\", \"Apps currently running on localhost:\");\n for (const f of found) lines.push(`- ${f.url}${f.title ? ` — ${f.title}` : \"\"}`);\n lines.push(\"\", \"Ask the user which of these to record (confirm the exact URL), then call start_recording again with that `url`.\");\n } else {\n lines.push(\n \"\",\n \"Nothing is responding on common localhost ports — the project may not be running locally.\",\n \"Ask the user for the URL to record: their local dev server URL, or a staging / production URL. Then call start_recording with that `url`.\",\n );\n }\n if (hasConfig) {\n lines.push(\n \"\",\n \"Alternatively, if the user wants PR Preview to start the project's own dev server (from pr-preview.config.js), call start_recording with `useDevServer: true`.\",\n );\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * Start the PR Preview MCP server on stdio. Exposes a small tool surface that\n * lets an agent (Claude Code) record a real journey through the user's app and\n * produce a before/after-style video clip — the agent chooses the actions, the\n * existing engine captures the live session, so nothing is synthesized.\n *\n * Resolves when the client disconnects (stdio transport closes).\n */\nexport async function startMcpServer(repoRoot: string): Promise<void> {\n const recorder = new PreviewRecorder(repoRoot);\n\n const server = new McpServer({\n name: \"pr-preview\",\n version: \"0.1.0\",\n });\n\n const text = (s: string) => ({ content: [{ type: \"text\" as const, text: s }] });\n const fail = (e: unknown) => ({\n content: [{ type: \"text\" as const, text: `Error: ${e instanceof Error ? e.message : String(e)}` }],\n isError: true,\n });\n\n const SNAPSHOT_HINT =\n \"\\n\\nEach line is an element with a [ref=eN] handle. To act on one, call `act` \" +\n \"with that ref. Always take a fresh snapshot after any action that changes the \" +\n \"page (refs are only valid for the most recent snapshot).\";\n\n server.tool(\n \"detect_localhost\",\n \"Probe common localhost ports and list the apps currently running. Use this when the \" +\n \"user hasn't given a URL, to offer them the running app(s) to record.\",\n {},\n async () => {\n try {\n const found = await detectLocalApps();\n if (!found.length) {\n return text(\n \"No app is responding on common localhost ports. Ask the user for a URL to record — \" +\n \"their local dev server, or a staging / production URL.\",\n );\n }\n return text(\n \"Apps running on localhost:\\n\" +\n found.map((f) => `- ${f.url}${f.title ? ` — ${f.title}` : \"\"}`).join(\"\\n\") +\n \"\\n\\nAsk the user which one to record, then call start_recording with that url.\",\n );\n } catch (e) {\n return fail(e);\n }\n },\n );\n\n server.tool(\n \"start_recording\",\n \"Open the user's app in the PR Preview harness — a real, VISIBLE Chrome window where the \" +\n \"app runs inside the harness iframe and the sidebar records each step — and begin recording. \" +\n \"Returns the app's accessibility snapshot so you can pick the first action.\\n\\n\" +\n \"URL handling — do NOT guess: if the user gave a URL, pass it as `url`. If they did NOT \" +\n \"specify one, call this with no `url`; it detects apps on localhost and returns them so you \" +\n \"can ASK the user which URL to record. If nothing is running locally, ask the user for a \" +\n \"staging or production URL. Only pass `useDevServer: true` when the user explicitly wants \" +\n \"PR Preview to start the project's own dev server (from pr-preview.config.js).\\n\\n\" +\n \"mode 'single' (default) records one clip. mode 'before-after' records the SAME journey \" +\n \"twice — first on the PR's base branch, then on your branch — for a true before/after (uses a \" +\n \"managed dev server and requires being on a git branch; `url` is ignored). In before-after \" +\n \"mode: drive the journey with `act`, call `next_pass`, redo the SAME journey, then finish.\",\n {\n url: z.string().optional().describe(\"URL of an already-running app to record (ask the user if unsure)\"),\n mode: z.enum([\"single\", \"before-after\"]).optional(),\n useDevServer: z\n .boolean()\n .optional()\n .describe(\"Start the project's own dev server from pr-preview.config.js instead of using a URL\"),\n },\n async ({ url, mode, useDevServer }) => {\n try {\n const m = mode ?? \"single\";\n // No URL, no explicit dev-server request → detect + ask rather than guess.\n if (m === \"single\" && !url && !useDevServer) {\n const found = await detectLocalApps();\n let hasConfig = false;\n try {\n await loadConfig(repoRoot);\n hasConfig = true;\n } catch {\n /* no config — dev-server path unavailable */\n }\n return text(urlAsk(found, hasConfig));\n }\n const r = await recorder.start({ url, mode });\n const win =\n \"A Chrome window has opened with the PR Preview harness — the app is running in the iframe and the sidebar records each step.\";\n const hint =\n r.mode === \"before-after\"\n ? \"\\n\\nThis is the BEFORE pass (base branch). Perform the journey, then call `next_pass`.\"\n : \"\";\n return text(`${win}\\nRecording started at ${r.startUrl}. Current page:\\n\\n${r.snapshot}${SNAPSHOT_HINT}${hint}`);\n } catch (e) {\n return fail(e);\n }\n },\n );\n\n server.tool(\n \"next_pass\",\n \"before-after mode only: finish the BEFORE clip, switch the app to your branch, and \" +\n \"begin the AFTER recording. After calling this, redo the SAME journey with `act`, \" +\n \"then call `finish_recording`.\",\n {},\n async () => {\n try {\n const { snapshot } = await recorder.nextPass();\n return text(\n `BEFORE clip captured. Now recording the AFTER pass (your branch) — redo the same journey.\\n\\n${snapshot}${SNAPSHOT_HINT}`,\n );\n } catch (e) {\n return fail(e);\n }\n },\n );\n\n server.tool(\n \"snapshot\",\n \"Return the current accessibility snapshot of the app (elements with [ref=eN] \" +\n \"handles). Use this to see the page before choosing the next action.\",\n {},\n async () => {\n try {\n return text((await recorder.snapshot()) + SNAPSHOT_HINT);\n } catch (e) {\n return fail(e);\n }\n },\n );\n\n server.tool(\n \"act\",\n \"Perform one action in the app, then return the fresh snapshot. Actions: \" +\n \"`click` (ref), `fill` (ref + text), `press` (ref + key, e.g. Enter), `hover` (ref), \" +\n \"`navigate` (url or path), `scroll` (optional ref to scroll into view), `wait` (ms). \" +\n \"The action is recorded into the live clip.\",\n {\n action: z.enum([\"click\", \"fill\", \"press\", \"hover\", \"navigate\", \"scroll\", \"wait\"]),\n ref: z.string().optional().describe(\"Element ref from the latest snapshot, e.g. e14\"),\n text: z.string().optional().describe(\"Text to type (for fill)\"),\n key: z.string().optional().describe(\"Key to press, e.g. Enter (for press)\"),\n url: z.string().optional().describe(\"URL or path (for navigate)\"),\n ms: z.number().optional().describe(\"Milliseconds to pause (for wait)\"),\n },\n async (args) => {\n try {\n const { snapshot } = await recorder.act(args as ActInput);\n return text(`Done: ${args.action}. Current page:\\n\\n${snapshot}${SNAPSHOT_HINT}`);\n } catch (e) {\n return fail(e);\n }\n },\n );\n\n server.tool(\n \"finish_recording\",\n \"Stop recording, encode the clip, and return the output file path(s). Call this \" +\n \"when the journey is complete. `name` optionally sets the output filename.\",\n { name: z.string().optional() },\n async ({ name }) => {\n try {\n const { files, fellBackToGif } = await recorder.finish({ name });\n const rel = files.map((f) => path.relative(repoRoot, f));\n const note = fellBackToGif\n ? \"\\n(ffmpeg was not found, so a GIF was produced instead of MP4 — `brew install ffmpeg` for MP4.)\"\n : \"\";\n return text(\n `Recording complete. Output:\\n${rel.map((r) => ` - ${r}`).join(\"\\n\")}${note}\\n\\n` +\n \"Drag the file into your PR description to embed it, or commit it and open the PR.\",\n );\n } catch (e) {\n return fail(e);\n }\n },\n );\n\n server.tool(\n \"open_pr\",\n \"Commit the recorded clip into the repo, push the current branch, and open a pull \" +\n \"request with the preview embedded in the body. Pass the file path(s) returned by \" +\n \"finish_recording. Requires the GitHub CLI (gh) authenticated and an `origin` remote. \" +\n \"Note: GitHub only plays inline video from its own CDN, so this embeds an animated GIF \" +\n \"(rendered inline) and links the full MP4. Side effects: it creates a commit on your \" +\n \"branch and pushes it.\",\n {\n files: z.array(z.string()).describe(\"Clip file path(s) from finish_recording\"),\n title: z.string().optional().describe(\"PR title (defaults to 'Preview: <branch>')\"),\n base: z.string().optional().describe(\"Base branch for the PR (e.g. main)\"),\n },\n async ({ files, title, base }) => {\n try {\n const { prUrl, committed, embedded } = await openPr(repoRoot, files, { title, base });\n const how = embedded === \"gif\" ? \"an inline GIF preview\" : \"a link to the MP4\";\n return text(\n `Pull request opened: ${prUrl}\\nCommitted ${committed.length} file(s) under pr-preview/ and embedded ${how}.`,\n );\n } catch (e) {\n return fail(e);\n }\n },\n );\n\n server.tool(\n \"cancel_recording\",\n \"Abort the current recording and close the browser without producing a clip.\",\n {},\n async () => {\n try {\n await recorder.dispose();\n return text(\"Recording cancelled — browser closed, no clip produced.\");\n } catch (e) {\n return fail(e);\n }\n },\n );\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n // Keep the process alive until the client disconnects; then clean up.\n await new Promise<void>((resolve) => {\n transport.onclose = () => {\n void recorder.dispose().finally(resolve);\n };\n });\n}\n","import { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport type { Locator, Page } from \"playwright\";\nimport { loadConfig, ConfigError } from \"../config/load.js\";\nimport { configSchema, type Config } from \"../config/schema.js\";\nimport { bootstrap, type Bootstrapped } from \"../session/bootstrap.js\";\nimport { getAppFrame } from \"../browser/frame.js\";\nimport { glideCursor, pulseCursor, showCursor, smoothScrollBy, MOTION } from \"./motion.js\";\nimport { detectBase } from \"../git/base.js\";\nimport { tryGit, GitError } from \"../git/exec.js\";\nimport { createBaseWorktree, type WorktreeHandle } from \"../git/worktree.js\";\nimport { detectPackageManager, installDependencies } from \"../server/pkgManager.js\";\nimport type { DevServerHandle } from \"../server/devServer.js\";\nimport { registerCleanup, runCleanups } from \"../cli/cleanup.js\";\n\nexport type ActionType = \"click\" | \"fill\" | \"press\" | \"hover\" | \"navigate\" | \"scroll\" | \"wait\";\nexport type RecordMode = \"single\" | \"before-after\";\n\nexport interface ActInput {\n action: ActionType;\n ref?: string;\n text?: string;\n key?: string;\n url?: string;\n ms?: number;\n}\n\n/** Short \"YYYY-MM-DD HH:MM\" caption stamp. */\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/** Compact filename stamp, e.g. 20260702-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\nconst shortRef = (ref: string): string => ref.replace(/^origin\\//, \"\");\nconst slug = (s: string): string =>\n shortRef(s).replace(/[^a-zA-Z0-9._-]+/g, \"-\").replace(/^-+|-+$/g, \"\") || \"clip\";\n\n/**\n * Drives an agent-recorded session. Claude Code calls start → snapshot → act* →\n * (next_pass → act*) → finish. Under the hood this reuses the exact same engine\n * as `pr-preview run`: a real headed Chrome, the live screencast IS the clip,\n * and the in-page recorder captures each action. The actions come from Claude\n * (via aria-ref locators) instead of a human's hands — so it stays a real\n * capture, never a synthesized video.\n */\nexport class PreviewRecorder {\n private boot: Bootstrapped | null = null;\n private page: Page | null = null;\n private config: Config | null = null;\n private outDir = \".pr-preview/output\";\n private mode: RecordMode = \"single\";\n private pass: \"before\" | \"after\" = \"before\";\n private started = false;\n\n // before/after bookkeeping\n private beforeServer: DevServerHandle | null = null;\n private worktree: WorktreeHandle | null = null;\n private baseLabel = \"\";\n private headLabel = \"\";\n private fstamp = \"\";\n private timestamp = \"\";\n private beforePaths: string[] = [];\n private fellBackToGif = false;\n\n constructor(private readonly repoRoot: string) {}\n\n get isActive(): boolean {\n return this.started;\n }\n\n /** Launch the browser + app and begin recording. Returns the first snapshot. */\n async start(opts: { url?: string; mode?: RecordMode } = {}): Promise<{ snapshot: string; startUrl: string; mode: RecordMode }> {\n if (this.started) throw new Error(\"A recording is already in progress — call finish_recording first.\");\n this.mode = opts.mode ?? \"single\";\n if (this.mode === \"before-after\") return this.startBeforeAfter();\n return this.startSingle(opts.url);\n }\n\n private async startSingle(url?: string): Promise<{ snapshot: string; startUrl: string; mode: RecordMode }> {\n let config: Config;\n try {\n config = (await loadConfig(this.repoRoot)).config;\n } catch (err) {\n if (url && err instanceof ConfigError) config = configSchema.parse({ devCommand: \"external\", url });\n else throw err;\n }\n this.config = config;\n this.outDir = path.resolve(this.repoRoot, config.output);\n this.headLabel = await this.currentBranch();\n this.timestamp = stamp();\n this.fstamp = fileStamp();\n\n const boot = url\n ? await bootstrap(this.repoRoot, config, \"run\", { fixedUrl: url })\n : await bootstrap(this.repoRoot, config, \"run\");\n this.boot = boot;\n boot.session.setPasses(1);\n\n if (!url) await boot.startApp(\"before\", this.repoRoot);\n const browser = await boot.openBrowser(\"before\");\n this.page = browser.page;\n\n await this.beginRecording();\n this.started = true;\n return { snapshot: await this.snapshot(), startUrl: boot.session.startUrl, mode: this.mode };\n }\n\n private async startBeforeAfter(): Promise<{ snapshot: string; startUrl: string; mode: RecordMode }> {\n const config = (await loadConfig(this.repoRoot)).config; // config required for a managed dev server\n this.config = config;\n this.outDir = path.resolve(this.repoRoot, config.output);\n\n const currentBranch = await tryGit(this.repoRoot, [\"symbolic-ref\", \"--quiet\", \"--short\", \"HEAD\"]);\n if (!currentBranch) {\n throw new GitError(\n \"before/after recording needs you to be on a branch (detached HEAD).\\n\" +\n \"Check out your PR branch first, or use single mode.\",\n );\n }\n const base = await detectBase(this.repoRoot, config.baseBranch);\n this.baseLabel = shortRef(base.ref);\n this.headLabel = shortRef(currentBranch);\n this.timestamp = stamp();\n this.fstamp = fileStamp();\n\n // Base app runs from a detached worktree at the base commit.\n const worktree = await createBaseWorktree(this.repoRoot, base.sha);\n this.worktree = worktree;\n if (!config.keepWorktree) registerCleanup(() => worktree.remove());\n\n if (config.install !== \"never\") {\n const appDir = path.join(worktree.dir, config.cwd);\n const hasModules = existsSync(path.join(appDir, \"node_modules\"));\n if (config.install === \"always\" || !hasModules) {\n await installDependencies(appDir, detectPackageManager(appDir));\n }\n }\n\n const boot = await bootstrap(this.repoRoot, config, \"run\");\n this.boot = boot;\n boot.session.setPasses(2);\n boot.session.setBranches(base.ref, currentBranch);\n\n this.beforeServer = await boot.startApp(\"before\", worktree.dir);\n const browser = await boot.openBrowser(\"before\");\n this.page = browser.page;\n\n this.pass = \"before\";\n await this.beginRecording();\n this.started = true;\n return { snapshot: await this.snapshot(), startUrl: boot.session.startUrl, mode: this.mode };\n }\n\n /** before/after only: finish the BEFORE clip, switch to the branch app, start AFTER. */\n async nextPass(): Promise<{ snapshot: string }> {\n if (!this.started) throw new Error(\"No active recording — call start_recording first.\");\n if (this.mode !== \"before-after\") throw new Error(\"next_pass is only valid in before-after mode.\");\n if (this.pass !== \"before\") throw new Error(\"Already recording the AFTER pass.\");\n const boot = this.boot!;\n\n boot.session.setPhase(\"idle\");\n const before = await boot.session.encodeClip(\"before\", this.outPath(\"before\", this.baseLabel), {\n branch: this.baseLabel,\n baseBranch: this.baseLabel,\n timestamp: this.timestamp,\n });\n this.beforePaths = before.paths;\n this.fellBackToGif ||= before.fellBackToGif;\n\n // Swap the app: stop the base dev server, start the branch app in place.\n await this.beforeServer?.stop();\n await boot.startApp(\"after\", this.repoRoot);\n boot.session.switchPass(\"after\");\n await boot.switchApp(\"after\");\n\n this.pass = \"after\";\n await this.beginRecording();\n return { snapshot: await this.snapshot() };\n }\n\n /** Accessibility tree of the app (with [ref=eN] handles) for Claude to target. */\n async snapshot(): Promise<string> {\n const frame = await this.frame();\n await frame.waitForLoadState(\"domcontentloaded\").catch(() => {});\n return frame.locator(\"body\").ariaSnapshot({ mode: \"ai\" });\n }\n\n /** Perform one action in the app, then return the fresh snapshot. Motions are\n * animated in-page (synthetic-cursor glide, rAF scroll, per-character typing)\n * so the live screencast captures continuous video, not a jumpy slideshow. */\n async act(a: ActInput): Promise<{ snapshot: string }> {\n if (!this.started) throw new Error(\"No active recording — call start_recording first.\");\n const frame = await this.frame();\n switch (a.action) {\n case \"click\": {\n const loc = frame.locator(`aria-ref=${req(a.ref, \"ref\")}`);\n await this.glideToLocator(loc);\n await pulseCursor(frame); // ripple at the target as it's clicked\n await loc.click({ timeout: 15_000 });\n break;\n }\n case \"fill\": {\n const loc = frame.locator(`aria-ref=${req(a.ref, \"ref\")}`);\n await this.glideToLocator(loc);\n await loc.click({ timeout: 15_000 }); // focus the field\n await loc.fill(\"\", { timeout: 15_000 }); // clear, then type it in for real\n await loc.pressSequentially(req(a.text, \"text\"), { delay: MOTION.typeDelayMs, timeout: 15_000 });\n break;\n }\n case \"press\": {\n // Glide the cursor onto the target first, so keyboard-driven activations\n // (e.g. answering a question when a native click is intercepted) still\n // read as the \"hand\" moving to the element and pressing it.\n const loc = frame.locator(`aria-ref=${req(a.ref, \"ref\")}`);\n const key = req(a.key, \"key\");\n await this.glideToLocator(loc);\n if (key === \"Enter\" || key === \" \" || key === \"Spacebar\") await pulseCursor(frame);\n await loc.press(key, { timeout: 15_000 });\n break;\n }\n case \"hover\": {\n const loc = frame.locator(`aria-ref=${req(a.ref, \"ref\")}`);\n await this.glideToLocator(loc);\n await loc.hover({ timeout: 15_000 });\n break;\n }\n case \"scroll\":\n if (a.ref) await this.smoothScrollToLocator(frame.locator(`aria-ref=${a.ref}`));\n else {\n const delta = await frame.evaluate(() => window.innerHeight * 0.8);\n await smoothScrollBy(frame, delta);\n }\n break;\n case \"navigate\": {\n const target = new URL(req(a.url, \"url\"), this.boot!.session.targetOrigin + \"/\").toString();\n await frame.goto(target, { waitUntil: \"domcontentloaded\" });\n await showCursor(await this.frame()); // re-render the pointer on the fresh page\n break;\n }\n case \"wait\":\n await this.page!.waitForTimeout(Math.min(a.ms ?? 1000, 15_000));\n break;\n default:\n throw new Error(`Unknown action: ${String((a as ActInput).action)}`);\n }\n await this.page!.waitForTimeout(MOTION.settleMs); // let the UI settle + the result render\n return { snapshot: await this.snapshot() };\n }\n\n /** Bring a target on-screen, then glide the synthetic cursor to its centre.\n * Coordinates are frame-local (what the in-page cursor uses). */\n private async glideToLocator(loc: Locator): Promise<void> {\n await loc.scrollIntoViewIfNeeded({ timeout: 15_000 }).catch(() => {});\n const c = await loc\n .evaluate((el) => {\n const r = el.getBoundingClientRect();\n return { x: r.left + r.width / 2, y: r.top + r.height / 2 };\n })\n .catch(() => null);\n if (c) {\n await glideCursor(await this.frame(), c.x, c.y);\n await this.page!.waitForTimeout(MOTION.holdMs); // let the arrival frame land\n }\n }\n\n /** Smoothly scroll a target to the vertical centre of the viewport. The delta\n * is measured inside the frame (frame-local coords). */\n private async smoothScrollToLocator(loc: Locator): Promise<void> {\n const delta = await loc\n .evaluate((el) => {\n const r = el.getBoundingClientRect();\n return r.top + r.height / 2 - window.innerHeight / 2;\n })\n .catch(() => null);\n if (delta === null) {\n await loc.scrollIntoViewIfNeeded({ timeout: 15_000 }).catch(() => {});\n return;\n }\n if (Math.abs(delta) < 8) return; // already centred\n await smoothScrollBy(await this.frame(), delta);\n }\n\n /** Stop recording, encode the final clip(s), and return the output file paths. */\n async finish(opts: { name?: string } = {}): Promise<{ files: string[]; fellBackToGif: boolean }> {\n if (!this.started) throw new Error(\"No active recording — call start_recording first.\");\n if (this.mode === \"before-after\" && this.pass !== \"after\") {\n throw new Error(\"Record the AFTER pass first: call next_pass, redo the journey, then finish_recording.\");\n }\n const boot = this.boot!;\n boot.session.setPhase(\"idle\");\n\n let files: string[];\n if (this.mode === \"before-after\") {\n const after = await boot.session.encodeClip(\"after\", this.outPath(\"after\", this.headLabel), {\n branch: this.headLabel,\n baseBranch: this.baseLabel,\n timestamp: this.timestamp,\n });\n this.fellBackToGif ||= after.fellBackToGif;\n files = [...this.beforePaths, ...after.paths];\n } else {\n const clip = await boot.session.encodeClip(\"before\", this.outPath(\"single\", opts.name ?? this.headLabel), {\n branch: this.headLabel,\n baseBranch: this.headLabel,\n timestamp: this.timestamp,\n });\n this.fellBackToGif ||= clip.fellBackToGif;\n files = clip.paths;\n }\n\n const fellBack = this.fellBackToGif;\n await this.dispose();\n return { files, fellBackToGif: fellBack };\n }\n\n /** Tear everything down (browser, harness, dev servers, worktree) without encoding. */\n async dispose(): Promise<void> {\n this.started = false;\n this.boot = null;\n this.page = null;\n this.beforeServer = null;\n this.worktree = null;\n await runCleanups();\n }\n\n // ── helpers ────────────────────────────────────────────────────────────────\n private outPath(which: string, label: string): string {\n return path.join(this.outDir, `${which === \"single\" ? slug(label) : `${which}-${slug(label)}`}-${this.fstamp}`);\n }\n\n /** Capture the start path, then begin the live screencast recording. */\n private async beginRecording(): Promise<void> {\n const boot = this.boot!;\n const frame = await getAppFrame(this.page!, boot.session.targetOrigin);\n await frame.waitForLoadState(\"domcontentloaded\").catch(() => {});\n boot.session.startUrl = await frame.evaluate(() => location.pathname + location.search).catch(() => \"/\");\n boot.session.setPhase(\"recording\");\n await showCursor(frame); // render the pointer from the first frame, at rest\n }\n\n private async frame() {\n if (!this.page || !this.boot) throw new Error(\"No active recording.\");\n return getAppFrame(this.page, this.boot.session.targetOrigin);\n }\n\n private async currentBranch(): Promise<string> {\n const ref = await tryGit(this.repoRoot, [\"rev-parse\", \"--abbrev-ref\", \"HEAD\"]);\n return ref ? shortRef(ref) : \"app\";\n }\n}\n\nfunction req<T>(v: T | undefined, name: string): T {\n if (v === undefined || v === null || v === \"\") throw new Error(`Missing required \"${name}\" for this action.`);\n return v;\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 * Motion smoothing for the MP4. The screencast delivers a low, variable\n * frame rate, so the raw capture can look choppy. Interpolation synthesises\n * intermediate frames up to `smoothFps` from the real captured frames:\n * - \"blend\" (default): cross-dissolves frames — natural motion-blur, never\n * warps text/geometry. Safe for any site.\n * - \"mci\": motion-compensated — sharper moving cursor, but can warp fast-\n * scrolling dense text on some pages.\n * - \"off\": no interpolation (raw capture rate).\n */\n interpolate: z.enum([\"blend\", \"mci\", \"off\"]).default(\"blend\"),\n /** Target fps for interpolation (only used when interpolate ≠ \"off\").\n * 60 = display-refresh smoothness; interpolation fills every gap between\n * the real captured frames up to this rate. */\n smoothFps: z.number().int().positive().max(120).default(60),\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 // Cap the capture to ~the CSS window size (bounded), so Chrome doesn't\n // JPEG-encode full device-pixel frames on Retina — that raises the delivered\n // frame rate. The clip is downscaled at encode, so this costs no real detail.\n const { maxWidth, maxHeight } = await page\n .evaluate(() => ({\n maxWidth: Math.min(window.innerWidth, 1920),\n maxHeight: Math.min(window.innerHeight, 1200),\n }))\n .catch(() => ({ maxWidth: 1920, maxHeight: 1200 }));\n try {\n this.recordingFeed = await startScreencast(page, { quality: 80, maxWidth, maxHeight });\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 interpolate: this.config.gif.interpolate,\n smoothFps: this.config.gif.smoothFps,\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; maxWidth?: number; maxHeight?: 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 // maxWidth/maxHeight cap the JPEG Chrome encodes per frame. Left unset, Chrome\n // encodes the FULL window at device pixels (≈4× the pixels on a 2× display) —\n // that per-frame encode+transfer cost is what starves the delivered frame rate.\n // Capping to roughly the CSS window size defeats the Retina bloat, so Chrome\n // delivers many more frames/sec; the clip is downscaled at encode anyway.\n await cdp.send(\"Page.startScreencast\", {\n format: \"jpeg\",\n quality: opts.quality ?? 90,\n everyNthFrame: opts.everyNthFrame ?? 1,\n ...(opts.maxWidth ? { maxWidth: opts.maxWidth } : {}),\n ...(opts.maxHeight ? { maxHeight: opts.maxHeight } : {}),\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, encodeMp4Interpolated, 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 interpolate = opts.interpolate ?? \"off\";\n const smooth = interpolate !== \"off\";\n // With interpolation we feed the RAW, distinct captured frames (real timing)\n // so ffmpeg can synthesise smooth in-between motion. Without it, keep the\n // old constant-fps sample-and-hold.\n const frames = smooth ? rawFrames : 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 if (smooth) {\n // Per-frame duration = real gap to the next captured frame (seconds),\n // CAPPED so a long idle gap (e.g. the agent's thinking time between\n // actions, when nothing repaints) isn't turned into a slow morph by the\n // interpolator — it's held briefly instead, which also trims dead air.\n const MAX_HOLD = 0.5;\n const durations = frames.map((f, i) =>\n i < frames.length - 1\n ? Math.min(MAX_HOLD, Math.max(0.001, (frames[i + 1]!.t - f.t) / 1000))\n : 1 / opts.fps,\n );\n const outFps = Math.max(opts.smoothFps ?? 30, opts.fps);\n await encodeMp4Interpolated(pngs, durations, outFps, interpolate as \"blend\" | \"mci\", file);\n } else {\n await encodeMp4(pngs, opts.fps, file);\n }\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 /** MP4 motion smoothing (see config `gif.interpolate`). GIF ignores it. */\n interpolate?: \"blend\" | \"mci\" | \"off\";\n /** Target fps for interpolation. */\n smoothFps?: 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\n/**\n * Smooth MP4 from the raw, variable-rate captured frames. Each frame is held for\n * its real duration (concat demuxer), then ffmpeg's `minterpolate` synthesises\n * intermediate frames up to `outFps` — turning a low, choppy capture rate into\n * fluid motion. `mode`:\n * - \"blend\": cross-dissolve (motion-blur); never warps text/geometry.\n * - \"mci\": motion-compensated; sharper moving cursor, slight warp risk on text.\n */\nexport async function encodeMp4Interpolated(\n pngFrames: Buffer[],\n durationsSec: number[],\n outFps: number,\n mode: \"blend\" | \"mci\",\n outFile: string,\n): Promise<void> {\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 // concat demuxer with per-frame durations = real capture timing. The last\n // file is repeated because the demuxer ignores the final `duration` line.\n const lines: string[] = [];\n pngFrames.forEach((_, i) => {\n lines.push(`file 'frame${String(i).padStart(5, \"0\")}.png'`);\n lines.push(`duration ${Math.max(0.001, durationsSec[i] ?? 1 / outFps).toFixed(4)}`);\n });\n lines.push(`file 'frame${String(pngFrames.length - 1).padStart(5, \"0\")}.png'`);\n await writeFile(path.join(dir, \"list.txt\"), lines.join(\"\\n\") + \"\\n\");\n\n await mkdir(path.dirname(outFile), { recursive: true });\n // scd=fdiff: skip interpolation across scene changes (big frame-to-frame\n // differences) — duplicate instead of morphing between unrelated states.\n // (The token is `fdiff`, not `fdi`; the latter is unparseable and makes the\n // whole minterpolate command fail, silently downgrading to a choppy hold.)\n const interp =\n mode === \"mci\"\n ? `minterpolate=fps=${outFps}:mi_mode=mci:me_mode=bidir:mc_mode=aobmc:vsbmc=1:scd=fdiff`\n : `minterpolate=fps=${outFps}:mi_mode=blend:scd=fdiff`;\n const common = [\"-c:v\", \"libx264\", \"-preset\", \"slow\", \"-crf\", \"19\", \"-pix_fmt\", \"yuv420p\", \"-movflags\", \"+faststart\", outFile]; // prettier-ignore\n const input = [\"-f\", \"concat\", \"-safe\", \"0\", \"-i\", path.join(dir, \"list.txt\")];\n const scale = \"scale=trunc(iw/2)*2:trunc(ih/2)*2\";\n try {\n await run(\"ffmpeg\", [\"-y\", ...input, \"-vf\", `${interp},${scale}`, \"-r\", String(outFps), ...common]);\n } catch (err) {\n // minterpolate can be unavailable or choke on odd input — fall back to a\n // plain constant-fps encode at real timing. Still a valid clip, just not\n // interpolated, so a recording never fails for want of smoothing. Warn\n // loudly: a silent fallback here once hid a bad filter arg and shipped\n // choppy (un-interpolated) video as if it were smooth.\n console.warn(\n `[pr-preview] motion smoothing (${mode}) failed; encoding without interpolation — the clip will look choppier.\\n ${(err as Error).message?.split(\"\\n\")[0] ?? err}`,\n );\n await run(\"ffmpeg\", [\"-y\", ...input, \"-vf\", scale, \"-r\", String(outFps), ...common]);\n }\n } finally {\n await rm(dir, { recursive: true, force: true });\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, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\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 type { Frame } from \"playwright\";\n\n/**\n * Human-like motion for the agent recorder.\n *\n * The clip is a live CDP screencast, which only emits a frame when the page\n * repaints. Instant Playwright actions (teleport click, `fill`, `scrollBy`)\n * change nothing *over time*, so the screencast captures a few static states\n * and the result looks like a slideshow.\n *\n * The smooth path is to animate *inside the page* with requestAnimationFrame:\n * one `frame.evaluate` kicks off a 60fps animation and resolves when it's done.\n * That is both smoother (every rAF tick repaints → a screencast frame) and\n * faster to drive (a single round-trip, not one per step) than nudging the real\n * OS mouse from Node. The synthetic cursor (`window.__prPreviewCursor`, injected\n * into the app frame) is the only pointer the screencast can show, so we move\n * that — the real click/scroll is applied separately by Playwright.\n */\n\nexport interface Point {\n x: number;\n y: number;\n}\n\n/** Durations (ms) tuned for a snappy-but-readable pace. Motions used to be\n * deliberately slow so a low-fps screencast could catch enough frames along\n * each path — but the capture is now capped to ~CSS size and delivers ~60fps\n * during motion, so that constraint is gone. These are back at a natural human\n * tempo; the clip feels responsive instead of dragging. */\nexport const MOTION = {\n glideMs: 750, // ceiling; the cursor scales the actual glide by distance\n scrollMs: 600,\n typeDelayMs: 35,\n settleMs: 140,\n holdMs: 80, // brief pause after a glide lands, before the click, so the\n // arrival + click-pulse frames are captured\n} as const;\n\n/**\n * Glide the synthetic cursor to a frame-local point over ~`ms`, animated in-page\n * at rAF rate. Resolves when the glide finishes. No-op (resolves) if the cursor\n * isn't installed.\n */\nexport async function glideCursor(\n frame: Frame,\n x: number,\n y: number,\n ms: number = MOTION.glideMs,\n): Promise<void> {\n await frame\n .evaluate(\n ({ x, y, ms }) =>\n (window.__prPreviewCursor?.moveTo(x, y, ms) as Promise<void> | undefined) ??\n Promise.resolve(),\n { x, y, ms },\n )\n .catch(() => {});\n}\n\n/** Render the synthetic cursor at a frame-local point (defaults to the viewport\n * centre) without animating. Used to keep the pointer visible during idle gaps\n * — at the start of the clip and after each navigation — so the \"hand\" is\n * always on screen, not just mid-glide. No-op if the cursor isn't installed. */\nexport async function showCursor(frame: Frame, x?: number, y?: number): Promise<void> {\n await frame\n .evaluate(\n ({ x, y }) => {\n const c = window.__prPreviewCursor;\n if (!c) return;\n c.show(x ?? (window.innerWidth || 1280) / 2, y ?? (window.innerHeight || 800) / 2);\n },\n { x: x ?? null, y: y ?? null },\n )\n .catch(() => {});\n}\n\n/** Pulse the synthetic cursor's click ring at its current spot. */\nexport async function pulseCursor(frame: Frame): Promise<void> {\n await frame\n .evaluate(\n () => (window.__prPreviewCursor?.clickPulse() as Promise<void> | undefined) ?? Promise.resolve(),\n )\n .catch(() => {});\n}\n\n/**\n * Smoothly scroll the frame's window by `deltaY` px over ~`ms`, animated in-page\n * with an ease-in-out curve so it accelerates and settles like a real flick.\n * Resolves when the scroll completes.\n */\nexport async function smoothScrollBy(\n frame: Frame,\n deltaY: number,\n ms: number = MOTION.scrollMs,\n): Promise<void> {\n await frame\n .evaluate(\n ({ deltaY, ms }) =>\n new Promise<void>((resolve) => {\n const startY = window.scrollY;\n const t0 = performance.now();\n const ease = (t: number) =>\n t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;\n const tick = (now: number) => {\n const t = Math.min(1, (now - t0) / ms);\n window.scrollTo(0, startY + deltaY * ease(t));\n if (t < 1) requestAnimationFrame(tick);\n else resolve();\n };\n requestAnimationFrame(tick);\n }),\n { deltaY, ms },\n )\n .catch(() => {});\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 { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { copyFile, mkdir } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { git, tryGit } from \"../git/exec.js\";\nimport { ffmpegAvailable } from \"../encode/ffmpeg.js\";\n\nconst pexec = promisify(execFile);\n\n/** Run a command, surfacing stderr in the thrown error for clear diagnostics. */\nasync function run(cmd: string, args: string[], cwd: string): Promise<string> {\n try {\n const { stdout } = await pexec(cmd, args, { cwd, maxBuffer: 16 * 1024 * 1024 });\n return stdout.trim();\n } catch (e) {\n const err = e as { stderr?: string; message?: string };\n throw new Error(`\\`${cmd} ${args.slice(0, 2).join(\" \")}\\` failed: ${(err.stderr || err.message || String(e)).trim()}`);\n }\n}\n\nexport interface OpenPrResult {\n prUrl: string;\n committed: string[];\n embedded: \"gif\" | \"mp4-link\";\n}\n\n/**\n * Commit an embeddable clip into `pr-preview/`, push the current branch, and\n * open a pull request with the preview in the body.\n *\n * Honest constraint: GitHub only plays inline video from its own attachment CDN\n * (no public API), so the open-source path embeds an animated **GIF** (which\n * renders inline) and links the full-quality MP4. One-click hosted video in the\n * PR body is what PR Preview for Teams adds on top.\n */\nexport async function openPr(\n repoRoot: string,\n files: string[],\n opts: { title?: string; base?: string } = {},\n): Promise<OpenPrResult> {\n if (files.length === 0) {\n throw new Error(\"No clip files provided — run finish_recording first and pass its output paths.\");\n }\n\n // 0. Preconditions.\n try {\n await run(\"gh\", [\"--version\"], repoRoot);\n } catch {\n throw new Error(\"GitHub CLI (gh) not found. Install it from https://cli.github.com, then `gh auth login`.\");\n }\n try {\n await run(\"gh\", [\"auth\", \"status\"], repoRoot);\n } catch {\n throw new Error(\"GitHub CLI isn't authenticated. Run `gh auth login`, then try again.\");\n }\n const branch = await tryGit(repoRoot, [\"symbolic-ref\", \"--quiet\", \"--short\", \"HEAD\"]);\n if (!branch) throw new Error(\"You're on a detached HEAD. Check out your PR branch first.\");\n if (!(await tryGit(repoRoot, [\"remote\", \"get-url\", \"origin\"]))) {\n throw new Error(\"No `origin` remote found. Add one (and push the branch), then retry.\");\n }\n const slug = await repoSlug(repoRoot);\n\n // 1. Copy clips into a committed folder; pick/derive an embeddable GIF.\n const abs = files.map((f) => (path.isAbsolute(f) ? f : path.resolve(repoRoot, f)));\n for (const f of abs) if (!existsSync(f)) throw new Error(`Clip file not found: ${f}`);\n\n const destDir = path.join(repoRoot, \"pr-preview\");\n await mkdir(destDir, { recursive: true });\n\n const committed: string[] = [];\n let embedGifRel: string | null = null;\n const mp4Rel: string[] = [];\n\n for (const f of abs) {\n const base = path.basename(f);\n await copyFile(f, path.join(destDir, base));\n committed.push(`pr-preview/${base}`);\n if (base.endsWith(\".gif\") && !embedGifRel) embedGifRel = `pr-preview/${base}`;\n if (base.endsWith(\".mp4\")) mp4Rel.push(`pr-preview/${base}`);\n }\n\n // No GIF among the outputs → transcode the first MP4 so the PR can show an\n // animated preview inline.\n if (!embedGifRel && mp4Rel.length && (await ffmpegAvailable())) {\n const src = path.join(repoRoot, mp4Rel[0]!);\n const gifName = path.basename(mp4Rel[0]!).replace(/\\.mp4$/, \".gif\");\n await run(\n \"ffmpeg\",\n [\"-y\", \"-i\", src, \"-vf\", \"fps=15,scale=900:-1:flags=lanczos,split[a][b];[a]palettegen[p];[b][p]paletteuse\", path.join(destDir, gifName)],\n repoRoot,\n );\n committed.push(`pr-preview/${gifName}`);\n embedGifRel = `pr-preview/${gifName}`;\n }\n\n // 2. Commit + push.\n await git(repoRoot, [\"add\", \"--\", ...committed]);\n try {\n await git(repoRoot, [\"commit\", \"-m\", \"Add PR preview clip\"]);\n } catch (e) {\n if (!/nothing to commit/i.test(String(e))) throw e;\n }\n await run(\"git\", [\"push\", \"-u\", \"origin\", branch], repoRoot);\n\n // 3. Body with an embedded preview + MP4 link(s).\n const rawBase = `https://github.com/${slug}/raw/${branch}`;\n const body = buildBody(embedGifRel, mp4Rel, rawBase);\n\n // 4. Open the PR.\n const args = [\"pr\", \"create\", \"--title\", opts.title ?? `Preview: ${branch}`, \"--body\", body, \"--head\", branch];\n if (opts.base) args.push(\"--base\", opts.base);\n const prUrl = await run(\"gh\", args, repoRoot);\n\n return { prUrl, committed, embedded: embedGifRel ? \"gif\" : \"mp4-link\" };\n}\n\nfunction buildBody(gifRel: string | null, mp4Rel: string[], rawBase: string): string {\n const lines = [\"## PR Preview\", \"\"];\n if (gifRel) lines.push(`![PR preview](${rawBase}/${gifRel})`, \"\");\n if (mp4Rel.length) {\n lines.push(\"Full-quality MP4:\");\n for (const m of mp4Rel) lines.push(`- [${path.basename(m)}](${rawBase}/${m})`);\n lines.push(\"\");\n }\n lines.push(\"<sub>Recorded with [PR Preview](https://pr-preview.com) via Claude Code.</sub>\");\n return lines.join(\"\\n\");\n}\n\nasync function repoSlug(repoRoot: string): Promise<string> {\n try {\n return await run(\"gh\", [\"repo\", \"view\", \"--json\", \"nameWithOwner\", \"-q\", \".nameWithOwner\"], repoRoot);\n } catch {\n /* fall back to parsing the origin remote */\n }\n const url = (await tryGit(repoRoot, [\"remote\", \"get-url\", \"origin\"])) ?? \"\";\n const m = url.match(/github\\.com[:/]([^/]+\\/[^/]+?)(?:\\.git)?$/);\n if (!m) throw new Error(\"Couldn't determine the GitHub owner/repo from the origin remote.\");\n return m[1]!;\n}\n","/** Detect apps already running on the local machine, so an agent can offer the\n * user a URL to record instead of guessing or silently starting a dev server. */\n\nexport interface LocalApp {\n url: string;\n title?: string;\n}\n\n/** Common dev-server ports across the popular frameworks/bundlers. */\nconst CANDIDATE_PORTS = [\n 3000, 3001, 3002, 3003, // Next.js / CRA / Node\n 5173, 5174, 4321, // Vite / Astro\n 4200, // Angular\n 8080, 8000, 5000, 4000, 3333, // misc dev servers\n 1420, // Tauri\n 6006, // Storybook\n];\n\n/** GET a URL with a short timeout; return it (with the page title) if it answers. */\nasync function probe(url: string, timeoutMs = 1200): Promise<LocalApp | null> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const res = await fetch(url, { signal: controller.signal, redirect: \"manual\" });\n // Anything that answers (even a redirect or 404) means something is listening;\n // only treat a hard server error as \"not a usable app\".\n if (res.status >= 500) return null;\n let title: string | undefined;\n try {\n const html = await res.text();\n title = html.match(/<title[^>]*>([^<]*)<\\/title>/i)?.[1]?.trim() || undefined;\n } catch {\n /* non-HTML or unreadable body — still a running app */\n }\n return { url, title };\n } catch {\n return null; // connection refused / timeout → nothing there\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * Probe common localhost ports and return the apps that answer. `preferred` (a\n * concrete URL, if one is known) is checked first.\n */\nexport async function detectLocalApps(preferred?: string): Promise<LocalApp[]> {\n const urls = new Set<string>();\n if (preferred && /^https?:\\/\\//.test(preferred)) urls.add(preferred);\n for (const port of CANDIDATE_PORTS) urls.add(`http://localhost:${port}`);\n const results = await Promise.all([...urls].map((u) => probe(u)));\n return results.filter((r): r is LocalApp => r !== null);\n}\n","import { startMcpServer } from \"../../mcp/server.js\";\n\n/**\n * `pr-preview mcp` — run the Model Context Protocol server on stdio so an agent\n * (Claude Code) can drive a recording. The MCP transport owns stdout for\n * JSON-RPC, so every human-facing log the engine emits (console.log) must be\n * redirected to stderr or it would corrupt the protocol stream.\n */\nexport async function mcpCommand(repoRoot: string): Promise<void> {\n const toStderr = (...args: unknown[]) => {\n process.stderr.write(\n args.map((a) => (typeof a === \"string\" ? a : String(a))).join(\" \") + \"\\n\",\n );\n };\n console.log = toStderr as typeof console.log;\n console.info = toStderr as typeof console.info;\n console.warn = toStderr as typeof console.warn;\n console.debug = toStderr as typeof console.debug;\n\n await startMcpServer(repoRoot);\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 { 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 { 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":["z","path","existsSync","path","existsSync","readFile","path","path","existsSync","readFile","path","path","readFile","existsSync","path","path","existsSync","req","readFile","readFile","existsSync","path","fileURLToPath","probe","sharp","sharp","mkdir","writeFile","path","sharp","spawn","writeFile","mkdir","path","existsSync","mkdirSync","path","tmpdir","fileURLToPath","quantize","sharp","mkdir","path","writeFile","sharp","sharp","path","x","y","ms","deltaY","execFile","existsSync","rm","path","path","existsSync","rm","existsSync","path","spawn","path","existsSync","execFile","mkdir","existsSync","path","execFile","run","slug","path","existsSync","mkdir","z","path","path","pc","readFile","writeFile","mkdir","path","z","mkdir","path","writeFile","path","pc","existsSync","path","pc","spawn","stamp","shortRef","slug","fileStamp","path","pc","existsSync"]}