ptywright 0.6.4 → 0.6.5

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.
@@ -843,6 +843,186 @@ function createAgentTemplateSpec(flavor) {
843
843
  };
844
844
  }
845
845
  //#endregion
846
+ //#region src/agent/report_theme_css.ts
847
+ const TOKENS_LIGHT = `
848
+ --canvas: #f5f7fa;
849
+ --panel: #ffffff;
850
+ --raised: #fbfcfe;
851
+ --line: #e4e8ee;
852
+ --line-strong: #ccd3dd;
853
+ --ink: #131822;
854
+ --muted: #5a6473;
855
+ --faint: #8b95a3;
856
+ --accent: #0a7fd4;
857
+ --accent-ink: #ffffff;
858
+ --accent-soft: rgba(10, 127, 212, 0.12);
859
+ --pass: #18794e;
860
+ --pass-soft: rgba(24, 121, 78, 0.12);
861
+ --fail: #d12d4f;
862
+ --fail-soft: rgba(209, 45, 79, 0.1);
863
+ --changed: #ad6800;
864
+ --changed-soft: rgba(173, 104, 0, 0.12);
865
+ --shadow: 0 1px 2px rgba(15, 23, 42, 0.06), 0 8px 24px -16px rgba(15, 23, 42, 0.24);`;
866
+ const TOKENS_DARK = `
867
+ --canvas: #0a0e16;
868
+ --panel: #111824;
869
+ --raised: #18212f;
870
+ --line: rgba(148, 163, 184, 0.16);
871
+ --line-strong: rgba(148, 163, 184, 0.3);
872
+ --ink: #e7edf6;
873
+ --muted: #9aa7b8;
874
+ --faint: #687486;
875
+ --accent: #58b6ff;
876
+ --accent-ink: #04101d;
877
+ --accent-soft: rgba(88, 182, 255, 0.16);
878
+ --pass: #46cd7c;
879
+ --pass-soft: rgba(70, 205, 124, 0.16);
880
+ --fail: #ff6b81;
881
+ --fail-soft: rgba(255, 107, 129, 0.16);
882
+ --changed: #f5b440;
883
+ --changed-soft: rgba(245, 180, 64, 0.16);
884
+ --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 18px 48px -22px rgba(0, 0, 0, 0.7);`;
885
+ /** Design tokens only (no component rules); reusable when a page needs just the palette. */
886
+ function renderReportThemeTokens() {
887
+ return ` :root {
888
+ color-scheme: light dark;${TOKENS_LIGHT}
889
+ --radius: 14px;
890
+ --radius-sm: 9px;
891
+ --radius-xs: 6px;
892
+ --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
893
+ --font-mono: ui-monospace, "SFMono-Regular", "JetBrains Mono", Menlo, Monaco, Consolas, "Liberation Mono", monospace;
894
+ }
895
+ @media (prefers-color-scheme: dark) {
896
+ :root {${TOKENS_DARK}
897
+ }
898
+ }
899
+ :root[data-theme="light"] {${TOKENS_LIGHT}
900
+ }
901
+ :root[data-theme="dark"] {${TOKENS_DARK}
902
+ }`;
903
+ }
904
+ /** Full shared sheet: tokens + reset + primitive components. */
905
+ function renderReportThemeCss() {
906
+ return `${renderReportThemeTokens()}
907
+
908
+ * { box-sizing: border-box; }
909
+ body {
910
+ margin: 0;
911
+ background: var(--canvas);
912
+ color: var(--ink);
913
+ font-family: var(--font-sans);
914
+ font-size: 14px;
915
+ line-height: 1.5;
916
+ -webkit-font-smoothing: antialiased;
917
+ text-rendering: optimizeLegibility;
918
+ }
919
+
920
+ /* Top status rail — a thin build-status bar coloured by the overall result. */
921
+ .rail {
922
+ position: sticky;
923
+ top: 0;
924
+ z-index: 30;
925
+ height: 3px;
926
+ background: var(--line-strong);
927
+ }
928
+ .rail.pass { background: linear-gradient(90deg, var(--pass), color-mix(in oklab, var(--pass) 55%, var(--accent))); }
929
+ .rail.fail { background: linear-gradient(90deg, var(--fail), color-mix(in oklab, var(--fail) 60%, var(--changed))); }
930
+
931
+ .shell {
932
+ width: min(1140px, 100% - 40px);
933
+ margin-inline: auto;
934
+ padding: 30px 0 64px;
935
+ }
936
+ @media (max-width: 640px) {
937
+ .shell { width: min(100% - 24px, 1140px); padding-top: 18px; }
938
+ }
939
+
940
+ h1 { margin: 0; font-size: 25px; line-height: 1.15; letter-spacing: -0.02em; font-weight: 680; }
941
+ h2 { margin: 0; font-size: 14px; font-weight: 640; letter-spacing: 0.01em; color: var(--ink); }
942
+ a { color: var(--accent); text-decoration: none; }
943
+ a:hover { text-decoration: underline; }
944
+ code, pre, .mono { font-family: var(--font-mono); }
945
+
946
+ /* Status line: a coloured dot + bold label (replaces fat pills). */
947
+ .statusline { display: inline-flex; align-items: center; gap: 8px; font-weight: 660; font-size: 13px; letter-spacing: 0.04em; }
948
+ .statusline .dot { width: 9px; height: 9px; border-radius: 50%; background: var(--muted); box-shadow: 0 0 0 4px color-mix(in oklab, var(--muted) 22%, transparent); }
949
+ .statusline.pass { color: var(--pass); }
950
+ .statusline.pass .dot { background: var(--pass); box-shadow: 0 0 0 4px var(--pass-soft); }
951
+ .statusline.fail { color: var(--fail); }
952
+ .statusline.fail .dot { background: var(--fail); box-shadow: 0 0 0 4px var(--fail-soft); }
953
+
954
+ /* Tiny inline status dot for dense rows. */
955
+ .dot-sm { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--muted); flex: none; }
956
+ .dot-sm.pass { background: var(--pass); }
957
+ .dot-sm.fail { background: var(--fail); }
958
+
959
+ /* Chips: low-key metadata tags. */
960
+ .chip {
961
+ display: inline-flex; align-items: center; gap: 6px; min-height: 26px;
962
+ padding: 0 10px; border: 1px solid var(--line); border-radius: 999px;
963
+ background: var(--panel); color: var(--muted); font-size: 12px; white-space: nowrap;
964
+ }
965
+ .chip.mono { font-family: var(--font-mono); }
966
+ .chip-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
967
+
968
+ /* Stat strip: compact metrics with tabular numerals + hairline dividers. */
969
+ .statstrip {
970
+ display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
971
+ border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden; background: var(--panel);
972
+ }
973
+ .statstrip .stat { padding: 12px 16px; border-left: 1px solid var(--line); }
974
+ .statstrip .stat:first-child { border-left: 0; }
975
+ .stat .num { display: block; font-size: 21px; font-weight: 680; line-height: 1.1; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
976
+ .stat .lbl { display: block; margin-top: 3px; color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; }
977
+ .stat.pass .num { color: var(--pass); }
978
+ .stat.fail .num { color: var(--fail); }
979
+ @media (max-width: 560px) {
980
+ .statstrip { grid-auto-flow: row; grid-auto-columns: auto; }
981
+ .statstrip .stat { border-left: 0; border-top: 1px solid var(--line); }
982
+ .statstrip .stat:first-child { border-top: 0; }
983
+ }
984
+
985
+ /* Pass-rate meter: a segmented progress bar. */
986
+ .meter { height: 8px; border-radius: 999px; background: var(--line); overflow: hidden; }
987
+ .meter > i { display: block; height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--pass), color-mix(in oklab, var(--pass) 60%, var(--accent))); }
988
+ .meter.has-fail > i { background: linear-gradient(90deg, var(--fail), var(--changed)); }
989
+
990
+ /* Panel: the standard surface card. */
991
+ .panel { border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel); padding: 18px; box-shadow: var(--shadow); }
992
+ .panel + .panel { margin-top: 16px; }
993
+ .panel > h2 { margin-bottom: 14px; }
994
+
995
+ /* Path display: muted parent dir (middle-ellipsis) + bright basename. */
996
+ .path { display: inline-flex; max-width: 100%; min-width: 0; align-items: baseline; font-family: var(--font-mono); font-size: 12.5px; }
997
+ .path .dir { min-width: 0; color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; direction: rtl; }
998
+ .path .base { color: var(--muted); font-weight: 600; flex: none; }
999
+
1000
+ /* Code block with one-click copy. */
1001
+ .codeblock { position: relative; border: 1px solid var(--line); border-radius: var(--radius-sm); background: color-mix(in oklab, var(--canvas) 55%, var(--panel)); }
1002
+ .codeblock > .lbl { display: block; padding: 8px 12px 0; color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; font-weight: 640; }
1003
+ .codeblock > pre { margin: 0; padding: 8px 44px 12px 12px; overflow-x: auto; font-size: 12.5px; line-height: 1.55; color: var(--ink); white-space: pre-wrap; word-break: break-word; }
1004
+ .copybtn {
1005
+ position: absolute; top: 8px; right: 8px; width: 28px; height: 28px;
1006
+ display: inline-flex; align-items: center; justify-content: center;
1007
+ border: 1px solid var(--line); border-radius: var(--radius-xs); background: var(--panel);
1008
+ color: var(--muted); cursor: pointer; font-size: 13px; line-height: 1; transition: all 0.12s ease;
1009
+ }
1010
+ .copybtn:hover { color: var(--ink); border-color: var(--line-strong); }
1011
+ .copybtn.copied { color: var(--pass); border-color: color-mix(in oklab, var(--pass) 45%, var(--line)); }
1012
+
1013
+ /* Action links / buttons. */
1014
+ .btn {
1015
+ display: inline-flex; align-items: center; gap: 5px; min-height: 28px; padding: 0 11px;
1016
+ border: 1px solid var(--line); border-radius: var(--radius-xs); background: var(--panel);
1017
+ color: var(--ink); font-size: 12.5px; font-weight: 560; cursor: pointer; transition: all 0.12s ease;
1018
+ }
1019
+ .btn:hover { border-color: var(--accent); color: var(--accent); text-decoration: none; }
1020
+ .btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
1021
+ .btn.primary:hover { color: var(--accent-ink); filter: brightness(1.05); }
1022
+
1023
+ :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }`;
1024
+ }
1025
+ //#endregion
846
1026
  //#region src/agent/browser.ts
