executable-stories-react 0.12.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.
@@ -47,8 +47,9 @@ __export(interactive_exports, {
47
47
  module.exports = __toCommonJS(interactive_exports);
48
48
 
49
49
  // src/interactive/ReportInteractive.tsx
50
- var import_lucide_react5 = require("lucide-react");
51
- var import_react21 = require("react");
50
+ var import_lucide_react6 = require("lucide-react");
51
+ var import_react22 = require("webmcpable/react");
52
+ var import_react23 = require("react");
52
53
 
53
54
  // src/components/ui/empty.tsx
54
55
  var import_class_variance_authority = require("class-variance-authority");
@@ -241,7 +242,7 @@ function formatValue(value) {
241
242
  function DocKv({ entry }) {
242
243
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("dl", { className: "my-2 flex flex-wrap items-baseline gap-x-2 text-sm", children: [
243
244
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { className: "font-semibold text-muted-foreground", children: entry.label }),
244
- /* @__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) })
245
246
  ] });
246
247
  }
247
248
 
@@ -449,7 +450,7 @@ function DocHtml({ entry }) {
449
450
  loading: "lazy",
450
451
  title,
451
452
  className: "block w-full border-0",
452
- style: { height }
453
+ style: { height, colorScheme: "light", background: "#fff" }
453
454
  }
454
455
  )
455
456
  ] });
@@ -1173,6 +1174,24 @@ function scenarioToPrompt(scenario) {
1173
1174
  }
1174
1175
  return lines.join("\n");
1175
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
+ }
1176
1195
  function scenarioPermalink(scenario) {
1177
1196
  if (typeof location === "undefined") return `#${scenario.id}`;
1178
1197
  return `${location.href.split("#")[0]}#${scenario.id}`;
@@ -1298,57 +1317,68 @@ function ReportScenario({ scenario, hideTitle = false }) {
1298
1317
  className: "gap-0 py-0",
1299
1318
  children: [
1300
1319
  /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(CardHeader, { className: "px-4 py-3", children: [
1301
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
1302
- hideTitle ? null : /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1303
- collapsible ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1304
- "button",
1305
- {
1306
- type: "button",
1307
- onClick: () => collapse.toggle(scenario.id),
1308
- "aria-expanded": !collapsed,
1309
- "aria-controls": bodyId,
1310
- "aria-label": `Toggle ${scenario.title}`,
1311
- className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
1312
- children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1313
- }
1314
- ) : null,
1315
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { "aria-hidden": true, className: cn("text-sm leading-none", meta.glyphColor), children: meta.glyph }),
1316
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { children: scenario.title })
1317
- ] }),
1318
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex shrink-0 items-center gap-2", children: [
1319
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ScenarioRunHistory, { scenarioId: scenario.id }),
1320
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ScenarioStaleness, { scenario }),
1321
- 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,
1322
- 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}` }),
1323
- actions ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenu, { children: [
1324
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1325
- DropdownMenuTrigger,
1326
- {
1327
- "aria-label": `Actions for ${scenario.title}`,
1328
- className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
1329
- children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.MoreHorizontalIcon, { "aria-hidden": true })
1330
- }
1331
- ),
1332
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuContent, { align: "end", children: [
1333
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
1334
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.LinkIcon, {}),
1335
- " Copy link"
1336
- ] }),
1337
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
1338
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.FileTextIcon, {}),
1339
- " Copy as Markdown"
1340
- ] }),
1341
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
1342
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.SparklesIcon, {}),
1343
- " Explain with AI"
1344
- ] })
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
1345
1375
  ] })
1346
- ] }) : null
1347
- ] })
1348
- ] }),
1376
+ ]
1377
+ }
1378
+ ),
1349
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: [
1350
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}`)),
1351
- (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}`))
1352
1382
  ] }) : null
1353
1383
  ] }),
1354
1384
  /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
@@ -1491,6 +1521,7 @@ function ReportFeatureList() {
1491
1521
 
1492
1522
  // src/components/ReportMeta.tsx
1493
1523
  var import_duration4 = require("executable-stories-core/utils/duration");
1524
+ var import_report_queries = require("executable-stories-core/report-queries");
1494
1525
  var import_jsx_runtime32 = require("react/jsx-runtime");
1495
1526
  function ReportMeta() {
1496
1527
  const report = useReport();
@@ -1534,7 +1565,9 @@ function ReportMeta() {
1534
1565
  value: url ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1535
1566
  });
1536
1567
  }
1537
- 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;
1538
1571
  return (
1539
1572
  // Run metadata is useful but secondary to "what failed" — tuck it into a
1540
1573
  // collapsed disclosure so it doesn't push the result down. Native <details>
@@ -1547,7 +1580,14 @@ function ReportMeta() {
1547
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: [
1548
1581
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("dt", { className: "font-medium", children: item.label }),
1549
1582
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("dd", { className: "font-mono text-foreground", children: item.value })
1550
- ] }, 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
1551
1591
  ] })
1552
1592
  );
1553
1593
  }
@@ -2023,21 +2063,26 @@ function allTags(report) {
2023
2063
  }
2024
2064
  return [...seen];
2025
2065
  }
2026
- function queryMatches(scenario, q) {
2066
+ function queryMatches(scenario, q, sourceFile) {
2027
2067
  if (q === "") return true;
2028
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;
2029
2071
  for (const tag of scenario.tags) {
2030
2072
  if (tag.toLowerCase().includes(q)) return true;
2031
2073
  }
2074
+ for (const ticket of scenario.tickets ?? []) {
2075
+ if (ticket.id.toLowerCase().includes(q)) return true;
2076
+ }
2032
2077
  for (const step of scenario.steps) {
2033
2078
  if (step.text.toLowerCase().includes(q)) return true;
2034
2079
  }
2035
2080
  return false;
2036
2081
  }
2037
- function scenarioMatches(scenario, q, status, tags) {
2082
+ function scenarioMatches(scenario, q, status, tags, sourceFile) {
2038
2083
  if (status !== "all" && scenario.status !== status) return false;
2039
2084
  if (tags.length > 0 && !tags.some((t) => scenario.tags.includes(t))) return false;
2040
- return queryMatches(scenario, q);
2085
+ return queryMatches(scenario, q, sourceFile);
2041
2086
  }
2042
2087
  function summarizeScenarios(scenarios) {
2043
2088
  let total = 0, passed = 0, failed = 0, skipped = 0, pending = 0, durationMs = 0;
@@ -2060,7 +2105,9 @@ function filterReport(report, criteria) {
2060
2105
  const features = [];
2061
2106
  let topTotal = 0, topPassed = 0, topFailed = 0, topSkipped = 0, topPending = 0, topDuration = 0;
2062
2107
  for (const feature of report.features) {
2063
- 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
+ );
2064
2111
  if (matched.length === 0) continue;
2065
2112
  const summary = summarizeScenarios(matched);
2066
2113
  features.push({ ...feature, summary, scenarios: matched });
@@ -2084,6 +2131,9 @@ function filterReport(report, criteria) {
2084
2131
  features
2085
2132
  };
2086
2133
  }
2134
+ function failedScenarios(report) {
2135
+ return report.features.flatMap((f) => f.scenarios.filter((s) => s.status === "failed"));
2136
+ }
2087
2137
  function listFailures(report) {
2088
2138
  const out = [];
2089
2139
  for (const feature of report.features) {
@@ -2104,17 +2154,77 @@ function listFailures(report) {
2104
2154
  return out;
2105
2155
  }
2106
2156
 
2157
+ // src/interactive/ReportAgentFilterNotice.tsx
2158
+ var import_lucide_react3 = require("lucide-react");
2159
+ var import_jsx_runtime40 = require("react/jsx-runtime");
2160
+ function ReportAgentFilterNotice({
2161
+ applied,
2162
+ onReset,
2163
+ onDismiss
2164
+ }) {
2165
+ const terms = [];
2166
+ if (applied.search) terms.push(`matching \u201C${applied.search}\u201D`);
2167
+ if (applied.status !== "all") terms.push(applied.status);
2168
+ if (applied.tags.length > 0) terms.push(applied.tags.join(", "));
2169
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
2170
+ "div",
2171
+ {
2172
+ role: "status",
2173
+ "aria-live": "polite",
2174
+ className: "flex flex-wrap items-center gap-x-3 gap-y-1 rounded-md border border-primary/40 bg-primary/5 px-3 py-2 text-sm",
2175
+ children: [
2176
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react3.Bot, { "aria-hidden": true, className: "size-4 shrink-0 text-primary" }),
2177
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "min-w-0 flex-1", children: [
2178
+ "An agent filtered this report",
2179
+ terms.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
2180
+ " to ",
2181
+ terms.join(" \xB7 ")
2182
+ ] }) : null,
2183
+ " \u2014",
2184
+ " ",
2185
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "tabular-nums", children: [
2186
+ applied.matched,
2187
+ " of ",
2188
+ applied.total,
2189
+ " scenarios"
2190
+ ] }),
2191
+ "."
2192
+ ] }),
2193
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2194
+ "button",
2195
+ {
2196
+ type: "button",
2197
+ onClick: onReset,
2198
+ className: "cursor-pointer rounded-md border border-border px-2 py-1 text-xs hover:bg-muted",
2199
+ children: "Show all"
2200
+ }
2201
+ ),
2202
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2203
+ "button",
2204
+ {
2205
+ type: "button",
2206
+ onClick: onDismiss,
2207
+ "aria-label": "Dismiss",
2208
+ className: "cursor-pointer rounded-md p-1 text-muted-foreground hover:text-foreground",
2209
+ children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react3.X, { "aria-hidden": true, className: "size-4" })
2210
+ }
2211
+ )
2212
+ ]
2213
+ }
2214
+ );
2215
+ }
2216
+
2107
2217
  // src/interactive/ReportFailureBanner.tsx
2108
2218
  var import_react10 = require("react");
2109
- var import_jsx_runtime40 = require("react/jsx-runtime");
2110
- function ReportFailureBanner({ failures }) {
2219
+ var import_jsx_runtime41 = require("react/jsx-runtime");
2220
+ function ReportFailureBanner({ failures, onCopyAll }) {
2111
2221
  const first = failures[0];
2112
2222
  const jumpToFirst = (0, import_react10.useCallback)(() => {
2113
2223
  if (!first) return;
2114
2224
  scrollToScenarioId(first.scenarioId);
2115
2225
  }, [first]);
2116
2226
  if (failures.length === 0) return null;
2117
- return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
2227
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
2118
2228
  "div",
2119
2229
  {
2120
2230
  className: "es-failure-banner",
@@ -2122,13 +2232,13 @@ function ReportFailureBanner({ failures }) {
2122
2232
  "aria-live": "polite",
2123
2233
  "aria-label": "Failure summary",
2124
2234
  children: [
2125
- /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "es-failure-banner-text", children: [
2126
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("strong", { children: failures.length }),
2235
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { className: "es-failure-banner-text", children: [
2236
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("strong", { children: failures.length }),
2127
2237
  " ",
2128
2238
  "failure",
2129
2239
  failures.length === 1 ? "" : "s"
2130
2240
  ] }),
2131
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2241
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
2132
2242
  "button",
2133
2243
  {
2134
2244
  type: "button",
@@ -2137,7 +2247,23 @@ function ReportFailureBanner({ failures }) {
2137
2247
  "aria-label": "View first failure",
2138
2248
  children: "View first failure \u2193"
2139
2249
  }
2140
- )
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
2141
2267
  ]
2142
2268
  }
2143
2269
  );
@@ -2145,7 +2271,7 @@ function ReportFailureBanner({ failures }) {
2145
2271
 
2146
2272
  // src/interactive/ReportFilters.tsx
2147
2273
  var import_react11 = require("react");
2148
- var import_jsx_runtime41 = require("react/jsx-runtime");
2274
+ var import_jsx_runtime42 = require("react/jsx-runtime");
2149
2275
  var TAG_LIMIT = 12;
2150
2276
  function ReportFilters({
2151
2277
  statuses,
@@ -2159,10 +2285,10 @@ function ReportFilters({
2159
2285
  if (statuses.length === 0 && tags.length === 0) return null;
2160
2286
  const tagsCollapsed = tags.length > TAG_LIMIT && !showAllTags;
2161
2287
  const visibleTags = tagsCollapsed ? tags.slice(0, TAG_LIMIT) : tags;
2162
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "flex flex-col gap-2", children: [
2163
- statuses.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { role: "group", "aria-label": "Filter by status", className: "flex flex-wrap gap-1", children: statuses.map((opt) => {
2288
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex flex-col gap-2", children: [
2289
+ statuses.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { role: "group", "aria-label": "Filter by status", className: "flex flex-wrap gap-1", children: statuses.map((opt) => {
2164
2290
  const active = status === opt.key;
2165
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
2291
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
2166
2292
  "button",
2167
2293
  {
2168
2294
  type: "button",
@@ -2174,23 +2300,23 @@ function ReportFilters({
2174
2300
  ),
2175
2301
  children: [
2176
2302
  opt.label,
2177
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "ml-1 font-mono opacity-70", children: opt.count })
2303
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "ml-1 font-mono opacity-70", children: opt.count })
2178
2304
  ]
2179
2305
  },
2180
2306
  opt.key
2181
2307
  );
2182
2308
  }) }) : null,
2183
- tags.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("ul", { "aria-label": "Filter by tag", className: "flex flex-wrap items-center gap-1.5", children: [
2309
+ tags.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("ul", { "aria-label": "Filter by tag", className: "flex flex-wrap items-center gap-1.5", children: [
2184
2310
  visibleTags.map((tag) => {
2185
2311
  const active = activeTags.includes(tag);
2186
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
2312
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2187
2313
  "button",
2188
2314
  {
2189
2315
  type: "button",
2190
2316
  "aria-pressed": active,
2191
2317
  onClick: () => onToggleTag(tag),
2192
2318
  className: "cursor-pointer",
2193
- children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
2319
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2194
2320
  Badge,
2195
2321
  {
2196
2322
  variant: "tag",
@@ -2203,7 +2329,7 @@ function ReportFilters({
2203
2329
  }
2204
2330
  ) }, tag);
2205
2331
  }),
2206
- tags.length > TAG_LIMIT ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
2332
+ tags.length > TAG_LIMIT ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2207
2333
  "button",
2208
2334
  {
2209
2335
  type: "button",
@@ -2219,7 +2345,7 @@ function ReportFilters({
2219
2345
 
2220
2346
  // src/interactive/ReportFreshness.tsx
2221
2347
  var import_react12 = require("react");
2222
- var import_jsx_runtime42 = require("react/jsx-runtime");
2348
+ var import_jsx_runtime43 = require("react/jsx-runtime");
2223
2349
  function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
2224
2350
  const [now, setNow] = (0, import_react12.useState)(nowMs);
2225
2351
  (0, import_react12.useEffect)(() => {
@@ -2227,7 +2353,7 @@ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
2227
2353
  }, [nowMs]);
2228
2354
  if (lastRunMs === void 0 || now === void 0) return null;
2229
2355
  const age = formatRelativeAge(lastRunMs, now);
2230
- const runLink = ciUrl ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2356
+ const runLink = ciUrl ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
2231
2357
  "a",
2232
2358
  {
2233
2359
  href: ciUrl,
@@ -2238,9 +2364,9 @@ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
2238
2364
  }
2239
2365
  ) : null;
2240
2366
  if (isReportStale(lastRunMs, staleAfterDays, now)) {
2241
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "es-stale-banner", role: "status", "aria-label": "Report freshness", children: [
2242
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { children: [
2243
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("strong", { children: [
2367
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "es-stale-banner", role: "status", "aria-label": "Report freshness", children: [
2368
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("span", { children: [
2369
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("strong", { children: [
2244
2370
  "Last verified ",
2245
2371
  age,
2246
2372
  "."
@@ -2250,10 +2376,10 @@ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
2250
2376
  runLink
2251
2377
  ] });
2252
2378
  }
2253
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("p", { className: "es-freshness", "aria-label": "Report freshness", children: [
2379
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("p", { className: "es-freshness", "aria-label": "Report freshness", children: [
2254
2380
  "Verified ",
2255
2381
  age,
2256
- runLink ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_jsx_runtime42.Fragment, { children: [
2382
+ runLink ? /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(import_jsx_runtime43.Fragment, { children: [
2257
2383
  " \xB7 ",
2258
2384
  runLink
2259
2385
  ] }) : null
@@ -2261,15 +2387,15 @@ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
2261
2387
  }
2262
2388
 
2263
2389
  // src/interactive/ReportLastRunDelta.tsx
2264
- var import_jsx_runtime43 = require("react/jsx-runtime");
2390
+ var import_jsx_runtime44 = require("react/jsx-runtime");
2265
2391
  var MAX_LINKS = 5;
2266
2392
  function ScenarioLinks({ scenarios, tone }) {
2267
- return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(import_jsx_runtime43.Fragment, { children: [
2268
- scenarios.slice(0, MAX_LINKS).map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("span", { children: [
2393
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_jsx_runtime44.Fragment, { children: [
2394
+ scenarios.slice(0, MAX_LINKS).map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("span", { children: [
2269
2395
  i > 0 ? ", " : "",
2270
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("a", { href: `#${s.id}`, className: cn("underline underline-offset-2 hover:no-underline", tone), children: s.title })
2396
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("a", { href: `#${s.id}`, className: cn("underline underline-offset-2 hover:no-underline", tone), children: s.title })
2271
2397
  ] }, s.id)),
2272
- scenarios.length > MAX_LINKS ? /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("span", { children: [
2398
+ scenarios.length > MAX_LINKS ? /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("span", { children: [
2273
2399
  " and ",
2274
2400
  scenarios.length - MAX_LINKS,
2275
2401
  " more"
@@ -2286,23 +2412,23 @@ function ReportLastRunDelta({
2286
2412
  if (!delta) return null;
2287
2413
  const { newlyFailing, newlyPassing, added } = delta;
2288
2414
  if (newlyFailing.length === 0 && newlyPassing.length === 0 && added.length === 0) {
2289
- return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: "Since last run: no behavior changes." });
2415
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: "Since last run: no behavior changes." });
2290
2416
  }
2291
- return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: [
2292
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "font-medium text-foreground", children: "Since last run:" }),
2293
- newlyFailing.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("span", { className: "ml-2 text-fail", children: [
2417
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: [
2418
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "font-medium text-foreground", children: "Since last run:" }),
2419
+ newlyFailing.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("span", { className: "ml-2 text-fail", children: [
2294
2420
  newlyFailing.length,
2295
2421
  " newly failing (",
2296
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(ScenarioLinks, { scenarios: newlyFailing, tone: "text-fail" }),
2422
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ScenarioLinks, { scenarios: newlyFailing, tone: "text-fail" }),
2297
2423
  ")"
2298
2424
  ] }) : null,
2299
- newlyPassing.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("span", { className: "ml-2 text-pass", children: [
2425
+ newlyPassing.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("span", { className: "ml-2 text-pass", children: [
2300
2426
  newlyPassing.length,
2301
2427
  " fixed (",
2302
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(ScenarioLinks, { scenarios: newlyPassing, tone: "text-pass" }),
2428
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ScenarioLinks, { scenarios: newlyPassing, tone: "text-pass" }),
2303
2429
  ")"
2304
2430
  ] }) : null,
2305
- added.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("span", { className: "ml-2", children: [
2431
+ added.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("span", { className: "ml-2", children: [
2306
2432
  added.length,
2307
2433
  " new"
2308
2434
  ] }) : null
@@ -2311,13 +2437,13 @@ function ReportLastRunDelta({
2311
2437
 
2312
2438
  // src/interactive/ReportSearch.tsx
2313
2439
  var import_react13 = require("react");
2314
- var import_lucide_react3 = require("lucide-react");
2440
+ var import_lucide_react4 = require("lucide-react");
2315
2441
 
2316
2442
  // src/components/ui/input.tsx
2317
2443
  var React6 = require("react");
2318
- var import_jsx_runtime44 = require("react/jsx-runtime");
2444
+ var import_jsx_runtime45 = require("react/jsx-runtime");
2319
2445
  function Input({ className, type, ...props }) {
2320
- return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
2446
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
2321
2447
  "input",
2322
2448
  {
2323
2449
  type,
@@ -2334,7 +2460,7 @@ function Input({ className, type, ...props }) {
2334
2460
  }
2335
2461
 
2336
2462
  // src/interactive/ReportSearch.tsx
2337
- var import_jsx_runtime45 = require("react/jsx-runtime");
2463
+ var import_jsx_runtime46 = require("react/jsx-runtime");
2338
2464
  var ReportSearch = (0, import_react13.forwardRef)(
2339
2465
  function ReportSearch2(props, ref) {
2340
2466
  const {
@@ -2342,7 +2468,7 @@ var ReportSearch = (0, import_react13.forwardRef)(
2342
2468
  onChange,
2343
2469
  matchedCount,
2344
2470
  totalCount,
2345
- placeholder = "Search scenarios or tags\u2026",
2471
+ placeholder = "Search scenarios, tags, tickets, errors\u2026",
2346
2472
  className
2347
2473
  } = props;
2348
2474
  const inputId = (0, import_react13.useId)();
@@ -2356,17 +2482,17 @@ var ReportSearch = (0, import_react13.forwardRef)(
2356
2482
  e.preventDefault();
2357
2483
  }
2358
2484
  }
2359
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: cn("es-search flex flex-col gap-1", className), children: [
2360
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("label", { htmlFor: inputId, className: "sr-only", children: "Search" }),
2361
- /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "relative w-full sm:w-72", children: [
2362
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
2363
- import_lucide_react3.Search,
2485
+ return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: cn("es-search flex flex-col gap-1", className), children: [
2486
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("label", { htmlFor: inputId, className: "sr-only", children: "Search" }),
2487
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "relative w-full sm:w-72", children: [
2488
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
2489
+ import_lucide_react4.Search,
2364
2490
  {
2365
2491
  "aria-hidden": true,
2366
2492
  className: "pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground"
2367
2493
  }
2368
2494
  ),
2369
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
2495
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
2370
2496
  Input,
2371
2497
  {
2372
2498
  ref,
@@ -2383,7 +2509,7 @@ var ReportSearch = (0, import_react13.forwardRef)(
2383
2509
  }
2384
2510
  )
2385
2511
  ] }),
2386
- showCounts ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(
2512
+ showCounts ? /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
2387
2513
  "span",
2388
2514
  {
2389
2515
  className: "text-xs tabular-nums text-muted-foreground",
@@ -2400,9 +2526,119 @@ var ReportSearch = (0, import_react13.forwardRef)(
2400
2526
  }
2401
2527
  );
2402
2528
 
2403
- // src/interactive/ReportShortcutsHelp.tsx
2529
+ // src/interactive/ReportSinceLastVisit.tsx
2404
2530
  var import_react14 = require("react");
2405
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
+
2406
2642
  // src/components/ui/button.tsx
2407
2643
  var React7 = require("react");
2408
2644
  var import_class_variance_authority4 = require("class-variance-authority");
@@ -2457,7 +2693,7 @@ function Button({
2457
2693
  }
2458
2694
 
2459
2695
  // src/interactive/ReportShortcutsHelp.tsx
2460
- var import_jsx_runtime46 = require("react/jsx-runtime");
2696
+ var import_jsx_runtime48 = require("react/jsx-runtime");
2461
2697
  var SHORTCUTS = [
2462
2698
  { keys: "/", description: "Focus search" },
2463
2699
  { keys: "f", description: "Jump to next failure" },
@@ -2469,20 +2705,20 @@ var SHORTCUTS = [
2469
2705
  ];
2470
2706
  function Keys({ keys }) {
2471
2707
  const parts = keys.split(" ");
2472
- return /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { className: "inline-flex items-center gap-1", children: parts.map((k, i) => /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("span", { className: "inline-flex items-center gap-1", children: [
2473
- i > 0 ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { className: "text-xs text-muted-foreground", children: "+" }) : null,
2474
- /* @__PURE__ */ (0, import_jsx_runtime46.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 })
2475
2711
  ] }, k)) });
