executable-stories-react 0.13.0 → 0.14.0

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.
@@ -9,7 +9,7 @@ import {
9
9
  useId as useId2,
10
10
  useMemo as useMemo3,
11
11
  useRef as useRef5,
12
- useState as useState9
12
+ useState as useState10
13
13
  } from "react";
14
14
 
15
15
  // src/components/ui/empty.tsx
@@ -206,7 +206,7 @@ function formatValue(value) {
206
206
  function DocKv({ entry }) {
207
207
  return /* @__PURE__ */ jsxs("dl", { className: "my-2 flex flex-wrap items-baseline gap-x-2 text-sm", children: [
208
208
  /* @__PURE__ */ jsx6("dt", { className: "font-semibold text-muted-foreground", children: entry.label }),
209
- /* @__PURE__ */ jsx6("dd", { className: "font-mono text-foreground", children: formatValue(entry.value) })
209
+ /* @__PURE__ */ jsx6("dd", { className: "min-w-0 break-words font-mono text-foreground", children: formatValue(entry.value) })
210
210
  ] });
211
211
  }
212
212
 
@@ -414,7 +414,7 @@ function DocHtml({ entry }) {
414
414
  loading: "lazy",
415
415
  title,
416
416
  className: "block w-full border-0",
417
- style: { height }
417
+ style: { height, colorScheme: "light", background: "#fff" }
418
418
  }
419
419
  )
420
420
  ] });
@@ -1138,6 +1138,24 @@ function scenarioToPrompt(scenario) {
1138
1138
  }
1139
1139
  return lines.join("\n");
1140
1140
  }
1141
+ function failuresToPrompt(scenarios) {
1142
+ if (scenarios.length === 0) return "";
1143
+ const noun = scenarios.length === 1 ? "test scenario is" : "test scenarios are";
1144
+ const lines = [
1145
+ `${scenarios.length} ${noun} failing. Investigate the likely cause (they may share one) and propose a fix.`
1146
+ ];
1147
+ scenarios.forEach((scenario, i) => {
1148
+ lines.push("", `${i + 1}. ${scenario.title}`, "Steps:");
1149
+ for (const step of scenario.steps) {
1150
+ const mark = step.status === "failed" ? " \u27F5 failed here" : "";
1151
+ lines.push(` ${step.keyword} ${step.text}${mark}`);
1152
+ }
1153
+ if (scenario.errorMessage) {
1154
+ lines.push("Error:", scenario.errorMessage.trim());
1155
+ }
1156
+ });
1157
+ return lines.join("\n");
1158
+ }
1141
1159
  function scenarioPermalink(scenario) {
1142
1160
  if (typeof location === "undefined") return `#${scenario.id}`;
1143
1161
  return `${location.href.split("#")[0]}#${scenario.id}`;
@@ -1263,57 +1281,68 @@ function ReportScenario({ scenario, hideTitle = false }) {
1263
1281
  className: "gap-0 py-0",
1264
1282
  children: [
1265
1283
  /* @__PURE__ */ jsxs17(CardHeader, { className: "px-4 py-3", children: [
1266
- /* @__PURE__ */ jsxs17("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
1267
- hideTitle ? null : /* @__PURE__ */ jsxs17("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1268
- collapsible ? /* @__PURE__ */ jsx26(
1269
- "button",
1270
- {
1271
- type: "button",
1272
- onClick: () => collapse.toggle(scenario.id),
1273
- "aria-expanded": !collapsed,
1274
- "aria-controls": bodyId,
1275
- "aria-label": `Toggle ${scenario.title}`,
1276
- className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
1277
- children: /* @__PURE__ */ jsx26("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1278
- }
1279
- ) : null,
1280
- /* @__PURE__ */ jsx26("span", { "aria-hidden": true, className: cn("text-sm leading-none", meta.glyphColor), children: meta.glyph }),
1281
- /* @__PURE__ */ jsx26("span", { children: scenario.title })
1282
- ] }),
1283
- /* @__PURE__ */ jsxs17("div", { className: "flex shrink-0 items-center gap-2", children: [
1284
- /* @__PURE__ */ jsx26(ScenarioRunHistory, { scenarioId: scenario.id }),
1285
- /* @__PURE__ */ jsx26(ScenarioStaleness, { scenario }),
1286
- scenario.durationMs > 0 ? /* @__PURE__ */ jsx26("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration3(scenario.durationMs) }) : null,
1287
- meta.showPill ? /* @__PURE__ */ jsx26(Badge, { variant: meta.badgeVariant, "aria-label": `Status: ${label}`, children: label }) : /* @__PURE__ */ jsx26("span", { className: "sr-only", children: `Status: ${label}` }),
1288
- actions ? /* @__PURE__ */ jsxs17(DropdownMenu, { children: [
1289
- /* @__PURE__ */ jsx26(
1290
- DropdownMenuTrigger,
1291
- {
1292
- "aria-label": `Actions for ${scenario.title}`,
1293
- className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
1294
- children: /* @__PURE__ */ jsx26(MoreHorizontalIcon, { "aria-hidden": true })
1295
- }
1296
- ),
1297
- /* @__PURE__ */ jsxs17(DropdownMenuContent, { align: "end", children: [
1298
- /* @__PURE__ */ jsxs17(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
1299
- /* @__PURE__ */ jsx26(LinkIcon, {}),
1300
- " Copy link"
1301
- ] }),
1302
- /* @__PURE__ */ jsxs17(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
1303
- /* @__PURE__ */ jsx26(FileTextIcon, {}),
1304
- " Copy as Markdown"
1305
- ] }),
1306
- /* @__PURE__ */ jsxs17(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
1307
- /* @__PURE__ */ jsx26(SparklesIcon, {}),
1308
- " Explain with AI"
1309
- ] })
1284
+ /* @__PURE__ */ jsxs17(
1285
+ "div",
1286
+ {
1287
+ className: cn(
1288
+ // Wraps rather than squeezing: on a phone the status/duration group
1289
+ // is ~160px, which leaves a title column a few characters wide.
1290
+ "flex flex-wrap items-start gap-x-4 gap-y-1",
1291
+ hideTitle ? "justify-end" : "justify-between"
1292
+ ),
1293
+ children: [
1294
+ hideTitle ? null : /* @__PURE__ */ jsxs17("h3", { id: titleId, className: "flex min-w-0 flex-1 basis-48 items-center gap-2 text-sm font-medium text-foreground", children: [
1295
+ collapsible ? /* @__PURE__ */ jsx26(
1296
+ "button",
1297
+ {
1298
+ type: "button",
1299
+ onClick: () => collapse.toggle(scenario.id),
1300
+ "aria-expanded": !collapsed,
1301
+ "aria-controls": bodyId,
1302
+ "aria-label": `Toggle ${scenario.title}`,
1303
+ className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
1304
+ children: /* @__PURE__ */ jsx26("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1305
+ }
1306
+ ) : null,
1307
+ /* @__PURE__ */ jsx26("span", { "aria-hidden": true, className: cn("text-sm leading-none", meta.glyphColor), children: meta.glyph }),
1308
+ /* @__PURE__ */ jsx26("span", { children: scenario.title })
1309
+ ] }),
1310
+ /* @__PURE__ */ jsxs17("div", { className: "flex shrink-0 items-center gap-2", children: [
1311
+ /* @__PURE__ */ jsx26(ScenarioRunHistory, { scenarioId: scenario.id }),
1312
+ /* @__PURE__ */ jsx26(ScenarioStaleness, { scenario }),
1313
+ scenario.durationMs > 0 ? /* @__PURE__ */ jsx26("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration3(scenario.durationMs) }) : null,
1314
+ meta.showPill ? /* @__PURE__ */ jsx26(Badge, { variant: meta.badgeVariant, "aria-label": `Status: ${label}`, children: label }) : /* @__PURE__ */ jsx26("span", { className: "sr-only", children: `Status: ${label}` }),
1315
+ actions ? /* @__PURE__ */ jsxs17(DropdownMenu, { children: [
1316
+ /* @__PURE__ */ jsx26(
1317
+ DropdownMenuTrigger,
1318
+ {
1319
+ "aria-label": `Actions for ${scenario.title}`,
1320
+ className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
1321
+ children: /* @__PURE__ */ jsx26(MoreHorizontalIcon, { "aria-hidden": true })
1322
+ }
1323
+ ),
1324
+ /* @__PURE__ */ jsxs17(DropdownMenuContent, { align: "end", children: [
1325
+ /* @__PURE__ */ jsxs17(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
1326
+ /* @__PURE__ */ jsx26(LinkIcon, {}),
1327
+ " Copy link"
1328
+ ] }),
1329
+ /* @__PURE__ */ jsxs17(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
1330
+ /* @__PURE__ */ jsx26(FileTextIcon, {}),
1331
+ " Copy as Markdown"
1332
+ ] }),
1333
+ /* @__PURE__ */ jsxs17(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
1334
+ /* @__PURE__ */ jsx26(SparklesIcon, {}),
1335
+ " Explain with AI"
1336
+ ] })
1337
+ ] })
1338
+ ] }) : null
1310
1339
  ] })
1311
- ] }) : null
1312
- ] })
1313
- ] }),
1340
+ ]
1341
+ }
1342
+ ),
1314
1343
  scenario.tags.length > 0 || (scenario.tickets?.length ?? 0) > 0 ? /* @__PURE__ */ jsxs17("ul", { "aria-label": "Tags and tickets", className: "flex flex-wrap gap-1.5", children: [
1315
1344
  scenario.tags.map((t) => /* @__PURE__ */ jsx26("li", { children: /* @__PURE__ */ jsx26(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
1316
- (scenario.tickets ?? []).map((ticket) => /* @__PURE__ */ jsx26("li", { children: ticket.url ? /* @__PURE__ */ jsx26("a", { href: ticket.url, target: "_blank", rel: "noreferrer noopener", children: /* @__PURE__ */ jsx26(Badge, { variant: "outline", children: ticket.id }) }) : /* @__PURE__ */ jsx26(Badge, { variant: "outline", children: ticket.id }) }, `ticket-${ticket.id}`))
1345
+ (scenario.tickets ?? []).map((ticket) => /* @__PURE__ */ jsx26("li", { children: ticket.url ? /* @__PURE__ */ jsx26("a", { href: ticket.url, target: "_blank", rel: "noreferrer noopener", className: "inline-flex min-h-6 items-center", children: /* @__PURE__ */ jsx26(Badge, { variant: "outline", children: ticket.id }) }) : /* @__PURE__ */ jsx26(Badge, { variant: "outline", children: ticket.id }) }, `ticket-${ticket.id}`))
1317
1346
  ] }) : null
1318
1347
  ] }),
1319
1348
  /* @__PURE__ */ jsxs17(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
@@ -1456,6 +1485,7 @@ function ReportFeatureList() {
1456
1485
 
1457
1486
  // src/components/ReportMeta.tsx
1458
1487
  import { formatDuration as formatDuration4 } from "executable-stories-core/utils/duration";
1488
+ import { slowestScenarios } from "executable-stories-core/report-queries";
1459
1489
  import { jsx as jsx31, jsxs as jsxs20 } from "react/jsx-runtime";
1460
1490
  function ReportMeta() {
1461
1491
  const report = useReport();
@@ -1499,7 +1529,9 @@ function ReportMeta() {
1499
1529
  value: url ? /* @__PURE__ */ jsx31("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1500
1530
  });
1501
1531
  }
1502
- if (items.length === 0) return null;
1532
+ const slowest = slowestScenarios(report, 5);
1533
+ const showSlowest = slowest.length >= 3;
1534
+ if (items.length === 0 && !showSlowest) return null;
1503
1535
  return (
1504
1536
  // Run metadata is useful but secondary to "what failed" — tuck it into a
1505
1537
  // collapsed disclosure so it doesn't push the result down. Native <details>
@@ -1512,7 +1544,14 @@ function ReportMeta() {
1512
1544
  /* @__PURE__ */ jsx31("dl", { className: "flex flex-wrap gap-x-5 gap-y-2 border-t border-border px-4 py-3 text-muted-foreground", "aria-label": "Run metadata", children: items.map((item) => /* @__PURE__ */ jsxs20("div", { className: "flex items-baseline gap-1.5", children: [
1513
1545
  /* @__PURE__ */ jsx31("dt", { className: "font-medium", children: item.label }),
1514
1546
  /* @__PURE__ */ jsx31("dd", { className: "font-mono text-foreground", children: item.value })
1515
- ] }, item.label)) })
1547
+ ] }, item.label)) }),
1548
+ showSlowest ? /* @__PURE__ */ jsxs20("div", { className: "border-t border-border px-4 py-3", children: [
1549
+ /* @__PURE__ */ jsx31("div", { className: "mb-1.5 font-medium text-muted-foreground", children: "Slowest scenarios" }),
1550
+ /* @__PURE__ */ jsx31("ol", { className: "flex flex-col gap-1", "aria-label": "Slowest scenarios", children: slowest.map((s) => /* @__PURE__ */ jsxs20("li", { className: "flex items-baseline justify-between gap-4", children: [
1551
+ /* @__PURE__ */ jsx31("a", { href: `#${s.id}`, className: "min-w-0 text-primary underline underline-offset-2", children: s.title }),
1552
+ /* @__PURE__ */ jsx31("span", { className: "shrink-0 font-mono tabular-nums text-muted-foreground", children: formatDuration4(s.durationMs) })
1553
+ ] }, s.id)) })
1554
+ ] }) : null
1516
1555
  ] })