847
1027
  async function launchAgentBrowser(args) {
848
1028
  let lastError;
@@ -1236,20 +1416,15 @@ function renderRawArtifactViewerCss() {
1236
1416
  width: max-content;
1237
1417
  min-height: 100%;
1238
1418
  margin: 0;
1239
- background:
1240
- linear-gradient(180deg, color-mix(in srgb, #162033 92%, black), #0c111d);
1241
- color: #e6edf7;
1242
- font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;
1419
+ background: var(--panel);
1420
+ color: var(--ink);
1421
+ font-family: var(--font-mono);
1243
1422
  font-size: 13px;
1244
1423
  line-height: 1.45;
1245
1424
  overflow: visible;
1246
1425
  padding: 12px;
1247
1426
  white-space: pre;
1248
1427
  }
1249
- .viewer-page[data-theme="light"] .raw-artifact-text {
1250
- background: #ffffff;
1251
- color: #4c4f69;
1252
- }
1253
1428
  ${renderReportViewportPanCss()}`;
1254
1429
  }
1255
1430
  function renderRawArtifactViewerScript() {
@@ -1290,32 +1465,15 @@ function renderArtifactViewerFragment(args) {
1290
1465
  //#endregion
1291
1466
  //#region src/agent/report_artifact_viewer_shell_css.ts
1292
1467
  function renderArtifactViewerShellCss() {
1293
- return ` :root {
1294
- color-scheme: dark;
1295
- --bg: #080d16;
1296
- --panel: #0c111d;
1297
- --line: rgba(148, 163, 184, 0.26);
1298
- --ink: #e6edf7;
1299
- --muted: #91a0b8;
1300
- --focus: #79c0ff;
1301
- font-family:
1302
- ui-sans-serif,
1303
- system-ui,
1304
- -apple-system,
1305
- BlinkMacSystemFont,
1306
- "Segoe UI",
1307
- sans-serif;
1308
- }
1309
- * {
1310
- box-sizing: border-box;
1311
- }
1468
+ return `${renderReportThemeCss()}
1469
+
1312
1470
  html,
1313
1471
  body {
1314
1472
  width: 100%;
1315
1473
  height: 100%;
1316
1474
  margin: 0;
1317
1475
  overflow: hidden;
1318
- background: var(--bg);
1476
+ background: var(--canvas);
1319
1477
  color: var(--ink);
1320
1478
  }
1321
1479
  .viewer-page {
@@ -1333,7 +1491,7 @@ function renderArtifactViewerShellCss() {
1333
1491
  align-items: center;
1334
1492
  min-width: 0;
1335
1493
  border-bottom: 1px solid var(--line);
1336
- background: color-mix(in srgb, var(--panel) 88%, black);
1494
+ background: var(--raised);
1337
1495
  padding: 10px 12px;
1338
1496
  }
1339
1497
  .viewer-title {
@@ -1341,7 +1499,8 @@ function renderArtifactViewerShellCss() {
1341
1499
  margin-right: auto;
1342
1500
  overflow-wrap: anywhere;
1343
1501
  font-size: 14px;
1344
- font-weight: 700;
1502
+ font-weight: 640;
1503
+ color: var(--ink);
1345
1504
  }
1346
1505
  .viewer-link,
1347
1506
  .viewer-pill {
@@ -1354,10 +1513,15 @@ function renderArtifactViewerShellCss() {
1354
1513
  color: var(--muted);
1355
1514
  font-size: 12px;
1356
1515
  text-decoration: none;
1516
+ transition: all 0.12s ease;
1357
1517
  }
1358
1518
  .viewer-link {
1359
- color: var(--focus);
1360
- font-weight: 700;
1519
+ color: var(--accent);
1520
+ font-weight: 640;
1521
+ }
1522
+ .viewer-link:hover {
1523
+ border-color: var(--accent);
1524
+ background: var(--accent-soft);
1361
1525
  }
1362
1526
  .viewer-stage {
1363
1527
  display: grid;
@@ -1368,9 +1532,7 @@ function renderArtifactViewerShellCss() {
1368
1532
  justify-items: center;
1369
1533
  align-content: start;
1370
1534
  overflow: hidden;
1371
- background:
1372
- radial-gradient(circle at top left, rgba(121, 192, 255, 0.1), transparent 32%),
1373
- #060a13;
1535
+ background: var(--canvas);
1374
1536
  padding: 14px;
1375
1537
  }
1376
1538
  .viewer-viewport {
@@ -1381,10 +1543,10 @@ function renderArtifactViewerShellCss() {
1381
1543
  overflow: auto;
1382
1544
  overscroll-behavior: contain;
1383
1545
  border: 0;
1384
- border-radius: 8px;
1385
- background: #0c111d;
1546
+ border-radius: var(--radius-sm);
1547
+ background: var(--panel);
1386
1548
  outline: 1px solid var(--line);
1387
- box-shadow: 0 18px 52px rgba(0, 0, 0, 0.34);
1549
+ box-shadow: var(--shadow);
1388
1550
  }
1389
1551
  .viewer-viewport[data-mobile="true"] {
1390
1552
  width: min(var(--config-viewport-width), 100%);
@@ -1397,22 +1559,7 @@ function renderArtifactViewerShellCss() {
1397
1559
  width: 100%;
1398
1560
  height: 100%;
1399
1561
  border: 0;
1400
- background: #0c111d;
1401
- }
1402
- .viewer-page[data-theme="light"] {
1403
- --bg: #f8fafc;
1404
- --panel: #ffffff;
1405
- --line: rgba(15, 23, 42, 0.14);
1406
- --ink: #0f172a;
1407
- --muted: #64748b;
1408
- --focus: #1e66f5;
1409
- }
1410
- .viewer-page[data-theme="light"] .viewer-stage {
1411
- background: #f8fafc;
1412
- }
1413
- .viewer-page[data-theme="light"] .viewer-viewport,
1414
- .viewer-page[data-theme="light"] .dom-viewer-frame {
1415
- background: #ffffff;
1562
+ background: var(--panel);
1416
1563
  }
1417
1564
  @media (max-width: 720px) {
1418
1565
  .viewer-toolbar {
@@ -2171,36 +2318,114 @@ function readArtifactText(path) {
2171
2318
  //#endregion
2172
2319
  //#region src/agent/report_index_artifacts.ts
2173
2320
  function renderAgentReportArtifacts(args) {
2174
- return args.artifacts.map((artifact) => renderArtifactRow({
2175
- artifact,
2321
+ if (args.artifacts.length === 0) return "<p>No artifacts were captured.</p>";
2322
+ const byViewport = /* @__PURE__ */ new Map();
2323
+ for (const artifact of args.artifacts) {
2324
+ const list = byViewport.get(artifact.viewport) ?? [];
2325
+ list.push(artifact);
2326
+ byViewport.set(artifact.viewport, list);
2327
+ }
2328
+ return Array.from(byViewport.entries()).map(([viewport, artifacts]) => renderViewportGroup({
2329
+ viewport,
2330
+ artifacts,
2176
2331
  artifactsDir: args.artifactsDir,
2177
2332
  reportPath: args.reportPath
2178
- })).join("\n") || "<p>No artifacts were captured.</p>";
2333
+ })).join("\n");
2334
+ }
2335
+ function renderViewportGroup(args) {
2336
+ const { viewport, artifacts, artifactsDir, reportPath } = args;
2337
+ const byName = /* @__PURE__ */ new Map();
2338
+ for (const artifact of artifacts) {
2339
+ const list = byName.get(artifact.name) ?? [];
2340
+ list.push(artifact);
2341
+ byName.set(artifact.name, list);
2342
+ }
2343
+ const artifactRows = Array.from(byName.entries()).map(([name, artifacts]) => renderArtifactGroup({
2344
+ name,
2345
+ artifacts,
2346
+ artifactsDir,
2347
+ reportPath
2348
+ })).join("\n");
2349
+ return `<div class="viewport-group">
2350
+ <h3 class="viewport-title">${escapeHtml(viewport)}</h3>
2351
+ <div class="viewport-artifacts">
2352
+ ${artifactRows}
2353
+ </div>
2354
+ </div>`;
2355
+ }
2356
+ function renderArtifactGroup(args) {
2357
+ const { name, artifacts, artifactsDir, reportPath } = args;
2358
+ const chips = artifacts.map((artifact) => {
2359
+ const state = artifact.ok ? "pass" : "fail";
2360
+ const viewerPath = artifactViewerPath(artifact);
2361
+ return `<a href="${escapeAttribute(viewerPath ? relativeHref(reportPath, viewerPath, artifactsDir) : relativeHref(reportPath, artifact.path, artifactsDir))}" class="artifact-chip ${state}" title="${escapeHtml(artifact.kind)}">
2362
+ ${escapeHtml(artifact.kind)}
2363
+ </a>`;
2364
+ }).join("");
2365
+ const failedArtifact = artifacts.find((a) => !a.ok);
2366
+ const detailView = failedArtifact ? renderFailedArtifactDetail({
2367
+ artifact: failedArtifact,
2368
+ artifactsDir,
2369
+ reportPath
2370
+ }) : "";
2371
+ return `<div class="artifact-group">
2372
+ <div class="artifact-group-header">
2373
+ <span class="artifact-name">${escapeHtml(name)}</span>
2374
+ <div class="artifact-chips">
2375
+ ${chips}
2376
+ </div>
2377
+ </div>
2378
+ ${detailView}
2379
+ </div>`;
2179
2380
  }
2180
- function renderArtifactRow(args) {
2381
+ function renderFailedArtifactDetail(args) {
2181
2382
  const { artifact, artifactsDir, reportPath } = args;
2182
- const state = artifact.ok ? "pass" : "fail";
2183
- const href = relativeHref(reportPath, artifact.path, artifactsDir);
2383
+ const currentHref = relativeHref(reportPath, artifact.path, artifactsDir);
2184
2384
  const baselineHref = artifact.baselinePath ? relativeHref(reportPath, artifact.baselinePath, artifactsDir) : "";
2185
2385
  const diffHref = artifact.diffPath ? relativeHref(reportPath, artifact.diffPath, artifactsDir) : "";
2186
- const viewerPath = artifactViewerPath(artifact);
2187
- const viewerHref = viewerPath ? relativeHref(reportPath, viewerPath, artifactsDir) : "";
2188
- return `<article class="artifact">
2189
- <div class="artifact-summary">
2190
- <span class="badge ${state}">${state}</span>
2191
- <div class="artifact-meta">
2192
- <div class="artifact-links">
2193
- <a href="${escapeAttribute(viewerHref || href)}">${escapeHtml(artifact.kind)}</a>
2194
- ${viewerHref ? `<a href="${escapeAttribute(href)}">raw</a>` : ""}
2195
- ${baselineHref ? `<a href="${escapeAttribute(baselineHref)}">baseline</a>` : ""}
2196
- ${diffHref ? `<a href="${escapeAttribute(diffHref)}">diff</a>` : ""}
2386
+ const isDom = artifact.kind === "dom";
2387
+ const isScreenshot = artifact.kind === "screenshot";
2388
+ if (isDom || isScreenshot) return `<div class="artifact-detail">
2389
+ <div class="artifact-comparison">
2390
+ ${baselineHref ? `<div class="artifact-preview">
2391
+ <div class="artifact-preview-label">Baseline</div>
2392
+ <div class="device-frame">
2393
+ <iframe src="${escapeAttribute(baselineHref)}" loading="lazy" sandbox="allow-same-origin"></iframe>
2394
+ </div>
2395
+ </div>` : ""}
2396
+ <div class="artifact-preview">
2397
+ <div class="artifact-preview-label">Current</div>
2398
+ <div class="device-frame ${artifact.ok ? "" : "failed"}">
2399
+ <iframe src="${escapeAttribute(currentHref)}" loading="lazy" sandbox="allow-same-origin"></iframe>
2400
+ </div>
2197
2401
  </div>
2198
- <div><code>${escapeHtml(artifact.viewport)} / ${escapeHtml(artifact.name)}</code></div>
2199
- ${artifact.error ? `<div><code>${escapeHtml(artifact.error)}</code></div>` : ""}
2402
+ ${diffHref ? `<div class="artifact-preview">
2403
+ <div class="artifact-preview-label">Diff</div>
2404
+ <div class="device-frame diff">
2405
+ <iframe src="${escapeAttribute(diffHref)}" loading="lazy" sandbox="allow-same-origin"></iframe>
2406
+ </div>
2407
+ </div>` : ""}
2408
+ </div>
2409
+ ${artifact.error ? `<div class="artifact-error">
2410
+ <strong>Error:</strong> ${escapeHtml(artifact.error)}
2411
+ </div>` : ""}
2412
+ <div class="artifact-meta-row">
2413
+ <code class="artifact-hash">${escapeHtml(artifact.hash ?? "")}</code>
2200
2414
  </div>
2415
+ </div>`;
2416
+ return `<div class="artifact-detail">
2417
+ <div class="artifact-links">
2418
+ <a href="${escapeAttribute(currentHref)}" class="btn">View Current</a>
2419
+ ${baselineHref ? `<a href="${escapeAttribute(baselineHref)}" class="btn">View Baseline</a>` : ""}
2420
+ ${diffHref ? `<a href="${escapeAttribute(diffHref)}" class="btn">View Diff</a>` : ""}
2421
+ </div>
2422
+ ${artifact.error ? `<div class="artifact-error">
2423
+ <strong>Error:</strong> ${escapeHtml(artifact.error)}
2424
+ </div>` : ""}
2425
+ <div class="artifact-meta-row">
2201
2426
  <code class="artifact-hash">${escapeHtml(artifact.hash ?? "")}</code>
2202
2427
  </div>
2203
- </article>`;
2428
+ </div>`;
2204
2429
  }
2205
2430
  //#endregion
2206
2431
  //#region src/agent/report_index_commands.ts
@@ -2213,109 +2438,213 @@ function renderAgentReportCommandBlock(label, argv) {
2213
2438
  //#endregion
2214
2439
  //#region src/agent/report_index_artifacts_css.ts
2215
2440
  function renderAgentReportArtifactsCss() {
2216
- return ` .artifacts {
2217
- display: grid;
2218
- gap: 14px;
2441
+ return ` /* Viewport grouping */
2442
+ .viewport-group {
2443
+ margin-bottom: 32px;
2219
2444
  min-width: 0;
2220
2445
  }
2221
- .artifact {
2446
+ .viewport-group:last-child {
2447
+ margin-bottom: 0;
2448
+ }
2449
+ .viewport-title {
2450
+ margin: 0 0 16px;
2451
+ font-size: 18px;
2452
+ font-weight: 660;
2453
+ color: var(--ink);
2454
+ padding-bottom: 10px;
2455
+ border-bottom: 2px solid var(--line);
2456
+ overflow-wrap: break-word;
2457
+ word-break: break-word;
2458
+ }
2459
+ .viewport-artifacts {
2222
2460
  display: grid;
2223
- gap: 12px;
2461
+ gap: 16px;
2462
+ min-width: 0;
2463
+ }
2464
+
2465
+ /* Artifact group (by name: status, ready, etc.) */
2466
+ .artifact-group {
2224
2467
  border: 1px solid var(--line);
2225
- border-radius: 8px;
2226
- padding: 12px;
2468
+ border-radius: var(--radius);
2227
2469
  background: var(--panel);
2470
+ overflow: hidden;
2471
+ box-shadow: var(--shadow);
2228
2472
  min-width: 0;
2229
2473
  }
2230
- .artifact-summary {
2231
- display: grid;
2232
- grid-template-columns: auto minmax(0, 1fr) minmax(0, auto);
2233
- gap: 14px;
2234
- align-items: start;
2474
+ .artifact-group-header {
2475
+ display: flex;
2476
+ align-items: center;
2477
+ justify-content: space-between;
2478
+ gap: 12px;
2479
+ padding: 14px 16px;
2480
+ background: var(--raised);
2481
+ border-bottom: 1px solid var(--line);
2235
2482
  min-width: 0;
2236
2483
  }
2237
- .artifact-meta {
2238
- display: grid;
2239
- gap: 4px;
2484
+ .artifact-name {
2485
+ font-weight: 640;
2486
+ font-size: 14px;
2487
+ color: var(--ink);
2488
+ font-family: var(--font-mono);
2489
+ overflow-wrap: break-word;
2490
+ word-break: break-word;
2240
2491
  min-width: 0;
2241
2492
  }
2242
- .artifact-links {
2493
+ .artifact-chips {
2243
2494
  display: flex;
2244
2495
  flex-wrap: wrap;
2245
- gap: 8px 12px;
2246
- min-width: 0;
2496
+ gap: 6px;
2497
+ flex-shrink: 0;
2247
2498
  }
2248
- .artifact a {
2249
- color: var(--focus);
2250
- font-weight: 700;
2499
+ .artifact-chip {
2500
+ display: inline-flex;
2501
+ align-items: center;
2502
+ padding: 4px 10px;
2503
+ border-radius: var(--radius-xs);
2504
+ font-size: 11px;
2505
+ font-weight: 640;
2506
+ text-transform: uppercase;
2507
+ letter-spacing: 0.03em;
2251
2508
  text-decoration: none;
2252
- overflow-wrap: anywhere;
2253
- }
2254
- .artifact code {
2509
+ transition: all 0.12s ease;
2510
+ border: 1px solid var(--line);
2511
+ background: var(--panel);
2255
2512
  color: var(--muted);
2256
- overflow-wrap: anywhere;
2513
+ white-space: nowrap;
2514
+ }
2515
+ .artifact-chip.pass {
2516
+ border-color: var(--pass);
2517
+ background: var(--pass-soft);
2518
+ color: var(--pass);
2519
+ }
2520
+ .artifact-chip.fail {
2521
+ border-color: var(--fail);
2522
+ background: var(--fail-soft);
2523
+ color: var(--fail);
2524
+ }
2525
+ .artifact-chip:hover {
2526
+ transform: translateY(-1px);
2527
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
2528
+ }
2529
+
2530
+ /* Artifact detail (for failed artifacts) */
2531
+ .artifact-detail {
2532
+ padding: 16px;
2257
2533
  min-width: 0;
2258
2534
  }
2259
- .artifact-hash {
2260
- justify-self: end;
2261
- text-align: right;
2262
- max-width: 100%;
2535
+ .artifact-comparison {
2536
+ display: grid;
2537
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
2538
+ gap: 20px;
2539
+ margin-bottom: 16px;
2540
+ min-width: 0;
2263
2541
  }
2264
- .badge {
2265
- justify-self: start;
2266
- border-radius: 999px;
2267
- padding: 5px 9px;
2268
- background: color-mix(in oklch, var(--line) 52%, transparent);
2269
- color: var(--muted);
2542
+ .artifact-preview {
2543
+ display: flex;
2544
+ flex-direction: column;
2545
+ gap: 10px;
2546
+ min-width: 0;
2547
+ }
2548
+ .artifact-preview-label {
2270
2549
  font-size: 12px;
2271
- font-weight: 700;
2550
+ font-weight: 640;
2551
+ color: var(--muted);
2552
+ text-transform: uppercase;
2553
+ letter-spacing: 0.05em;
2554
+ overflow-wrap: break-word;
2555
+ }
2556
+
2557
+ /* Device frame for DOM/screenshot previews */
2558
+ .device-frame {
2559
+ position: relative;
2560
+ border: 1px solid var(--line);
2561
+ border-radius: var(--radius-sm);
2562
+ background: var(--canvas);
2563
+ overflow: hidden;
2564
+ box-shadow: var(--shadow);
2565
+ aspect-ratio: 3 / 4;
2566
+ max-height: 600px;
2567
+ min-width: 0;
2568
+ }
2569
+ .device-frame.failed {
2570
+ border-color: var(--fail);
2571
+ box-shadow: 0 0 0 2px var(--fail-soft);
2572
+ }
2573
+ .device-frame.diff {
2574
+ border-color: var(--changed);
2575
+ box-shadow: 0 0 0 2px var(--changed-soft);
2576
+ }
2577
+ .device-frame iframe {
2578
+ width: 100%;
2579
+ height: 100%;
2580
+ border: 0;
2581
+ display: block;
2272
2582
  }
2273
- .badge.fail {
2274
- background: color-mix(in oklch, var(--bad) 12%, var(--panel));
2275
- color: var(--bad);
2583
+
2584
+ /* Artifact error message */
2585
+ .artifact-error {
2586
+ padding: 12px;
2587
+ background: var(--fail-soft);
2588
+ border: 1px solid color-mix(in oklab, var(--fail) 25%, var(--line));
2589
+ border-radius: var(--radius-xs);
2590
+ color: var(--fail);
2591
+ font-size: 13px;
2592
+ margin-bottom: 12px;
2593
+ overflow-wrap: break-word;
2594
+ word-break: break-word;
2595
+ min-width: 0;
2596
+ }
2597
+ .artifact-error strong {
2598
+ font-weight: 660;
2276
2599
  }
2277
- .badge.pass {
2278
- background: color-mix(in oklch, var(--good) 12%, var(--panel));
2279
- color: var(--good);
2600
+
2601
+ /* Artifact metadata row */
2602
+ .artifact-meta-row {
2603
+ display: flex;
2604
+ justify-content: flex-end;
2605
+ padding-top: 12px;
2606
+ border-top: 1px solid var(--line);
2607
+ min-width: 0;
2608
+ }
2609
+ .artifact-hash {
2610
+ font-family: var(--font-mono);
2611
+ font-size: 11px;
2612
+ color: var(--faint);
2613
+ overflow: hidden;
2614
+ text-overflow: ellipsis;
2615
+ white-space: nowrap;
2616
+ max-width: 100%;
2617
+ min-width: 0;
2618
+ }
2619
+
2620
+ /* Artifact links (for non-visual artifacts) */
2621
+ .artifact-links {
2622
+ display: flex;
2623
+ flex-wrap: wrap;
2624
+ gap: 8px;
2625
+ margin-bottom: 12px;
2626
+ min-width: 0;
2280
2627
  }
2628
+
2629
+ /* Responsive adjustments */
2281
2630
  @media (max-width: 720px) {
2282
- .artifact-summary {
2631
+ .artifact-comparison {
2283
2632
  grid-template-columns: 1fr;
2284
2633
  }
2285
- .artifact-hash {
2286
- justify-self: start;
2287
- text-align: left;
2634
+ .artifact-group-header {
2635
+ flex-direction: column;
2636
+ align-items: flex-start;
2637
+ }
2638
+ .device-frame {
2639
+ max-height: 500px;
2288
2640
  }
2289
2641
  }`;
2290
2642
  }
2291
2643
  //#endregion
2292
2644
  //#region src/agent/report_index_base_css.ts
2293
2645
  function renderAgentReportBaseCss() {
2294
- return ` :root {
2295
- color-scheme: light;
2296
- --bg: oklch(97.5% 0.008 210);
2297
- --ink: oklch(19% 0.018 230);
2298
- --muted: oklch(48% 0.02 230);
2299
- --line: oklch(86% 0.018 230);
2300
- --panel: oklch(99% 0.006 210);
2301
- --good: oklch(55% 0.15 155);
2302
- --bad: oklch(58% 0.19 25);
2303
- --focus: oklch(55% 0.14 235);
2304
- font-family:
2305
- ui-sans-serif,
2306
- system-ui,
2307
- -apple-system,
2308
- BlinkMacSystemFont,
2309
- "Segoe UI",
2310
- sans-serif;
2311
- }
2312
- * { box-sizing: border-box; }
2313
- body {
2314
- margin: 0;
2315
- background: var(--bg);
2316
- color: var(--ink);
2317
- overflow-wrap: anywhere;
2318
- }
2646
+ return `${renderReportThemeCss()}
2647
+
2319
2648
  main {
2320
2649
  display: grid;
2321
2650
  gap: 24px;
@@ -2353,6 +2682,12 @@ function renderAgentReportBaseCss() {
2353
2682
  line-height: 1.25;
2354
2683
  overflow-wrap: anywhere;
2355
2684
  }
2685
+ h3 {
2686
+ margin: 0;
2687
+ font-size: 16px;
2688
+ line-height: 1.3;
2689
+ overflow-wrap: anywhere;
2690
+ }
2356
2691
  .meta {
2357
2692
  display: flex;
2358
2693
  flex-wrap: wrap;
@@ -2378,21 +2713,24 @@ function renderAgentReportBaseCss() {
2378
2713
  font-weight: 700;
2379
2714
  }
2380
2715
  .status.pass {
2381
- border-color: color-mix(in oklch, var(--good) 42%, var(--line));
2382
- color: var(--good);
2716
+ border-color: var(--pass);
2717
+ background: var(--pass-soft);
2718
+ color: var(--pass);
2383
2719
  }
2384
2720
  .status.fail {
2385
- border-color: color-mix(in oklch, var(--bad) 44%, var(--line));
2386
- color: var(--bad);
2721
+ border-color: var(--fail);
2722
+ background: var(--fail-soft);
2723
+ color: var(--fail);
2387
2724
  }
2388
2725
  .panel {
2389
2726
  display: grid;
2390
2727
  gap: 16px;
2391
2728
  border: 1px solid var(--line);
2392
- border-radius: 8px;
2729
+ border-radius: var(--radius);
2393
2730
  background: var(--panel);
2394
2731
  padding: 18px;
2395
2732
  min-width: 0;
2733
+ box-shadow: var(--shadow);
2396
2734
  }
2397
2735
  .panel > * {
2398
2736
  min-width: 0;
@@ -2400,6 +2738,13 @@ function renderAgentReportBaseCss() {
2400
2738
  .panel p {
2401
2739
  margin: 0;
2402
2740
  max-width: 100%;
2741
+ overflow-wrap: break-word;
2742
+ word-break: break-word;
2743
+ }
2744
+ code {
2745
+ font-family: var(--font-mono);
2746
+ overflow-wrap: break-word;
2747
+ word-break: break-all;
2403
2748
  }
2404
2749
  @media (max-width: 720px) {
2405
2750
  main {
@@ -2438,15 +2783,20 @@ function renderAgentReportCommandsCss() {
2438
2783
  font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
2439
2784
  }
2440
2785
  pre {
2441
- overflow: auto;
2786
+ overflow-x: auto;
2787
+ overflow-y: hidden;
2442
2788
  max-width: 100%;
2443
2789
  min-width: 0;
2790
+ width: 100%;
2444
2791
  margin: 0;
2445
2792
  border-radius: 8px;
2446
2793
  background: oklch(20% 0.015 230);
2447
2794
  color: oklch(92% 0.012 230);
2448
2795
  padding: 14px;
2449
2796
  line-height: 1.5;
2797
+ white-space: pre;
2798
+ word-wrap: normal;
2799
+ word-break: normal;
2450
2800
  }`;
2451
2801
  }
2452
2802
  //#endregion
@@ -3449,4 +3799,4 @@ function defaultSpecNameForPath(path) {
3449
3799
  return sanitizeArtifactName(basename(path, extname(path)));
3450
3800
  }
3451
3801
  //#endregion
3452
- export { writeAgentManifestPath as C, escapeHtml as D, escapeAttribute as E, normalizeAgentFlowSpec as O, validateAgentManifestFiles as S, readAgentCassettePath as T, formatArgv as _, formatAgentLaunchPlan as a, isAgentManifestLike as b, launchAgentBrowser as c, AGENT_RUN_RECORD_SCHEMA_URL as d, agentRunModeSchema as f, writeAgentRunRecordPath as g, readAgentRunRecordPath as h, runAgentSpecPath as i, sanitizeArtifactName as k, createAgentTemplateSpec as l, isAgentRunRecordLike as m, replayAgentRecordPath as n, resolveAgentLaunchTarget as o, formatAgentArgv as p, runAgentSpec as r, normalizeAgentFlowSpecWithConfig as s, defaultSpecNameForPath as t, loadAgentSpec as u, AGENT_MANIFEST_FILE_NAME as v, isAgentCassetteLike as w, readAgentManifestPath as x, agentManifestPath as y };
3802
+ export { sanitizeArtifactName as A, validateAgentManifestFiles as C, escapeAttribute as D, readAgentCassettePath as E, escapeHtml as O, readAgentManifestPath as S, isAgentCassetteLike as T, writeAgentRunRecordPath as _, formatAgentLaunchPlan as a, agentManifestPath as b, launchAgentBrowser as c, loadAgentSpec as d, AGENT_RUN_RECORD_SCHEMA_URL as f, readAgentRunRecordPath as g, isAgentRunRecordLike as h, runAgentSpecPath as i, normalizeAgentFlowSpec as k, renderReportThemeCss as l, formatAgentArgv as m, replayAgentRecordPath as n, resolveAgentLaunchTarget as o, agentRunModeSchema as p, runAgentSpec as r, normalizeAgentFlowSpecWithConfig as s, defaultSpecNameForPath as t, createAgentTemplateSpec as u, formatArgv as v, writeAgentManifestPath as w, isAgentManifestLike as x, AGENT_MANIFEST_FILE_NAME as y };