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.
@@ -48,8 +48,8 @@ module.exports = __toCommonJS(interactive_exports);
48
48
 
49
49
  // src/interactive/ReportInteractive.tsx
50
50
  var import_lucide_react6 = require("lucide-react");
51
- var import_react21 = require("webmcpable/react");
52
- var import_react22 = require("react");
51
+ var import_react22 = require("webmcpable/react");
52
+ var import_react23 = require("react");
53
53
 
54
54
  // src/components/ui/empty.tsx
55
55
  var import_class_variance_authority = require("class-variance-authority");
@@ -242,7 +242,7 @@ function formatValue(value) {
242
242
  function DocKv({ entry }) {
243
243
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("dl", { className: "my-2 flex flex-wrap items-baseline gap-x-2 text-sm", children: [
244
244
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { className: "font-semibold text-muted-foreground", children: entry.label }),
245
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dd", { className: "font-mono text-foreground", children: formatValue(entry.value) })
245
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dd", { className: "min-w-0 break-words font-mono text-foreground", children: formatValue(entry.value) })
246
246
  ] });
247
247
  }
248
248
 
@@ -450,7 +450,7 @@ function DocHtml({ entry }) {
450
450
  loading: "lazy",
451
451
  title,
452
452
  className: "block w-full border-0",
453
- style: { height }
453
+ style: { height, colorScheme: "light", background: "#fff" }
454
454
  }
455
455
  )
456
456
  ] });
@@ -1174,6 +1174,24 @@ function scenarioToPrompt(scenario) {
1174
1174
  }
1175
1175
  return lines.join("\n");
1176
1176
  }
1177
+ function failuresToPrompt(scenarios) {
1178
+ if (scenarios.length === 0) return "";
1179
+ const noun = scenarios.length === 1 ? "test scenario is" : "test scenarios are";
1180
+ const lines = [
1181
+ `${scenarios.length} ${noun} failing. Investigate the likely cause (they may share one) and propose a fix.`
1182
+ ];
1183
+ scenarios.forEach((scenario, i) => {
1184
+ lines.push("", `${i + 1}. ${scenario.title}`, "Steps:");
1185
+ for (const step of scenario.steps) {
1186
+ const mark = step.status === "failed" ? " \u27F5 failed here" : "";
1187
+ lines.push(` ${step.keyword} ${step.text}${mark}`);
1188
+ }
1189
+ if (scenario.errorMessage) {
1190
+ lines.push("Error:", scenario.errorMessage.trim());
1191
+ }
1192
+ });
1193
+ return lines.join("\n");
1194
+ }
1177
1195
  function scenarioPermalink(scenario) {
1178
1196
  if (typeof location === "undefined") return `#${scenario.id}`;
1179
1197
  return `${location.href.split("#")[0]}#${scenario.id}`;
@@ -1299,57 +1317,68 @@ function ReportScenario({ scenario, hideTitle = false }) {
1299
1317
  className: "gap-0 py-0",
1300
1318
  children: [
1301
1319
  /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(CardHeader, { className: "px-4 py-3", children: [
1302
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
1303
- hideTitle ? null : /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1304
- collapsible ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1305
- "button",
1306
- {
1307
- type: "button",
1308
- onClick: () => collapse.toggle(scenario.id),
1309
- "aria-expanded": !collapsed,
1310
- "aria-controls": bodyId,
1311
- "aria-label": `Toggle ${scenario.title}`,
1312
- className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
1313
- children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1314
- }
1315
- ) : null,
1316
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { "aria-hidden": true, className: cn("text-sm leading-none", meta.glyphColor), children: meta.glyph }),
1317
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { children: scenario.title })
1318
- ] }),
1319
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex shrink-0 items-center gap-2", children: [
1320
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ScenarioRunHistory, { scenarioId: scenario.id }),
1321
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ScenarioStaleness, { scenario }),
1322
- scenario.durationMs > 0 ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: (0, import_duration3.formatDuration)(scenario.durationMs) }) : null,
1323
- meta.showPill ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Badge, { variant: meta.badgeVariant, "aria-label": `Status: ${label}`, children: label }) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "sr-only", children: `Status: ${label}` }),
1324
- actions ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenu, { children: [
1325
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1326
- DropdownMenuTrigger,
1327
- {
1328
- "aria-label": `Actions for ${scenario.title}`,
1329
- className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
1330
- children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.MoreHorizontalIcon, { "aria-hidden": true })
1331
- }
1332
- ),
1333
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuContent, { align: "end", children: [
1334
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
1335
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.LinkIcon, {}),
1336
- " Copy link"
1337
- ] }),
1338
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
1339
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.FileTextIcon, {}),
1340
- " Copy as Markdown"
1341
- ] }),
1342
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
1343
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.SparklesIcon, {}),
1344
- " Explain with AI"
1345
- ] })
1320
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
1321
+ "div",
1322
+ {
1323
+ className: cn(
1324
+ // Wraps rather than squeezing: on a phone the status/duration group
1325
+ // is ~160px, which leaves a title column a few characters wide.
1326
+ "flex flex-wrap items-start gap-x-4 gap-y-1",
1327
+ hideTitle ? "justify-end" : "justify-between"
1328
+ ),
1329
+ children: [
1330
+ hideTitle ? null : /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("h3", { id: titleId, className: "flex min-w-0 flex-1 basis-48 items-center gap-2 text-sm font-medium text-foreground", children: [
1331
+ collapsible ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1332
+ "button",
1333
+ {
1334
+ type: "button",
1335
+ onClick: () => collapse.toggle(scenario.id),
1336
+ "aria-expanded": !collapsed,
1337
+ "aria-controls": bodyId,
1338
+ "aria-label": `Toggle ${scenario.title}`,
1339
+ className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
1340
+ children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1341
+ }
1342
+ ) : null,
1343
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { "aria-hidden": true, className: cn("text-sm leading-none", meta.glyphColor), children: meta.glyph }),
1344
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { children: scenario.title })
1345
+ ] }),
1346
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex shrink-0 items-center gap-2", children: [
1347
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ScenarioRunHistory, { scenarioId: scenario.id }),
1348
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ScenarioStaleness, { scenario }),
1349
+ scenario.durationMs > 0 ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: (0, import_duration3.formatDuration)(scenario.durationMs) }) : null,
1350
+ meta.showPill ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Badge, { variant: meta.badgeVariant, "aria-label": `Status: ${label}`, children: label }) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "sr-only", children: `Status: ${label}` }),
1351
+ actions ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenu, { children: [
1352
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1353
+ DropdownMenuTrigger,
1354
+ {
1355
+ "aria-label": `Actions for ${scenario.title}`,
1356
+ className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
1357
+ children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.MoreHorizontalIcon, { "aria-hidden": true })
1358
+ }
1359
+ ),
1360
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuContent, { align: "end", children: [
1361
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
1362
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.LinkIcon, {}),
1363
+ " Copy link"
1364
+ ] }),
1365
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
1366
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.FileTextIcon, {}),
1367
+ " Copy as Markdown"
1368
+ ] }),
1369
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
1370
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.SparklesIcon, {}),
1371
+ " Explain with AI"
1372
+ ] })
1373
+ ] })
1374
+ ] }) : null
1346
1375
  ] })
1347
- ] }) : null
1348
- ] })
1349
- ] }),
1376
+ ]
1377
+ }
1378
+ ),
1350
1379
  scenario.tags.length > 0 || (scenario.tickets?.length ?? 0) > 0 ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("ul", { "aria-label": "Tags and tickets", className: "flex flex-wrap gap-1.5", children: [
1351
1380
  scenario.tags.map((t) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
1352
- (scenario.tickets ?? []).map((ticket) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("li", { children: ticket.url ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("a", { href: ticket.url, target: "_blank", rel: "noreferrer noopener", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Badge, { variant: "outline", children: ticket.id }) }) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Badge, { variant: "outline", children: ticket.id }) }, `ticket-${ticket.id}`))
1381
+ (scenario.tickets ?? []).map((ticket) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("li", { children: ticket.url ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("a", { href: ticket.url, target: "_blank", rel: "noreferrer noopener", className: "inline-flex min-h-6 items-center", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Badge, { variant: "outline", children: ticket.id }) }) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Badge, { variant: "outline", children: ticket.id }) }, `ticket-${ticket.id}`))
1353
1382
  ] }) : null
