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