1517
1556
  );
1518
1557
  }
@@ -1988,21 +2027,26 @@ function allTags(report) {
1988
2027
  }
1989
2028
  return [...seen];
1990
2029
  }
1991
- function queryMatches(scenario, q) {
2030
+ function queryMatches(scenario, q, sourceFile) {
1992
2031
  if (q === "") return true;
1993
2032
  if (scenario.title.toLowerCase().includes(q)) return true;
2033
+ if (sourceFile?.toLowerCase().includes(q)) return true;
2034
+ if (scenario.errorMessage?.toLowerCase().includes(q)) return true;
1994
2035
  for (const tag of scenario.tags) {
1995
2036
  if (tag.toLowerCase().includes(q)) return true;
1996
2037
  }
2038
+ for (const ticket of scenario.tickets ?? []) {
2039
+ if (ticket.id.toLowerCase().includes(q)) return true;
2040
+ }
1997
2041
  for (const step of scenario.steps) {
1998
2042
  if (step.text.toLowerCase().includes(q)) return true;
1999
2043
  }
2000
2044
  return false;
2001
2045
  }
2002
- function scenarioMatches(scenario, q, status, tags) {
2046
+ function scenarioMatches(scenario, q, status, tags, sourceFile) {
2003
2047
  if (status !== "all" && scenario.status !== status) return false;
2004
2048
  if (tags.length > 0 && !tags.some((t) => scenario.tags.includes(t))) return false;
2005
- return queryMatches(scenario, q);
2049
+ return queryMatches(scenario, q, sourceFile);
2006
2050
  }
2007
2051
  function summarizeScenarios(scenarios) {
2008
2052
  let total = 0, passed = 0, failed = 0, skipped = 0, pending = 0, durationMs = 0;
@@ -2025,7 +2069,9 @@ function filterReport(report, criteria) {
2025
2069
  const features = [];
2026
2070
  let topTotal = 0, topPassed = 0, topFailed = 0, topSkipped = 0, topPending = 0, topDuration = 0;
2027
2071
  for (const feature of report.features) {
2028
- const matched = feature.scenarios.filter((s) => scenarioMatches(s, q, status, tags));
2072
+ const matched = feature.scenarios.filter(
2073
+ (s) => scenarioMatches(s, q, status, tags, feature.sourceFile)
2074
+ );
2029
2075
  if (matched.length === 0) continue;
2030
2076
  const summary = summarizeScenarios(matched);
2031
2077
  features.push({ ...feature, summary, scenarios: matched });
@@ -2049,6 +2095,9 @@ function filterReport(report, criteria) {
2049
2095
  features
2050
2096
  };
2051
2097
  }
2098
+ function failedScenarios(report) {
2099
+ return report.features.flatMap((f) => f.scenarios.filter((s) => s.status === "failed"));
2100
+ }
2052
2101
  function listFailures(report) {
2053
2102
  const out = [];
2054
2103
  for (const feature of report.features) {
@@ -2132,7 +2181,7 @@ function ReportAgentFilterNotice({
2132
2181
  // src/interactive/ReportFailureBanner.tsx
2133
2182
  import { useCallback as useCallback2 } from "react";
2134
2183
  import { jsx as jsx40, jsxs as jsxs26 } from "react/jsx-runtime";
2135
- function ReportFailureBanner({ failures }) {
2184
+ function ReportFailureBanner({ failures, onCopyAll }) {
2136
2185
  const first = failures[0];
2137
2186
  const jumpToFirst = useCallback2(() => {
2138
2187
  if (!first) return;
@@ -2162,7 +2211,23 @@ function ReportFailureBanner({ failures }) {
2162
2211
  "aria-label": "View first failure",
2163
2212
  children: "View first failure \u2193"
2164
2213
  }
2165
- )
2214
+ ),
2215
+ onCopyAll ? /* @__PURE__ */ jsxs26(
2216
+ "button",
2217
+ {
2218
+ type: "button",
2219
+ className: "es-failure-banner-jump",
2220
+ onClick: onCopyAll,
2221
+ "aria-label": "Copy failures for an agent",
2222
+ children: [
2223
+ "Copy ",
2224
+ failures.length,
2225
+ " failure",
2226
+ failures.length === 1 ? "" : "s",
2227
+ " for an agent"
2228
+ ]
2229
+ }
2230
+ ) : null
2166
2231
  ]
2167
2232
  }
2168
2233
  );
@@ -2370,7 +2435,7 @@ var ReportSearch = forwardRef(
2370
2435
  onChange,
2371
2436
  matchedCount,
2372
2437
  totalCount,
2373
- placeholder = "Search scenarios or tags\u2026",
2438
+ placeholder = "Search scenarios, tags, tickets, errors\u2026",
2374
2439
  className
2375
2440
  } = props;
2376
2441
  const inputId = useId();
@@ -2428,8 +2493,118 @@ var ReportSearch = forwardRef(
2428
2493
  }
2429
2494
  );
2430
2495
 
2496
+ // src/interactive/ReportSinceLastVisit.tsx
2497
+ import { useEffect as useEffect3, useState as useState5 } from "react";
2498
+
2499
+ // src/interactive/last-visit.ts
2500
+ function snapshotOf(report, atMs) {
2501
+ const statuses = {};
2502
+ for (const feature of report.features) {
2503
+ for (const scenario of feature.scenarios) {
2504
+ statuses[scenario.id] = scenario.status;
2505
+ }
2506
+ }
2507
+ return { runId: report.runId, atMs, statuses };
2508
+ }
2509
+ function diffSinceVisit(previous, report) {
2510
+ if (!previous) return null;
2511
+ if (previous.runId === report.runId) return null;
2512
+ const newlyFailing = [];
2513
+ const newlyPassing = [];
2514
+ const added = [];
2515
+ for (const feature of report.features) {
2516
+ for (const scenario of feature.scenarios) {
2517
+ const ref = { id: scenario.id, title: scenario.title };
2518
+ const before = previous.statuses[scenario.id];
2519
+ if (before === void 0) {
2520
+ added.push(ref);
2521
+ } else if (before !== "failed" && scenario.status === "failed") {
2522
+ newlyFailing.push(ref);
2523
+ } else if (before === "failed" && scenario.status === "passed") {
2524
+ newlyPassing.push(ref);
2525
+ }
2526
+ }
2527
+ }
2528
+ if (newlyFailing.length === 0 && newlyPassing.length === 0 && added.length === 0) return null;
2529
+ return { sinceMs: previous.atMs, newlyFailing, newlyPassing, added };
2530
+ }
2531
+
2532
+ // src/interactive/ReportSinceLastVisit.tsx
2533
+ import { Fragment as Fragment14, jsx as jsx46, jsxs as jsxs31 } from "react/jsx-runtime";
2534
+ var KEY = "es-last-visit";
2535
+ var MAX_LINKS2 = 5;
2536
+ function readVisit() {
2537
+ try {
2538
+ const raw = localStorage.getItem(KEY);
2539
+ if (!raw) return null;
2540
+ const parsed = JSON.parse(raw);
2541
+ if (typeof parsed !== "object" || parsed === null) return null;
2542
+ const v = parsed;
2543
+ if (typeof v.runId !== "string" || typeof v.atMs !== "number" || typeof v.statuses !== "object") {
2544
+ return null;
2545
+ }
2546
+ return { runId: v.runId, atMs: v.atMs, statuses: v.statuses };
2547
+ } catch {
2548
+ return null;
2549
+ }
2550
+ }
2551
+ function writeVisit(snapshot) {
2552
+ try {
2553
+ localStorage.setItem(KEY, JSON.stringify(snapshot));
2554
+ } catch {
2555
+ }
2556
+ }
2557
+ function ScenarioLinks2({ scenarios, tone }) {
2558
+ return /* @__PURE__ */ jsxs31(Fragment14, { children: [
2559
+ scenarios.slice(0, MAX_LINKS2).map((s, i) => /* @__PURE__ */ jsxs31("span", { children: [
2560
+ i > 0 ? ", " : "",
2561
+ /* @__PURE__ */ jsx46("a", { href: `#${s.id}`, className: cn("underline underline-offset-2 hover:no-underline", tone), children: s.title })
2562
+ ] }, s.id)),
2563
+ scenarios.length > MAX_LINKS2 ? /* @__PURE__ */ jsxs31("span", { children: [
2564
+ " and ",
2565
+ scenarios.length - MAX_LINKS2,
2566
+ " more"
2567
+ ] }) : null
2568
+ ] });
2569
+ }
2570
+ function ReportSinceLastVisit({ report }) {
2571
+ const [delta, setDelta] = useState5(null);
2572
+ const [nowMs, setNowMs] = useState5(0);
2573
+ useEffect3(() => {
2574
+ const now = Date.now();
2575
+ setDelta(diffSinceVisit(readVisit(), report));
2576
+ setNowMs(now);
2577
+ writeVisit(snapshotOf(report, now));
2578
+ }, [report]);
2579
+ if (!delta) return null;
2580
+ const { newlyFailing, newlyPassing, added } = delta;
2581
+ return /* @__PURE__ */ jsxs31("p", { className: "text-xs text-muted-foreground", "data-testid": "es-since-last-visit", children: [
2582
+ /* @__PURE__ */ jsxs31("span", { className: "font-medium text-foreground", children: [
2583
+ "Since your last visit (",
2584
+ formatRelativeAge(delta.sinceMs, nowMs),
2585
+ "):"
2586
+ ] }),
2587
+ newlyFailing.length > 0 ? /* @__PURE__ */ jsxs31("span", { className: "ml-2 text-fail", children: [
2588
+ newlyFailing.length,
2589
+ " started failing (",
2590
+ /* @__PURE__ */ jsx46(ScenarioLinks2, { scenarios: newlyFailing, tone: "text-fail" }),
2591
+ ")"
2592
+ ] }) : null,
2593
+ newlyPassing.length > 0 ? /* @__PURE__ */ jsxs31("span", { className: "ml-2 text-pass", children: [
2594
+ newlyPassing.length,
2595
+ " fixed (",
2596
+ /* @__PURE__ */ jsx46(ScenarioLinks2, { scenarios: newlyPassing, tone: "text-pass" }),
2597
+ ")"
2598
+ ] }) : null,
2599
+ added.length > 0 ? /* @__PURE__ */ jsxs31("span", { className: "ml-2", children: [
2600
+ added.length,
2601
+ " new"
2602
+ ] }) : null
2603
+ ] });
2604
+ }
2605
+
2431
2606
  // src/interactive/ReportShortcutsHelp.tsx
2432
- import { useEffect as useEffect3, useRef } from "react";
2607
+ import { useEffect as useEffect4, useRef } from "react";
2433
2608
 
2434
2609
  // src/components/ui/button.tsx
2435
2610
  import "react";
@@ -2485,7 +2660,7 @@ function Button({
2485
2660
  }
2486
2661
 
2487
2662
  // src/interactive/ReportShortcutsHelp.tsx
2488
- import { jsx as jsx46, jsxs as jsxs31 } from "react/jsx-runtime";
2663
+ import { jsx as jsx47, jsxs as jsxs32 } from "react/jsx-runtime";
2489
2664
  var SHORTCUTS = [
2490
2665
  { keys: "/", description: "Focus search" },
2491
2666
  { keys: "f", description: "Jump to next failure" },
@@ -2497,20 +2672,20 @@ var SHORTCUTS = [
2497
2672
  ];
2498
2673
  function Keys({ keys }) {
2499
2674
  const parts = keys.split(" ");
2500
- return /* @__PURE__ */ jsx46("span", { className: "inline-flex items-center gap-1", children: parts.map((k, i) => /* @__PURE__ */ jsxs31("span", { className: "inline-flex items-center gap-1", children: [
2501
- i > 0 ? /* @__PURE__ */ jsx46("span", { className: "text-xs text-muted-foreground", children: "+" }) : null,
2502
- /* @__PURE__ */ jsx46("kbd", { className: "inline-flex h-6 min-w-6 items-center justify-center rounded border border-border bg-muted px-1.5 font-mono text-xs font-medium text-muted-foreground", children: k })
2675
+ return /* @__PURE__ */ jsx47("span", { className: "inline-flex items-center gap-1", children: parts.map((k, i) => /* @__PURE__ */ jsxs32("span", { className: "inline-flex items-center gap-1", children: [
2676
+ i > 0 ? /* @__PURE__ */ jsx47("span", { className: "text-xs text-muted-foreground", children: "+" }) : null,
2677
+ /* @__PURE__ */ jsx47("kbd", { className: "inline-flex h-6 min-w-6 items-center justify-center rounded border border-border bg-muted px-1.5 font-mono text-xs font-medium text-muted-foreground", children: k })
2503
2678
  ] }, k)) });
2504
2679
  }
2505
2680
  function ReportShortcutsHelp({ open, onClose }) {
2506
2681
  const dialogRef = useRef(null);
2507
- useEffect3(() => {
2682
+ useEffect4(() => {
2508
2683
  const dialog = dialogRef.current;
2509
2684
  if (!dialog) return;
2510
2685
  if (open && !dialog.open) dialog.showModal();
2511
2686
  else if (!open && dialog.open) dialog.close();
2512
2687
  }, [open]);
2513
- return /* @__PURE__ */ jsxs31(
2688
+ return /* @__PURE__ */ jsxs32(
2514
2689
  "dialog",
2515
2690
  {
2516
2691
  ref: dialogRef,
@@ -2518,20 +2693,20 @@ function ReportShortcutsHelp({ open, onClose }) {
2518
2693
  "aria-label": "Keyboard shortcuts",
2519
2694
  onClose,
2520
2695
  children: [
2521
- /* @__PURE__ */ jsx46("h2", { className: "text-base font-semibold", children: "Keyboard shortcuts" }),
2522
- /* @__PURE__ */ jsx46("dl", { className: "mt-4 grid grid-cols-[auto_1fr] items-center gap-x-4 gap-y-2.5", children: SHORTCUTS.map((s) => /* @__PURE__ */ jsxs31("div", { className: "col-span-2 grid grid-cols-subgrid items-center", children: [
2523
- /* @__PURE__ */ jsx46("dt", { className: "justify-self-start", children: /* @__PURE__ */ jsx46(Keys, { keys: s.keys }) }),
2524
- /* @__PURE__ */ jsx46("dd", { className: "text-sm text-muted-foreground", children: s.description })
2696
+ /* @__PURE__ */ jsx47("h2", { className: "text-base font-semibold", children: "Keyboard shortcuts" }),
2697
+ /* @__PURE__ */ jsx47("dl", { className: "mt-4 grid grid-cols-[auto_1fr] items-center gap-x-4 gap-y-2.5", children: SHORTCUTS.map((s) => /* @__PURE__ */ jsxs32("div", { className: "col-span-2 grid grid-cols-subgrid items-center", children: [
2698
+ /* @__PURE__ */ jsx47("dt", { className: "justify-self-start", children: /* @__PURE__ */ jsx47(Keys, { keys: s.keys }) }),
2699
+ /* @__PURE__ */ jsx47("dd", { className: "text-sm text-muted-foreground", children: s.description })
2525
2700
  ] }, s.keys)) }),
2526
- /* @__PURE__ */ jsx46("form", { method: "dialog", className: "mt-6", children: /* @__PURE__ */ jsx46(Button, { type: "submit", variant: "outline", size: "sm", children: "Close" }) })
2701
+ /* @__PURE__ */ jsx47("form", { method: "dialog", className: "mt-6", children: /* @__PURE__ */ jsx47(Button, { type: "submit", variant: "outline", size: "sm", children: "Close" }) })
2527
2702
  ]
2528
2703
  }
2529
2704
  );
2530
2705
  }
2531
2706
 
2532
2707
  // src/interactive/ReportToc.tsx
2533
- import { useEffect as useEffect4, useMemo as useMemo2, useState as useState5 } from "react";
2534
- import { jsx as jsx47, jsxs as jsxs32 } from "react/jsx-runtime";
2708
+ import { useEffect as useEffect5, useMemo as useMemo2, useState as useState6 } from "react";
2709
+ import { jsx as jsx48, jsxs as jsxs33 } from "react/jsx-runtime";
2535
2710
  var DOT = {
2536
2711
  passed: "bg-pass",
2537
2712
  failed: "bg-fail",
@@ -2540,12 +2715,12 @@ var DOT = {
2540
2715
  };
2541
2716
  function TocContent({ onNavigate }) {
2542
2717
  const report = useReport();
2543
- const [activeId, setActiveId] = useState5(null);
2718
+ const [activeId, setActiveId] = useState6(null);
2544
2719
  const scenarioIds = useMemo2(
2545
2720
  () => report.features.flatMap((f) => f.scenarios.map((s) => s.id)),
2546
2721
  [report]
2547
2722
  );
2548
- useEffect4(() => {
2723
+ useEffect5(() => {
2549
2724
  if (typeof IntersectionObserver === "undefined") return;
2550
2725
  const els = scenarioIds.map((id) => document.getElementById(id)).filter((el) => el !== null);
2551
2726
  if (els.length === 0) return;
@@ -2560,11 +2735,11 @@ function TocContent({ onNavigate }) {
2560
2735
  return () => observer.disconnect();
2561
2736
  }, [scenarioIds]);
2562
2737
  if (report.features.length === 0) return null;
2563
- return /* @__PURE__ */ jsx47("ul", { className: "flex list-none flex-col gap-4 pl-0", children: report.features.map((feature) => /* @__PURE__ */ jsxs32("li", { children: [
2564
- /* @__PURE__ */ jsx47("p", { className: "mb-1.5 font-semibold leading-snug text-foreground", children: feature.title }),
2565
- /* @__PURE__ */ jsx47("ul", { className: "flex list-none flex-col gap-0.5 pl-0", children: feature.scenarios.map((scenario) => {
2738
+ return /* @__PURE__ */ jsx48("ul", { className: "flex list-none flex-col gap-4 pl-0", children: report.features.map((feature) => /* @__PURE__ */ jsxs33("li", { children: [
2739
+ /* @__PURE__ */ jsx48("p", { className: "mb-1.5 font-semibold leading-snug text-foreground", children: feature.title }),
2740
+ /* @__PURE__ */ jsx48("ul", { className: "flex list-none flex-col gap-0.5 pl-0", children: feature.scenarios.map((scenario) => {
2566
2741
  const active = activeId === scenario.id;
2567
- return /* @__PURE__ */ jsx47("li", { children: /* @__PURE__ */ jsxs32(
2742
+ return /* @__PURE__ */ jsx48("li", { children: /* @__PURE__ */ jsxs33(
2568
2743
  "a",
2569
2744
  {
2570
2745
  href: `#${scenario.id}`,
@@ -2579,14 +2754,14 @@ function TocContent({ onNavigate }) {
2579
2754
  active ? "border-primary font-medium text-foreground" : "border-border text-muted-foreground"
2580
2755
  ),
2581
2756
  children: [
2582
- /* @__PURE__ */ jsx47(
2757
+ /* @__PURE__ */ jsx48(
2583
2758
  "span",
2584
2759
  {
2585
2760
  "aria-hidden": true,
2586
2761
  className: cn("mt-1 size-1.5 shrink-0 rounded-full", DOT[scenario.status])
2587
2762
  }
2588
2763
  ),
2589
- /* @__PURE__ */ jsx47("span", { className: "break-words", children: scenario.title })
2764
+ /* @__PURE__ */ jsx48("span", { className: "break-words", children: scenario.title })
2590
2765
  ]
2591
2766
  }
2592
2767
  ) }, scenario.id);
@@ -2596,48 +2771,48 @@ function TocContent({ onNavigate }) {
2596
2771
  function ReportToc() {
2597
2772
  const report = useReport();
2598
2773
  if (report.features.length === 0) return null;
2599
- return /* @__PURE__ */ jsxs32(
2774
+ return /* @__PURE__ */ jsxs33(
2600
2775
  "nav",
2601
2776
  {
2602
2777
  "aria-label": "Table of contents",
2603
2778
  className: "sticky top-4 hidden h-fit w-64 shrink-0 self-start text-xs lg:block",
2604
2779
  children: [
2605
- /* @__PURE__ */ jsx47("p", { className: "mb-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Contents" }),
2606
- /* @__PURE__ */ jsx47(TocContent, {})
2780
+ /* @__PURE__ */ jsx48("p", { className: "mb-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Contents" }),
2781
+ /* @__PURE__ */ jsx48(TocContent, {})
2607
2782
  ]
2608
2783
  }
2609
2784
  );
2610
2785
  }
2611
2786
 
2612
2787
  // src/interactive/ReportTocDrawer.tsx
2613
- import { useState as useState6 } from "react";
2788
+ import { useState as useState7 } from "react";
2614
2789
  import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
2615
2790
  import { ListIcon, XIcon } from "lucide-react";
2616
- import { jsx as jsx48, jsxs as jsxs33 } from "react/jsx-runtime";
2791
+ import { jsx as jsx49, jsxs as jsxs34 } from "react/jsx-runtime";
2617
2792
  function ReportTocDrawer() {
2618
2793
  const report = useReport();
2619
- const [open, setOpen] = useState6(false);
2794
+ const [open, setOpen] = useState7(false);
2620
2795
  if (report.features.length === 0) return null;
2621
- return /* @__PURE__ */ jsxs33(DialogPrimitive.Root, { open, onOpenChange: setOpen, children: [
2622
- /* @__PURE__ */ jsxs33(DialogPrimitive.Trigger, { className: "flex cursor-pointer items-center gap-1 rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground lg:hidden [&_svg]:size-3.5", children: [
2623
- /* @__PURE__ */ jsx48(ListIcon, { "aria-hidden": true }),
2796
+ return /* @__PURE__ */ jsxs34(DialogPrimitive.Root, { open, onOpenChange: setOpen, children: [
2797
+ /* @__PURE__ */ jsxs34(DialogPrimitive.Trigger, { className: "flex cursor-pointer items-center gap-1 rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground lg:hidden [&_svg]:size-3.5", children: [
2798
+ /* @__PURE__ */ jsx49(ListIcon, { "aria-hidden": true }),
2624
2799
  " Contents"
2625
2800
  ] }),
2626
- /* @__PURE__ */ jsxs33(DialogPrimitive.Portal, { children: [
2627
- /* @__PURE__ */ jsx48(DialogPrimitive.Backdrop, { className: "fixed inset-0 z-50 bg-black/50 transition-opacity duration-200 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0" }),
2628
- /* @__PURE__ */ jsxs33(DialogPrimitive.Popup, { className: "fixed inset-y-0 left-0 z-50 flex w-72 max-w-[80vw] flex-col gap-3 overflow-y-auto border-r border-border bg-background p-4 font-sans text-xs shadow-lg outline-none transition-transform duration-200 data-[ending-style]:-translate-x-full data-[starting-style]:-translate-x-full", children: [
2629
- /* @__PURE__ */ jsxs33("div", { className: "flex items-center justify-between", children: [
2630
- /* @__PURE__ */ jsx48(DialogPrimitive.Title, { className: "text-xs font-semibold tracking-wide text-muted-foreground uppercase", children: "Contents" }),
2631
- /* @__PURE__ */ jsx48(
2801
+ /* @__PURE__ */ jsxs34(DialogPrimitive.Portal, { children: [
2802
+ /* @__PURE__ */ jsx49(DialogPrimitive.Backdrop, { className: "fixed inset-0 z-50 bg-black/50 transition-opacity duration-200 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0" }),
2803
+ /* @__PURE__ */ jsxs34(DialogPrimitive.Popup, { className: "fixed inset-y-0 left-0 z-50 flex w-72 max-w-[80vw] flex-col gap-3 overflow-y-auto border-r border-border bg-background p-4 font-sans text-xs shadow-lg outline-none transition-transform duration-200 data-[ending-style]:-translate-x-full data-[starting-style]:-translate-x-full", children: [
2804
+ /* @__PURE__ */ jsxs34("div", { className: "flex items-center justify-between", children: [
2805
+ /* @__PURE__ */ jsx49(DialogPrimitive.Title, { className: "text-xs font-semibold tracking-wide text-muted-foreground uppercase", children: "Contents" }),
2806
+ /* @__PURE__ */ jsx49(
2632
2807
  DialogPrimitive.Close,
2633
2808
  {
2634
2809
  "aria-label": "Close contents",
2635
2810
  className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
2636
- children: /* @__PURE__ */ jsx48(XIcon, { "aria-hidden": true })
2811
+ children: /* @__PURE__ */ jsx49(XIcon, { "aria-hidden": true })
2637
2812
  }
2638
2813
  )
2639
2814
  ] }),
2640
- /* @__PURE__ */ jsx48(TocContent, { onNavigate: () => setOpen(false) })
2815
+ /* @__PURE__ */ jsx49(TocContent, { onNavigate: () => setOpen(false) })
2641
2816
  ] })
2642
2817
  ] })
2643
2818
  ] });
@@ -2671,10 +2846,10 @@ function failuresFirst(report) {
2671
2846
  }
2672
2847
 
2673
2848
  // src/interactive/use-deep-link-scroll.ts
2674
- import { useEffect as useEffect5, useRef as useRef2 } from "react";
2849
+ import { useEffect as useEffect6, useRef as useRef2 } from "react";
2675
2850
  function useDeepLinkScroll() {
2676
2851
  const lastAnchor = useRef2(null);
2677
- useEffect5(() => {
2852
+ useEffect6(() => {
2678
2853
  function scrollToHash() {
2679
2854
  const { anchor } = splitHash(window.location.hash);
2680
2855
  if (!anchor || anchor === lastAnchor.current) return;
@@ -2690,7 +2865,7 @@ function useDeepLinkScroll() {
2690
2865
  }
2691
2866
 
2692
2867
  // src/interactive/use-keyboard-shortcuts.ts
2693
- import { useEffect as useEffect6, useRef as useRef3 } from "react";
2868
+ import { useEffect as useEffect7, useRef as useRef3 } from "react";
2694
2869
  function isEditableTarget(target) {
2695
2870
  if (!(target instanceof HTMLElement)) return false;
2696
2871
  const tag = target.tagName;
@@ -2701,7 +2876,7 @@ function isEditableTarget(target) {
2701
2876
  function useKeyboardShortcuts(handlers) {
2702
2877
  const ref = useRef3(handlers);
2703
2878
  ref.current = handlers;
2704
- useEffect6(() => {
2879
+ useEffect7(() => {
2705
2880
  function onKeyDown(e) {
2706
2881
  const h = ref.current;
2707
2882
  if (e.key === "Escape") {
@@ -2745,8 +2920,8 @@ function useKeyboardShortcuts(handlers) {
2745
2920
  }
2746
2921
 
2747
2922
  // src/interactive/use-theme.ts
2748
- import { useCallback as useCallback3, useEffect as useEffect7, useState as useState7 } from "react";
2749
- var KEY = "es-theme";
2923
+ import { useCallback as useCallback3, useEffect as useEffect8, useState as useState8 } from "react";
2924
+ var KEY2 = "es-theme";
2750
2925
  function systemDark() {
2751
2926
  return typeof matchMedia !== "undefined" && matchMedia("(prefers-color-scheme: dark)").matches;
2752
2927
  }
@@ -2760,19 +2935,19 @@ function applyTheme(pref) {
2760
2935
  }
2761
2936
  function initialPref() {
2762
2937
  if (typeof localStorage !== "undefined") {
2763
- const stored = localStorage.getItem(KEY);
2938
+ const stored = localStorage.getItem(KEY2);
2764
2939
  if (stored === "light" || stored === "dark" || stored === "system") return stored;
2765
2940
  }
2766
2941
  return "system";
2767
2942
  }
2768
2943
  function useTheme() {
2769
- const [pref, setPrefState] = useState7("system");
2770
- useEffect7(() => {
2944
+ const [pref, setPrefState] = useState8("system");
2945
+ useEffect8(() => {
2771
2946
  const p = initialPref();
2772
2947
  setPrefState(p);
2773
2948
  applyTheme(p);
2774
2949
  }, []);
2775
- useEffect7(() => {
2950
+ useEffect8(() => {
2776
2951
  if (typeof matchMedia === "undefined") return;
2777
2952
  const mq = matchMedia("(prefers-color-scheme: dark)");
2778
2953
  const onChange = () => {
@@ -2785,7 +2960,7 @@ function useTheme() {
2785
2960
  setPrefState(p);
2786
2961
  applyTheme(p);
2787
2962
  try {
2788
- localStorage.setItem(KEY, p);
2963
+ localStorage.setItem(KEY2, p);
2789
2964
  } catch {
2790
2965
  }
2791
2966
  }, []);
@@ -2793,19 +2968,19 @@ function useTheme() {
2793
2968
  }
2794
2969
 
2795
2970
  // src/interactive/use-url-state.ts
2796
- import { useCallback as useCallback4, useEffect as useEffect8, useRef as useRef4, useState as useState8 } from "react";
2971
+ import { useCallback as useCallback4, useEffect as useEffect9, useRef as useRef4, useState as useState9 } from "react";
2797
2972
  var WRITE_DELAY_MS = 250;
2798
2973
  function useUrlState() {
2799
- const [state, setState] = useState8(DEFAULT_URL_STATE);
2974
+ const [state, setState] = useState9(DEFAULT_URL_STATE);
2800
2975
  const ready = useRef4(false);
2801
- useEffect8(() => {
2976
+ useEffect9(() => {
2802
2977
  const read = () => setState(decodeUrlState(splitHash(window.location.hash).params));
2803
2978
  read();
2804
2979
  ready.current = true;
2805
2980
  window.addEventListener("hashchange", read);
2806
2981
  return () => window.removeEventListener("hashchange", read);
2807
2982
  }, []);
2808
- useEffect8(() => {
2983
+ useEffect9(() => {
2809
2984
  if (!ready.current) return;
2810
2985
  const timer = setTimeout(
2811
2986
  () => writeHash(splitHash(window.location.hash).anchor, encodeUrlState(state)),
@@ -3041,12 +3216,12 @@ function reportTools(deps) {
3041
3216
  }
3042
3217
 
3043
3218
  // src/interactive/ReportInteractive.tsx
3044
- import { Fragment as Fragment14, jsx as jsx49, jsxs as jsxs34 } from "react/jsx-runtime";
3219
+ import { Fragment as Fragment15, jsx as jsx50, jsxs as jsxs35 } from "react/jsx-runtime";
3045
3220
  function ReportInteractive(props) {
3046
3221
  const { report, className, title, dataTheme } = props;
3047
3222
  const result = unwrapReport(report);
3048
3223
  if (!result.ok) {
3049
- return /* @__PURE__ */ jsx49(
3224
+ return /* @__PURE__ */ jsx50(
3050
3225
  ReportErrorShell,
3051
3226
  {
3052
3227
  error: result.error,
@@ -3056,7 +3231,7 @@ function ReportInteractive(props) {
3056
3231
  }
3057
3232
  );
3058
3233
  }
3059
- return /* @__PURE__ */ jsx49(ReportInteractiveView, { ...props, report: result.data });
3234
+ return /* @__PURE__ */ jsx50(ReportInteractiveView, { ...props, report: result.data });
3060
3235
  }
3061
3236
  var THEME_OPTIONS = [
3062
3237
  { value: "light", label: "Light", Icon: Sun },
@@ -3067,7 +3242,7 @@ function ThemeSegment({
3067
3242
  pref,
3068
3243
  onPref
3069
3244
  }) {
3070
- return /* @__PURE__ */ jsx49(
3245
+ return /* @__PURE__ */ jsx50(
3071
3246
  ToggleGroup,
3072
3247
  {
3073
3248
  size: "sm",
@@ -3077,14 +3252,14 @@ function ThemeSegment({
3077
3252
  if (next) onPref(next);
3078
3253
  },
3079
3254
  "aria-label": "Color theme",
3080
- children: THEME_OPTIONS.map(({ value, label, Icon }) => /* @__PURE__ */ jsx49(
3255
+ children: THEME_OPTIONS.map(({ value, label, Icon }) => /* @__PURE__ */ jsx50(
3081
3256
  ToggleGroupItem,
3082
3257
  {
3083
3258
  value,
3084
3259
  "aria-label": label,
3085
3260
  title: label,
3086
3261
  className: "px-2",
3087
- children: /* @__PURE__ */ jsx49(Icon, {})
3262
+ children: /* @__PURE__ */ jsx50(Icon, {})
3088
3263
  },
3089
3264
  value
3090
3265
  ))
@@ -3117,7 +3292,7 @@ function ReportInteractiveView({
3117
3292
  (next) => setUrlState({ detail: next }),
3118
3293
  [setUrlState]
3119
3294
  );
3120
- const [helpOpen, setHelpOpen] = useState9(false);
3295
+ const [helpOpen, setHelpOpen] = useState10(false);
3121
3296
  const { pref: themePref, setPref: setThemePref } = useTheme();
3122
3297
  const searchRef = useRef5(null);
3123
3298
  const expandId = useId2();
@@ -3159,7 +3334,7 @@ function ReportInteractiveView({
3159
3334
  }),
3160
3335
  [activeTags, setUrlState]
3161
3336
  );
3162
- const [toast, setToast] = useState9(null);
3337
+ const [toast, setToast] = useState10(null);
3163
3338
  const toastTimer = useRef5(null);
3164
3339
  const showToast = useCallback5((msg) => {
3165
3340
  setToast(msg);
@@ -3185,6 +3360,10 @@ function ReportInteractiveView({
3185
3360
  }),
3186
3361
  [copy]
3187
3362
  );
3363
+ const copyAllFailures = useCallback5(
3364
+ () => copy(failuresToPrompt(failedScenarios(filtered)), "Failures copied"),
3365
+ [copy, filtered]
3366
+ );
3188
3367
  const failureIndexRef = useRef5(0);
3189
3368
  const allCollapsibleIds = useMemo3(
3190
3369
  () => report.features.flatMap((f) => [f.id, ...f.scenarios.map((s) => s.id)]),
@@ -3202,7 +3381,7 @@ function ReportInteractiveView({
3202
3381
  () => collapse.collapseAll(allCollapsibleIds),
3203
3382
  [collapse, allCollapsibleIds]
3204
3383
  );
3205
- const [expandedAll, setExpandedAll] = useState9(
3384
+ const [expandedAll, setExpandedAll] = useState10(
3206
3385
  () => report.summary.failed === 0
3207
3386
  );
3208
3387
  const setExpanded = useCallback5(
@@ -3213,7 +3392,7 @@ function ReportInteractiveView({
3213
3392
  },
3214
3393
  [collapse, collapseAll]
3215
3394
  );
3216
- const [agentFilter, setAgentFilter] = useState9(null);
3395
+ const [agentFilter, setAgentFilter] = useState10(null);
3217
3396
  useTools(
3218
3397
  reportTools({
3219
3398
  report,
@@ -3260,13 +3439,13 @@ function ReportInteractiveView({
3260
3439
  const totalScenarios = report.summary.total;
3261
3440
  const matchedScenarios = filtered.summary.total;
3262
3441
  const showToc = !hideToc && totalScenarios > 3;
3263
- return /* @__PURE__ */ jsx49(CollapseProvider, { value: visibleCollapse, children: /* @__PURE__ */ jsx49(ScenarioActionsProvider, { value: scenarioActions, children: /* @__PURE__ */ jsx49(ScenarioHistoryProvider, { value: scenarioHistory ?? null, children: /* @__PURE__ */ jsx49(ScenarioFreshnessProvider, { value: { staleAfterDays, report }, children: /* @__PURE__ */ jsx49(
3442
+ return /* @__PURE__ */ jsx50(CollapseProvider, { value: visibleCollapse, children: /* @__PURE__ */ jsx50(ScenarioActionsProvider, { value: scenarioActions, children: /* @__PURE__ */ jsx50(ScenarioHistoryProvider, { value: scenarioHistory ?? null, children: /* @__PURE__ */ jsx50(ScenarioFreshnessProvider, { value: { staleAfterDays, report }, children: /* @__PURE__ */ jsx50(
3264
3443
  ReportRoot,
3265
3444
  {
3266
3445
  report: filtered,
3267
3446
  customRenderers,
3268
3447
  renderers,
3269
- children: /* @__PURE__ */ jsxs34(
3448
+ children: /* @__PURE__ */ jsxs35(
3270
3449
  "main",
3271
3450
  {
3272
3451
  className: cn("es-report", "es-report-interactive", className),
@@ -3275,8 +3454,8 @@ function ReportInteractiveView({
3275
3454
  "data-theme": dataTheme,
3276
3455
  "data-detail-level": detail,
3277
3456
  children: [
3278
- /* @__PURE__ */ jsxs34("header", { className: "es-report-header", children: [
3279
- /* @__PURE__ */ jsxs34(
3457
+ /* @__PURE__ */ jsxs35("header", { className: "es-report-header", children: [
3458
+ /* @__PURE__ */ jsxs35(
3280
3459
  "div",
3281
3460
  {
3282
3461
  "data-es-navbar": true,
@@ -3285,9 +3464,9 @@ function ReportInteractiveView({
3285
3464
  hideHeader ? "justify-end" : "justify-between"
3286
3465
  ),
3287
3466
  children: [
3288
- hideHeader ? null : /* @__PURE__ */ jsx49("h1", { className: "min-w-0", children: title ?? "Story Report" }),
3289
- /* @__PURE__ */ jsxs34("div", { className: "flex items-center gap-2", children: [
3290
- /* @__PURE__ */ jsx49(
3467
+ hideHeader ? null : /* @__PURE__ */ jsx50("h1", { className: "min-w-0", children: title ?? "Story Report" }),
3468
+ /* @__PURE__ */ jsxs35("div", { className: "flex items-center gap-2", children: [
3469
+ /* @__PURE__ */ jsx50(
3291
3470
  ReportSearch,
3292
3471
  {
3293
3472
  ref: searchRef,
@@ -3297,16 +3476,16 @@ function ReportInteractiveView({
3297
3476
  totalCount: totalScenarios
3298
3477
  }
3299
3478
  ),
3300
- /* @__PURE__ */ jsx49(ThemeSegment, { pref: themePref, onPref: setThemePref })
3479
+ /* @__PURE__ */ jsx50(ThemeSegment, { pref: themePref, onPref: setThemePref })
3301
3480
  ] })
3302
3481
  ]
3303
3482
  }
3304
3483
  ),
3305
- hideHeader ? null : /* @__PURE__ */ jsxs34(Fragment14, { children: [
3306
- /* @__PURE__ */ jsx49(ReportSummary, {}),
3307
- /* @__PURE__ */ jsx49(ReportMeta, {})
3484
+ hideHeader ? null : /* @__PURE__ */ jsxs35(Fragment15, { children: [
3485
+ /* @__PURE__ */ jsx50(ReportSummary, {}),
3486
+ /* @__PURE__ */ jsx50(ReportMeta, {})
3308
3487
  ] }),
3309
- /* @__PURE__ */ jsx49(
3488
+ /* @__PURE__ */ jsx50(
3310
3489
  ReportFreshness,
3311
3490
  {
3312
3491
  lastRunMs: reportLastRunMs(report),
@@ -3314,14 +3493,15 @@ function ReportInteractiveView({
3314
3493
  staleAfterDays
3315
3494
  }
3316
3495
  ),
3317
- /* @__PURE__ */ jsx49(
3496
+ /* @__PURE__ */ jsx50(
3318
3497
  ReportLastRunDelta,
3319
3498
  {
3320
3499
  history: scenarioHistory,
3321
3500
  report
3322
3501
  }
3323
3502
  ),
3324
- /* @__PURE__ */ jsx49(
3503
+ /* @__PURE__ */ jsx50(ReportSinceLastVisit, { report }),
3504
+ /* @__PURE__ */ jsx50(
3325
3505
  ReportFilters,
3326
3506
  {
3327
3507
  statuses: statusOptions,
@@ -3332,10 +3512,10 @@ function ReportInteractiveView({
3332
3512
  onToggleTag: toggleTag
3333
3513
  }
3334
3514
  ),
3335
- /* @__PURE__ */ jsxs34("div", { className: "flex flex-wrap items-center gap-x-6 gap-y-2 border-t border-border/60 pt-3", children: [
3336
- showToc ? /* @__PURE__ */ jsx49(ReportTocDrawer, {}) : null,
3337
- /* @__PURE__ */ jsxs34("div", { className: "flex items-center gap-2", children: [
3338
- /* @__PURE__ */ jsx49(
3515
+ /* @__PURE__ */ jsxs35("div", { className: "flex flex-wrap items-center gap-x-6 gap-y-2 border-t border-border/60 pt-3", children: [
3516
+ showToc ? /* @__PURE__ */ jsx50(ReportTocDrawer, {}) : null,
3517
+ /* @__PURE__ */ jsxs35("div", { className: "flex items-center gap-2", children: [
3518
+ /* @__PURE__ */ jsx50(
3339
3519
  Switch,
3340
3520
  {
3341
3521
  id: expandId,
@@ -3343,7 +3523,7 @@ function ReportInteractiveView({
3343
3523
  onCheckedChange: setExpanded
3344
3524
  }
3345
3525
  ),
3346
- /* @__PURE__ */ jsx49(
3526
+ /* @__PURE__ */ jsx50(
3347
3527
  "label",
3348
3528
  {
3349
3529
  htmlFor: expandId,
@@ -3352,8 +3532,8 @@ function ReportInteractiveView({
3352
3532
  }
3353
3533
  )
3354
3534
  ] }),
3355
- /* @__PURE__ */ jsxs34("div", { className: "flex items-center gap-2", children: [
3356
- /* @__PURE__ */ jsx49(
3535
+ /* @__PURE__ */ jsxs35("div", { className: "flex items-center gap-2", children: [
3536
+ /* @__PURE__ */ jsx50(
3357
3537
  Switch,
3358
3538
  {
3359
3539
  id: docsId,
@@ -3361,7 +3541,7 @@ function ReportInteractiveView({
3361
3541
  onCheckedChange: (checked) => setDetail(checked ? "full" : "minimal")
3362
3542
  }
3363
3543
  ),
3364
- /* @__PURE__ */ jsx49(
3544
+ /* @__PURE__ */ jsx50(
3365
3545
  "label",
3366
3546
  {
3367
3547
  htmlFor: docsId,
@@ -3372,7 +3552,7 @@ function ReportInteractiveView({
3372
3552
  ] })
3373
3553
  ] })
3374
3554
  ] }),
3375
- showAgentFilter ? /* @__PURE__ */ jsx49(
3555
+ showAgentFilter ? /* @__PURE__ */ jsx50(
3376
3556
  ReportAgentFilterNotice,
3377
3557
  {
3378
3558
  applied: agentFilter,
@@ -3380,17 +3560,17 @@ function ReportInteractiveView({
3380
3560
  onDismiss: () => setAgentFilter(null)
3381
3561
  }
3382
3562
  ) : null,
3383
- /* @__PURE__ */ jsx49(ReportFailureBanner, { failures }),
3384
- hasContent ? showToc ? /* @__PURE__ */ jsxs34("div", { className: "flex gap-6", children: [
3385
- /* @__PURE__ */ jsx49(ReportToc, {}),
3386
- /* @__PURE__ */ jsx49("div", { className: "flex min-w-0 flex-1 flex-col gap-4", children: /* @__PURE__ */ jsx49(ReportFeatureList, {}) })
3387
- ] }) : /* @__PURE__ */ jsx49("div", { className: "flex flex-col gap-4", children: /* @__PURE__ */ jsx49(ReportFeatureList, {}) }) : /* @__PURE__ */ jsx49(
3563
+ /* @__PURE__ */ jsx50(ReportFailureBanner, { failures, onCopyAll: copyAllFailures }),
3564
+ hasContent ? showToc ? /* @__PURE__ */ jsxs35("div", { className: "flex gap-6", children: [
3565
+ /* @__PURE__ */ jsx50(ReportToc, {}),
3566
+ /* @__PURE__ */ jsx50("div", { className: "flex min-w-0 flex-1 flex-col gap-4", children: /* @__PURE__ */ jsx50(ReportFeatureList, {}) })
3567
+ ] }) : /* @__PURE__ */ jsx50("div", { className: "flex flex-col gap-4", children: /* @__PURE__ */ jsx50(ReportFeatureList, {}) }) : /* @__PURE__ */ jsx50(
3388
3568
  ReportEmpty,
3389
3569
  {
3390
3570
  message: query ? "No scenarios match the search." : void 0
3391
3571
  }
3392
3572
  ),
3393
- /* @__PURE__ */ jsx49(
3573
+ /* @__PURE__ */ jsx50(
3394
3574
  "button",
3395
3575
  {
3396
3576
  type: "button",
@@ -3401,14 +3581,14 @@ function ReportInteractiveView({
3401
3581
  children: "?"
3402
3582
  }
3403
3583
  ),
3404
- /* @__PURE__ */ jsx49(
3584
+ /* @__PURE__ */ jsx50(
3405
3585
  ReportShortcutsHelp,
3406
3586
  {
3407
3587
  open: helpOpen,
3408
3588
  onClose: () => setHelpOpen(false)
3409
3589
  }
3410
3590
  ),
3411
- toast ? /* @__PURE__ */ jsx49(
3591
+ toast ? /* @__PURE__ */ jsx50(
3412
3592
  "div",
3413
3593
  {
3414
3594
  role: "status",
@@ -3428,11 +3608,11 @@ function ReportInteractiveView({
3428
3608
  import { useMemo as useMemo4 } from "react";
3429
3609
 
3430
3610
  // src/interactive/island-renderers.tsx
3431
- import { createElement, useEffect as useEffect10, useState as useState11 } from "react";
3611
+ import { createElement, useEffect as useEffect11, useState as useState12 } from "react";
3432
3612
 
3433
3613
  // src/components/doc/MermaidView.tsx
3434
- import { useEffect as useEffect9, useRef as useRef6, useState as useState10 } from "react";
3435
- import { Fragment as Fragment15, jsx as jsx50, jsxs as jsxs35 } from "react/jsx-runtime";
3614
+ import { useEffect as useEffect10, useRef as useRef6, useState as useState11 } from "react";
3615
+ import { Fragment as Fragment16, jsx as jsx51, jsxs as jsxs36 } from "react/jsx-runtime";
3436
3616
  function prefersDark(el) {
3437
3617
  const themed = el?.closest("[data-theme]");
3438
3618
  if (themed?.dataset.theme === "dark") return true;
@@ -3441,9 +3621,9 @@ function prefersDark(el) {
3441
3621
  }
3442
3622
  function MermaidView({ entry, load }) {
3443
3623
  const ref = useRef6(null);
3444
- const [svg, setSvg] = useState10(null);
3445
- const [error, setError] = useState10(null);
3446
- useEffect9(() => {
3624
+ const [svg, setSvg] = useState11(null);
3625
+ const [error, setError] = useState11(null);
3626
+ useEffect10(() => {
3447
3627
  let cancelled = false;
3448
3628
  const draw = async () => {
3449
3629
  let mermaid;
@@ -3486,9 +3666,9 @@ function MermaidView({ entry, load }) {
3486
3666
  };
3487
3667
  }, [entry.code, load]);
3488
3668
  if (svg) {
3489
- return /* @__PURE__ */ jsxs35("figure", { ref, className: "my-3", children: [
3490
- entry.title ? /* @__PURE__ */ jsx50("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
3491
- /* @__PURE__ */ jsx50(
3669
+ return /* @__PURE__ */ jsxs36("figure", { ref, className: "my-3", children: [
3670
+ entry.title ? /* @__PURE__ */ jsx51("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
3671
+ /* @__PURE__ */ jsx51(
3492
3672
  "div",
3493
3673
  {
3494
3674
  role: "img",
@@ -3499,8 +3679,8 @@ function MermaidView({ entry, load }) {
3499
3679
  )
3500
3680
  ] });
3501
3681
  }
3502
- return /* @__PURE__ */ jsxs35(Fragment15, { children: [
3503
- error ? /* @__PURE__ */ jsxs35(
3682
+ return /* @__PURE__ */ jsxs36(Fragment16, { children: [
3683
+ error ? /* @__PURE__ */ jsxs36(
3504
3684
  "p",
3505
3685
  {
3506
3686
  role: "alert",
@@ -3512,12 +3692,12 @@ function MermaidView({ entry, load }) {
3512
3692
  ]
3513
3693
  }
3514
3694
  ) : null,
3515
- /* @__PURE__ */ jsx50(MermaidSource, { entry, ref })
3695
+ /* @__PURE__ */ jsx51(MermaidSource, { entry, ref })
3516
3696
  ] });
3517
3697
  }
3518
3698
 
3519
3699
  // src/interactive/island-renderers.tsx
3520
- import { jsx as jsx51 } from "react/jsx-runtime";
3700
+ import { jsx as jsx52 } from "react/jsx-runtime";
3521
3701
  var HLJS_ESM = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/es/highlight.min.js";
3522
3702
  var MERMAID_ESM = "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
3523
3703
  function cachedCdnModule(url) {
@@ -3534,8 +3714,8 @@ function cachedCdnModule(url) {
3534
3714
  var loadMermaidFromCdn = cachedCdnModule(MERMAID_ESM);
3535
3715
  var loadHljs = cachedCdnModule(HLJS_ESM);
3536
3716
  function CdnHighlightedCode({ entry }) {
3537
- const [html, setHtml] = useState11(null);
3538
- useEffect10(() => {
3717
+ const [html, setHtml] = useState12(null);
3718
+ useEffect11(() => {
3539
3719
  let cancelled = false;
3540
3720
  void (async () => {
3541
3721
  try {
@@ -3551,13 +3731,13 @@ function CdnHighlightedCode({ entry }) {
3551
3731
  cancelled = true;
3552
3732
  };
3553
3733
  }, [entry.content, entry.lang]);
3554
- return /* @__PURE__ */ jsx51(CodeFigure, { label: entry.label, children: html !== null ? /* @__PURE__ */ jsx51(
3734
+ return /* @__PURE__ */ jsx52(CodeFigure, { label: entry.label, children: html !== null ? /* @__PURE__ */ jsx52(
3555
3735
  "code",
3556
3736
  {
3557
3737
  className: cn("hljs font-mono text-xs", entry.lang && `language-${entry.lang}`),
3558
3738
  dangerouslySetInnerHTML: { __html: html }
3559
3739
  }
3560
- ) : /* @__PURE__ */ jsx51("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
3740
+ ) : /* @__PURE__ */ jsx52("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
3561
3741
  }
3562
3742
  function buildIslandRenderers(opts) {
3563
3743
  const renderers = {};
@@ -3571,7 +3751,7 @@ function buildIslandRenderers(opts) {
3571
3751
  }
3572
3752
 
3573
3753
  // src/interactive/ReportInteractiveIsland.tsx
3574
- import { jsx as jsx52 } from "react/jsx-runtime";
3754
+ import { jsx as jsx53 } from "react/jsx-runtime";
3575
3755
  function ReportInteractiveIsland({
3576
3756
  report,
3577
3757
  title,
@@ -3585,7 +3765,7 @@ function ReportInteractiveIsland({
3585
3765
  () => buildIslandRenderers({ syntaxHighlighting, mermaid }),
3586
3766
  [syntaxHighlighting, mermaid]
3587
3767
  );
3588
- return /* @__PURE__ */ jsx52(
3768
+ return /* @__PURE__ */ jsx53(
3589
3769
  ReportInteractive,
3590
3770
  {
3591
3771
  report,