1354
1383
  ] }),
1355
1384
  /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
@@ -1492,6 +1521,7 @@ function ReportFeatureList() {
1492
1521
 
1493
1522
  // src/components/ReportMeta.tsx
1494
1523
  var import_duration4 = require("executable-stories-core/utils/duration");
1524
+ var import_report_queries = require("executable-stories-core/report-queries");
1495
1525
  var import_jsx_runtime32 = require("react/jsx-runtime");
1496
1526
  function ReportMeta() {
1497
1527
  const report = useReport();
@@ -1535,7 +1565,9 @@ function ReportMeta() {
1535
1565
  value: url ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1536
1566
  });
1537
1567
  }
1538
- if (items.length === 0) return null;
1568
+ const slowest = (0, import_report_queries.slowestScenarios)(report, 5);
1569
+ const showSlowest = slowest.length >= 3;
1570
+ if (items.length === 0 && !showSlowest) return null;
1539
1571
  return (
1540
1572
  // Run metadata is useful but secondary to "what failed" — tuck it into a
1541
1573
  // collapsed disclosure so it doesn't push the result down. Native <details>
@@ -1548,7 +1580,14 @@ function ReportMeta() {
1548
1580
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("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__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-baseline gap-1.5", children: [
1549
1581
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("dt", { className: "font-medium", children: item.label }),
1550
1582
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("dd", { className: "font-mono text-foreground", children: item.value })
1551
- ] }, item.label)) })
1583
+ ] }, item.label)) }),
1584
+ showSlowest ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "border-t border-border px-4 py-3", children: [
1585
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "mb-1.5 font-medium text-muted-foreground", children: "Slowest scenarios" }),
1586
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("ol", { className: "flex flex-col gap-1", "aria-label": "Slowest scenarios", children: slowest.map((s) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("li", { className: "flex items-baseline justify-between gap-4", children: [
1587
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("a", { href: `#${s.id}`, className: "min-w-0 text-primary underline underline-offset-2", children: s.title }),
1588
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "shrink-0 font-mono tabular-nums text-muted-foreground", children: (0, import_duration4.formatDuration)(s.durationMs) })
1589
+ ] }, s.id)) })
1590
+ ] }) : null
1552
1591
  ] })
1553
1592
  );
1554
1593
  }
@@ -2024,21 +2063,26 @@ function allTags(report) {
2024
2063
  }
2025
2064
  return [...seen];
2026
2065
  }
