afterbefore 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # afterbefore
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/afterbefore?color=blue)](https://www.npmjs.com/package/afterbefore)
4
- [![npm downloads](https://img.shields.io/npm/dm/afterbefore?color=green)](https://www.npmjs.com/package/afterbefore)
4
+ [![npm downloads](https://img.shields.io/npm/dm/afterbefore)](https://www.npmjs.com/package/afterbefore)
5
5
 
6
6
  Automatic before/after screenshot capture for Next.js pull requests.
7
7
 
@@ -17,15 +17,13 @@ Run it from a feature branch in any Next.js app router project:
17
17
  npx afterbefore
18
18
  ```
19
19
 
20
- It spins up two dev servers (your branch and the base branch), captures screenshots of affected routes, diffs them pixel by pixel, and generates an HTML report with interactive before/after sliders.
20
+ It spins up two dev servers (your branch and the base branch), captures screenshots of affected routes, and diffs them pixel by pixel.
21
21
 
22
22
  Output lands in `.afterbefore/`:
23
23
 
24
24
  ```
25
25
  .afterbefore/
26
26
  └── feature-branch_2026-02-26/
27
- ├── index.html # visual report
28
- ├── summary.md # markdown table
29
27
  ├── about-before.png
30
28
  ├── about-after.png
31
29
  ├── about-diff.png
@@ -33,12 +31,6 @@ Output lands in `.afterbefore/`:
33
31
  └── about-slider.html # interactive slider
34
32
  ```
35
33
 
36
- Open the report automatically:
37
-
38
- ```bash
39
- npx afterbefore --open
40
- ```
41
-
42
34
  ## How it works
43
35
 
44
36
  1. Reads `git diff` to find changed files
@@ -49,7 +41,6 @@ npx afterbefore --open
49
41
  6. Starts two Next.js dev servers in parallel (base branch + current branch)
50
42
  7. Captures full-page screenshots of each affected route on both servers using Playwright
51
43
  8. Compares screenshots with pixelmatch and generates diff images
52
- 9. Builds an HTML report with side-by-side comparisons and interactive sliders
53
44
 
54
45
  Layouts work the same way. Edit `app/dashboard/layout.tsx` and it captures every page under `app/dashboard/`.
55
46
 
@@ -64,7 +55,6 @@ If only global files changed (like `globals.css` or `tailwind.config.ts`) and no
64
55
  | `--post` | `false` | Post results as a GitHub PR comment |
65
56
  | `--threshold <percent>` | `0.1` | Ignore diffs below this percentage |
66
57
  | `--max-routes <count>` | `6` | Cap the number of routes captured (0 = unlimited) |
67
- | `--open` | `false` | Open the HTML report in your browser |
68
58
  | `--width <pixels>` | `1280` | Viewport width |
69
59
  | `--height <pixels>` | `720` | Viewport height |
70
60
  | `--device <name>` | — | Playwright device, e.g. `"iPhone 14"` |
package/dist/cli.js CHANGED
@@ -116,14 +116,14 @@ var Logger = class {
116
116
  if (this.spinner) {
117
117
  if (finished) {
118
118
  this.spinner.stop();
119
- const bar = "#".repeat(BAR_WIDTH);
119
+ const bar = "\u2588".repeat(BAR_WIDTH);
120
120
  console.log(`${chalk.green("\u2714")} ${bar}`);
121
121
  } else {
122
122
  this.spinner.stop();
123
123
  }
124
124
  this.spinner = null;
125
125
  } else if (finished) {
126
- const bar = "#".repeat(BAR_WIDTH);
126
+ const bar = "\u2588".repeat(BAR_WIDTH);
127
127
  console.log(`${chalk.green("\u2714")} ${bar}`);
128
128
  }
129
129
  this.pipelineTotal = 0;
@@ -138,7 +138,7 @@ var Logger = class {
138
138
  const total = this.pipelineTotal || 1;
139
139
  const clampedStep = Math.max(0, Math.min(step, total));
140
140
  const filled = Math.round(clampedStep / total * BAR_WIDTH);
141
- const bar = "#".repeat(filled) + "-".repeat(BAR_WIDTH - filled);
141
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(BAR_WIDTH - filled);
142
142
  return ` ${bar} ${clampedStep}/${total} ${label}`;
143
143
  }
144
144
  clearSpinner() {
@@ -241,7 +241,6 @@ function getCurrentBranch(cwd) {
241
241
  // src/pipeline.ts
242
242
  import { resolve as resolve4 } from "path";
243
243
  import { unlinkSync } from "fs";
244
- import { exec } from "child_process";
245
244
 
246
245
  // src/config.ts
247
246
  import { resolve } from "path";
@@ -900,8 +899,8 @@ function waitForServer(url, timeoutMs) {
900
899
  async function startServer(projectDir, port) {
901
900
  const url = `http://localhost:${port}`;
902
901
  const pm = detectPackageManager(projectDir);
903
- const exec2 = pmExec(pm);
904
- const [cmd, ...baseArgs] = exec2.split(" ");
902
+ const exec = pmExec(pm);
903
+ const [cmd, ...baseArgs] = exec.split(" ");
905
904
  const lockFile = join5(projectDir, ".next", "dev", "lock");
906
905
  if (existsSync5(lockFile)) {
907
906
  throw new AfterbeforeError(
@@ -1201,6 +1200,49 @@ async function tagChangedComponentInstances(page, changedComponents, maxPerSourc
1201
1200
  });
1202
1201
  bySource.set(match.source, list);
1203
1202
  }
1203
+ for (const target of targets) {
1204
+ if (bySource.has(target.original) && bySource.get(target.original).length > 0) continue;
1205
+ const fileName = target.original.split("/").pop() ?? "";
1206
+ const componentName = fileName.replace(/\.[a-z0-9]+$/i, "");
1207
+ if (!componentName || componentName.length < 2) continue;
1208
+ const pattern = new RegExp("\\b" + componentName + "\\b", "i");
1209
+ const headings = Array.from(document.querySelectorAll("h1, h2, h3, h4"));
1210
+ for (const heading of headings) {
1211
+ const text = (heading.textContent ?? "").trim();
1212
+ if (!pattern.test(text)) continue;
1213
+ let container = heading;
1214
+ const headingTag = heading.tagName.toLowerCase();
1215
+ let walkParent = heading.parentElement;
1216
+ while (walkParent && walkParent !== document.body) {
1217
+ const parentTag = walkParent.tagName.toLowerCase();
1218
+ if (parentTag === "section" || parentTag === "article") {
1219
+ container = walkParent;
1220
+ break;
1221
+ }
1222
+ if (walkParent.getAttribute("role") === "region") {
1223
+ container = walkParent;
1224
+ break;
1225
+ }
1226
+ if (parentTag === "body" || parentTag === "main") break;
1227
+ const siblingHeadings = walkParent.querySelectorAll(`:scope > * ${headingTag}, :scope > ${headingTag}`);
1228
+ if (siblingHeadings.length > 1) break;
1229
+ container = walkParent;
1230
+ walkParent = walkParent.parentElement;
1231
+ }
1232
+ const rect = container.getBoundingClientRect();
1233
+ if (rect.width < 4 || rect.height < 4) continue;
1234
+ const list = bySource.get(target.original) ?? [];
1235
+ list.push({
1236
+ el: container,
1237
+ parent: pickParentContainer(container),
1238
+ name: componentName,
1239
+ top: rect.top + window.scrollY,
1240
+ left: rect.left + window.scrollX
1241
+ });
1242
+ bySource.set(target.original, list);
1243
+ break;
1244
+ }
1245
+ }
1204
1246
  const tagged = [];
1205
1247
  for (let sourceIndex = 0; sourceIndex < targets.length; sourceIndex++) {
1206
1248
  const source = targets[sourceIndex].original;
@@ -1227,6 +1269,94 @@ async function tagChangedComponentInstances(page, changedComponents, maxPerSourc
1227
1269
  { changed: normalized, maxPerSource }
1228
1270
  );
1229
1271
  }
1272
+ async function captureComponentInstances(afterPage, beforePage, changedComponents, capturePrefix, captureLabel, outputDir, contextBeforePath, contextAfterPath, results) {
1273
+ const deduped = Array.from(new Set(changedComponents.map(normalizePath)));
1274
+ const [afterInstances, beforeInstances] = await Promise.all([
1275
+ tagChangedComponentInstances(afterPage, deduped),
1276
+ tagChangedComponentInstances(beforePage, deduped)
1277
+ ]);
1278
+ logger.dim(` Component detection on ${captureLabel}: ${deduped.length} source(s), ${afterInstances.length} after / ${beforeInstances.length} before instance(s)`);
1279
+ const afterBySource = groupBySource(afterInstances);
1280
+ const beforeBySource = groupBySource(beforeInstances);
1281
+ for (const source of deduped) {
1282
+ const afterList = afterBySource.get(source) ?? [];
1283
+ const beforeList = beforeBySource.get(source) ?? [];
1284
+ const pairCount = Math.min(afterList.length, beforeList.length);
1285
+ if (pairCount === 0) {
1286
+ if (afterList.length > 0 || beforeList.length > 0) {
1287
+ logger.dim(` ${source}: ${afterList.length} after / ${beforeList.length} before (skipping unpaired)`);
1288
+ }
1289
+ continue;
1290
+ }
1291
+ for (let pairIndex = 0; pairIndex < pairCount; pairIndex++) {
1292
+ const afterInstance = afterList[pairIndex];
1293
+ const beforeInstance = beforeList[pairIndex];
1294
+ const sourceSlug = sanitizeComponentLabel(source) || "component";
1295
+ const itemSlug = `${sourceSlug}-${pairIndex + 1}`;
1296
+ const componentName = afterInstance.name || beforeInstance.name || source.split("/").pop() || source;
1297
+ const baseLabel = `${captureLabel} [${componentName} #${pairIndex + 1}]`;
1298
+ const parentPrefix = `${capturePrefix}~cmp.${itemSlug}~parent`;
1299
+ const parentBeforePath = join6(outputDir, `${parentPrefix}-before.png`);
1300
+ const parentAfterPath = join6(outputDir, `${parentPrefix}-after.png`);
1301
+ const [parentBeforeOk, parentAfterOk] = await Promise.all([
1302
+ captureByAttr(
1303
+ beforePage,
1304
+ "data-ab-parent-key",
1305
+ beforeInstance.parentKey,
1306
+ parentBeforePath
1307
+ ),
1308
+ captureByAttr(
1309
+ afterPage,
1310
+ "data-ab-parent-key",
1311
+ afterInstance.parentKey,
1312
+ parentAfterPath
1313
+ )
1314
+ ]);
1315
+ if (parentBeforeOk && parentAfterOk) {
1316
+ results.push({
1317
+ route: `${baseLabel} [parent]`,
1318
+ prefix: parentPrefix,
1319
+ beforePath: parentBeforePath,
1320
+ afterPath: parentAfterPath
1321
+ });
1322
+ }
1323
+ const componentPrefix = `${capturePrefix}~cmp.${itemSlug}~component`;
1324
+ const componentBeforePath = join6(outputDir, `${componentPrefix}-before.png`);
1325
+ const componentAfterPath = join6(outputDir, `${componentPrefix}-after.png`);
1326
+ const [componentBeforeOk, componentAfterOk] = await Promise.all([
1327
+ captureByAttr(
1328
+ beforePage,
1329
+ "data-ab-comp-key",
1330
+ beforeInstance.componentKey,
1331
+ componentBeforePath
1332
+ ),
1333
+ captureByAttr(
1334
+ afterPage,
1335
+ "data-ab-comp-key",
1336
+ afterInstance.componentKey,
1337
+ componentAfterPath
1338
+ )
1339
+ ]);
1340
+ if (componentBeforeOk && componentAfterOk) {
1341
+ results.push({
1342
+ route: `${baseLabel} [component]`,
1343
+ prefix: componentPrefix,
1344
+ beforePath: componentBeforePath,
1345
+ afterPath: componentAfterPath
1346
+ });
1347
+ }
1348
+ if (parentBeforeOk && parentAfterOk || componentBeforeOk && componentAfterOk) {
1349
+ const contextPrefix = `${capturePrefix}~cmp.${itemSlug}~context`;
1350
+ results.push({
1351
+ route: `${baseLabel} [context]`,
1352
+ prefix: contextPrefix,
1353
+ beforePath: contextBeforePath,
1354
+ afterPath: contextAfterPath
1355
+ });
1356
+ }
1357
+ }
1358
+ }
1359
+ }
1230
1360
  async function captureAutoSections(afterPage, beforePage, parentPrefix, parentLabel, outputDir, options, settle, results) {
1231
1361
  const sections = await detectSections(afterPage, options.maxSectionsPerRoute);
1232
1362
  if (sections.length === 0) return;
@@ -1323,94 +1453,17 @@ async function captureRoutes(tasks, beforeUrl, afterUrl, outputDir, options) {
1323
1453
  ]);
1324
1454
  results.push({ route: task.label, prefix: task.prefix, beforePath, afterPath });
1325
1455
  if ((task.changedComponents?.length ?? 0) > 0 && !task.actions && !task.selector) {
1326
- const changedComponents = Array.from(
1327
- new Set(task.changedComponents.map(normalizePath))
1456
+ await captureComponentInstances(
1457
+ afterPage,
1458
+ beforePage,
1459
+ task.changedComponents,
1460
+ task.prefix,
1461
+ task.label,
1462
+ outputDir,
1463
+ beforePath,
1464
+ afterPath,
1465
+ results
1328
1466
  );
1329
- const [afterInstances, beforeInstances] = await Promise.all([
1330
- tagChangedComponentInstances(afterPage, changedComponents),
1331
- tagChangedComponentInstances(beforePage, changedComponents)
1332
- ]);
1333
- logger.dim(` Component detection on ${task.route}: ${changedComponents.length} source(s), ${afterInstances.length} after / ${beforeInstances.length} before instance(s)`);
1334
- const afterBySource = groupBySource(afterInstances);
1335
- const beforeBySource = groupBySource(beforeInstances);
1336
- for (const source of changedComponents) {
1337
- const afterList = afterBySource.get(source) ?? [];
1338
- const beforeList = beforeBySource.get(source) ?? [];
1339
- const pairCount = Math.min(afterList.length, beforeList.length);
1340
- if (pairCount === 0) {
1341
- if (afterList.length > 0 || beforeList.length > 0) {
1342
- logger.dim(` ${source}: ${afterList.length} after / ${beforeList.length} before (skipping unpaired)`);
1343
- }
1344
- continue;
1345
- }
1346
- for (let pairIndex = 0; pairIndex < pairCount; pairIndex++) {
1347
- const afterInstance = afterList[pairIndex];
1348
- const beforeInstance = beforeList[pairIndex];
1349
- const sourceSlug = sanitizeComponentLabel(source) || "component";
1350
- const itemSlug = `${sourceSlug}-${pairIndex + 1}`;
1351
- const componentName = afterInstance.name || beforeInstance.name || source.split("/").pop() || source;
1352
- const baseLabel = `${task.label} [${componentName} #${pairIndex + 1}]`;
1353
- const parentPrefix = `${task.prefix}~cmp.${itemSlug}~parent`;
1354
- const parentBeforePath = join6(outputDir, `${parentPrefix}-before.png`);
1355
- const parentAfterPath = join6(outputDir, `${parentPrefix}-after.png`);
1356
- const [parentBeforeOk, parentAfterOk] = await Promise.all([
1357
- captureByAttr(
1358
- beforePage,
1359
- "data-ab-parent-key",
1360
- beforeInstance.parentKey,
1361
- parentBeforePath
1362
- ),
1363
- captureByAttr(
1364
- afterPage,
1365
- "data-ab-parent-key",
1366
- afterInstance.parentKey,
1367
- parentAfterPath
1368
- )
1369
- ]);
1370
- if (parentBeforeOk && parentAfterOk) {
1371
- results.push({
1372
- route: `${baseLabel} [parent]`,
1373
- prefix: parentPrefix,
1374
- beforePath: parentBeforePath,
1375
- afterPath: parentAfterPath
1376
- });
1377
- }
1378
- const componentPrefix = `${task.prefix}~cmp.${itemSlug}~component`;
1379
- const componentBeforePath = join6(outputDir, `${componentPrefix}-before.png`);
1380
- const componentAfterPath = join6(outputDir, `${componentPrefix}-after.png`);
1381
- const [componentBeforeOk, componentAfterOk] = await Promise.all([
1382
- captureByAttr(
1383
- beforePage,
1384
- "data-ab-comp-key",
1385
- beforeInstance.componentKey,
1386
- componentBeforePath
1387
- ),
1388
- captureByAttr(
1389
- afterPage,
1390
- "data-ab-comp-key",
1391
- afterInstance.componentKey,
1392
- componentAfterPath
1393
- )
1394
- ]);
1395
- if (componentBeforeOk && componentAfterOk) {
1396
- results.push({
1397
- route: `${baseLabel} [component]`,
1398
- prefix: componentPrefix,
1399
- beforePath: componentBeforePath,
1400
- afterPath: componentAfterPath
1401
- });
1402
- }
1403
- if (parentBeforeOk && parentAfterOk || componentBeforeOk && componentAfterOk) {
1404
- const contextPrefix = `${task.prefix}~cmp.${itemSlug}~context`;
1405
- results.push({
1406
- route: `${baseLabel} [context]`,
1407
- prefix: contextPrefix,
1408
- beforePath,
1409
- afterPath
1410
- });
1411
- }
1412
- }
1413
- }
1414
1467
  }
1415
1468
  if (options.autoSections && !task.actions && !task.selector && !task.skipAutoSections) {
1416
1469
  await captureAutoSections(
@@ -1470,6 +1523,19 @@ async function captureRoutes(tasks, beforeUrl, afterUrl, outputDir, options) {
1470
1523
  beforePath: tabBeforePath,
1471
1524
  afterPath: tabAfterPath
1472
1525
  });
1526
+ if ((task.changedComponents?.length ?? 0) > 0) {
1527
+ await captureComponentInstances(
1528
+ afterPage,
1529
+ beforePage,
1530
+ task.changedComponents,
1531
+ tabPrefix,
1532
+ tabLabel,
1533
+ outputDir,
1534
+ tabBeforePath,
1535
+ tabAfterPath,
1536
+ results
1537
+ );
1538
+ }
1473
1539
  if (options.autoSections && !task.skipAutoSections) {
1474
1540
  await captureAutoSections(
1475
1541
  afterPage,
@@ -1598,79 +1664,8 @@ async function compareScreenshots(captures, outputDir, threshold = 0.1, options)
1598
1664
  }
1599
1665
 
1600
1666
  // src/stages/report.ts
1601
- import { writeFileSync as writeFileSync2 } from "fs";
1602
- import { join as join8 } from "path";
1603
1667
  import { execSync as execSync4 } from "child_process";
1604
1668
 
1605
- // src/templates/report.html.ts
1606
- import { readFileSync as readFileSync6 } from "fs";
1607
- function toBase64(filePath) {
1608
- return readFileSync6(filePath).toString("base64");
1609
- }
1610
- function imgSrc(filePath) {
1611
- return `data:image/png;base64,${toBase64(filePath)}`;
1612
- }
1613
- function generateReportHtml(results, outputDir) {
1614
- const changed = results.filter((r) => r.changed);
1615
- const unchanged = results.filter((r) => !r.changed);
1616
- const card = (r) => {
1617
- return `
1618
- <div class="card ${r.changed ? "changed" : "unchanged"}">
1619
- <div class="card-header">
1620
- <span class="route">${r.route}</span>
1621
- <span class="badge ${r.changed ? "badge-changed" : "badge-unchanged"}">
1622
- ${r.changed ? `${r.diffPercentage.toFixed(2)}% changed` : "No change"}
1623
- </span>
1624
- </div>
1625
- <div class="images">
1626
- <div class="img-col">
1627
- <div class="label">Before</div>
1628
- <img src="${imgSrc(r.beforePath)}" alt="Before" />
1629
- </div>
1630
- <div class="img-col">
1631
- <div class="label">After</div>
1632
- <img src="${imgSrc(r.afterPath)}" alt="After" />
1633
- </div>
1634
- </div>
1635
- </div>`;
1636
- };
1637
- return `<!DOCTYPE html>
1638
- <html lang="en">
1639
- <head>
1640
- <meta charset="utf-8" />
1641
- <meta name="viewport" content="width=device-width, initial-scale=1" />
1642
- <title>afterbefore Report</title>
1643
- <style>
1644
- * { box-sizing: border-box; margin: 0; padding: 0; }
1645
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f9fafb; color: #111827; padding: 24px; }
1646
- h1 { font-size: 24px; margin-bottom: 8px; }
1647
- .summary { color: #6b7280; margin-bottom: 24px; font-size: 14px; }
1648
- .grid { display: grid; grid-template-columns: 1fr; gap: 24px; }
1649
- .card { background: #fff; border-radius: 8px; border: 1px solid #e5e7eb; overflow: hidden; }
1650
- .card.changed { border-color: #fbbf24; }
1651
- .card-header { display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; border-bottom: 1px solid #e5e7eb; }
1652
- .route { font-weight: 600; font-size: 16px; font-family: monospace; }
1653
- .badge { font-size: 12px; padding: 2px 8px; border-radius: 9999px; font-weight: 500; }
1654
- .badge-changed { background: #fef3c7; color: #92400e; }
1655
- .badge-unchanged { background: #d1fae5; color: #065f46; }
1656
- .images { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; background: #e5e7eb; }
1657
- .img-col { background: #fff; padding: 8px; }
1658
- .img-col img { width: 100%; height: auto; display: block; border-radius: 4px; }
1659
- .label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: #6b7280; margin-bottom: 4px; font-weight: 600; }
1660
- .section-title { font-size: 18px; font-weight: 600; margin: 24px 0 12px; }
1661
- </style>
1662
- </head>
1663
- <body>
1664
- <h1>afterbefore Report</h1>
1665
- <p class="summary">${results.length} route(s) captured, ${changed.length} with visual changes.</p>
1666
-
1667
- ${changed.length > 0 ? `<h2 class="section-title">Changed (${changed.length})</h2><div class="grid">${changed.map(card).join("")}</div>` : ""}
1668
- ${unchanged.length > 0 ? `<h2 class="section-title">Unchanged (${unchanged.length})</h2><div class="grid">${unchanged.map(card).join("")}</div>` : ""}
1669
-
1670
- </body>
1671
- </html>`;
1672
- }
1673
-
1674
1669
  // src/templates/summary.md.ts
1675
1670
  function generateSummaryMd(results, gitDiff, options) {
1676
1671
  const includeFilePaths = options?.includeFilePaths ?? true;
@@ -1768,15 +1763,6 @@ function postOrUpdateComment(prNumber, body) {
1768
1763
  }
1769
1764
  }
1770
1765
  async function generateReport(results, outputDir, options) {
1771
- await ensureDir(outputDir);
1772
- const summaryMd = generateSummaryMd(results);
1773
- const summaryPath = join8(outputDir, "summary.md");
1774
- writeFileSync2(summaryPath, summaryMd, "utf-8");
1775
- logger.success(`Written summary to ${summaryPath}`);
1776
- const reportHtml = generateReportHtml(results, outputDir);
1777
- const indexPath = join8(outputDir, "index.html");
1778
- writeFileSync2(indexPath, reportHtml, "utf-8");
1779
- logger.success(`Written report to ${indexPath}`);
1780
1766
  if (options.post) {
1781
1767
  const prNumber = findPrNumber();
1782
1768
  if (!prNumber) {
@@ -1868,17 +1854,13 @@ function expandRoutes(routes, config, routeComponentMap) {
1868
1854
  }
1869
1855
  return tasks;
1870
1856
  }
1871
- function openInBrowser(filePath) {
1872
- const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
1873
- exec(`${cmd} "${filePath}"`);
1874
- }
1875
1857
  async function runPipeline(options) {
1876
1858
  const { base, output, post, cwd } = options;
1877
1859
  const sessionName = generateSessionName(cwd);
1878
1860
  const outputDir = resolve4(cwd, output, sessionName);
1879
1861
  const startTime = Date.now();
1880
1862
  try {
1881
- const version = true ? "0.1.6" : "dev";
1863
+ const version = true ? "0.1.7" : "dev";
1882
1864
  console.log(`
1883
1865
  afterbefore v${version} \xB7 Comparing against ${base}
1884
1866
  `);
@@ -2011,10 +1993,6 @@ afterbefore v${version} \xB7 Comparing against ${base}
2011
1993
  logger.success(
2012
1994
  `Done in ${elapsed}s \u2014 ${results.length} route(s) captured, ${changedCount} with visual changes`
2013
1995
  );
2014
- logger.dim(` Report: ${outputDir}/index.html`);
2015
- if (options.open) {
2016
- openInBrowser(resolve4(outputDir, "index.html"));
2017
- }
2018
1996
  } finally {
2019
1997
  try {
2020
1998
  logger.writeLogFile(resolve4(outputDir, "debug.log"));
@@ -2028,7 +2006,7 @@ afterbefore v${version} \xB7 Comparing against ${base}
2028
2006
  var program = new Command();
2029
2007
  program.name("afterbefore").description(
2030
2008
  "Automatic before/after screenshot capture for PRs. Git diff is the config."
2031
- ).version("0.1.6").option("--base <ref>", "Base branch or ref to compare against", "main").option("--output <dir>", "Output directory for screenshots", ".afterbefore").option("--post", "Post results as a PR comment via gh CLI", false).option(
2009
+ ).version("0.1.7").option("--base <ref>", "Base branch or ref to compare against", "main").option("--output <dir>", "Output directory for screenshots", ".afterbefore").option("--post", "Post results as a PR comment via gh CLI", false).option(
2032
2010
  "--threshold <percent>",
2033
2011
  "Diff threshold percentage (changes below this are ignored)",
2034
2012
  "0.1"
@@ -2036,7 +2014,7 @@ program.name("afterbefore").description(
2036
2014
  "--max-routes <count>",
2037
2015
  "Maximum routes to capture (0 = unlimited)",
2038
2016
  "6"
2039
- ).option("--open", "Auto-open the HTML report in your browser", false).option("--width <pixels>", "Viewport width", "1280").option("--height <pixels>", "Viewport height", "720").option("--device <name>", 'Playwright device descriptor (e.g. "iPhone 14")').option("--delay <ms>", "Extra wait time (ms) after page load", "0").option("--no-auto-tabs", "Disable auto-detection of ARIA tab states").option("--max-tabs <count>", "Max auto-detected tabs per route", "5").option("--auto-sections", "Auto-detect and capture heading-labeled sections").option("--max-sections <count>", "Max auto-detected sections per page state", "10").action(async (opts) => {
2017
+ ).option("--width <pixels>", "Viewport width", "1280").option("--height <pixels>", "Viewport height", "720").option("--device <name>", 'Playwright device descriptor (e.g. "iPhone 14")').option("--delay <ms>", "Extra wait time (ms) after page load", "0").option("--no-auto-tabs", "Disable auto-detection of ARIA tab states").option("--max-tabs <count>", "Max auto-detected tabs per route", "5").option("--auto-sections", "Auto-detect and capture heading-labeled sections").option("--max-sections <count>", "Max auto-detected sections per page state", "10").action(async (opts) => {
2040
2018
  const cwd = process.cwd();
2041
2019
  if (!isGitRepo(cwd)) {
2042
2020
  logger.error("Not a git repository. Run this from inside a git repo.");
@@ -2048,7 +2026,6 @@ program.name("afterbefore").description(
2048
2026
  post: opts.post,
2049
2027
  threshold: parseFloat(opts.threshold),
2050
2028
  maxRoutes: parseInt(opts.maxRoutes, 10),
2051
- open: opts.open,
2052
2029
  width: parseInt(opts.width, 10),
2053
2030
  height: parseInt(opts.height, 10),
2054
2031
  device: opts.device,