2476
2712
  }
2477
2713
  function ReportShortcutsHelp({ open, onClose }) {
2478
- const dialogRef = (0, import_react14.useRef)(null);
2479
- (0, import_react14.useEffect)(() => {
2714
+ const dialogRef = (0, import_react15.useRef)(null);
2715
+ (0, import_react15.useEffect)(() => {
2480
2716
  const dialog = dialogRef.current;
2481
2717
  if (!dialog) return;
2482
2718
  if (open && !dialog.open) dialog.showModal();
2483
2719
  else if (!open && dialog.open) dialog.close();
2484
2720
  }, [open]);
2485
- return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
2721
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(
2486
2722
  "dialog",
2487
2723
  {
2488
2724
  ref: dialogRef,
@@ -2490,20 +2726,20 @@ function ReportShortcutsHelp({ open, onClose }) {
2490
2726
  "aria-label": "Keyboard shortcuts",
2491
2727
  onClose,
2492
2728
  children: [
2493
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("h2", { className: "text-base font-semibold", children: "Keyboard shortcuts" }),
2494
- /* @__PURE__ */ (0, import_jsx_runtime46.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_runtime46.jsxs)("div", { className: "col-span-2 grid grid-cols-subgrid items-center", children: [
2495
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("dt", { className: "justify-self-start", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(Keys, { keys: s.keys }) }),
2496
- /* @__PURE__ */ (0, import_jsx_runtime46.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 })
2497
2733
  ] }, s.keys)) }),
2498
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("form", { method: "dialog", className: "mt-6", children: /* @__PURE__ */ (0, import_jsx_runtime46.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" }) })
2499
2735
  ]
2500
2736
  }
2501
2737
  );
2502
2738
  }
2503
2739
 
2504
2740
  // src/interactive/ReportToc.tsx
2505
- var import_react15 = require("react");
2506
- var import_jsx_runtime47 = require("react/jsx-runtime");
2741
+ var import_react16 = require("react");
2742
+ var import_jsx_runtime49 = require("react/jsx-runtime");
2507
2743
  var DOT = {
2508
2744
  passed: "bg-pass",
2509
2745
  failed: "bg-fail",
@@ -2512,12 +2748,12 @@ var DOT = {
2512
2748
  };
2513
2749
  function TocContent({ onNavigate }) {
2514
2750
  const report = useReport();
2515
- const [activeId, setActiveId] = (0, import_react15.useState)(null);
2516
- const scenarioIds = (0, import_react15.useMemo)(
2751
+ const [activeId, setActiveId] = (0, import_react16.useState)(null);
2752
+ const scenarioIds = (0, import_react16.useMemo)(
2517
2753
  () => report.features.flatMap((f) => f.scenarios.map((s) => s.id)),
2518
2754
  [report]
2519
2755
  );
2520
- (0, import_react15.useEffect)(() => {
2756
+ (0, import_react16.useEffect)(() => {
2521
2757
  if (typeof IntersectionObserver === "undefined") return;
2522
2758
  const els = scenarioIds.map((id) => document.getElementById(id)).filter((el) => el !== null);
2523
2759
  if (els.length === 0) return;
@@ -2532,11 +2768,11 @@ function TocContent({ onNavigate }) {
2532
2768
  return () => observer.disconnect();
2533
2769
  }, [scenarioIds]);
2534
2770
  if (report.features.length === 0) return null;
2535
- return /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("ul", { className: "flex list-none flex-col gap-4 pl-0", children: report.features.map((feature) => /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("li", { children: [
2536
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("p", { className: "mb-1.5 font-semibold leading-snug text-foreground", children: feature.title }),
2537
- /* @__PURE__ */ (0, import_jsx_runtime47.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) => {
2538
2774
  const active = activeId === scenario.id;
2539
- return /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(
2775
+ return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
2540
2776
  "a",
2541
2777
  {
2542
2778
  href: `#${scenario.id}`,
@@ -2551,14 +2787,14 @@ function TocContent({ onNavigate }) {
2551
2787
  active ? "border-primary font-medium text-foreground" : "border-border text-muted-foreground"
2552
2788
  ),
2553
2789
  children: [
2554
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
2790
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
2555
2791
  "span",
2556
2792
  {
2557
2793
  "aria-hidden": true,
2558
2794
  className: cn("mt-1 size-1.5 shrink-0 rounded-full", DOT[scenario.status])
2559
2795
  }
2560
2796
  ),
2561
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("span", { className: "break-words", children: scenario.title })
2797
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "break-words", children: scenario.title })
2562
2798
  ]
2563
2799
  }
2564
2800
  ) }, scenario.id);
@@ -2568,48 +2804,48 @@ function TocContent({ onNavigate }) {
2568
2804
  function ReportToc() {
2569
2805
  const report = useReport();
2570
2806
  if (report.features.length === 0) return null;
2571
- return /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(
2807
+ return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
2572
2808
  "nav",
2573
2809
  {
2574
2810
  "aria-label": "Table of contents",
2575
2811
  className: "sticky top-4 hidden h-fit w-64 shrink-0 self-start text-xs lg:block",
2576
2812
  children: [
2577
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("p", { className: "mb-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Contents" }),
2578
- /* @__PURE__ */ (0, import_jsx_runtime47.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, {})
2579
2815
  ]
2580
2816
  }
2581
2817
  );
2582
2818
  }
2583
2819
 
2584
2820
  // src/interactive/ReportTocDrawer.tsx
2585
- var import_react16 = require("react");
2821
+ var import_react17 = require("react");
2586
2822
  var import_dialog = require("@base-ui/react/dialog");
2587
- var import_lucide_react4 = require("lucide-react");
2588
- var import_jsx_runtime48 = require("react/jsx-runtime");
2823
+ var import_lucide_react5 = require("lucide-react");
2824
+ var import_jsx_runtime50 = require("react/jsx-runtime");
2589
2825
  function ReportTocDrawer() {
2590
2826
  const report = useReport();
2591
- const [open, setOpen] = (0, import_react16.useState)(false);
2827
+ const [open, setOpen] = (0, import_react17.useState)(false);
2592
2828
  if (report.features.length === 0) return null;
2593
- return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(import_dialog.Dialog.Root, { open, onOpenChange: setOpen, children: [
2594
- /* @__PURE__ */ (0, import_jsx_runtime48.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: [
2595
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_lucide_react4.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 }),
2596
2832
  " Contents"
2597
2833
  ] }),
2598
- /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(import_dialog.Dialog.Portal, { children: [
2599
- /* @__PURE__ */ (0, import_jsx_runtime48.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" }),
2600
- /* @__PURE__ */ (0, import_jsx_runtime48.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: [
2601
- /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex items-center justify-between", children: [
2602
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_dialog.Dialog.Title, { className: "text-xs font-semibold tracking-wide text-muted-foreground uppercase", children: "Contents" }),
2603
- /* @__PURE__ */ (0, import_jsx_runtime48.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)(
2604
2840
  import_dialog.Dialog.Close,
2605
2841
  {
2606
2842
  "aria-label": "Close contents",
2607
2843
  className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
2608
- children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_lucide_react4.XIcon, { "aria-hidden": true })
2844
+ children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_lucide_react5.XIcon, { "aria-hidden": true })
2609
2845
  }
2610
2846
  )
2611
2847
  ] }),
2612
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(TocContent, { onNavigate: () => setOpen(false) })
2848
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(TocContent, { onNavigate: () => setOpen(false) })
2613
2849
  ] })
2614
2850
  ] })
2615
2851
  ] });
@@ -2643,10 +2879,10 @@ function failuresFirst(report) {
2643
2879
  }
2644
2880
 
2645
2881
  // src/interactive/use-deep-link-scroll.ts
2646
- var import_react17 = require("react");
2882
+ var import_react18 = require("react");
2647
2883
  function useDeepLinkScroll() {
2648
- const lastAnchor = (0, import_react17.useRef)(null);
2649
- (0, import_react17.useEffect)(() => {
2884
+ const lastAnchor = (0, import_react18.useRef)(null);
2885
+ (0, import_react18.useEffect)(() => {
2650
2886
  function scrollToHash() {
2651
2887
  const { anchor } = splitHash(window.location.hash);
2652
2888
  if (!anchor || anchor === lastAnchor.current) return;
@@ -2662,7 +2898,7 @@ function useDeepLinkScroll() {
2662
2898
  }
2663
2899
 
2664
2900
  // src/interactive/use-keyboard-shortcuts.ts
2665
- var import_react18 = require("react");
2901
+ var import_react19 = require("react");
2666
2902
  function isEditableTarget(target) {
2667
2903
  if (!(target instanceof HTMLElement)) return false;
2668
2904
  const tag = target.tagName;
@@ -2671,9 +2907,9 @@ function isEditableTarget(target) {
2671
2907
  return false;
2672
2908
  }
2673
2909
  function useKeyboardShortcuts(handlers) {
2674
- const ref = (0, import_react18.useRef)(handlers);
2910
+ const ref = (0, import_react19.useRef)(handlers);
2675
2911
  ref.current = handlers;
2676
- (0, import_react18.useEffect)(() => {
2912
+ (0, import_react19.useEffect)(() => {
2677
2913
  function onKeyDown(e) {
2678
2914
  const h = ref.current;
2679
2915
  if (e.key === "Escape") {
@@ -2717,8 +2953,8 @@ function useKeyboardShortcuts(handlers) {
2717
2953
  }
2718
2954
 
2719
2955
  // src/interactive/use-theme.ts
2720
- var import_react19 = require("react");
2721
- var KEY = "es-theme";
2956
+ var import_react20 = require("react");
2957
+ var KEY2 = "es-theme";
2722
2958
  function systemDark() {
2723
2959
  return typeof matchMedia !== "undefined" && matchMedia("(prefers-color-scheme: dark)").matches;
2724
2960
  }
@@ -2732,19 +2968,19 @@ function applyTheme(pref) {
2732
2968
  }
2733
2969
  function initialPref() {
2734
2970
  if (typeof localStorage !== "undefined") {
2735
- const stored = localStorage.getItem(KEY);
2971
+ const stored = localStorage.getItem(KEY2);
2736
2972
  if (stored === "light" || stored === "dark" || stored === "system") return stored;
2737
2973
  }
2738
2974
  return "system";
2739
2975
  }
2740
2976
  function useTheme() {
2741
- const [pref, setPrefState] = (0, import_react19.useState)("system");
2742
- (0, import_react19.useEffect)(() => {
2977
+ const [pref, setPrefState] = (0, import_react20.useState)("system");
2978
+ (0, import_react20.useEffect)(() => {
2743
2979
  const p = initialPref();
2744
2980
  setPrefState(p);
2745
2981
  applyTheme(p);
2746
2982
  }, []);
2747
- (0, import_react19.useEffect)(() => {
2983
+ (0, import_react20.useEffect)(() => {
2748
2984
  if (typeof matchMedia === "undefined") return;
2749
2985
  const mq = matchMedia("(prefers-color-scheme: dark)");
2750
2986
  const onChange = () => {
@@ -2753,11 +2989,11 @@ function useTheme() {
2753
2989
  mq.addEventListener("change", onChange);
2754
2990
  return () => mq.removeEventListener("change", onChange);
2755
2991
  }, [pref]);
2756
- const setPref = (0, import_react19.useCallback)((p) => {
2992
+ const setPref = (0, import_react20.useCallback)((p) => {
2757
2993
  setPrefState(p);
2758
2994
  applyTheme(p);
2759
2995
  try {
2760
- localStorage.setItem(KEY, p);
2996
+ localStorage.setItem(KEY2, p);
2761
2997
  } catch {
2762
2998
  }
2763
2999
  }, []);
@@ -2765,19 +3001,19 @@ function useTheme() {
2765
3001
  }
2766
3002
 
2767
3003
  // src/interactive/use-url-state.ts
2768
- var import_react20 = require("react");
3004
+ var import_react21 = require("react");
2769
3005
  var WRITE_DELAY_MS = 250;
2770
3006
  function useUrlState() {
2771
- const [state, setState] = (0, import_react20.useState)(DEFAULT_URL_STATE);
2772
- const ready = (0, import_react20.useRef)(false);
2773
- (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)(() => {
2774
3010
  const read = () => setState(decodeUrlState(splitHash(window.location.hash).params));
2775
3011
  read();
2776
3012
  ready.current = true;
2777
3013
  window.addEventListener("hashchange", read);
2778
3014
  return () => window.removeEventListener("hashchange", read);
2779
3015
  }, []);
2780
- (0, import_react20.useEffect)(() => {
3016
+ (0, import_react21.useEffect)(() => {
2781
3017
  if (!ready.current) return;
2782
3018
  const timer = setTimeout(
2783
3019
  () => writeHash(splitHash(window.location.hash).anchor, encodeUrlState(state)),
@@ -2785,20 +3021,234 @@ function useUrlState() {
2785
3021
  );
2786
3022
  return () => clearTimeout(timer);
2787
3023
  }, [state]);
2788
- const update = (0, import_react20.useCallback)(
3024
+ const update = (0, import_react21.useCallback)(
2789
3025
  (patch) => setState((prev) => ({ ...prev, ...patch })),
2790
3026
  []
2791
3027
  );
2792
3028
  return [state, update];
2793
3029
  }
2794
3030
 
3031
+ // src/interactive/webmcp-tools.ts
3032
+ var import_report_queries2 = require("executable-stories-core/report-queries");
3033
+ var STATUSES2 = [
3034
+ "passed",
3035
+ "failed",
3036
+ "skipped",
3037
+ "pending"
3038
+ ];
3039
+ var VIEW_STATUSES = ["all", ...STATUSES2];
3040
+ var ToolArgumentError = class extends Error {
3041
+ };
3042
+ function asRecord(input) {
3043
+ return typeof input === "object" && input !== null ? input : {};
3044
+ }
3045
+ function readString(input, key) {
3046
+ const value = input[key];
3047
+ if (value === void 0 || value === null) return void 0;
3048
+ if (typeof value !== "string") {
3049
+ throw new ToolArgumentError(`"${key}" must be a string, received ${typeof value}.`);
3050
+ }
3051
+ return value;
3052
+ }
3053
+ function readStringArray(input, key) {
3054
+ const value = input[key];
3055
+ if (value === void 0 || value === null) return void 0;
3056
+ if (typeof value === "string") return [value];
3057
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
3058
+ throw new ToolArgumentError(`"${key}" must be an array of strings.`);
3059
+ }
3060
+ return value;
3061
+ }
3062
+ function readEnum(input, key, allowed) {
3063
+ const value = readString(input, key);
3064
+ if (value === void 0) return void 0;
3065
+ if (!allowed.includes(value)) {
3066
+ throw new ToolArgumentError(
3067
+ `"${key}" must be one of: ${allowed.join(", ")}. Received "${value}".`
3068
+ );
3069
+ }
3070
+ return value;
3071
+ }
3072
+ function readStatuses(input) {
3073
+ const values = readStringArray(input, "statuses");
3074
+ if (values === void 0) return void 0;
3075
+ const invalid = values.filter((v) => !STATUSES2.includes(v));
3076
+ if (invalid.length > 0) {
3077
+ throw new ToolArgumentError(
3078
+ `"statuses" must contain only: ${STATUSES2.join(", ")}. Received "${invalid.join('", "')}".`
3079
+ );
3080
+ }
3081
+ return values;
3082
+ }
3083
+ function guarded(run) {
3084
+ try {
3085
+ return run();
3086
+ } catch (error) {
3087
+ if (error instanceof ToolArgumentError) return `Invalid input \u2014 ${error.message}`;
3088
+ throw error;
3089
+ }
3090
+ }
3091
+ var scenarioListSchema = {
3092
+ type: "object",
3093
+ properties: {
3094
+ statuses: {
3095
+ type: "array",
3096
+ items: { type: "string", enum: [...STATUSES2] },
3097
+ description: "Keep only scenarios with any of these statuses."
3098
+ },
3099
+ tags: {
3100
+ type: "array",
3101
+ items: { type: "string" },
3102
+ description: "Keep only scenarios carrying any of these tags."
3103
+ },
3104
+ sourceFiles: {
3105
+ type: "array",
3106
+ items: { type: "string" },
3107
+ description: "Keep only scenarios whose source file path contains any of these."
3108
+ }
3109
+ }
3110
+ };
3111
+ var READ_ONLY = { readOnlyHint: true, untrustedContentHint: true };
3112
+ function reportTools(deps) {
3113
+ const { report, setView, onAgentFilter } = deps;
3114
+ const now = deps.now ?? Date.now;
3115
+ const provenance = () => ({ run: (0, import_report_queries2.runProvenance)(report, now()) });
3116
+ const list = (filters) => {
3117
+ const scenarios = (0, import_report_queries2.listScenarioSummaries)(report, filters);
3118
+ return { ...provenance(), total: scenarios.length, scenarios };
3119
+ };
3120
+ return {
3121
+ list_scenarios: {
3122
+ title: "List scenarios",
3123
+ description: "List the behaviour scenarios in this report, optionally filtered by status, tag, or source file. Returns each scenario with its steps, tags, timing and failure message.",
3124
+ annotations: READ_ONLY,
3125
+ input: scenarioListSchema,
3126
+ execute: (input) => guarded(() => {
3127
+ const args = asRecord(input);
3128
+ const filters = {};
3129
+ const statuses = readStatuses(args);
3130
+ const tags = readStringArray(args, "tags");
3131
+ const sourceFiles = readStringArray(args, "sourceFiles");
3132
+ if (statuses) filters.statuses = statuses;
3133
+ if (tags) filters.tags = tags;
3134
+ if (sourceFiles) filters.sourceFiles = sourceFiles;
3135
+ return list(filters);
3136
+ })
3137
+ },
3138
+ get_failing_scenarios: {
3139
+ title: "Get failing scenarios",
3140
+ description: "List only the scenarios that failed in this run, each with the step that failed and its error message. Use this first when asked what is broken.",
3141
+ annotations: READ_ONLY,
3142
+ execute: () => list({ statuses: ["failed"] })
3143
+ },
3144
+ get_feature_summary: {
3145
+ title: "Get feature summary",
3146
+ description: 'Summarise each feature in the report with its scenario counts by status. Use this for "how are we doing" questions rather than listing every scenario.',
3147
+ annotations: READ_ONLY,
3148
+ execute: () => ({
3149
+ ...provenance(),
3150
+ summary: report.summary,
3151
+ features: (0, import_report_queries2.getFeatureSummary)(report)
3152
+ })
3153
+ },
3154
+ get_scenario: {
3155
+ title: "Get scenario",
3156
+ description: "Get one scenario by its id or its exact title, with every step and any failure detail. Attachment and document bodies are omitted \u2014 `docKinds` names what the page shows.",
3157
+ annotations: READ_ONLY,
3158
+ input: {
3159
+ type: "object",
3160
+ properties: {
3161
+ idOrTitle: {
3162
+ type: "string",
3163
+ description: "The scenario id, or its exact title as shown in the report."
3164
+ }
3165
+ },
3166
+ required: ["idOrTitle"]
3167
+ },
3168
+ execute: (input) => guarded(() => {
3169
+ const idOrTitle = readString(asRecord(input), "idOrTitle") ?? "";
3170
+ const found = (0, import_report_queries2.getScenario)(report, idOrTitle);
3171
+ if (!found) {
3172
+ return {
3173
+ ...provenance(),
3174
+ error: `No scenario found with id or title "${idOrTitle}". Call list_scenarios to see what this report contains.`
3175
+ };
3176
+ }
3177
+ return {
3178
+ ...provenance(),
3179
+ feature: {
3180
+ id: found.feature.id,
3181
+ title: found.feature.title,
3182
+ sourceFile: found.feature.sourceFile
3183
+ },
3184
+ // The summary projection, not the raw scenario: doc entry bodies and
3185
+ // base64 attachments are unbounded, and this payload goes straight
3186
+ // into a model's context window.
3187
+ scenario: (0, import_report_queries2.toScenarioSummary)(found.feature, found.scenario)
3188
+ };
3189
+ })
3190
+ },
3191
+ filter_scenarios: {
3192
+ title: "Filter the report",
3193
+ description: `Change what the reader is looking at: set the report's search text, status filter and tag filter. Omitted fields are left as they are; pass an empty string, "all", or an empty array to clear one.`,
3194
+ input: {
3195
+ type: "object",
3196
+ properties: {
3197
+ search: {
3198
+ type: "string",
3199
+ description: "Free text matched against scenario titles, tags and step text. Empty string clears it."
3200
+ },
3201
+ status: {
3202
+ type: "string",
3203
+ enum: [...VIEW_STATUSES],
3204
+ description: 'Show only scenarios with this status. "all" clears the filter.'
3205
+ },
3206
+ tags: {
3207
+ type: "array",
3208
+ items: { type: "string" },
3209
+ description: "Show only scenarios carrying any of these tags. Empty array clears the filter."
3210
+ }
3211
+ }
3212
+ },
3213
+ execute: (input) => guarded(() => {
3214
+ const args = asRecord(input);
3215
+ const search = readString(args, "search");
3216
+ const status = readEnum(args, "status", VIEW_STATUSES);
3217
+ const tags = readStringArray(args, "tags");
3218
+ const applied = {
3219
+ search: search ?? deps.view.query,
3220
+ status: status ?? deps.view.status,
3221
+ tags: tags ?? deps.view.tags
3222
+ };
3223
+ const patch = {};
3224
+ if (search !== void 0) patch.query = search;
3225
+ if (status !== void 0) patch.status = status;
3226
+ if (tags !== void 0) patch.tags = tags;
3227
+ setView(patch);
3228
+ const matched = filterReport(report, {
3229
+ query: applied.search,
3230
+ status: applied.status,
3231
+ tags: applied.tags
3232
+ }).summary.total;
3233
+ const result = {
3234
+ ...applied,
3235
+ matched,
3236
+ total: report.summary.total
3237
+ };
3238
+ onAgentFilter?.(result);
3239
+ return { ...provenance(), applied: result };
3240
+ })
3241
+ }
3242
+ };
3243
+ }
3244
+
2795
3245
  // src/interactive/ReportInteractive.tsx
2796
- var import_jsx_runtime49 = require("react/jsx-runtime");
3246
+ var import_jsx_runtime51 = require("react/jsx-runtime");
2797
3247
  function ReportInteractive(props) {
2798
3248
  const { report, className, title, dataTheme } = props;
2799
3249
  const result = unwrapReport(report);
2800
3250
  if (!result.ok) {
2801
- return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3251
+ return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
2802
3252
  ReportErrorShell,
2803
3253
  {
2804
3254
  error: result.error,
@@ -2808,18 +3258,18 @@ function ReportInteractive(props) {
2808
3258
  }
2809
3259
  );
2810
3260
  }
2811
- return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(ReportInteractiveView, { ...props, report: result.data });
3261
+ return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ReportInteractiveView, { ...props, report: result.data });
2812
3262
  }
2813
3263
  var THEME_OPTIONS = [
2814
- { value: "light", label: "Light", Icon: import_lucide_react5.Sun },
2815
- { value: "system", label: "System", Icon: import_lucide_react5.Monitor },
2816
- { value: "dark", label: "Dark", Icon: import_lucide_react5.Moon }
3264
+ { value: "light", label: "Light", Icon: import_lucide_react6.Sun },
3265
+ { value: "system", label: "System", Icon: import_lucide_react6.Monitor },
3266
+ { value: "dark", label: "Dark", Icon: import_lucide_react6.Moon }
2817
3267
  ];
2818
3268
  function ThemeSegment({
2819
3269
  pref,
2820
3270
  onPref
2821
3271
  }) {
2822
- return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3272
+ return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
2823
3273
  ToggleGroup,
2824
3274
  {
2825
3275
  size: "sm",
@@ -2829,14 +3279,14 @@ function ThemeSegment({
2829
3279
  if (next) onPref(next);
2830
3280
  },
2831
3281
  "aria-label": "Color theme",
2832
- children: THEME_OPTIONS.map(({ value, label, Icon }) => /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3282
+ children: THEME_OPTIONS.map(({ value, label, Icon }) => /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
2833
3283
  ToggleGroupItem,
2834
3284
  {
2835
3285
  value,
2836
3286
  "aria-label": label,
2837
3287
  title: label,
2838
3288
  className: "px-2",
2839
- children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(Icon, {})
3289
+ children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(Icon, {})
2840
3290
  },
2841
3291
  value
2842
3292
  ))
@@ -2857,26 +3307,26 @@ function ReportInteractiveView({
2857
3307
  }) {
2858
3308
  const [urlState, setUrlState] = useUrlState();
2859
3309
  const { query, status: statusFilter, tags: activeTags, detail } = urlState;
2860
- const setQuery = (0, import_react21.useCallback)(
3310
+ const setQuery = (0, import_react23.useCallback)(
2861
3311
  (next) => setUrlState({ query: next }),
2862
3312
  [setUrlState]
2863
3313
  );
2864
- const setStatusFilter = (0, import_react21.useCallback)(
3314
+ const setStatusFilter = (0, import_react23.useCallback)(
2865
3315
  (next) => setUrlState({ status: next }),
2866
3316
  [setUrlState]
2867
3317
  );
2868
- const setDetail = (0, import_react21.useCallback)(
3318
+ const setDetail = (0, import_react23.useCallback)(
2869
3319
  (next) => setUrlState({ detail: next }),
2870
3320
  [setUrlState]
2871
3321
  );
2872
- const [helpOpen, setHelpOpen] = (0, import_react21.useState)(false);
3322
+ const [helpOpen, setHelpOpen] = (0, import_react23.useState)(false);
2873
3323
  const { pref: themePref, setPref: setThemePref } = useTheme();
2874
- const searchRef = (0, import_react21.useRef)(null);
2875
- const expandId = (0, import_react21.useId)();
2876
- const docsId = (0, import_react21.useId)();
2877
- const deferredQuery = (0, import_react21.useDeferredValue)(query);
2878
- const failures = (0, import_react21.useMemo)(() => listFailures(report), [report]);
2879
- const filtered = (0, import_react21.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)(
2880
3330
  // Features carrying failures float to the top so the first thing on screen
2881
3331
  // is the thing that needs attention; an all-green run keeps source order.
2882
3332
  () => failuresFirst(
@@ -2889,7 +3339,7 @@ function ReportInteractiveView({
2889
3339
  [report, deferredQuery, statusFilter, activeTags]
2890
3340
  );
2891
3341
  const isFiltering = query !== deferredQuery;
2892
- const statusOptions = (0, import_react21.useMemo)(() => {
3342
+ const statusOptions = (0, import_react23.useMemo)(() => {
2893
3343
  const s = report.summary;
2894
3344
  const opts = [
2895
3345
  { key: "all", label: "All", count: s.total }
@@ -2904,21 +3354,21 @@ function ReportInteractiveView({
2904
3354
  opts.push({ key: "pending", label: "Pending", count: s.pending });
2905
3355
  return opts;
2906
3356
  }, [report]);
2907
- const tagOptions = (0, import_react21.useMemo)(() => allTags(report), [report]);
2908
- const toggleTag = (0, import_react21.useCallback)(
3357
+ const tagOptions = (0, import_react23.useMemo)(() => allTags(report), [report]);
3358
+ const toggleTag = (0, import_react23.useCallback)(
2909
3359
  (tag) => setUrlState({
2910
3360
  tags: activeTags.includes(tag) ? activeTags.filter((t) => t !== tag) : [...activeTags, tag]
2911
3361
  }),
2912
3362
  [activeTags, setUrlState]
2913
3363
  );
2914
- const [toast, setToast] = (0, import_react21.useState)(null);
2915
- const toastTimer = (0, import_react21.useRef)(null);
2916
- const showToast = (0, import_react21.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) => {
2917
3367
  setToast(msg);
2918
3368
  if (toastTimer.current) clearTimeout(toastTimer.current);
2919
3369
  toastTimer.current = setTimeout(() => setToast(null), 1800);
2920
3370
  }, []);
2921
- const copy = (0, import_react21.useCallback)(
3371
+ const copy = (0, import_react23.useCallback)(
2922
3372
  (text, msg) => {
2923
3373
  if (typeof navigator !== "undefined" && navigator.clipboard) {
2924
3374
  navigator.clipboard.writeText(text).then(
@@ -2929,7 +3379,7 @@ function ReportInteractiveView({
2929
3379
  },
2930
3380
  [showToast]
2931
3381
  );
2932
- const scenarioActions = (0, import_react21.useMemo)(
3382
+ const scenarioActions = (0, import_react23.useMemo)(
2933
3383
  () => ({
2934
3384
  copyLink: (s) => copy(scenarioPermalink(s), "Link copied"),
2935
3385
  copyMarkdown: (s) => copy(scenarioToMarkdown(s), "Markdown copied"),
@@ -2937,27 +3387,31 @@ function ReportInteractiveView({
2937
3387
  }),
2938
3388
  [copy]
2939
3389
  );
2940
- const failureIndexRef = (0, import_react21.useRef)(0);
2941
- const allCollapsibleIds = (0, import_react21.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)(
2942
3396
  () => report.features.flatMap((f) => [f.id, ...f.scenarios.map((s) => s.id)]),
2943
3397
  [report]
2944
3398
  );
2945
3399
  const collapse = useCollapseState(
2946
- (0, import_react21.useCallback)(() => defaultCollapsedIds(report), [report])
3400
+ (0, import_react23.useCallback)(() => defaultCollapsedIds(report), [report])
2947
3401
  );
2948
3402
  const revealFilteredResults = deferredQuery.trim() !== "" || statusFilter !== "all" || activeTags.length > 0;
2949
- const visibleCollapse = (0, import_react21.useMemo)(
3403
+ const visibleCollapse = (0, import_react23.useMemo)(
2950
3404
  () => revealFilteredResults ? { ...collapse.api, isCollapsed: () => false } : collapse.api,
2951
3405
  [collapse.api, revealFilteredResults]
2952
3406
  );
2953
- const collapseAll = (0, import_react21.useCallback)(
3407
+ const collapseAll = (0, import_react23.useCallback)(
2954
3408
  () => collapse.collapseAll(allCollapsibleIds),
2955
3409
  [collapse, allCollapsibleIds]
2956
3410
  );
2957
- const [expandedAll, setExpandedAll] = (0, import_react21.useState)(
3411
+ const [expandedAll, setExpandedAll] = (0, import_react23.useState)(
2958
3412
  () => report.summary.failed === 0
2959
3413
  );
2960
- const setExpanded = (0, import_react21.useCallback)(
3414
+ const setExpanded = (0, import_react23.useCallback)(
2961
3415
  (expanded) => {
2962
3416
  if (expanded) collapse.expandAll();
2963
3417
  else collapseAll();
@@ -2965,10 +3419,24 @@ function ReportInteractiveView({
2965
3419
  },
2966
3420
  [collapse, collapseAll]
2967
3421
  );
2968
- const focusSearch = (0, import_react21.useCallback)(() => {
3422
+ const [agentFilter, setAgentFilter] = (0, import_react23.useState)(null);
3423
+ (0, import_react22.useTools)(
3424
+ reportTools({
3425
+ report,
3426
+ view: urlState,
3427
+ setView: setUrlState,
3428
+ onAgentFilter: setAgentFilter
3429
+ })
3430
+ );
3431
+ const showAgentFilter = agentFilter !== null && agentFilter.search === query && agentFilter.status === statusFilter && agentFilter.tags.length === activeTags.length && agentFilter.tags.every((tag) => activeTags.includes(tag));
3432
+ const clearFilters = (0, import_react23.useCallback)(
3433
+ () => setUrlState({ query: "", status: "all", tags: [] }),
3434
+ [setUrlState]
3435
+ );
3436
+ const focusSearch = (0, import_react23.useCallback)(() => {
2969
3437
  searchRef.current?.focus();
2970
3438
  }, []);
2971
- const stepFailure = (0, import_react21.useCallback)(
3439
+ const stepFailure = (0, import_react23.useCallback)(
2972
3440
  (direction) => {
2973
3441
  if (failures.length === 0) return;
2974
3442
  failureIndexRef.current = (failureIndexRef.current + direction + failures.length) % failures.length;
@@ -2977,10 +3445,10 @@ function ReportInteractiveView({
2977
3445
  },
2978
3446
  [failures]
2979
3447
  );
2980
- const toggleHelp = (0, import_react21.useCallback)(() => {
3448
+ const toggleHelp = (0, import_react23.useCallback)(() => {
2981
3449
  setHelpOpen((v) => !v);
2982
3450
  }, []);
2983
- const escape = (0, import_react21.useCallback)(() => {
3451
+ const escape = (0, import_react23.useCallback)(() => {
2984
3452
  if (helpOpen) setHelpOpen(false);
2985
3453
  else if (query !== "") setQuery("");
2986
3454
  }, [helpOpen, query, setQuery]);
@@ -2998,13 +3466,13 @@ function ReportInteractiveView({
2998
3466
  const totalScenarios = report.summary.total;
2999
3467
  const matchedScenarios = filtered.summary.total;
3000
3468
  const showToc = !hideToc && totalScenarios > 3;
3001
- return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(CollapseProvider, { value: visibleCollapse, children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(ScenarioActionsProvider, { value: scenarioActions, children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(ScenarioHistoryProvider, { value: scenarioHistory ?? null, children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(ScenarioFreshnessProvider, { value: { staleAfterDays, report }, children: /* @__PURE__ */ (0, import_jsx_runtime49.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)(
3002
3470
  ReportRoot,
3003
3471
  {
3004
3472
  report: filtered,
3005
3473
  customRenderers,
3006
3474
  renderers,
3007
- children: /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
3475
+ children: /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(
3008
3476
  "main",
3009
3477
  {
3010
3478
  className: cn("es-report", "es-report-interactive", className),
@@ -3013,8 +3481,8 @@ function ReportInteractiveView({
3013
3481
  "data-theme": dataTheme,
3014
3482
  "data-detail-level": detail,
3015
3483
  children: [
3016
- /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("header", { className: "es-report-header", children: [
3017
- /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
3484
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("header", { className: "es-report-header", children: [
3485
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(
3018
3486
  "div",
3019
3487
  {
3020
3488
  "data-es-navbar": true,
@@ -3023,9 +3491,9 @@ function ReportInteractiveView({
3023
3491
  hideHeader ? "justify-end" : "justify-between"
3024
3492
  ),
3025
3493
  children: [
3026
- hideHeader ? null : /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("h1", { className: "min-w-0", children: title ?? "Story Report" }),
3027
- /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "flex items-center gap-2", children: [
3028
- /* @__PURE__ */ (0, import_jsx_runtime49.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)(
3029
3497
  ReportSearch,
3030
3498
  {
3031
3499
  ref: searchRef,
@@ -3035,16 +3503,16 @@ function ReportInteractiveView({
3035
3503
  totalCount: totalScenarios
3036
3504
  }
3037
3505
  ),
3038
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(ThemeSegment, { pref: themePref, onPref: setThemePref })
3506
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ThemeSegment, { pref: themePref, onPref: setThemePref })
3039
3507
  ] })
3040
3508
  ]
3041
3509
  }
3042
3510
  ),
3043
- hideHeader ? null : /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_jsx_runtime49.Fragment, { children: [
3044
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(ReportSummary, {}),
3045
- /* @__PURE__ */ (0, import_jsx_runtime49.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, {})
3046
3514
  ] }),
3047
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3515
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3048
3516
  ReportFreshness,
3049
3517
  {
3050
3518
  lastRunMs: reportLastRunMs(report),
@@ -3052,14 +3520,15 @@ function ReportInteractiveView({
3052
3520
  staleAfterDays
3053
3521
  }
3054
3522
  ),
3055
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3523
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3056
3524
  ReportLastRunDelta,
3057
3525
  {
3058
3526
  history: scenarioHistory,
3059
3527
  report
3060
3528
  }
3061
3529
  ),
3062
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3530
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ReportSinceLastVisit, { report }),
3531
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3063
3532
  ReportFilters,
3064
3533
  {
3065
3534
  statuses: statusOptions,
@@ -3070,10 +3539,10 @@ function ReportInteractiveView({
3070
3539
  onToggleTag: toggleTag
3071
3540
  }
3072
3541
  ),
3073
- /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "flex flex-wrap items-center gap-x-6 gap-y-2 border-t border-border/60 pt-3", children: [
3074
- showToc ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(ReportTocDrawer, {}) : null,
3075
- /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "flex items-center gap-2", children: [
3076
- /* @__PURE__ */ (0, import_jsx_runtime49.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)(
3077
3546
  Switch,
3078
3547
  {
3079
3548
  id: expandId,
@@ -3081,7 +3550,7 @@ function ReportInteractiveView({
3081
3550
  onCheckedChange: setExpanded
3082
3551
  }
3083
3552
  ),
3084
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3553
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3085
3554
  "label",
3086
3555
  {
3087
3556
  htmlFor: expandId,
@@ -3090,8 +3559,8 @@ function ReportInteractiveView({
3090
3559
  }
3091
3560
  )
3092
3561
  ] }),
3093
- /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "flex items-center gap-2", children: [
3094
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3562
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "flex items-center gap-2", children: [
3563
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3095
3564
  Switch,
3096
3565
  {
3097
3566
  id: docsId,
@@ -3099,7 +3568,7 @@ function ReportInteractiveView({
3099
3568
  onCheckedChange: (checked) => setDetail(checked ? "full" : "minimal")
3100
3569
  }
3101
3570
  ),
3102
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3571
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3103
3572
  "label",
3104
3573
  {
3105
3574
  htmlFor: docsId,
@@ -3110,17 +3579,25 @@ function ReportInteractiveView({
3110
3579
  ] })
3111
3580
  ] })
3112
3581
  ] }),
3113
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(ReportFailureBanner, { failures }),
3114
- hasContent ? showToc ? /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "flex gap-6", children: [
3115
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(ReportToc, {}),
3116
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("div", { className: "flex min-w-0 flex-1 flex-col gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(ReportFeatureList, {}) })
3117
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("div", { className: "flex flex-col gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(ReportFeatureList, {}) }) : /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3582
+ showAgentFilter ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3583
+ ReportAgentFilterNotice,
3584
+ {
3585
+ applied: agentFilter,
3586
+ onReset: clearFilters,
3587
+ onDismiss: () => setAgentFilter(null)
3588
+ }
3589
+ ) : null,
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)(
3118
3595
  ReportEmpty,
3119
3596
  {
3120
3597
  message: query ? "No scenarios match the search." : void 0
3121
3598
  }
3122
3599
  ),
3123
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3600
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3124
3601
  "button",
3125
3602
  {
3126
3603
  type: "button",
@@ -3131,14 +3608,14 @@ function ReportInteractiveView({
3131
3608
  children: "?"
3132
3609
  }
3133
3610
  ),
3134
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3611
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3135
3612
  ReportShortcutsHelp,
3136
3613
  {
3137
3614
  open: helpOpen,
3138
3615
  onClose: () => setHelpOpen(false)
3139
3616
  }
3140
3617
  ),
3141
- toast ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3618
+ toast ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3142
3619
  "div",
3143
3620
  {
3144
3621
  role: "status",
@@ -3155,14 +3632,14 @@ function ReportInteractiveView({
3155
3632
  }
3156
3633
 
3157
3634
  // src/interactive/ReportInteractiveIsland.tsx
3158
- var import_react24 = require("react");
3635
+ var import_react26 = require("react");
3159
3636
 
3160
3637
  // src/interactive/island-renderers.tsx
3161
- var import_react23 = require("react");
3638
+ var import_react25 = require("react");
3162
3639
 
3163
3640
  // src/components/doc/MermaidView.tsx
3164
- var import_react22 = require("react");
3165
- var import_jsx_runtime50 = require("react/jsx-runtime");
3641
+ var import_react24 = require("react");
3642
+ var import_jsx_runtime52 = require("react/jsx-runtime");
3166
3643
  function prefersDark(el) {
3167
3644
  const themed = el?.closest("[data-theme]");
3168
3645
  if (themed?.dataset.theme === "dark") return true;
@@ -3170,10 +3647,10 @@ function prefersDark(el) {
3170
3647
  return typeof window !== "undefined" && !!window.matchMedia?.("(prefers-color-scheme: dark)").matches;
3171
3648
  }
3172
3649
  function MermaidView({ entry, load }) {
3173
- const ref = (0, import_react22.useRef)(null);
3174
- const [svg, setSvg] = (0, import_react22.useState)(null);
3175
- const [error, setError] = (0, import_react22.useState)(null);
3176
- (0, import_react22.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)(() => {
3177
3654
  let cancelled = false;
3178
3655
  const draw = async () => {
3179
3656
  let mermaid;
@@ -3216,9 +3693,9 @@ function MermaidView({ entry, load }) {
3216
3693
  };
3217
3694
  }, [entry.code, load]);
3218
3695
  if (svg) {
3219
- return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("figure", { ref, className: "my-3", children: [
3220
- entry.title ? /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
3221
- /* @__PURE__ */ (0, import_jsx_runtime50.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)(
3222
3699
  "div",
3223
3700
  {
3224
3701
  role: "img",
@@ -3229,8 +3706,8 @@ function MermaidView({ entry, load }) {
3229
3706
  )
3230
3707
  ] });
3231
3708
  }
3232
- return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_jsx_runtime50.Fragment, { children: [
3233
- error ? /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(
3709
+ return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(import_jsx_runtime52.Fragment, { children: [
3710
+ error ? /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
3234
3711
  "p",
3235
3712
  {
3236
3713
  role: "alert",
@@ -3242,12 +3719,12 @@ function MermaidView({ entry, load }) {
3242
3719
  ]
3243
3720
  }
3244
3721
  ) : null,
3245
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(MermaidSource, { entry, ref })
3722
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(MermaidSource, { entry, ref })
3246
3723
  ] });
3247
3724
  }
3248
3725
 
3249
3726
  // src/interactive/island-renderers.tsx
3250
- var import_jsx_runtime51 = require("react/jsx-runtime");
3727
+ var import_jsx_runtime53 = require("react/jsx-runtime");
3251
3728
  var HLJS_ESM = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/es/highlight.min.js";
3252
3729
  var MERMAID_ESM = "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
3253
3730
  function cachedCdnModule(url) {
@@ -3264,8 +3741,8 @@ function cachedCdnModule(url) {
3264
3741
  var loadMermaidFromCdn = cachedCdnModule(MERMAID_ESM);
3265
3742
  var loadHljs = cachedCdnModule(HLJS_ESM);
3266
3743
  function CdnHighlightedCode({ entry }) {
3267
- const [html, setHtml] = (0, import_react23.useState)(null);
3268
- (0, import_react23.useEffect)(() => {
3744
+ const [html, setHtml] = (0, import_react25.useState)(null);
3745
+ (0, import_react25.useEffect)(() => {
3269
3746
  let cancelled = false;
3270
3747
  void (async () => {
3271
3748
  try {
@@ -3281,27 +3758,27 @@ function CdnHighlightedCode({ entry }) {
3281
3758
  cancelled = true;
3282
3759
  };
3283
3760
  }, [entry.content, entry.lang]);
3284
- return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(CodeFigure, { label: entry.label, children: html !== null ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3761
+ return /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(CodeFigure, { label: entry.label, children: html !== null ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
3285
3762
  "code",
3286
3763
  {
3287
3764
  className: cn("hljs font-mono text-xs", entry.lang && `language-${entry.lang}`),
3288
3765
  dangerouslySetInnerHTML: { __html: html }
3289
3766
  }
3290
- ) : /* @__PURE__ */ (0, import_jsx_runtime51.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 }) });
3291
3768
  }
3292
3769
  function buildIslandRenderers(opts) {
3293
3770
  const renderers = {};
3294
3771
  if (opts.mermaid) {
3295
- renderers.mermaid = (entry) => (0, import_react23.createElement)(MermaidView, { entry, load: loadMermaidFromCdn });
3772
+ renderers.mermaid = (entry) => (0, import_react25.createElement)(MermaidView, { entry, load: loadMermaidFromCdn });
3296
3773
  }
3297
3774
  if (opts.syntaxHighlighting) {
3298
- renderers.code = (entry) => (0, import_react23.createElement)(CdnHighlightedCode, { entry });
3775
+ renderers.code = (entry) => (0, import_react25.createElement)(CdnHighlightedCode, { entry });
3299
3776
  }
3300
3777
  return renderers;
3301
3778
  }
3302
3779
 
3303
3780
  // src/interactive/ReportInteractiveIsland.tsx
3304
- var import_jsx_runtime52 = require("react/jsx-runtime");
3781
+ var import_jsx_runtime54 = require("react/jsx-runtime");
3305
3782
  function ReportInteractiveIsland({
3306
3783
  report,
3307
3784
  title,
@@ -3311,11 +3788,11 @@ function ReportInteractiveIsland({
3311
3788
  syntaxHighlighting = true,
3312
3789
  mermaid = true
3313
3790
  }) {
3314
- const renderers = (0, import_react24.useMemo)(
3791
+ const renderers = (0, import_react26.useMemo)(
3315
3792
  () => buildIslandRenderers({ syntaxHighlighting, mermaid }),
3316
3793
  [syntaxHighlighting, mermaid]
3317
3794
  );
3318
- return /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
3795
+ return /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
3319
3796
  ReportInteractive,
3320
3797
  {
3321
3798
  report,