2027
- function queryMatches(scenario, q) {
2066
+ function queryMatches(scenario, q, sourceFile) {
2028
2067
  if (q === "") return true;
2029
2068
  if (scenario.title.toLowerCase().includes(q)) return true;
2069
+ if (sourceFile?.toLowerCase().includes(q)) return true;
2070
+ if (scenario.errorMessage?.toLowerCase().includes(q)) return true;
2030
2071
  for (const tag of scenario.tags) {
2031
2072
  if (tag.toLowerCase().includes(q)) return true;
2032
2073
  }
2074
+ for (const ticket of scenario.tickets ?? []) {
2075
+ if (ticket.id.toLowerCase().includes(q)) return true;
2076
+ }
2033
2077
  for (const step of scenario.steps) {
2034
2078
  if (step.text.toLowerCase().includes(q)) return true;
2035
2079
  }
2036
2080
  return false;
2037
2081
  }
2038
- function scenarioMatches(scenario, q, status, tags) {
2082
+ function scenarioMatches(scenario, q, status, tags, sourceFile) {
2039
2083
  if (status !== "all" && scenario.status !== status) return false;
2040
2084
  if (tags.length > 0 && !tags.some((t) => scenario.tags.includes(t))) return false;
2041
- return queryMatches(scenario, q);
2085
+ return queryMatches(scenario, q, sourceFile);
2042
2086
  }
2043
2087
  function summarizeScenarios(scenarios) {
2044
2088
  let total = 0, passed = 0, failed = 0, skipped = 0, pending = 0, durationMs = 0;
@@ -2061,7 +2105,9 @@ function filterReport(report, criteria) {
2061
2105
  const features = [];
2062
2106
  let topTotal = 0, topPassed = 0, topFailed = 0, topSkipped = 0, topPending = 0, topDuration = 0;
2063
2107
  for (const feature of report.features) {
2064
- const matched = feature.scenarios.filter((s) => scenarioMatches(s, q, status, tags));
2108
+ const matched = feature.scenarios.filter(
2109
+ (s) => scenarioMatches(s, q, status, tags, feature.sourceFile)
2110
+ );
2065
2111
  if (matched.length === 0) continue;
2066
2112
  const summary = summarizeScenarios(matched);
2067
2113
  features.push({ ...feature, summary, scenarios: matched });
@@ -2085,6 +2131,9 @@ function filterReport(report, criteria) {
2085
2131
  features
2086
2132
  };
2087
2133
  }
2134
+ function failedScenarios(report) {
2135
+ return report.features.flatMap((f) => f.scenarios.filter((s) => s.status === "failed"));
2136
+ }
2088
2137
  function listFailures(report) {
2089
2138
  const out = [];
2090
2139
  for (const feature of report.features) {
@@ -2168,7 +2217,7 @@ function ReportAgentFilterNotice({
2168
2217
  // src/interactive/ReportFailureBanner.tsx
2169
2218
  var import_react10 = require("react");
2170
2219
  var import_jsx_runtime41 = require("react/jsx-runtime");
2171
- function ReportFailureBanner({ failures }) {
2220
+ function ReportFailureBanner({ failures, onCopyAll }) {
2172
2221
  const first = failures[0];
2173
2222
  const jumpToFirst = (0, import_react10.useCallback)(() => {
2174
2223
  if (!first) return;
@@ -2198,7 +2247,23 @@ function ReportFailureBanner({ failures }) {
2198
2247
  "aria-label": "View first failure",
2199
2248
  children: "View first failure \u2193"
2200
2249
  }
2201
- )
2250
+ ),
2251
+ onCopyAll ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
2252
+ "button",
2253
+ {
2254
+ type: "button",
2255
+ className: "es-failure-banner-jump",
2256
+ onClick: onCopyAll,
2257
+ "aria-label": "Copy failures for an agent",
2258
+ children: [
2259
+ "Copy ",
2260
+ failures.length,
2261
+ " failure",
2262
+ failures.length === 1 ? "" : "s",
2263
+ " for an agent"
2264
+ ]
2265
+ }
2266
+ ) : null
2202
2267
  ]
2203
2268
  }
2204
2269
  );
@@ -2403,7 +2468,7 @@ var ReportSearch = (0, import_react13.forwardRef)(
2403
2468
  onChange,
2404
2469
  matchedCount,
2405
2470
  totalCount,
2406
- placeholder = "Search scenarios or tags\u2026",
2471
+ placeholder = "Search scenarios, tags, tickets, errors\u2026",
2407
2472
  className
2408
2473
  } = props;
2409
2474
  const inputId = (0, import_react13.useId)();
@@ -2461,9 +2526,119 @@ var ReportSearch = (0, import_react13.forwardRef)(
2461
2526
  }
2462
2527
  );
2463
2528
 
2464
- // src/interactive/ReportShortcutsHelp.tsx
2529
+ // src/interactive/ReportSinceLastVisit.tsx
2465
2530
  var import_react14 = require("react");
2466
2531
 
2532
+ // src/interactive/last-visit.ts
2533
+ function snapshotOf(report, atMs) {
2534
+ const statuses = {};
2535
+ for (const feature of report.features) {
2536
+ for (const scenario of feature.scenarios) {
2537
+ statuses[scenario.id] = scenario.status;
2538
+ }
2539
+ }
2540
+ return { runId: report.runId, atMs, statuses };
2541
+ }
2542
+ function diffSinceVisit(previous, report) {
2543
+ if (!previous) return null;
2544
+ if (previous.runId === report.runId) return null;
2545
+ const newlyFailing = [];
2546
+ const newlyPassing = [];
2547
+ const added = [];
2548
+ for (const feature of report.features) {
2549
+ for (const scenario of feature.scenarios) {
2550
+ const ref = { id: scenario.id, title: scenario.title };
2551
+ const before = previous.statuses[scenario.id];
2552
+ if (before === void 0) {
2553
+ added.push(ref);
2554
+ } else if (before !== "failed" && scenario.status === "failed") {
2555
+ newlyFailing.push(ref);
2556
+ } else if (before === "failed" && scenario.status === "passed") {
2557
+ newlyPassing.push(ref);
2558
+ }
2559
+ }
2560
+ }
2561
+ if (newlyFailing.length === 0 && newlyPassing.length === 0 && added.length === 0) return null;
2562
+ return { sinceMs: previous.atMs, newlyFailing, newlyPassing, added };
2563
+ }
2564
+
2565
+ // src/interactive/ReportSinceLastVisit.tsx
2566
+ var import_jsx_runtime47 = require("react/jsx-runtime");
2567
+ var KEY = "es-last-visit";
2568
+ var MAX_LINKS2 = 5;
2569
+ function readVisit() {
2570
+ try {
2571
+ const raw = localStorage.getItem(KEY);
2572
+ if (!raw) return null;
2573
+ const parsed = JSON.parse(raw);
2574
+ if (typeof parsed !== "object" || parsed === null) return null;
2575
+ const v = parsed;
2576
+ if (typeof v.runId !== "string" || typeof v.atMs !== "number" || typeof v.statuses !== "object") {
2577
+ return null;
2578
+ }
2579
+ return { runId: v.runId, atMs: v.atMs, statuses: v.statuses };
2580
+ } catch {
2581
+ return null;
2582
+ }
2583
+ }
2584
+ function writeVisit(snapshot) {
2585
+ try {
2586
+ localStorage.setItem(KEY, JSON.stringify(snapshot));
2587
+ } catch {
2588
+ }
2589
+ }
2590
+ function ScenarioLinks2({ scenarios, tone }) {
2591
+ return /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(import_jsx_runtime47.Fragment, { children: [
2592
+ scenarios.slice(0, MAX_LINKS2).map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("span", { children: [
2593
+ i > 0 ? ", " : "",
2594
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("a", { href: `#${s.id}`, className: cn("underline underline-offset-2 hover:no-underline", tone), children: s.title })
2595
+ ] }, s.id)),
2596
+ scenarios.length > MAX_LINKS2 ? /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("span", { children: [
2597
+ " and ",
2598
+ scenarios.length - MAX_LINKS2,
2599
+ " more"
2600
+ ] }) : null
2601
+ ] });
2602
+ }
2603
+ function ReportSinceLastVisit({ report }) {
2604
+ const [delta, setDelta] = (0, import_react14.useState)(null);
2605
+ const [nowMs, setNowMs] = (0, import_react14.useState)(0);
2606
+ (0, import_react14.useEffect)(() => {
2607
+ const now = Date.now();
2608
+ setDelta(diffSinceVisit(readVisit(), report));
2609
+ setNowMs(now);
2610
+ writeVisit(snapshotOf(report, now));
2611
+ }, [report]);
2612
+ if (!delta) return null;
2613
+ const { newlyFailing, newlyPassing, added } = delta;
2614
+ return /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("p", { className: "text-xs text-muted-foreground", "data-testid": "es-since-last-visit", children: [
2615
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("span", { className: "font-medium text-foreground", children: [
2616
+ "Since your last visit (",
2617
+ formatRelativeAge(delta.sinceMs, nowMs),
2618
+ "):"
2619
+ ] }),
2620
+ newlyFailing.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("span", { className: "ml-2 text-fail", children: [
2621
+ newlyFailing.length,
2622
+ " started failing (",
2623
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(ScenarioLinks2, { scenarios: newlyFailing, tone: "text-fail" }),
2624
+ ")"
2625
+ ] }) : null,
2626
+ newlyPassing.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("span", { className: "ml-2 text-pass", children: [
2627
+ newlyPassing.length,
2628
+ " fixed (",
2629
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(ScenarioLinks2, { scenarios: newlyPassing, tone: "text-pass" }),
2630
+ ")"
2631
+ ] }) : null,
2632
+ added.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("span", { className: "ml-2", children: [
2633
+ added.length,
2634
+ " new"
2635
+ ] }) : null
2636
+ ] });
2637
+ }
2638
+
2639
+ // src/interactive/ReportShortcutsHelp.tsx
2640
+ var import_react15 = require("react");
2641
+
2467
2642
  // src/components/ui/button.tsx
2468
2643
  var React7 = require("react");
2469
2644
  var import_class_variance_authority4 = require("class-variance-authority");
@@ -2518,7 +2693,7 @@ function Button({
2518
2693
  }
2519
2694
 
2520
2695
  // src/interactive/ReportShortcutsHelp.tsx
2521
- var import_jsx_runtime47 = require("react/jsx-runtime");
2696
+ var import_jsx_runtime48 = require("react/jsx-runtime");
2522
2697
  var SHORTCUTS = [
2523
2698
  { keys: "/", description: "Focus search" },
2524
2699
  { keys: "f", description: "Jump to next failure" },
@@ -2530,20 +2705,20 @@ var SHORTCUTS = [
2530
2705
  ];
2531
2706
  function Keys({ keys }) {
2532
2707
  const parts = keys.split(" ");
2533
- return /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("span", { className: "inline-flex items-center gap-1", children: parts.map((k, i) => /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("span", { className: "inline-flex items-center gap-1", children: [
2534
- i > 0 ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("span", { className: "text-xs text-muted-foreground", children: "+" }) : null,
2535
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("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 })
2708
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "inline-flex items-center gap-1", children: parts.map((k, i) => /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("span", { className: "inline-flex items-center gap-1", children: [
2709
+ i > 0 ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "text-xs text-muted-foreground", children: "+" }) : null,
2710
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("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 })
2536
2711
  ] }, k)) });
2537
2712
  }
2538
2713
  function ReportShortcutsHelp({ open, onClose }) {
2539
- const dialogRef = (0, import_react14.useRef)(null);
2540
- (0, import_react14.useEffect)(() => {
2714
+ const dialogRef = (0, import_react15.useRef)(null);
2715
+ (0, import_react15.useEffect)(() => {
2541
2716
  const dialog = dialogRef.current;
2542
2717
  if (!dialog) return;
2543
2718
  if (open && !dialog.open) dialog.showModal();
2544
2719
  else if (!open && dialog.open) dialog.close();
2545
2720
  }, [open]);
2546
- return /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(
2721
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(
2547
2722
  "dialog",
2548
2723
  {
2549
2724
  ref: dialogRef,
@@ -2551,20 +2726,20 @@ function ReportShortcutsHelp({ open, onClose }) {
2551
2726
  "aria-label": "Keyboard shortcuts",
2552
2727
  onClose,
2553
2728
  children: [
2554
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("h2", { className: "text-base font-semibold", children: "Keyboard shortcuts" }),
2555
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("dl", { className: "mt-4 grid grid-cols-[auto_1fr] items-center gap-x-4 gap-y-2.5", children: SHORTCUTS.map((s) => /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "col-span-2 grid grid-cols-subgrid items-center", children: [
2556
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("dt", { className: "justify-self-start", children: /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(Keys, { keys: s.keys }) }),
2557
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("dd", { className: "text-sm text-muted-foreground", children: s.description })
2729
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("h2", { className: "text-base font-semibold", children: "Keyboard shortcuts" }),
2730
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("dl", { className: "mt-4 grid grid-cols-[auto_1fr] items-center gap-x-4 gap-y-2.5", children: SHORTCUTS.map((s) => /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "col-span-2 grid grid-cols-subgrid items-center", children: [
2731
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("dt", { className: "justify-self-start", children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(Keys, { keys: s.keys }) }),
2732
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("dd", { className: "text-sm text-muted-foreground", children: s.description })
2558
2733
  ] }, s.keys)) }),
2559
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("form", { method: "dialog", className: "mt-6", children: /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(Button, { type: "submit", variant: "outline", size: "sm", children: "Close" }) })
2734
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("form", { method: "dialog", className: "mt-6", children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(Button, { type: "submit", variant: "outline", size: "sm", children: "Close" }) })
2560
2735
  ]
2561
2736
  }
2562
2737
  );
2563
2738
  }
2564
2739
 
2565
2740
  // src/interactive/ReportToc.tsx
2566
- var import_react15 = require("react");
2567
- var import_jsx_runtime48 = require("react/jsx-runtime");
2741
+ var import_react16 = require("react");
2742
+ var import_jsx_runtime49 = require("react/jsx-runtime");
2568
2743
  var DOT = {
2569
2744
  passed: "bg-pass",
2570
2745
  failed: "bg-fail",
@@ -2573,12 +2748,12 @@ var DOT = {
2573
2748
  };
2574
2749
  function TocContent({ onNavigate }) {
2575
2750
  const report = useReport();
2576
- const [activeId, setActiveId] = (0, import_react15.useState)(null);
2577
- const scenarioIds = (0, import_react15.useMemo)(
2751
+ const [activeId, setActiveId] = (0, import_react16.useState)(null);
2752
+ const scenarioIds = (0, import_react16.useMemo)(
2578
2753
  () => report.features.flatMap((f) => f.scenarios.map((s) => s.id)),
2579
2754
  [report]
2580
2755
  );
2581
- (0, import_react15.useEffect)(() => {
2756
+ (0, import_react16.useEffect)(() => {
2582
2757
  if (typeof IntersectionObserver === "undefined") return;
2583
2758
  const els = scenarioIds.map((id) => document.getElementById(id)).filter((el) => el !== null);
2584
2759
  if (els.length === 0) return;
@@ -2593,11 +2768,11 @@ function TocContent({ onNavigate }) {
2593
2768
  return () => observer.disconnect();
2594
2769
  }, [scenarioIds]);
2595
2770
  if (report.features.length === 0) return null;
2596
- return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("ul", { className: "flex list-none flex-col gap-4 pl-0", children: report.features.map((feature) => /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("li", { children: [
2597
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("p", { className: "mb-1.5 font-semibold leading-snug text-foreground", children: feature.title }),
2598
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("ul", { className: "flex list-none flex-col gap-0.5 pl-0", children: feature.scenarios.map((scenario) => {
2771
+ return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("ul", { className: "flex list-none flex-col gap-4 pl-0", children: report.features.map((feature) => /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("li", { children: [
2772
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("p", { className: "mb-1.5 font-semibold leading-snug text-foreground", children: feature.title }),
2773
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("ul", { className: "flex list-none flex-col gap-0.5 pl-0", children: feature.scenarios.map((scenario) => {
2599
2774
  const active = activeId === scenario.id;
2600
- return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(
2775
+ return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
2601
2776
  "a",
2602
2777
  {
2603
2778
  href: `#${scenario.id}`,
@@ -2612,14 +2787,14 @@ function TocContent({ onNavigate }) {
2612
2787
  active ? "border-primary font-medium text-foreground" : "border-border text-muted-foreground"
2613
2788
  ),
2614
2789
  children: [
2615
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
2790
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
2616
2791
  "span",
2617
2792
  {
2618
2793
  "aria-hidden": true,
2619
2794
  className: cn("mt-1 size-1.5 shrink-0 rounded-full", DOT[scenario.status])
2620
2795
  }
2621
2796
  ),
2622
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "break-words", children: scenario.title })
2797
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "break-words", children: scenario.title })
2623
2798
  ]
2624
2799
  }
2625
2800
  ) }, scenario.id);
@@ -2629,48 +2804,48 @@ function TocContent({ onNavigate }) {
2629
2804
  function ReportToc() {
2630
2805
  const report = useReport();
2631
2806
  if (report.features.length === 0) return null;
2632
- return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(
2807
+ return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
2633
2808
  "nav",
2634
2809
  {
2635
2810
  "aria-label": "Table of contents",
2636
2811
  className: "sticky top-4 hidden h-fit w-64 shrink-0 self-start text-xs lg:block",
2637
2812
  children: [
2638
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("p", { className: "mb-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Contents" }),
2639
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(TocContent, {})
2813
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("p", { className: "mb-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Contents" }),
2814
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(TocContent, {})
2640
2815
  ]
2641
2816
  }
2642
2817
  );
2643
2818
  }
2644
2819
 
2645
2820
  // src/interactive/ReportTocDrawer.tsx
2646
- var import_react16 = require("react");
2821
+ var import_react17 = require("react");
2647
2822
  var import_dialog = require("@base-ui/react/dialog");
2648
2823
  var import_lucide_react5 = require("lucide-react");
2649
- var import_jsx_runtime49 = require("react/jsx-runtime");
2824
+ var import_jsx_runtime50 = require("react/jsx-runtime");
2650
2825
  function ReportTocDrawer() {
2651
2826
  const report = useReport();
2652
- const [open, setOpen] = (0, import_react16.useState)(false);
2827
+ const [open, setOpen] = (0, import_react17.useState)(false);
2653
2828
  if (report.features.length === 0) return null;
2654
- return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_dialog.Dialog.Root, { open, onOpenChange: setOpen, children: [
2655
- /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_dialog.Dialog.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: [
2656
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_lucide_react5.ListIcon, { "aria-hidden": true }),
2829
+ return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_dialog.Dialog.Root, { open, onOpenChange: setOpen, children: [
2830
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_dialog.Dialog.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: [
2831
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_lucide_react5.ListIcon, { "aria-hidden": true }),
2657
2832
  " Contents"
2658
2833
  ] }),
2659
- /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_dialog.Dialog.Portal, { children: [
2660
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_dialog.Dialog.Backdrop, { className: "fixed inset-0 z-50 bg-black/50 transition-opacity duration-200 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0" }),
2661
- /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_dialog.Dialog.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: [
2662
- /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "flex items-center justify-between", children: [
2663
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_dialog.Dialog.Title, { className: "text-xs font-semibold tracking-wide text-muted-foreground uppercase", children: "Contents" }),
2664
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
2834
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_dialog.Dialog.Portal, { children: [
2835
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_dialog.Dialog.Backdrop, { className: "fixed inset-0 z-50 bg-black/50 transition-opacity duration-200 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0" }),
2836
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_dialog.Dialog.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: [
2837
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { className: "flex items-center justify-between", children: [
2838
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_dialog.Dialog.Title, { className: "text-xs font-semibold tracking-wide text-muted-foreground uppercase", children: "Contents" }),
2839
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
2665
2840
  import_dialog.Dialog.Close,
2666
2841
  {
2667
2842
  "aria-label": "Close contents",
2668
2843
  className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
2669
- children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_lucide_react5.XIcon, { "aria-hidden": true })
2844
+ children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_lucide_react5.XIcon, { "aria-hidden": true })
2670
2845
  }
2671
2846
  )
2672
2847
  ] }),
2673
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(TocContent, { onNavigate: () => setOpen(false) })
2848
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(TocContent, { onNavigate: () => setOpen(false) })
2674
2849
  ] })
2675
2850
  ] })
2676
2851
  ] });
@@ -2704,10 +2879,10 @@ function failuresFirst(report) {
2704
2879
  }
2705
2880
 
2706
2881
  // src/interactive/use-deep-link-scroll.ts
2707
- var import_react17 = require("react");
2882
+ var import_react18 = require("react");
2708
2883
  function useDeepLinkScroll() {
2709
- const lastAnchor = (0, import_react17.useRef)(null);
2710
- (0, import_react17.useEffect)(() => {
2884
+ const lastAnchor = (0, import_react18.useRef)(null);
2885
+ (0, import_react18.useEffect)(() => {
2711
2886
  function scrollToHash() {
2712
2887
  const { anchor } = splitHash(window.location.hash);
2713
2888
  if (!anchor || anchor === lastAnchor.current) return;
@@ -2723,7 +2898,7 @@ function useDeepLinkScroll() {
2723
2898
  }
2724
2899
 
2725
2900
  // src/interactive/use-keyboard-shortcuts.ts
2726
- var import_react18 = require("react");
2901
+ var import_react19 = require("react");
2727
2902
  function isEditableTarget(target) {
2728
2903
  if (!(target instanceof HTMLElement)) return false;
2729
2904
  const tag = target.tagName;
@@ -2732,9 +2907,9 @@ function isEditableTarget(target) {
2732
2907
  return false;
2733
2908
  }
2734
2909
  function useKeyboardShortcuts(handlers) {
2735
- const ref = (0, import_react18.useRef)(handlers);
2910
+ const ref = (0, import_react19.useRef)(handlers);
2736
2911
  ref.current = handlers;
2737
- (0, import_react18.useEffect)(() => {
2912
+ (0, import_react19.useEffect)(() => {
2738
2913
  function onKeyDown(e) {
2739
2914
  const h = ref.current;
2740
2915
  if (e.key === "Escape") {
@@ -2778,8 +2953,8 @@ function useKeyboardShortcuts(handlers) {
2778
2953
  }
2779
2954
 
2780
2955
  // src/interactive/use-theme.ts
2781
- var import_react19 = require("react");
2782
- var KEY = "es-theme";
2956
+ var import_react20 = require("react");
2957
+ var KEY2 = "es-theme";
2783
2958
  function systemDark() {
2784
2959
  return typeof matchMedia !== "undefined" && matchMedia("(prefers-color-scheme: dark)").matches;
2785
2960
  }
@@ -2793,19 +2968,19 @@ function applyTheme(pref) {
2793
2968
  }
2794
2969
  function initialPref() {
2795
2970
  if (typeof localStorage !== "undefined") {
2796
- const stored = localStorage.getItem(KEY);
2971
+ const stored = localStorage.getItem(KEY2);
2797
2972
  if (stored === "light" || stored === "dark" || stored === "system") return stored;
2798
2973
  }
2799
2974
  return "system";
2800
2975
  }
2801
2976
  function useTheme() {
2802
- const [pref, setPrefState] = (0, import_react19.useState)("system");
2803
- (0, import_react19.useEffect)(() => {
2977
+ const [pref, setPrefState] = (0, import_react20.useState)("system");
2978
+ (0, import_react20.useEffect)(() => {
2804
2979
  const p = initialPref();
2805
2980
  setPrefState(p);
2806
2981
  applyTheme(p);
2807
2982
  }, []);
2808
- (0, import_react19.useEffect)(() => {
2983
+ (0, import_react20.useEffect)(() => {
2809
2984
  if (typeof matchMedia === "undefined") return;
2810
2985
  const mq = matchMedia("(prefers-color-scheme: dark)");
2811
2986
  const onChange = () => {
@@ -2814,11 +2989,11 @@ function useTheme() {
2814
2989
  mq.addEventListener("change", onChange);
2815
2990
  return () => mq.removeEventListener("change", onChange);
2816
2991
  }, [pref]);
2817
- const setPref = (0, import_react19.useCallback)((p) => {
2992
+ const setPref = (0, import_react20.useCallback)((p) => {
2818
2993
  setPrefState(p);
2819
2994
  applyTheme(p);
2820
2995
  try {
2821
- localStorage.setItem(KEY, p);
2996
+ localStorage.setItem(KEY2, p);
2822
2997
  } catch {
2823
2998
  }
2824
2999
  }, []);
@@ -2826,19 +3001,19 @@ function useTheme() {
2826
3001
  }
2827
3002
 
2828
3003
  // src/interactive/use-url-state.ts
2829
- var import_react20 = require("react");
3004
+ var import_react21 = require("react");
2830
3005
  var WRITE_DELAY_MS = 250;
2831
3006
  function useUrlState() {
2832
- const [state, setState] = (0, import_react20.useState)(DEFAULT_URL_STATE);
2833
- const ready = (0, import_react20.useRef)(false);
2834
- (0, import_react20.useEffect)(() => {
3007
+ const [state, setState] = (0, import_react21.useState)(DEFAULT_URL_STATE);
3008
+ const ready = (0, import_react21.useRef)(false);
3009
+ (0, import_react21.useEffect)(() => {
2835
3010
  const read = () => setState(decodeUrlState(splitHash(window.location.hash).params));
2836
3011
  read();
2837
3012
  ready.current = true;
2838
3013
  window.addEventListener("hashchange", read);
2839
3014
  return () => window.removeEventListener("hashchange", read);
2840
3015
  }, []);
2841
- (0, import_react20.useEffect)(() => {
3016
+ (0, import_react21.useEffect)(() => {
2842
3017
  if (!ready.current) return;
2843
3018
  const timer = setTimeout(
2844
3019
  () => writeHash(splitHash(window.location.hash).anchor, encodeUrlState(state)),
@@ -2846,7 +3021,7 @@ function useUrlState() {
2846
3021
  );
2847
3022
  return () => clearTimeout(timer);
2848
3023
  }, [state]);
2849
- const update = (0, import_react20.useCallback)(
3024
+ const update = (0, import_react21.useCallback)(
2850
3025
  (patch) => setState((prev) => ({ ...prev, ...patch })),
2851
3026
  []
2852
3027
  );
@@ -2854,7 +3029,7 @@ function useUrlState() {
2854
3029
  }
2855
3030
 
2856
3031
  // src/interactive/webmcp-tools.ts
2857
- var import_report_queries = require("executable-stories-core/report-queries");
3032
+ var import_report_queries2 = require("executable-stories-core/report-queries");
2858
3033
  var STATUSES2 = [
2859
3034
  "passed",
2860
3035
  "failed",
@@ -2937,9 +3112,9 @@ var READ_ONLY = { readOnlyHint: true, untrustedContentHint: true };
2937
3112
  function reportTools(deps) {
2938
3113
  const { report, setView, onAgentFilter } = deps;
2939
3114
  const now = deps.now ?? Date.now;
2940
- const provenance = () => ({ run: (0, import_report_queries.runProvenance)(report, now()) });
3115
+ const provenance = () => ({ run: (0, import_report_queries2.runProvenance)(report, now()) });
2941
3116
  const list = (filters) => {
2942
- const scenarios = (0, import_report_queries.listScenarioSummaries)(report, filters);
3117
+ const scenarios = (0, import_report_queries2.listScenarioSummaries)(report, filters);
2943
3118
  return { ...provenance(), total: scenarios.length, scenarios };
2944
3119
  };
2945
3120
  return {
@@ -2973,7 +3148,7 @@ function reportTools(deps) {
2973
3148
  execute: () => ({
2974
3149
  ...provenance(),
2975
3150
  summary: report.summary,
2976
- features: (0, import_report_queries.getFeatureSummary)(report)
3151
+ features: (0, import_report_queries2.getFeatureSummary)(report)
2977
3152
  })
2978
3153
  },
2979
3154
  get_scenario: {
@@ -2992,7 +3167,7 @@ function reportTools(deps) {
2992
3167
  },
2993
3168
  execute: (input) => guarded(() => {
2994
3169
  const idOrTitle = readString(asRecord(input), "idOrTitle") ?? "";
2995
- const found = (0, import_report_queries.getScenario)(report, idOrTitle);
3170
+ const found = (0, import_report_queries2.getScenario)(report, idOrTitle);
2996
3171
  if (!found) {
2997
3172
  return {
2998
3173
  ...provenance(),
@@ -3009,7 +3184,7 @@ function reportTools(deps) {
3009
3184
  // The summary projection, not the raw scenario: doc entry bodies and
3010
3185
  // base64 attachments are unbounded, and this payload goes straight
3011
3186
  // into a model's context window.
3012
- scenario: (0, import_report_queries.toScenarioSummary)(found.feature, found.scenario)
3187
+ scenario: (0, import_report_queries2.toScenarioSummary)(found.feature, found.scenario)
3013
3188
  };
3014
3189
  })
3015
3190
  },
@@ -3068,12 +3243,12 @@ function reportTools(deps) {
3068
3243
  }
3069
3244
 
3070
3245
  // src/interactive/ReportInteractive.tsx
3071
- var import_jsx_runtime50 = require("react/jsx-runtime");
3246
+ var import_jsx_runtime51 = require("react/jsx-runtime");
3072
3247
  function ReportInteractive(props) {
3073
3248
  const { report, className, title, dataTheme } = props;
3074
3249
  const result = unwrapReport(report);
3075
3250
  if (!result.ok) {
3076
- return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3251
+ return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3077
3252
  ReportErrorShell,
3078
3253
  {
3079
3254
  error: result.error,
@@ -3083,7 +3258,7 @@ function ReportInteractive(props) {
3083
3258
  }
3084
3259
  );
3085
3260
  }
3086
- return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(ReportInteractiveView, { ...props, report: result.data });
3261
+ return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ReportInteractiveView, { ...props, report: result.data });
3087
3262
  }
3088
3263
  var THEME_OPTIONS = [
3089
3264
  { value: "light", label: "Light", Icon: import_lucide_react6.Sun },
@@ -3094,7 +3269,7 @@ function ThemeSegment({
3094
3269
  pref,
3095
3270
  onPref
3096
3271
  }) {
3097
- return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3272
+ return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3098
3273
  ToggleGroup,
3099
3274
  {
3100
3275
  size: "sm",
@@ -3104,14 +3279,14 @@ function ThemeSegment({
3104
3279
  if (next) onPref(next);
3105
3280
  },
3106
3281
  "aria-label": "Color theme",
3107
- children: THEME_OPTIONS.map(({ value, label, Icon }) => /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3282
+ children: THEME_OPTIONS.map(({ value, label, Icon }) => /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3108
3283
  ToggleGroupItem,
3109
3284
  {
3110
3285
  value,
3111
3286
  "aria-label": label,
3112
3287
  title: label,
3113
3288
  className: "px-2",
3114
- children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(Icon, {})
3289
+ children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(Icon, {})
3115
3290
  },
3116
3291
  value
3117
3292
  ))
@@ -3132,26 +3307,26 @@ function ReportInteractiveView({
3132
3307
  }) {
3133
3308
  const [urlState, setUrlState] = useUrlState();
3134
3309
  const { query, status: statusFilter, tags: activeTags, detail } = urlState;
3135
- const setQuery = (0, import_react22.useCallback)(
3310
+ const setQuery = (0, import_react23.useCallback)(
3136
3311
  (next) => setUrlState({ query: next }),
3137
3312
  [setUrlState]
3138
3313
  );
3139
- const setStatusFilter = (0, import_react22.useCallback)(
3314
+ const setStatusFilter = (0, import_react23.useCallback)(
3140
3315
  (next) => setUrlState({ status: next }),
3141
3316
  [setUrlState]
3142
3317
  );
3143
- const setDetail = (0, import_react22.useCallback)(
3318
+ const setDetail = (0, import_react23.useCallback)(
3144
3319
  (next) => setUrlState({ detail: next }),
3145
3320
  [setUrlState]
3146
3321
  );
3147
- const [helpOpen, setHelpOpen] = (0, import_react22.useState)(false);
3322
+ const [helpOpen, setHelpOpen] = (0, import_react23.useState)(false);
3148
3323
  const { pref: themePref, setPref: setThemePref } = useTheme();
3149
- const searchRef = (0, import_react22.useRef)(null);
3150
- const expandId = (0, import_react22.useId)();
3151
- const docsId = (0, import_react22.useId)();
3152
- const deferredQuery = (0, import_react22.useDeferredValue)(query);
3153
- const failures = (0, import_react22.useMemo)(() => listFailures(report), [report]);
3154
- const filtered = (0, import_react22.useMemo)(
3324
+ const searchRef = (0, import_react23.useRef)(null);
3325
+ const expandId = (0, import_react23.useId)();
3326
+ const docsId = (0, import_react23.useId)();
3327
+ const deferredQuery = (0, import_react23.useDeferredValue)(query);
3328
+ const failures = (0, import_react23.useMemo)(() => listFailures(report), [report]);
3329
+ const filtered = (0, import_react23.useMemo)(
3155
3330
  // Features carrying failures float to the top so the first thing on screen
3156
3331
  // is the thing that needs attention; an all-green run keeps source order.
3157
3332
  () => failuresFirst(
@@ -3164,7 +3339,7 @@ function ReportInteractiveView({
3164
3339
  [report, deferredQuery, statusFilter, activeTags]
3165
3340
  );
3166
3341
  const isFiltering = query !== deferredQuery;
3167
- const statusOptions = (0, import_react22.useMemo)(() => {
3342
+ const statusOptions = (0, import_react23.useMemo)(() => {
3168
3343
  const s = report.summary;
3169
3344
  const opts = [
3170
3345
  { key: "all", label: "All", count: s.total }
@@ -3179,21 +3354,21 @@ function ReportInteractiveView({
3179
3354
  opts.push({ key: "pending", label: "Pending", count: s.pending });
3180
3355
  return opts;
3181
3356
  }, [report]);
3182
- const tagOptions = (0, import_react22.useMemo)(() => allTags(report), [report]);
3183
- const toggleTag = (0, import_react22.useCallback)(
3357
+ const tagOptions = (0, import_react23.useMemo)(() => allTags(report), [report]);
3358
+ const toggleTag = (0, import_react23.useCallback)(
3184
3359
  (tag) => setUrlState({
3185
3360
  tags: activeTags.includes(tag) ? activeTags.filter((t) => t !== tag) : [...activeTags, tag]
3186
3361
  }),
3187
3362
  [activeTags, setUrlState]
3188
3363
  );
3189
- const [toast, setToast] = (0, import_react22.useState)(null);
3190
- const toastTimer = (0, import_react22.useRef)(null);
3191
- const showToast = (0, import_react22.useCallback)((msg) => {
3364
+ const [toast, setToast] = (0, import_react23.useState)(null);
3365
+ const toastTimer = (0, import_react23.useRef)(null);
3366
+ const showToast = (0, import_react23.useCallback)((msg) => {
3192
3367
  setToast(msg);
3193
3368
  if (toastTimer.current) clearTimeout(toastTimer.current);
3194
3369
  toastTimer.current = setTimeout(() => setToast(null), 1800);
3195
3370
  }, []);
3196
- const copy = (0, import_react22.useCallback)(
3371
+ const copy = (0, import_react23.useCallback)(
3197
3372
  (text, msg) => {
3198
3373
  if (typeof navigator !== "undefined" && navigator.clipboard) {
3199
3374
  navigator.clipboard.writeText(text).then(
@@ -3204,7 +3379,7 @@ function ReportInteractiveView({
3204
3379
  },
3205
3380
  [showToast]
3206
3381
  );
3207
- const scenarioActions = (0, import_react22.useMemo)(
3382
+ const scenarioActions = (0, import_react23.useMemo)(
3208
3383
  () => ({
3209
3384
  copyLink: (s) => copy(scenarioPermalink(s), "Link copied"),
3210
3385
  copyMarkdown: (s) => copy(scenarioToMarkdown(s), "Markdown copied"),
@@ -3212,27 +3387,31 @@ function ReportInteractiveView({
3212
3387
  }),
3213
3388
  [copy]
3214
3389
  );
3215
- const failureIndexRef = (0, import_react22.useRef)(0);
3216
- const allCollapsibleIds = (0, import_react22.useMemo)(
3390
+ const copyAllFailures = (0, import_react23.useCallback)(
3391
+ () => copy(failuresToPrompt(failedScenarios(filtered)), "Failures copied"),
3392
+ [copy, filtered]
3393
+ );
3394
+ const failureIndexRef = (0, import_react23.useRef)(0);
3395
+ const allCollapsibleIds = (0, import_react23.useMemo)(
3217
3396
  () => report.features.flatMap((f) => [f.id, ...f.scenarios.map((s) => s.id)]),
3218
3397
  [report]
3219
3398
  );
3220
3399
  const collapse = useCollapseState(
3221
- (0, import_react22.useCallback)(() => defaultCollapsedIds(report), [report])
3400
+ (0, import_react23.useCallback)(() => defaultCollapsedIds(report), [report])
3222
3401
  );
3223
3402
  const revealFilteredResults = deferredQuery.trim() !== "" || statusFilter !== "all" || activeTags.length > 0;
3224
- const visibleCollapse = (0, import_react22.useMemo)(
3403
+ const visibleCollapse = (0, import_react23.useMemo)(
3225
3404
  () => revealFilteredResults ? { ...collapse.api, isCollapsed: () => false } : collapse.api,
3226
3405
  [collapse.api, revealFilteredResults]
3227
3406
  );
3228
- const collapseAll = (0, import_react22.useCallback)(
3407
+ const collapseAll = (0, import_react23.useCallback)(
3229
3408
  () => collapse.collapseAll(allCollapsibleIds),
3230
3409
  [collapse, allCollapsibleIds]
3231
3410
  );
3232
- const [expandedAll, setExpandedAll] = (0, import_react22.useState)(
3411
+ const [expandedAll, setExpandedAll] = (0, import_react23.useState)(
3233
3412
  () => report.summary.failed === 0
3234
3413
  );
3235
- const setExpanded = (0, import_react22.useCallback)(
3414
+ const setExpanded = (0, import_react23.useCallback)(
3236
3415
  (expanded) => {
3237
3416
  if (expanded) collapse.expandAll();
3238
3417
  else collapseAll();
@@ -3240,8 +3419,8 @@ function ReportInteractiveView({
3240
3419
  },
3241
3420
  [collapse, collapseAll]
3242
3421
  );
3243
- const [agentFilter, setAgentFilter] = (0, import_react22.useState)(null);
3244
- (0, import_react21.useTools)(
3422
+ const [agentFilter, setAgentFilter] = (0, import_react23.useState)(null);
3423
+ (0, import_react22.useTools)(
3245
3424
  reportTools({
3246
3425
  report,
3247
3426
  view: urlState,
@@ -3250,14 +3429,14 @@ function ReportInteractiveView({
3250
3429
  })
3251
3430
  );
3252
3431
  const showAgentFilter = agentFilter !== null && agentFilter.search === query && agentFilter.status === statusFilter && agentFilter.tags.length === activeTags.length && agentFilter.tags.every((tag) => activeTags.includes(tag));
3253
- const clearFilters = (0, import_react22.useCallback)(
3432
+ const clearFilters = (0, import_react23.useCallback)(
3254
3433
  () => setUrlState({ query: "", status: "all", tags: [] }),
3255
3434
  [setUrlState]
3256
3435
  );
3257
- const focusSearch = (0, import_react22.useCallback)(() => {
3436
+ const focusSearch = (0, import_react23.useCallback)(() => {
3258
3437
  searchRef.current?.focus();
3259
3438
  }, []);
3260
- const stepFailure = (0, import_react22.useCallback)(
3439
+ const stepFailure = (0, import_react23.useCallback)(
3261
3440
  (direction) => {
3262
3441
  if (failures.length === 0) return;
3263
3442
  failureIndexRef.current = (failureIndexRef.current + direction + failures.length) % failures.length;
@@ -3266,10 +3445,10 @@ function ReportInteractiveView({
3266
3445
  },
3267
3446
  [failures]
3268
3447
  );
3269
- const toggleHelp = (0, import_react22.useCallback)(() => {
3448
+ const toggleHelp = (0, import_react23.useCallback)(() => {
3270
3449
  setHelpOpen((v) => !v);
3271
3450
  }, []);
3272
- const escape = (0, import_react22.useCallback)(() => {
3451
+ const escape = (0, import_react23.useCallback)(() => {
3273
3452
  if (helpOpen) setHelpOpen(false);
3274
3453
  else if (query !== "") setQuery("");
3275
3454
  }, [helpOpen, query, setQuery]);
@@ -3287,13 +3466,13 @@ function ReportInteractiveView({
3287
3466
  const totalScenarios = report.summary.total;
3288
3467
  const matchedScenarios = filtered.summary.total;
3289
3468
  const showToc = !hideToc && totalScenarios > 3;
3290
- return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(CollapseProvider, { value: visibleCollapse, children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(ScenarioActionsProvider, { value: scenarioActions, children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(ScenarioHistoryProvider, { value: scenarioHistory ?? null, children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(ScenarioFreshnessProvider, { value: { staleAfterDays, report }, children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3469
+ return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(CollapseProvider, { value: visibleCollapse, children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ScenarioActionsProvider, { value: scenarioActions, children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ScenarioHistoryProvider, { value: scenarioHistory ?? null, children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ScenarioFreshnessProvider, { value: { staleAfterDays, report }, children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3291
3470
  ReportRoot,
3292
3471
  {
3293
3472
  report: filtered,
3294
3473
  customRenderers,
3295
3474
  renderers,
3296
- children: /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(
3475
+ children: /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(
3297
3476
  "main",
3298
3477
  {
3299
3478
  className: cn("es-report", "es-report-interactive", className),
@@ -3302,8 +3481,8 @@ function ReportInteractiveView({
3302
3481
  "data-theme": dataTheme,
3303
3482
  "data-detail-level": detail,
3304
3483
  children: [
3305
- /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("header", { className: "es-report-header", children: [
3306
- /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(
3484
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("header", { className: "es-report-header", children: [
3485
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(
3307
3486
  "div",
3308
3487
  {
3309
3488
  "data-es-navbar": true,
@@ -3312,9 +3491,9 @@ function ReportInteractiveView({
3312
3491
  hideHeader ? "justify-end" : "justify-between"
3313
3492
  ),
3314
3493
  children: [
3315
- hideHeader ? null : /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("h1", { className: "min-w-0", children: title ?? "Story Report" }),
3316
- /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { className: "flex items-center gap-2", children: [
3317
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3494
+ hideHeader ? null : /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("h1", { className: "min-w-0", children: title ?? "Story Report" }),
3495
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "flex items-center gap-2", children: [
3496
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3318
3497
  ReportSearch,
3319
3498
  {
3320
3499
  ref: searchRef,
@@ -3324,16 +3503,16 @@ function ReportInteractiveView({
3324
3503
  totalCount: totalScenarios
3325
3504
  }
3326
3505
  ),
3327
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(ThemeSegment, { pref: themePref, onPref: setThemePref })
3506
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ThemeSegment, { pref: themePref, onPref: setThemePref })
3328
3507
  ] })
3329
3508
  ]
3330
3509
  }
3331
3510
  ),
3332
- hideHeader ? null : /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_jsx_runtime50.Fragment, { children: [
3333
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(ReportSummary, {}),
3334
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(ReportMeta, {})
3511
+ hideHeader ? null : /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(import_jsx_runtime51.Fragment, { children: [
3512
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ReportSummary, {}),
3513
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ReportMeta, {})
3335
3514
  ] }),
3336
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3515
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3337
3516
  ReportFreshness,
3338
3517
  {
3339
3518
  lastRunMs: reportLastRunMs(report),
@@ -3341,14 +3520,15 @@ function ReportInteractiveView({
3341
3520
  staleAfterDays
3342
3521
  }
3343
3522
  ),
3344
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3523
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3345
3524
  ReportLastRunDelta,
3346
3525
  {
3347
3526
  history: scenarioHistory,
3348
3527
  report
3349
3528
  }
3350
3529
  ),
3351
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3530
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ReportSinceLastVisit, { report }),
3531
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3352
3532
  ReportFilters,
3353
3533
  {
3354
3534
  statuses: statusOptions,
@@ -3359,10 +3539,10 @@ function ReportInteractiveView({
3359
3539
  onToggleTag: toggleTag
3360
3540
  }
3361
3541
  ),
3362
- /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { className: "flex flex-wrap items-center gap-x-6 gap-y-2 border-t border-border/60 pt-3", children: [
3363
- showToc ? /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(ReportTocDrawer, {}) : null,
3364
- /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { className: "flex items-center gap-2", children: [
3365
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3542
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "flex flex-wrap items-center gap-x-6 gap-y-2 border-t border-border/60 pt-3", children: [
3543
+ showToc ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ReportTocDrawer, {}) : null,
3544
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "flex items-center gap-2", children: [
3545
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3366
3546
  Switch,
3367
3547
  {
3368
3548
  id: expandId,
@@ -3370,7 +3550,7 @@ function ReportInteractiveView({
3370
3550
  onCheckedChange: setExpanded
3371
3551
  }
3372
3552
  ),
3373
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3553
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3374
3554
  "label",
3375
3555
  {
3376
3556
  htmlFor: expandId,
@@ -3379,8 +3559,8 @@ function ReportInteractiveView({
3379
3559
  }
3380
3560
  )
3381
3561
  ] }),
3382
- /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { className: "flex items-center gap-2", children: [
3383
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3562
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "flex items-center gap-2", children: [
3563
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3384
3564
  Switch,
3385
3565
  {
3386
3566
  id: docsId,
@@ -3388,7 +3568,7 @@ function ReportInteractiveView({
3388
3568
  onCheckedChange: (checked) => setDetail(checked ? "full" : "minimal")
3389
3569
  }
3390
3570
  ),
3391
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3571
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3392
3572
  "label",
3393
3573
  {
3394
3574
  htmlFor: docsId,
@@ -3399,7 +3579,7 @@ function ReportInteractiveView({
3399
3579
  ] })
3400
3580
  ] })
3401
3581
  ] }),
3402
- showAgentFilter ? /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3582
+ showAgentFilter ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3403
3583
  ReportAgentFilterNotice,
3404
3584
  {
3405
3585
  applied: agentFilter,
@@ -3407,17 +3587,17 @@ function ReportInteractiveView({
3407
3587
  onDismiss: () => setAgentFilter(null)
3408
3588
  }
3409
3589
  ) : null,
3410
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(ReportFailureBanner, { failures }),
3411
- hasContent ? showToc ? /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { className: "flex gap-6", children: [
3412
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(ReportToc, {}),
3413
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("div", { className: "flex min-w-0 flex-1 flex-col gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(ReportFeatureList, {}) })
3414
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("div", { className: "flex flex-col gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(ReportFeatureList, {}) }) : /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3590
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ReportFailureBanner, { failures, onCopyAll: copyAllFailures }),
3591
+ hasContent ? showToc ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "flex gap-6", children: [
3592
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ReportToc, {}),
3593
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("div", { className: "flex min-w-0 flex-1 flex-col gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ReportFeatureList, {}) })
3594
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("div", { className: "flex flex-col gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ReportFeatureList, {}) }) : /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3415
3595
  ReportEmpty,
3416
3596
  {
3417
3597
  message: query ? "No scenarios match the search." : void 0
3418
3598
  }
3419
3599
  ),
3420
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3600
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3421
3601
  "button",
3422
3602
  {
3423
3603
  type: "button",
@@ -3428,14 +3608,14 @@ function ReportInteractiveView({
3428
3608
  children: "?"
3429
3609
  }
3430
3610
  ),
3431
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3611
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3432
3612
  ReportShortcutsHelp,
3433
3613
  {
3434
3614
  open: helpOpen,
3435
3615
  onClose: () => setHelpOpen(false)
3436
3616
  }
3437
3617
  ),
3438
- toast ? /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3618
+ toast ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3439
3619
  "div",
3440
3620
  {
3441
3621
  role: "status",
@@ -3452,14 +3632,14 @@ function ReportInteractiveView({
3452
3632
  }
3453
3633
 
3454
3634
  // src/interactive/ReportInteractiveIsland.tsx
3455
- var import_react25 = require("react");
3635
+ var import_react26 = require("react");
3456
3636
 
3457
3637
  // src/interactive/island-renderers.tsx
3458
- var import_react24 = require("react");
3638
+ var import_react25 = require("react");
3459
3639
 
3460
3640
  // src/components/doc/MermaidView.tsx
3461
- var import_react23 = require("react");
3462
- var import_jsx_runtime51 = require("react/jsx-runtime");
3641
+ var import_react24 = require("react");
3642
+ var import_jsx_runtime52 = require("react/jsx-runtime");
3463
3643
  function prefersDark(el) {
3464
3644
  const themed = el?.closest("[data-theme]");
3465
3645
  if (themed?.dataset.theme === "dark") return true;
@@ -3467,10 +3647,10 @@ function prefersDark(el) {
3467
3647
  return typeof window !== "undefined" && !!window.matchMedia?.("(prefers-color-scheme: dark)").matches;
3468
3648
  }
3469
3649
  function MermaidView({ entry, load }) {
3470
- const ref = (0, import_react23.useRef)(null);
3471
- const [svg, setSvg] = (0, import_react23.useState)(null);
3472
- const [error, setError] = (0, import_react23.useState)(null);
3473
- (0, import_react23.useEffect)(() => {
3650
+ const ref = (0, import_react24.useRef)(null);
3651
+ const [svg, setSvg] = (0, import_react24.useState)(null);
3652
+ const [error, setError] = (0, import_react24.useState)(null);
3653
+ (0, import_react24.useEffect)(() => {
3474
3654
  let cancelled = false;
3475
3655
  const draw = async () => {
3476
3656
  let mermaid;
@@ -3513,9 +3693,9 @@ function MermaidView({ entry, load }) {
3513
3693
  };
3514
3694
  }, [entry.code, load]);
3515
3695
  if (svg) {
3516
- return /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("figure", { ref, className: "my-3", children: [
3517
- entry.title ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
3518
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3696
+ return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("figure", { ref, className: "my-3", children: [
3697
+ entry.title ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
3698
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
3519
3699
  "div",
3520
3700
  {
3521
3701
  role: "img",
@@ -3526,8 +3706,8 @@ function MermaidView({ entry, load }) {
3526
3706
  )
3527
3707
  ] });
3528
3708
  }
3529
- return /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(import_jsx_runtime51.Fragment, { children: [
3530
- error ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(
3709
+ return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(import_jsx_runtime52.Fragment, { children: [
3710
+ error ? /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
3531
3711
  "p",
3532
3712
  {
3533
3713
  role: "alert",
@@ -3539,12 +3719,12 @@ function MermaidView({ entry, load }) {
3539
3719
  ]
3540
3720
  }
3541
3721
  ) : null,
3542
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(MermaidSource, { entry, ref })
3722
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(MermaidSource, { entry, ref })
3543
3723
  ] });
3544
3724
  }
3545
3725
 
3546
3726
  // src/interactive/island-renderers.tsx
3547
- var import_jsx_runtime52 = require("react/jsx-runtime");
3727
+ var import_jsx_runtime53 = require("react/jsx-runtime");
3548
3728
  var HLJS_ESM = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/es/highlight.min.js";
3549
3729
  var MERMAID_ESM = "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
3550
3730
  function cachedCdnModule(url) {
@@ -3561,8 +3741,8 @@ function cachedCdnModule(url) {
3561
3741
  var loadMermaidFromCdn = cachedCdnModule(MERMAID_ESM);
3562
3742
  var loadHljs = cachedCdnModule(HLJS_ESM);
3563
3743
  function CdnHighlightedCode({ entry }) {
3564
- const [html, setHtml] = (0, import_react24.useState)(null);
3565
- (0, import_react24.useEffect)(() => {
3744
+ const [html, setHtml] = (0, import_react25.useState)(null);
3745
+ (0, import_react25.useEffect)(() => {
3566
3746
  let cancelled = false;
3567
3747
  void (async () => {
3568
3748
  try {
@@ -3578,27 +3758,27 @@ function CdnHighlightedCode({ entry }) {
3578
3758
  cancelled = true;
3579
3759
  };
3580
3760
  }, [entry.content, entry.lang]);
3581
- return /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(CodeFigure, { label: entry.label, children: html !== null ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
3761
+ return /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(CodeFigure, { label: entry.label, children: html !== null ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
3582
3762
  "code",
3583
3763
  {
3584
3764
  className: cn("hljs font-mono text-xs", entry.lang && `language-${entry.lang}`),
3585
3765
  dangerouslySetInnerHTML: { __html: html }
3586
3766
  }
3587
- ) : /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
3767
+ ) : /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
3588
3768
  }
3589
3769
  function buildIslandRenderers(opts) {
3590
3770
  const renderers = {};
3591
3771
  if (opts.mermaid) {
3592
- renderers.mermaid = (entry) => (0, import_react24.createElement)(MermaidView, { entry, load: loadMermaidFromCdn });
3772
+ renderers.mermaid = (entry) => (0, import_react25.createElement)(MermaidView, { entry, load: loadMermaidFromCdn });
3593
3773
  }
3594
3774
  if (opts.syntaxHighlighting) {
3595
- renderers.code = (entry) => (0, import_react24.createElement)(CdnHighlightedCode, { entry });
3775
+ renderers.code = (entry) => (0, import_react25.createElement)(CdnHighlightedCode, { entry });
3596
3776
  }
3597
3777
  return renderers;
3598
3778
  }
3599
3779
 
3600
3780
  // src/interactive/ReportInteractiveIsland.tsx
3601
- var import_jsx_runtime53 = require("react/jsx-runtime");
3781
+ var import_jsx_runtime54 = require("react/jsx-runtime");
3602
3782
  function ReportInteractiveIsland({
3603
3783
  report,
3604
3784
  title,
@@ -3608,11 +3788,11 @@ function ReportInteractiveIsland({
3608
3788
  syntaxHighlighting = true,
3609
3789
  mermaid = true
3610
3790
  }) {
3611
- const renderers = (0, import_react25.useMemo)(
3791
+ const renderers = (0, import_react26.useMemo)(
3612
3792
  () => buildIslandRenderers({ syntaxHighlighting, mermaid }),
3613
3793
  [syntaxHighlighting, mermaid]
3614
3794
  );
3615
- return /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
3795
+ return /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
3616
3796
  ReportInteractive,
3617
3797
  {
3618
3798
  report,