executable-stories-react 0.10.1 → 0.12.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.
package/dist/index.cjs CHANGED
@@ -84,6 +84,7 @@ __export(src_exports, {
84
84
  parseStoryReport: () => parseStoryReport,
85
85
  prUrl: () => prUrl,
86
86
  reportLastRunMs: () => reportLastRunMs,
87
+ scenarioLastRunMs: () => scenarioLastRunMs,
87
88
  storyReportSchema: () => storyReportSchema,
88
89
  useBuiltinRenderers: () => useBuiltinRenderers,
89
90
  useCustomRenderers: () => useCustomRenderers,
@@ -380,23 +381,45 @@ function useReport() {
380
381
  // src/components/ReportScenario.tsx
381
382
  var import_duration3 = require("executable-stories-core/utils/duration");
382
383
 
384
+ // src/lib/step-params.tsx
385
+ var import_jsx_runtime3 = require("react/jsx-runtime");
386
+ var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.-])\d+(?:\.\d+)?(?![\w.-])/g;
387
+ function highlightStepParams(text) {
388
+ const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
389
+ if (matches.length === 0) return text;
390
+ const nodes = [];
391
+ let lastIndex = 0;
392
+ matches.forEach((match, i) => {
393
+ const start = match.index;
394
+ const end = start + match[0].length;
395
+ if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
396
+ nodes.push(
397
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "font-medium text-step-param italic", children: match[0] }, i)
398
+ );
399
+ lastIndex = end;
400
+ });
401
+ if (lastIndex < text.length) nodes.push(text.slice(lastIndex));
402
+ return nodes;
403
+ }
404
+
383
405
  // src/components/ReportSteps.tsx
406
+ var import_assertive_steps = require("executable-stories-core/utils/assertive-steps");
384
407
  var import_duration = require("executable-stories-core/utils/duration");
385
408
 
386
409
  // src/components/doc/DocNote.tsx
387
- var import_jsx_runtime3 = require("react/jsx-runtime");
410
+ var import_jsx_runtime4 = require("react/jsx-runtime");
388
411
  function DocNote({ entry }) {
389
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "my-2 text-sm text-muted-foreground", children: entry.text });
412
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "my-2 text-sm text-muted-foreground", children: entry.text });
390
413
  }
391
414
 
392
415
  // src/components/doc/DocTag.tsx
393
- var import_jsx_runtime4 = require("react/jsx-runtime");
416
+ var import_jsx_runtime5 = require("react/jsx-runtime");
394
417
  function DocTag({ entry }) {
395
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "my-2 flex flex-wrap gap-1.5", "aria-label": "Tags", children: entry.names.map((n) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Badge, { variant: "tag", children: n }) }, n)) });
418
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "my-2 flex flex-wrap gap-1.5", "aria-label": "Tags", children: entry.names.map((n) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Badge, { variant: "tag", children: n }) }, n)) });
396
419
  }
397
420
 
398
421
  // src/components/doc/DocKv.tsx
399
- var import_jsx_runtime5 = require("react/jsx-runtime");
422
+ var import_jsx_runtime6 = require("react/jsx-runtime");
400
423
  function formatValue(value) {
401
424
  if (value === null) return "null";
402
425
  if (typeof value === "string") return value;
@@ -408,9 +431,9 @@ function formatValue(value) {
408
431
  }
409
432
  }
410
433
  function DocKv({ entry }) {
411
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("dl", { className: "my-2 flex flex-wrap items-baseline gap-x-2 text-sm", children: [
412
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dt", { className: "font-semibold text-muted-foreground", children: entry.label }),
413
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dd", { className: "font-mono text-foreground", children: formatValue(entry.value) })
434
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("dl", { className: "my-2 flex flex-wrap items-baseline gap-x-2 text-sm", children: [
435
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { className: "font-semibold text-muted-foreground", children: entry.label }),
436
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dd", { className: "font-mono text-foreground", children: formatValue(entry.value) })
414
437
  ] });
415
438
  }
416
439
 
@@ -428,37 +451,37 @@ function useBuiltinRenderers() {
428
451
  }
429
452
 
430
453
  // src/components/doc/DocCode.tsx
431
- var import_jsx_runtime6 = require("react/jsx-runtime");
454
+ var import_jsx_runtime7 = require("react/jsx-runtime");
432
455
  function CodeFigure({ label, children }) {
433
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("figure", { className: "my-3", children: [
434
- label ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: label }) : null,
435
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3", children })
456
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("figure", { className: "my-3", children: [
457
+ label ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: label }) : null,
458
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3", children })
436
459
  ] });
437
460
  }
438
461
  function DocCode({ entry }) {
439
462
  const renderers = useBuiltinRenderers();
440
463
  if (renderers.code) {
441
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: renderers.code(entry) });
464
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: renderers.code(entry) });
442
465
  }
443
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CodeFigure, { label: entry.label, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
466
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CodeFigure, { label: entry.label, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
444
467
  }
445
468
 
446
469
  // src/components/doc/DocTable.tsx
447
- var import_jsx_runtime7 = require("react/jsx-runtime");
470
+ var import_jsx_runtime8 = require("react/jsx-runtime");
448
471
  function DocTable({ entry }) {
449
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("figure", { className: "my-3", children: [
450
- entry.label ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
451
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { tabIndex: 0, className: "overflow-x-auto rounded-md border border-border", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("table", { className: "w-full border-collapse text-sm", children: [
452
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tr", { className: "bg-muted/50", children: entry.columns.map((c) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: c }, c)) }) }),
453
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tbody", { children: entry.rows.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tr", { children: row.map((cell, j) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { className: "border-b border-border px-3 py-2 text-foreground", children: cell }, j)) }, i)) })
472
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("figure", { className: "my-3", children: [
473
+ entry.label ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
474
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { tabIndex: 0, className: "overflow-x-auto rounded-md border border-border", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("table", { className: "w-full border-collapse text-sm", children: [
475
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("tr", { className: "bg-muted/50", children: entry.columns.map((c) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: c }, c)) }) }),
476
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("tbody", { children: entry.rows.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("tr", { children: row.map((cell, j) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("td", { className: "border-b border-border px-3 py-2 text-foreground", children: cell }, j)) }, i)) })
454
477
  ] }) })
455
478
  ] });
456
479
  }
457
480
 
458
481
  // src/components/doc/DocLink.tsx
459
- var import_jsx_runtime8 = require("react/jsx-runtime");
482
+ var import_jsx_runtime9 = require("react/jsx-runtime");
460
483
  function DocLink({ entry }) {
461
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
484
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
462
485
  "a",
463
486
  {
464
487
  className: "text-sm font-medium text-primary underline underline-offset-2 hover:no-underline",
@@ -470,30 +493,38 @@ function DocLink({ entry }) {
470
493
  );
471
494
  }
472
495
 
473
- // src/components/doc/DocSection.tsx
496
+ // src/lib/markdown.ts
474
497
  var import_marked = require("marked");
475
498
 
476
499
  // src/lib/url.ts
477
500
  var import_url = require("executable-stories-core/utils/url");
478
501
 
479
- // src/components/doc/DocSection.tsx
480
- var import_jsx_runtime9 = require("react/jsx-runtime");
481
- function neutralizeUrl(value) {
482
- return (0, import_url.safeUrl)(value) ?? "#";
483
- }
502
+ // src/lib/markdown.ts
484
503
  function safeMarkdownHtml(markdown) {
485
504
  const raw = import_marked.marked.parse(markdown, { async: false });
486
505
  return raw.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<\/?(?:iframe|object|embed|form|base)\b[^>]*>/gi, "").replace(/\son[a-z]+\s*=\s*"[^"]*"/gi, "").replace(/\son[a-z]+\s*=\s*'[^']*'/gi, "").replace(/\son[a-z]+\s*=\s*[^\s>]+/gi, "").replace(/(href|src)\s*=\s*"([^"]*)"/gi, (_m, attr, val) => `${attr}="${neutralizeUrl(val)}"`).replace(/(href|src)\s*=\s*'([^']*)'/gi, (_m, attr, val) => `${attr}='${neutralizeUrl(val)}'`);
487
506
  }
507
+ function dedent(text) {
508
+ const lines = text.replace(/\t/g, " ").split("\n");
509
+ const indents = lines.filter((line) => line.trim().length > 0).map((line) => line.length - line.trimStart().length);
510
+ const shortest = indents.length > 0 ? Math.min(...indents) : 0;
511
+ return lines.map((line) => line.slice(shortest)).join("\n").trim();
512
+ }
513
+ function neutralizeUrl(value) {
514
+ return (0, import_url.safeUrl)(value) ?? "#";
515
+ }
516
+
517
+ // src/components/doc/DocSection.tsx
518
+ var import_jsx_runtime10 = require("react/jsx-runtime");
488
519
  function DocSection({ entry }) {
489
520
  const renderers = useBuiltinRenderers();
490
521
  if (renderers.section) {
491
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_jsx_runtime9.Fragment, { children: renderers.section(entry) });
522
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_jsx_runtime10.Fragment, { children: renderers.section(entry) });
492
523
  }
493
524
  const html = safeMarkdownHtml(entry.markdown);
494
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("section", { className: "my-3 text-sm", "aria-label": entry.title, children: [
495
- entry.title ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h4", { className: "mb-1 font-semibold text-foreground", children: entry.title }) : null,
496
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
525
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: "my-3 text-sm", "aria-label": entry.title, children: [
526
+ entry.title ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h4", { className: "mb-1 font-semibold text-foreground", children: entry.title }) : null,
527
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
497
528
  "div",
498
529
  {
499
530
  className: "es-doc-prose prose prose-sm max-w-none",
@@ -504,11 +535,11 @@ function DocSection({ entry }) {
504
535
  }
505
536
 
506
537
  // src/components/doc/DocMermaid.tsx
507
- var import_jsx_runtime10 = require("react/jsx-runtime");
538
+ var import_jsx_runtime11 = require("react/jsx-runtime");
508
539
  function MermaidSource({ entry, ref }) {
509
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("figure", { ref, className: "my-3", "aria-label": entry.title ?? "Diagram", children: [
510
- entry.title ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
511
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
540
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("figure", { ref, className: "my-3", "aria-label": entry.title ?? "Diagram", children: [
541
+ entry.title ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
542
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
512
543
  "pre",
513
544
  {
514
545
  "data-mermaid": true,
@@ -522,24 +553,24 @@ function MermaidSource({ entry, ref }) {
522
553
  function DocMermaid({ entry }) {
523
554
  const renderers = useBuiltinRenderers();
524
555
  if (renderers.mermaid) {
525
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_jsx_runtime10.Fragment, { children: renderers.mermaid(entry) });
556
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_jsx_runtime11.Fragment, { children: renderers.mermaid(entry) });
526
557
  }
527
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MermaidSource, { entry });
558
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MermaidSource, { entry });
528
559
  }
529
560
 
530
561
  // src/components/doc/DocScreenshot.tsx
531
- var import_jsx_runtime11 = require("react/jsx-runtime");
562
+ var import_jsx_runtime12 = require("react/jsx-runtime");
532
563
  function DocScreenshot({ entry }) {
533
564
  const src = (0, import_url.isLocalFsPath)(entry.path) ? void 0 : (0, import_url.safeImageUrl)(entry.path);
534
565
  if (!src) {
535
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("figure", { className: "my-3 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground", children: [
536
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "font-medium", children: "Screenshot unavailable" }),
537
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
538
- entry.alt ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("figcaption", { className: "mt-1.5 text-xs", children: entry.alt }) : null
566
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("figure", { className: "my-3 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground", children: [
567
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "font-medium", children: "Screenshot unavailable" }),
568
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
569
+ entry.alt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("figcaption", { className: "mt-1.5 text-xs", children: entry.alt }) : null
539
570
  ] });
540
571
  }
541
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("figure", { className: "my-3", children: [
542
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
572
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("figure", { className: "my-3", children: [
573
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
543
574
  "img",
544
575
  {
545
576
  src,
@@ -548,24 +579,24 @@ function DocScreenshot({ entry }) {
548
579
  className: "max-w-full rounded-md border border-border"
549
580
  }
550
581
  ),
551
- entry.alt ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: entry.alt }) : null
582
+ entry.alt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: entry.alt }) : null
552
583
  ] });
553
584
  }
554
585
 
555
586
  // src/components/doc/DocVideo.tsx
556
- var import_jsx_runtime12 = require("react/jsx-runtime");
587
+ var import_jsx_runtime13 = require("react/jsx-runtime");
557
588
  function DocVideo({ entry }) {
558
589
  if ((0, import_url.isLocalFsPath)(entry.path)) {
559
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("figure", { className: "my-3 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground", children: [
560
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "font-medium", children: "Video unavailable" }),
561
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
562
- entry.caption ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("figcaption", { className: "mt-1.5 text-xs", children: entry.caption }) : null
590
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("figure", { className: "my-3 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground", children: [
591
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "font-medium", children: "Video unavailable" }),
592
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
593
+ entry.caption ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("figcaption", { className: "mt-1.5 text-xs", children: entry.caption }) : null
563
594
  ] });
564
595
  }
565
596
  const src = (0, import_url.safeUrl)(entry.path);
566
597
  const poster = (0, import_url.safeUrl)(entry.poster);
567
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("figure", { className: "my-3", children: [
568
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
598
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("figure", { className: "my-3", children: [
599
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
569
600
  "video",
570
601
  {
571
602
  controls: true,
@@ -575,21 +606,21 @@ function DocVideo({ entry }) {
575
606
  className: "max-w-full rounded-md border border-border"
576
607
  }
577
608
  ),
578
- entry.caption ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: entry.caption }) : null
609
+ entry.caption ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: entry.caption }) : null
579
610
  ] });
580
611
  }
581
612
 
582
613
  // src/components/doc/DocHtml.tsx
583
- var import_jsx_runtime13 = require("react/jsx-runtime");
614
+ var import_jsx_runtime14 = require("react/jsx-runtime");
584
615
  function DocHtml({ entry }) {
585
616
  const height = typeof entry.height === "number" ? `${entry.height}px` : entry.height ?? "400px";
586
617
  const title = entry.title ?? "Embedded HTML";
587
618
  const safeHref = (0, import_url.safeUrl)(entry.url ?? entry.path);
588
619
  const frameProps = entry.content !== void 0 ? { srcDoc: entry.content } : { src: safeHref ?? "" };
589
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("figure", { className: "my-3 overflow-hidden rounded-md border border-border", children: [
590
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("figcaption", { className: "flex items-center justify-between border-b border-border bg-muted/40 px-3 py-1.5 text-xs font-medium text-muted-foreground", children: [
591
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: entry.title ?? "HTML" }),
592
- safeHref ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
620
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("figure", { className: "my-3 overflow-hidden rounded-md border border-border", children: [
621
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("figcaption", { className: "flex items-center justify-between border-b border-border bg-muted/40 px-3 py-1.5 text-xs font-medium text-muted-foreground", children: [
622
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { children: entry.title ?? "HTML" }),
623
+ safeHref ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
593
624
  "a",
594
625
  {
595
626
  href: safeHref,
@@ -602,7 +633,7 @@ function DocHtml({ entry }) {
602
633
  }
603
634
  ) : null
604
635
  ] }),
605
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
636
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
606
637
  "iframe",
607
638
  {
608
639
  ...frameProps,
@@ -617,7 +648,7 @@ function DocHtml({ entry }) {
617
648
  }
618
649
 
619
650
  // src/components/doc/DocState.tsx
620
- var import_jsx_runtime14 = require("react/jsx-runtime");
651
+ var import_jsx_runtime15 = require("react/jsx-runtime");
621
652
  function DocState({ entry }) {
622
653
  let json;
623
654
  try {
@@ -625,20 +656,20 @@ function DocState({ entry }) {
625
656
  } catch {
626
657
  json = String(entry.value);
627
658
  }
628
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(CodeFigure, { label: entry.label ?? "State", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("code", { className: "font-mono text-xs language-json", children: json }) });
659
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CodeFigure, { label: entry.label ?? "State", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("code", { className: "font-mono text-xs language-json", children: json }) });
629
660
  }
630
661
 
631
662
  // src/components/doc/DocCustom.tsx
632
- var import_jsx_runtime15 = require("react/jsx-runtime");
663
+ var import_jsx_runtime16 = require("react/jsx-runtime");
633
664
  function DocCustom({ entry }) {
634
665
  const renderers = useCustomRenderers();
635
666
  const renderer = renderers[entry.type];
636
667
  if (renderer) {
637
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_jsx_runtime15.Fragment, { children: renderer(entry) });
668
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_jsx_runtime16.Fragment, { children: renderer(entry) });
638
669
  }
639
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "my-2", "data-type": entry.type, children: [
640
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: entry.type }),
641
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3 font-mono text-xs text-foreground", children: safeStringify2(entry.data) })
670
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "my-2", "data-type": entry.type, children: [
671
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: entry.type }),
672
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3 font-mono text-xs text-foreground", children: safeStringify2(entry.data) })
642
673
  ] });
643
674
  }
644
675
  function safeStringify2(value) {
@@ -650,64 +681,43 @@ function safeStringify2(value) {
650
681
  }
651
682
 
652
683
  // src/components/doc/DocEntry.tsx
653
- var import_jsx_runtime16 = require("react/jsx-runtime");
684
+ var import_jsx_runtime17 = require("react/jsx-runtime");
654
685
  function DocEntry({ entry }) {
655
686
  switch (entry.kind) {
656
687
  case "note":
657
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocNote, { entry });
688
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocNote, { entry });
658
689
  case "tag":
659
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocTag, { entry });
690
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocTag, { entry });
660
691
  case "kv":
661
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocKv, { entry });
692
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocKv, { entry });
662
693
  case "code":
663
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocCode, { entry });
694
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocCode, { entry });
664
695
  case "table":
665
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocTable, { entry });
696
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocTable, { entry });
666
697
  case "link":
667
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocLink, { entry });
698
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocLink, { entry });
668
699
  case "section":
669
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocSection, { entry });
700
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocSection, { entry });
670
701
  case "mermaid":
671
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocMermaid, { entry });
702
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocMermaid, { entry });
672
703
  case "screenshot":
673
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocScreenshot, { entry });
704
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocScreenshot, { entry });
674
705
  case "video":
675
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocVideo, { entry });
706
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocVideo, { entry });
676
707
  case "html":
677
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocHtml, { entry });
708
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocHtml, { entry });
678
709
  case "state":
679
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocState, { entry });
710
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocState, { entry });
680
711
  case "custom":
681
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocCustom, { entry });
712
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocCustom, { entry });
682
713
  }
683
714
  }
684
715
 
685
716
  // src/components/ReportDocEntries.tsx
686
- var import_jsx_runtime17 = require("react/jsx-runtime");
717
+ var import_jsx_runtime18 = require("react/jsx-runtime");
687
718
  function ReportDocEntries({ entries }) {
688
719
  if (entries.length === 0) return null;
689
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_jsx_runtime17.Fragment, { children: entries.map((entry, i) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocEntry, { entry }, i)) });
690
- }
691
-
692
- // src/lib/step-params.tsx
693
- var import_jsx_runtime18 = require("react/jsx-runtime");
694
- var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.-])\d+(?:\.\d+)?(?![\w.-])/g;
695
- function highlightStepParams(text) {
696
- const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
697
- if (matches.length === 0) return text;
698
- const nodes = [];
699
- let lastIndex = 0;
700
- matches.forEach((match, i) => {
701
- const start = match.index;
702
- const end = start + match[0].length;
703
- if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
704
- nodes.push(
705
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "font-medium text-step-param italic", children: match[0] }, i)
706
- );
707
- lastIndex = end;
708
- });
709
- if (lastIndex < text.length) nodes.push(text.slice(lastIndex));
710
- return nodes;
720
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_jsx_runtime18.Fragment, { children: entries.map((entry, i) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DocEntry, { entry }, i)) });
711
721
  }
712
722
 
713
723
  // src/components/ReportSteps.tsx
@@ -726,9 +736,22 @@ var STEP_GLYPH_COLOR = {
726
736
  };
727
737
  function ReportSteps({ scenario }) {
728
738
  if (scenario.steps.length === 0) return null;
729
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("ol", { "data-slot": "steps", className: "flex flex-col", children: scenario.steps.map((step) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ReportStepItem, { step }, step.id)) });
739
+ const unasserted = new Set(
740
+ (0, import_assertive_steps.assertionState)(scenario.steps) === "unasserted" ? (0, import_assertive_steps.assertiveSteps)(scenario.steps).map((step) => step.id) : []
741
+ );
742
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("ol", { "data-slot": "steps", className: "flex flex-col", children: scenario.steps.map((step) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
743
+ ReportStepItem,
744
+ {
745
+ step,
746
+ unasserted: unasserted.has(step.id)
747
+ },
748
+ step.id
749
+ )) });
730
750
  }
731
- function ReportStepItem({ step }) {
751
+ function ReportStepItem({
752
+ step,
753
+ unasserted = false
754
+ }) {
732
755
  const kw = step.keyword.toLowerCase();
733
756
  const continuation = kw === "and" || kw === "but";
734
757
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
@@ -736,10 +759,23 @@ function ReportStepItem({ step }) {
736
759
  {
737
760
  id: step.id,
738
761
  "data-status": step.status,
739
- className: cn("py-1.5 text-[0.8125rem] leading-normal", continuation && "pl-5"),
762
+ className: cn(
763
+ "py-1.5 text-[0.8125rem] leading-normal",
764
+ continuation && "pl-5"
765
+ ),
740
766
  children: [
741
767
  /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-baseline gap-2", children: [
742
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { "aria-hidden": true, className: cn("w-4 shrink-0 text-center text-xs", STEP_GLYPH_COLOR[step.status]), children: STEP_GLYPH[step.status] ?? "\u25CB" }),
768
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
769
+ "span",
770
+ {
771
+ "aria-hidden": true,
772
+ className: cn(
773
+ "w-4 shrink-0 text-center text-xs",
774
+ STEP_GLYPH_COLOR[step.status]
775
+ ),
776
+ children: STEP_GLYPH[step.status] ?? "\u25CB"
777
+ }
778
+ ),
743
779
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
744
780
  "span",
745
781
  {
@@ -751,6 +787,7 @@ function ReportStepItem({ step }) {
751
787
  }
752
788
  ),
753
789
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "flex-1 text-foreground", children: highlightStepParams(step.text) }),
790
+ unasserted ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "shrink-0 rounded-full border border-pend-border bg-pend-bg px-2 py-0.5 text-[0.6875rem] font-medium text-pend", children: "No assertion" }) : null,
754
791
  step.durationMs > 0 ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "shrink-0 whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: (0, import_duration.formatDuration)(step.durationMs) }) : null
755
792
  ] }),
756
793
  step.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
@@ -1146,28 +1183,111 @@ function ScenarioRunHistory({ scenarioId }) {
1146
1183
  ] });
1147
1184
  }
1148
1185
 
1149
- // src/interactive/collapse-context.ts
1186
+ // src/components/ScenarioStaleness.tsx
1187
+ var import_react7 = require("react");
1188
+
1189
+ // src/lib/provenance.ts
1190
+ function reportLastRunMs(report) {
1191
+ if (report.finishedAtMs && report.finishedAtMs > 0) return report.finishedAtMs;
1192
+ if (report.startedAtMs && report.startedAtMs > 0) return report.startedAtMs;
1193
+ return void 0;
1194
+ }
1195
+ function scenarioLastRunMs(scenario, report) {
1196
+ if (scenario.lastRunAtMs && scenario.lastRunAtMs > 0) return scenario.lastRunAtMs;
1197
+ return reportLastRunMs(report);
1198
+ }
1199
+ function formatRelativeAge(thenMs, nowMs) {
1200
+ const deltaMs = Math.max(0, nowMs - thenMs);
1201
+ const minutes = Math.floor(deltaMs / 6e4);
1202
+ if (minutes < 1) return "just now";
1203
+ if (minutes < 60) return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
1204
+ const hours = Math.floor(minutes / 60);
1205
+ if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
1206
+ const days = Math.floor(hours / 24);
1207
+ return `${days} day${days === 1 ? "" : "s"} ago`;
1208
+ }
1209
+ function isReportStale(lastRunMs, staleAfterDays, nowMs) {
1210
+ if (lastRunMs === void 0 || staleAfterDays <= 0) return false;
1211
+ return nowMs - lastRunMs >= staleAfterDays * 864e5;
1212
+ }
1213
+ var CI_DISPLAY_NAMES = {
1214
+ github: "GitHub Actions",
1215
+ gitlab: "GitLab CI",
1216
+ circleci: "CircleCI",
1217
+ jenkins: "Jenkins",
1218
+ azure: "Azure DevOps",
1219
+ buildkite: "Buildkite",
1220
+ travis: "Travis CI",
1221
+ ci: "CI"
1222
+ };
1223
+ function ciDisplayName(name) {
1224
+ return CI_DISPLAY_NAMES[name] ?? name;
1225
+ }
1226
+ function commitUrl(ci, sha) {
1227
+ const commitSha = ci?.commitSha ?? sha;
1228
+ if (!ci?.url || !commitSha) return void 0;
1229
+ const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1230
+ if (github) return `${github[1]}/commit/${commitSha}`;
1231
+ const gitlab = ci.url.match(/^(https?:\/\/.+)\/-\/pipelines\//);
1232
+ if (gitlab) return `${gitlab[1]}/-/commit/${commitSha}`;
1233
+ return void 0;
1234
+ }
1235
+ function prUrl(ci) {
1236
+ if (!ci?.url || !ci.prNumber) return void 0;
1237
+ const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1238
+ if (github) return `${github[1]}/pull/${ci.prNumber}`;
1239
+ return void 0;
1240
+ }
1241
+
1242
+ // src/interactive/scenario-freshness-context.tsx
1150
1243
  var import_react6 = require("react");
1151
- var CollapseContext = (0, import_react6.createContext)(null);
1244
+ var ScenarioFreshnessContext = (0, import_react6.createContext)(null);
1245
+ var ScenarioFreshnessProvider = ScenarioFreshnessContext.Provider;
1246
+ function useScenarioFreshness() {
1247
+ return (0, import_react6.useContext)(ScenarioFreshnessContext);
1248
+ }
1249
+
1250
+ // src/components/ScenarioStaleness.tsx
1251
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1252
+ function ScenarioStaleness({ scenario }) {
1253
+ const freshness = useScenarioFreshness();
1254
+ const fixedNow = freshness?.nowMs;
1255
+ const [now, setNow] = (0, import_react7.useState)(fixedNow);
1256
+ (0, import_react7.useEffect)(() => {
1257
+ if (fixedNow === void 0) setNow(Date.now());
1258
+ }, [fixedNow]);
1259
+ if (!freshness || now === void 0) return null;
1260
+ const lastRunAtMs = scenario.lastRunAtMs;
1261
+ if (!lastRunAtMs || lastRunAtMs <= 0) return null;
1262
+ if (!isReportStale(lastRunAtMs, freshness.staleAfterDays, now)) return null;
1263
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(Badge, { variant: "outline", "aria-label": "Scenario freshness", children: [
1264
+ "Last ran ",
1265
+ formatRelativeAge(lastRunAtMs, now)
1266
+ ] });
1267
+ }
1268
+
1269
+ // src/interactive/collapse-context.ts
1270
+ var import_react8 = require("react");
1271
+ var CollapseContext = (0, import_react8.createContext)(null);
1152
1272
  var CollapseProvider = CollapseContext.Provider;
1153
1273
  function useCollapse() {
1154
- return (0, import_react6.useContext)(CollapseContext);
1274
+ return (0, import_react8.useContext)(CollapseContext);
1155
1275
  }
1156
1276
 
1157
1277
  // src/interactive/scenario-actions.tsx
1158
- var import_react7 = require("react");
1278
+ var import_react9 = require("react");
1159
1279
  var import_scenario_markdown = require("executable-stories-core/utils/scenario-markdown");
1160
- var ScenarioActionsContext = (0, import_react7.createContext)(null);
1280
+ var ScenarioActionsContext = (0, import_react9.createContext)(null);
1161
1281
  var ScenarioActionsProvider = ScenarioActionsContext.Provider;
1162
1282
  function useScenarioActions() {
1163
- return (0, import_react7.useContext)(ScenarioActionsContext);
1283
+ return (0, import_react9.useContext)(ScenarioActionsContext);
1164
1284
  }
1165
1285
 
1166
1286
  // src/components/ui/card.tsx
1167
1287
  var React2 = require("react");
1168
- var import_jsx_runtime24 = require("react/jsx-runtime");
1288
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1169
1289
  function Card({ className, ...props }) {
1170
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1290
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1171
1291
  "div",
1172
1292
  {
1173
1293
  "data-slot": "card",
@@ -1180,7 +1300,7 @@ function Card({ className, ...props }) {
1180
1300
  );
1181
1301
  }
1182
1302
  function CardHeader({ className, ...props }) {
1183
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1303
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1184
1304
  "div",
1185
1305
  {
1186
1306
  "data-slot": "card-header",
@@ -1193,7 +1313,7 @@ function CardHeader({ className, ...props }) {
1193
1313
  );
1194
1314
  }
1195
1315
  function CardContent({ className, ...props }) {
1196
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1316
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1197
1317
  "div",
1198
1318
  {
1199
1319
  "data-slot": "card-content",
@@ -1207,13 +1327,13 @@ function CardContent({ className, ...props }) {
1207
1327
  var React3 = require("react");
1208
1328
  var import_menu = require("@base-ui/react/menu");
1209
1329
  var import_lucide_react = require("lucide-react");
1210
- var import_jsx_runtime25 = require("react/jsx-runtime");
1330
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1211
1331
  var itemClasses = "relative flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 [&_svg:not([class*='text-'])]:text-muted-foreground";
1212
1332
  function DropdownMenu(props) {
1213
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_menu.Menu.Root, { "data-slot": "dropdown-menu", ...props });
1333
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_menu.Menu.Root, { "data-slot": "dropdown-menu", ...props });
1214
1334
  }
1215
1335
  function DropdownMenuTrigger(props) {
1216
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_menu.Menu.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
1336
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_menu.Menu.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
1217
1337
  }
1218
1338
  function DropdownMenuContent({
1219
1339
  className,
@@ -1221,14 +1341,14 @@ function DropdownMenuContent({
1221
1341
  sideOffset = 4,
1222
1342
  ...props
1223
1343
  }) {
1224
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_menu.Menu.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1344
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_menu.Menu.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1225
1345
  import_menu.Menu.Positioner,
1226
1346
  {
1227
1347
  "data-slot": "dropdown-menu-positioner",
1228
1348
  className: "z-50",
1229
1349
  align,
1230
1350
  sideOffset,
1231
- children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1351
+ children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1232
1352
  import_menu.Menu.Popup,
1233
1353
  {
1234
1354
  "data-slot": "dropdown-menu-content",
@@ -1246,7 +1366,7 @@ function DropdownMenuItem({
1246
1366
  className,
1247
1367
  ...props
1248
1368
  }) {
1249
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1369
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1250
1370
  import_menu.Menu.Item,
1251
1371
  {
1252
1372
  "data-slot": "dropdown-menu-item",
@@ -1258,7 +1378,7 @@ function DropdownMenuItem({
1258
1378
 
1259
1379
  // src/components/ReportScenario.tsx
1260
1380
  var import_lucide_react2 = require("lucide-react");
1261
- var import_jsx_runtime26 = require("react/jsx-runtime");
1381
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1262
1382
  var STATUS_META = {
1263
1383
  passed: { label: "Passed", glyph: "\u2713", glyphColor: "text-pass", badgeVariant: "passed", showPill: false },
1264
1384
  failed: { label: "Failed", glyph: "\u2717", glyphColor: "text-fail", badgeVariant: "failed", showPill: true },
@@ -1274,7 +1394,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
1274
1394
  const collapsible = collapse !== null && !hideTitle;
1275
1395
  const collapsed = collapsible ? collapse.isCollapsed(scenario.id) : false;
1276
1396
  const actions = useScenarioActions();
1277
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
1397
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
1278
1398
  Card,
1279
1399
  {
1280
1400
  id: scenario.id,
@@ -1282,10 +1402,10 @@ function ReportScenario({ scenario, hideTitle = false }) {
1282
1402
  ...hideTitle ? { "aria-label": `${scenario.title}, ${label}` } : { "aria-labelledby": titleId },
1283
1403
  className: "gap-0 py-0",
1284
1404
  children: [
1285
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(CardHeader, { className: "px-4 py-3", children: [
1286
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
1287
- hideTitle ? null : /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1288
- collapsible ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1405
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(CardHeader, { className: "px-4 py-3", children: [
1406
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
1407
+ hideTitle ? null : /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1408
+ collapsible ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1289
1409
  "button",
1290
1410
  {
1291
1411
  type: "button",
@@ -1294,49 +1414,50 @@ function ReportScenario({ scenario, hideTitle = false }) {
1294
1414
  "aria-controls": bodyId,
1295
1415
  "aria-label": `Toggle ${scenario.title}`,
1296
1416
  className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
1297
- children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1417
+ children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1298
1418
  }
1299
1419
  ) : null,
1300
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { "aria-hidden": true, className: cn("text-sm leading-none", meta.glyphColor), children: meta.glyph }),
1301
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { children: scenario.title })
1420
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { "aria-hidden": true, className: cn("text-sm leading-none", meta.glyphColor), children: meta.glyph }),
1421
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { children: scenario.title })
1302
1422
  ] }),
1303
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex shrink-0 items-center gap-2", children: [
1304
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ScenarioRunHistory, { scenarioId: scenario.id }),
1305
- scenario.durationMs > 0 ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: (0, import_duration3.formatDuration)(scenario.durationMs) }) : null,
1306
- meta.showPill ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Badge, { variant: meta.badgeVariant, "aria-label": `Status: ${label}`, children: label }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "sr-only", children: `Status: ${label}` }),
1307
- actions ? /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenu, { children: [
1308
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1423
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex shrink-0 items-center gap-2", children: [
1424
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ScenarioRunHistory, { scenarioId: scenario.id }),
1425
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ScenarioStaleness, { scenario }),
1426
+ 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,
1427
+ 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}` }),
1428
+ actions ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenu, { children: [
1429
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1309
1430
  DropdownMenuTrigger,
1310
1431
  {
1311
1432
  "aria-label": `Actions for ${scenario.title}`,
1312
1433
  className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
1313
- children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react2.MoreHorizontalIcon, { "aria-hidden": true })
1434
+ children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.MoreHorizontalIcon, { "aria-hidden": true })
1314
1435
  }
1315
1436
  ),
1316
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenuContent, { align: "end", children: [
1317
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
1318
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react2.LinkIcon, {}),
1437
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuContent, { align: "end", children: [
1438
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
1439
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.LinkIcon, {}),
1319
1440
  " Copy link"
1320
1441
  ] }),
1321
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
1322
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react2.FileTextIcon, {}),
1442
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
1443
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.FileTextIcon, {}),
1323
1444
  " Copy as Markdown"
1324
1445
  ] }),
1325
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
1326
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react2.SparklesIcon, {}),
1446
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
1447
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.SparklesIcon, {}),
1327
1448
  " Explain with AI"
1328
1449
  ] })
1329
1450
  ] })
1330
1451
  ] }) : null
1331
1452
  ] })
1332
1453
  ] }),
1333
- scenario.tags.length > 0 || (scenario.tickets?.length ?? 0) > 0 ? /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("ul", { "aria-label": "Tags and tickets", className: "flex flex-wrap gap-1.5", children: [
1334
- scenario.tags.map((t) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
1335
- (scenario.tickets ?? []).map((ticket) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("li", { children: ticket.url ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("a", { href: ticket.url, target: "_blank", rel: "noreferrer noopener", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Badge, { variant: "outline", children: ticket.id }) }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Badge, { variant: "outline", children: ticket.id }) }, `ticket-${ticket.id}`))
1454
+ 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: [
1455
+ 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}`)),
1456
+ (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}`))
1336
1457
  ] }) : null
1337
1458
  ] }),
1338
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
1339
- scenario.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1459
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
1460
+ scenario.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1340
1461
  "pre",
1341
1462
  {
1342
1463
  role: "alert",
@@ -1344,11 +1465,11 @@ function ReportScenario({ scenario, hideTitle = false }) {
1344
1465
  children: scenario.errorMessage
1345
1466
  }
1346
1467
  ) : null,
1347
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ReportStoryboard, { scenario }),
1348
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ReportSteps, { scenario }),
1349
- scenario.docEntries.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { "data-es-docs": true, className: "mt-3", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
1350
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ReportAttachments, { attachments: scenario.attachments }),
1351
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ReportTrace, { spans: scenario.otelSpans })
1468
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ReportStoryboard, { scenario }),
1469
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ReportSteps, { scenario }),
1470
+ scenario.docEntries.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { "data-es-docs": true, className: "mt-3", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
1471
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ReportAttachments, { attachments: scenario.attachments }),
1472
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ReportTrace, { spans: scenario.otelSpans })
1352
1473
  ] })
1353
1474
  ]
1354
1475
  }
@@ -1356,13 +1477,40 @@ function ReportScenario({ scenario, hideTitle = false }) {
1356
1477
  }
1357
1478
 
1358
1479
  // src/components/ReportScenarioList.tsx
1359
- var import_jsx_runtime27 = require("react/jsx-runtime");
1480
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1360
1481
  function ReportScenarioList({ feature }) {
1361
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_jsx_runtime27.Fragment, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ReportScenario, { scenario }, scenario.id)) });
1482
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_jsx_runtime28.Fragment, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ReportScenario, { scenario }, scenario.id)) });
1483
+ }
1484
+
1485
+ // src/components/ReportFeatureIntro.tsx
1486
+ var import_jsx_runtime29 = require("react/jsx-runtime");
1487
+ function ReportFeatureIntro({ feature }) {
1488
+ const hasNarrative = Boolean(feature.narrative?.trim());
1489
+ const hasGlossary = Boolean(feature.glossary?.length);
1490
+ if (!hasNarrative && !hasGlossary) return null;
1491
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-col gap-3 border-l-2 border-border pl-4 text-sm", children: [
1492
+ hasNarrative ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1493
+ "div",
1494
+ {
1495
+ className: "es-doc-prose prose prose-sm max-w-none text-muted-foreground",
1496
+ dangerouslySetInnerHTML: { __html: safeMarkdownHtml(dedent(feature.narrative)) }
1497
+ }
1498
+ ) : null,
1499
+ hasGlossary ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("dl", { className: "grid gap-1.5", "aria-label": "Glossary", children: feature.glossary.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-wrap gap-x-2 text-xs", children: [
1500
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("dt", { className: "font-medium text-foreground", children: entry.term }),
1501
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("dd", { className: "flex-1 text-muted-foreground", children: entry.definition })
1502
+ ] }, entry.term)) }) : null
1503
+ ] });
1504
+ }
1505
+ function featureKindLabel(kind) {
1506
+ if (kind === "ability") return "Ability";
1507
+ if (kind === "business-need") return "Business need";
1508
+ if (kind === "feature") return "Feature";
1509
+ return null;
1362
1510
  }
1363
1511
 
1364
1512
  // src/components/ReportFeature.tsx
1365
- var import_jsx_runtime28 = require("react/jsx-runtime");
1513
+ var import_jsx_runtime30 = require("react/jsx-runtime");
1366
1514
  function ReportFeature({ feature }) {
1367
1515
  const titleId = `${feature.id}-title`;
1368
1516
  const bodyId = `${feature.id}-body`;
@@ -1370,10 +1518,11 @@ function ReportFeature({ feature }) {
1370
1518
  const skipped = s.skipped + s.pending;
1371
1519
  const collapse = useCollapse();
1372
1520
  const collapsed = collapse?.isCollapsed(feature.id) ?? false;
1521
+ const kindLabel = featureKindLabel(feature.kind);
1373
1522
  return (
1374
1523
  // Suite = a section heading + a divider, NOT another bordered card. Drops a
1375
1524
  // layer of nesting so the scenario cards below read as the primary unit.
1376
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1525
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
1377
1526
  "section",
1378
1527
  {
1379
1528
  id: feature.id,
@@ -1381,9 +1530,9 @@ function ReportFeature({ feature }) {
1381
1530
  "aria-labelledby": titleId,
1382
1531
  className: "flex flex-col gap-3",
1383
1532
  children: [
1384
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1385
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "min-w-0", children: [
1386
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("h2", { id: titleId, className: "text-base font-semibold tracking-tight text-foreground", children: collapse ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1533
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1534
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "min-w-0", children: [
1535
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("h2", { id: titleId, className: "text-base font-semibold tracking-tight text-foreground", children: collapse ? /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
1387
1536
  "button",
1388
1537
  {
1389
1538
  type: "button",
@@ -1392,31 +1541,34 @@ function ReportFeature({ feature }) {
1392
1541
  "aria-controls": bodyId,
1393
1542
  className: "flex w-full cursor-pointer items-center gap-2 text-left hover:text-foreground/80",
1394
1543
  children: [
1395
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { "aria-hidden": true, className: cn("shrink-0 text-xs text-muted-foreground transition-transform", !collapsed && "rotate-90"), children: "\u25B8" }),
1396
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "truncate", children: feature.title })
1544
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { "aria-hidden": true, className: cn("shrink-0 text-xs text-muted-foreground transition-transform", !collapsed && "rotate-90"), children: "\u25B8" }),
1545
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "truncate", children: feature.title })
1397
1546
  ]
1398
1547
  }
1399
1548
  ) : feature.title }),
1400
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("p", { className: cn("mt-0.5 truncate font-mono text-xs text-muted-foreground", collapse && "pl-5"), children: feature.sourceFile })
1549
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("p", { className: cn("mt-0.5 flex items-center gap-2 text-xs text-muted-foreground", collapse && "pl-5"), children: [
1550
+ kindLabel ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "shrink-0 rounded-sm bg-muted px-1.5 py-0.5 font-medium text-foreground", children: kindLabel }) : null,
1551
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "truncate font-mono", children: feature.sourceFile })
1552
+ ] })
1401
1553
  ] }),
1402
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1554
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
1403
1555
  "p",
1404
1556
  {
1405
1557
  className: "flex shrink-0 items-center gap-1.5 text-xs text-muted-foreground",
1406
1558
  "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
1407
1559
  children: [
1408
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { children: [
1560
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("span", { children: [
1409
1561
  s.passed,
1410
1562
  " passed"
1411
1563
  ] }),
1412
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1413
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
1564
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1565
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
1414
1566
  s.failed,
1415
1567
  " failed"
1416
1568
  ] }),
1417
- skipped > 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
1418
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1419
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { children: [
1569
+ skipped > 0 ? /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
1570
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1571
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("span", { children: [
1420
1572
  skipped,
1421
1573
  " skipped"
1422
1574
  ] })
@@ -1425,7 +1577,10 @@ function ReportFeature({ feature }) {
1425
1577
  }
1426
1578
  )
1427
1579
  ] }),
1428
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-3", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ReportScenarioList, { feature }) })
1580
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-3", children: [
1581
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ReportFeatureIntro, { feature }),
1582
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ReportScenarioList, { feature })
1583
+ ] })
1429
1584
  ]
1430
1585
  }
1431
1586
  )
@@ -1433,17 +1588,17 @@ function ReportFeature({ feature }) {
1433
1588
  }
1434
1589
 
1435
1590
  // src/components/ReportFeatureList.tsx
1436
- var import_jsx_runtime29 = require("react/jsx-runtime");
1591
+ var import_jsx_runtime31 = require("react/jsx-runtime");
1437
1592
  function ReportFeatureList() {
1438
1593
  const report = useReport();
1439
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "flex flex-col gap-8", children: report.features.map((feature) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(ReportFeature, { feature }, feature.id)) });
1594
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "flex flex-col gap-8", children: report.features.map((feature) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ReportFeature, { feature }, feature.id)) });
1440
1595
  }
1441
1596
 
1442
1597
  // src/components/ui/empty.tsx
1443
1598
  var import_class_variance_authority2 = require("class-variance-authority");
1444
- var import_jsx_runtime30 = require("react/jsx-runtime");
1599
+ var import_jsx_runtime32 = require("react/jsx-runtime");
1445
1600
  function Empty({ className, ...props }) {
1446
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1601
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1447
1602
  "div",
1448
1603
  {
1449
1604
  "data-slot": "empty",
@@ -1456,7 +1611,7 @@ function Empty({ className, ...props }) {
1456
1611
  );
1457
1612
  }
1458
1613
  function EmptyHeader({ className, ...props }) {
1459
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1614
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1460
1615
  "div",
1461
1616
  {
1462
1617
  "data-slot": "empty-header",
@@ -1483,7 +1638,7 @@ var emptyMediaVariants = (0, import_class_variance_authority2.cva)(
1483
1638
  }
1484
1639
  );
1485
1640
  function EmptyTitle({ className, ...props }) {
1486
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1641
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1487
1642
  "div",
1488
1643
  {
1489
1644
  "data-slot": "empty-title",
@@ -1494,16 +1649,16 @@ function EmptyTitle({ className, ...props }) {
1494
1649
  }
1495
1650
 
1496
1651
  // src/components/ReportEmpty.tsx
1497
- var import_jsx_runtime31 = require("react/jsx-runtime");
1652
+ var import_jsx_runtime33 = require("react/jsx-runtime");
1498
1653
  function ReportEmpty({ message }) {
1499
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(EmptyHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1654
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(EmptyHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1500
1655
  }
1501
1656
 
1502
1657
  // src/components/ReportSummary.tsx
1503
- var import_jsx_runtime32 = require("react/jsx-runtime");
1658
+ var import_jsx_runtime34 = require("react/jsx-runtime");
1504
1659
  function ReportSummary({ className }) {
1505
1660
  const report = useReport();
1506
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1661
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
1507
1662
  ReportSummaryView,
1508
1663
  {
1509
1664
  summary: report.summary,
@@ -1529,7 +1684,7 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1529
1684
  if (summary.pending > 0) {
1530
1685
  cards.push({ status: "pending", label: "Pending", value: summary.pending });
1531
1686
  }
1532
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1687
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
1533
1688
  "dl",
1534
1689
  {
1535
1690
  className: cn(
@@ -1542,17 +1697,17 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1542
1697
  "aria-label": ariaLabel,
1543
1698
  children: cards.map((c) => {
1544
1699
  const tone = TONE[c.status];
1545
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
1700
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
1546
1701
  "div",
1547
1702
  {
1548
1703
  "data-status": c.status,
1549
1704
  className: "rounded-lg border bg-card px-4 py-3",
1550
1705
  children: [
1551
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("dt", { className: "flex items-center gap-1.5 font-mono text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: [
1552
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
1706
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("dt", { className: "flex items-center gap-1.5 font-mono text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: [
1707
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
1553
1708
  c.label
1554
1709
  ] }),
1555
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("dd", { className: cn("mt-2 text-3xl font-semibold leading-none tracking-tight tabular-nums", tone.num), children: c.value })
1710
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("dd", { className: cn("mt-2 text-3xl font-semibold leading-none tracking-tight tabular-nums", tone.num), children: c.value })
1556
1711
  ]
1557
1712
  },
1558
1713
  c.status
@@ -1564,58 +1719,7 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1564
1719
 
1565
1720
  // src/components/ReportMeta.tsx
1566
1721
  var import_duration4 = require("executable-stories-core/utils/duration");
1567
-
1568
- // src/lib/provenance.ts
1569
- function reportLastRunMs(report) {
1570
- if (report.finishedAtMs && report.finishedAtMs > 0) return report.finishedAtMs;
1571
- if (report.startedAtMs && report.startedAtMs > 0) return report.startedAtMs;
1572
- return void 0;
1573
- }
1574
- function formatRelativeAge(thenMs, nowMs) {
1575
- const deltaMs = Math.max(0, nowMs - thenMs);
1576
- const minutes = Math.floor(deltaMs / 6e4);
1577
- if (minutes < 1) return "just now";
1578
- if (minutes < 60) return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
1579
- const hours = Math.floor(minutes / 60);
1580
- if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
1581
- const days = Math.floor(hours / 24);
1582
- return `${days} day${days === 1 ? "" : "s"} ago`;
1583
- }
1584
- function isReportStale(lastRunMs, staleAfterDays, nowMs) {
1585
- if (lastRunMs === void 0 || staleAfterDays <= 0) return false;
1586
- return nowMs - lastRunMs >= staleAfterDays * 864e5;
1587
- }
1588
- var CI_DISPLAY_NAMES = {
1589
- github: "GitHub Actions",
1590
- gitlab: "GitLab CI",
1591
- circleci: "CircleCI",
1592
- jenkins: "Jenkins",
1593
- azure: "Azure DevOps",
1594
- buildkite: "Buildkite",
1595
- travis: "Travis CI",
1596
- ci: "CI"
1597
- };
1598
- function ciDisplayName(name) {
1599
- return CI_DISPLAY_NAMES[name] ?? name;
1600
- }
1601
- function commitUrl(ci, sha) {
1602
- const commitSha = ci?.commitSha ?? sha;
1603
- if (!ci?.url || !commitSha) return void 0;
1604
- const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1605
- if (github) return `${github[1]}/commit/${commitSha}`;
1606
- const gitlab = ci.url.match(/^(https?:\/\/.+)\/-\/pipelines\//);
1607
- if (gitlab) return `${gitlab[1]}/-/commit/${commitSha}`;
1608
- return void 0;
1609
- }
1610
- function prUrl(ci) {
1611
- if (!ci?.url || !ci.prNumber) return void 0;
1612
- const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1613
- if (github) return `${github[1]}/pull/${ci.prNumber}`;
1614
- return void 0;
1615
- }
1616
-
1617
- // src/components/ReportMeta.tsx
1618
- var import_jsx_runtime33 = require("react/jsx-runtime");
1722
+ var import_jsx_runtime35 = require("react/jsx-runtime");
1619
1723
  function ReportMeta() {
1620
1724
  const report = useReport();
1621
1725
  const items = [];
@@ -1638,13 +1742,13 @@ function ReportMeta() {
1638
1742
  const short = sha.slice(0, 8);
1639
1743
  items.push({
1640
1744
  label: "Commit",
1641
- value: url ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1745
+ value: url ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1642
1746
  });
1643
1747
  }
1644
1748
  if (report.ci) {
1645
1749
  items.push({
1646
1750
  label: "CI",
1647
- value: report.ci.url ? /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1751
+ value: report.ci.url ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1648
1752
  ciDisplayName(report.ci.name),
1649
1753
  report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
1650
1754
  ] }) : `${ciDisplayName(report.ci.name)}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
@@ -1655,7 +1759,7 @@ function ReportMeta() {
1655
1759
  const label = `#${report.ci.prNumber}`;
1656
1760
  items.push({
1657
1761
  label: "PR",
1658
- value: url ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1762
+ value: url ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1659
1763
  });
1660
1764
  }
1661
1765
  if (items.length === 0) return null;
@@ -1663,48 +1767,48 @@ function ReportMeta() {
1663
1767
  // Run metadata is useful but secondary to "what failed" — tuck it into a
1664
1768
  // collapsed disclosure so it doesn't push the result down. Native <details>
1665
1769
  // so it works with no JS (the SSR/no-hydration report still toggles).
1666
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("details", { "data-es-meta-card": true, className: "es-run-details group rounded-lg border border-border bg-card text-xs", children: [
1667
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("summary", { className: "flex cursor-pointer list-none select-none items-center gap-1.5 px-4 py-2 font-medium text-muted-foreground [&::-webkit-details-marker]:hidden", children: [
1668
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
1770
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("details", { "data-es-meta-card": true, className: "es-run-details group rounded-lg border border-border bg-card text-xs", children: [
1771
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("summary", { className: "flex cursor-pointer list-none select-none items-center gap-1.5 px-4 py-2 font-medium text-muted-foreground [&::-webkit-details-marker]:hidden", children: [
1772
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
1669
1773
  "Run details"
1670
1774
  ] }),
1671
- /* @__PURE__ */ (0, import_jsx_runtime33.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_runtime33.jsxs)("div", { className: "flex items-baseline gap-1.5", children: [
1672
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("dt", { className: "font-medium", children: item.label }),
1673
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("dd", { className: "font-mono text-foreground", children: item.value })
1775
+ /* @__PURE__ */ (0, import_jsx_runtime35.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_runtime35.jsxs)("div", { className: "flex items-baseline gap-1.5", children: [
1776
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("dt", { className: "font-medium", children: item.label }),
1777
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("dd", { className: "font-mono text-foreground", children: item.value })
1674
1778
  ] }, item.label)) })
1675
1779
  ] })
1676
1780
  );
1677
1781
  }
1678
1782
 
1679
1783
  // src/components/ReportSchemaError.tsx
1680
- var import_jsx_runtime34 = require("react/jsx-runtime");
1784
+ var import_jsx_runtime36 = require("react/jsx-runtime");
1681
1785
  function ReportSchemaError({ error }) {
1682
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1683
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("strong", { children: "Report could not be displayed." }) }),
1684
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { children: error.message }),
1685
- error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("p", { children: [
1786
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1787
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("strong", { children: "Report could not be displayed." }) }),
1788
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { children: error.message }),
1789
+ error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("p", { children: [
1686
1790
  "The report bundle is newer than this version of ",
1687
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("code", { children: "executable-stories-react" }),
1791
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("code", { children: "executable-stories-react" }),
1688
1792
  ". Upgrade the package, or regenerate the report with an older formatters CLI."
1689
1793
  ] }) : null,
1690
- error.issues && error.issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("details", { children: [
1691
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("summary", { children: [
1794
+ error.issues && error.issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("details", { children: [
1795
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("summary", { children: [
1692
1796
  error.issues.length,
1693
1797
  " validation issue",
1694
1798
  error.issues.length === 1 ? "" : "s"
1695
1799
  ] }),
1696
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1800
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1697
1801
  ] }) : null
1698
1802
  ] });
1699
1803
  }
1700
1804
 
1701
1805
  // src/components/ReportShell.tsx
1702
- var import_jsx_runtime35 = require("react/jsx-runtime");
1806
+ var import_jsx_runtime37 = require("react/jsx-runtime");
1703
1807
  function ReportTitleBlock({ title }) {
1704
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
1705
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("h1", { children: title ?? "Story Report" }),
1706
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ReportSummary, {}),
1707
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ReportMeta, {})
1808
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
1809
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("h1", { children: title ?? "Story Report" }),
1810
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ReportSummary, {}),
1811
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ReportMeta, {})
1708
1812
  ] });
1709
1813
  }
1710
1814
  function ReportErrorShell({
@@ -1713,28 +1817,28 @@ function ReportErrorShell({
1713
1817
  title,
1714
1818
  dataTheme
1715
1819
  }) {
1716
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ReportSchemaError, { error }) });
1820
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ReportSchemaError, { error }) });
1717
1821
  }
1718
1822
 
1719
1823
  // src/components/Report.tsx
1720
- var import_jsx_runtime36 = require("react/jsx-runtime");
1824
+ var import_jsx_runtime38 = require("react/jsx-runtime");
1721
1825
  function Report(props) {
1722
1826
  const { report, customRenderers, renderers, className, title, dataTheme } = props;
1723
1827
  const result = unwrapReport(report);
1724
1828
  if (!result.ok) {
1725
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportErrorShell, { error: result.error, className, title, dataTheme });
1829
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ReportErrorShell, { error: result.error, className, title, dataTheme });
1726
1830
  }
1727
1831
  const data = result.data;
1728
1832
  const hasContent = data.features.length > 0;
1729
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportRoot, { report: data, customRenderers, renderers, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: [
1730
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("header", { className: "es-report-header", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportTitleBlock, { title }) }),
1731
- hasContent ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportFeatureList, {}) : /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportEmpty, {})
1833
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ReportRoot, { report: data, customRenderers, renderers, children: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: [
1834
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("header", { className: "es-report-header", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ReportTitleBlock, { title }) }),
1835
+ hasContent ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ReportFeatureList, {}) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ReportEmpty, {})
1732
1836
  ] }) });
1733
1837
  }
1734
1838
 
1735
1839
  // src/components/doc/MermaidView.tsx
1736
- var import_react8 = require("react");
1737
- var import_jsx_runtime37 = require("react/jsx-runtime");
1840
+ var import_react10 = require("react");
1841
+ var import_jsx_runtime39 = require("react/jsx-runtime");
1738
1842
  function prefersDark(el) {
1739
1843
  const themed = el?.closest("[data-theme]");
1740
1844
  if (themed?.dataset.theme === "dark") return true;
@@ -1742,10 +1846,10 @@ function prefersDark(el) {
1742
1846
  return typeof window !== "undefined" && !!window.matchMedia?.("(prefers-color-scheme: dark)").matches;
1743
1847
  }
1744
1848
  function MermaidView({ entry, load }) {
1745
- const ref = (0, import_react8.useRef)(null);
1746
- const [svg, setSvg] = (0, import_react8.useState)(null);
1747
- const [error, setError] = (0, import_react8.useState)(null);
1748
- (0, import_react8.useEffect)(() => {
1849
+ const ref = (0, import_react10.useRef)(null);
1850
+ const [svg, setSvg] = (0, import_react10.useState)(null);
1851
+ const [error, setError] = (0, import_react10.useState)(null);
1852
+ (0, import_react10.useEffect)(() => {
1749
1853
  let cancelled = false;
1750
1854
  const draw = async () => {
1751
1855
  let mermaid;
@@ -1788,9 +1892,9 @@ function MermaidView({ entry, load }) {
1788
1892
  };
1789
1893
  }, [entry.code, load]);
1790
1894
  if (svg) {
1791
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("figure", { ref, className: "my-3", children: [
1792
- entry.title ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
1793
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
1895
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("figure", { ref, className: "my-3", children: [
1896
+ entry.title ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
1897
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1794
1898
  "div",
1795
1899
  {
1796
1900
  role: "img",
@@ -1801,8 +1905,8 @@ function MermaidView({ entry, load }) {
1801
1905
  )
1802
1906
  ] });
1803
1907
  }
1804
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
1805
- error ? /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
1908
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
1909
+ error ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
1806
1910
  "p",
1807
1911
  {
1808
1912
  role: "alert",
@@ -1814,34 +1918,34 @@ function MermaidView({ entry, load }) {
1814
1918
  ]
1815
1919
  }
1816
1920
  ) : null,
1817
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(MermaidSource, { entry, ref })
1921
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(MermaidSource, { entry, ref })
1818
1922
  ] });
1819
1923
  }
1820
1924
 
1821
1925
  // src/components/doc/MermaidDiagram.tsx
1822
- var import_jsx_runtime38 = require("react/jsx-runtime");
1926
+ var import_jsx_runtime40 = require("react/jsx-runtime");
1823
1927
  var mermaidLoader;
1824
1928
  function loadMermaid() {
1825
1929
  mermaidLoader ??= import("mermaid").then((m) => m.default);
1826
1930
  return mermaidLoader;
1827
1931
  }
1828
1932
  function MermaidDiagram({ entry, load = loadMermaid }) {
1829
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(MermaidView, { entry, load });
1933
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(MermaidView, { entry, load });
1830
1934
  }
1831
1935
 
1832
1936
  // src/components/ScenarioExplorer.tsx
1833
- var import_react9 = require("react");
1937
+ var import_react11 = require("react");
1834
1938
 
1835
1939
  // src/components/ui/command.tsx
1836
1940
  var React4 = require("react");
1837
1941
  var import_cmdk = require("cmdk");
1838
1942
  var import_lucide_react3 = require("lucide-react");
1839
- var import_jsx_runtime39 = require("react/jsx-runtime");
1943
+ var import_jsx_runtime41 = require("react/jsx-runtime");
1840
1944
  function Command({
1841
1945
  className,
1842
1946
  ...props
1843
1947
  }) {
1844
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1948
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
1845
1949
  import_cmdk.Command,
1846
1950
  {
1847
1951
  "data-slot": "command",
@@ -1857,14 +1961,14 @@ function CommandInput({
1857
1961
  className,
1858
1962
  ...props
1859
1963
  }) {
1860
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
1964
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
1861
1965
  "div",
1862
1966
  {
1863
1967
  "data-slot": "command-input-wrapper",
1864
1968
  className: "flex h-9 items-center gap-2 border-b px-3",
1865
1969
  children: [
1866
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_lucide_react3.SearchIcon, { className: "size-4 shrink-0 opacity-50" }),
1867
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1970
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react3.SearchIcon, { className: "size-4 shrink-0 opacity-50" }),
1971
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
1868
1972
  import_cmdk.Command.Input,
1869
1973
  {
1870
1974
  "data-slot": "command-input",
@@ -1883,7 +1987,7 @@ function CommandList({
1883
1987
  className,
1884
1988
  ...props
1885
1989
  }) {
1886
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1990
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
1887
1991
  import_cmdk.Command.List,
1888
1992
  {
1889
1993
  "data-slot": "command-list",
@@ -1898,7 +2002,7 @@ function CommandList({
1898
2002
  function CommandEmpty({
1899
2003
  ...props
1900
2004
  }) {
1901
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2005
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
1902
2006
  import_cmdk.Command.Empty,
1903
2007
  {
1904
2008
  "data-slot": "command-empty",
@@ -1911,7 +2015,7 @@ function CommandGroup({
1911
2015
  className,
1912
2016
  ...props
1913
2017
  }) {
1914
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2018
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
1915
2019
  import_cmdk.Command.Group,
1916
2020
  {
1917
2021
  "data-slot": "command-group",
@@ -1927,7 +2031,7 @@ function CommandItem({
1927
2031
  className,
1928
2032
  ...props
1929
2033
  }) {
1930
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2034
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
1931
2035
  import_cmdk.Command.Item,
1932
2036
  {
1933
2037
  "data-slot": "command-item",
@@ -1941,7 +2045,7 @@ function CommandItem({
1941
2045
  }
1942
2046
 
1943
2047
  // src/components/ScenarioExplorer.tsx
1944
- var import_jsx_runtime40 = require("react/jsx-runtime");
2048
+ var import_jsx_runtime42 = require("react/jsx-runtime");
1945
2049
  var STATUS_FILTERS = [
1946
2050
  { value: "all", label: "All" },
1947
2051
  { value: "passed", label: "Passed" },
@@ -1950,24 +2054,24 @@ var STATUS_FILTERS = [
1950
2054
  { value: "pending", label: "Pending" }
1951
2055
  ];
1952
2056
  function ScenarioExplorer({ scenarios, summary }) {
1953
- const [status, setStatus] = (0, import_react9.useState)("all");
2057
+ const [status, setStatus] = (0, import_react11.useState)("all");
1954
2058
  const visible = status === "all" ? scenarios : scenarios.filter((s) => s.status === status);
1955
- return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "font-sans text-foreground", children: [
1956
- summary ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("p", { className: "mb-3 text-sm text-muted-foreground", children: [
2059
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "font-sans text-foreground", children: [
2060
+ summary ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("p", { className: "mb-3 text-sm text-muted-foreground", children: [
1957
2061
  summary.total,
1958
2062
  " scenarios \xB7 ",
1959
- /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "text-pass", children: [
2063
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "text-pass", children: [
1960
2064
  summary.passed,
1961
2065
  " passed"
1962
2066
  ] }),
1963
2067
  " \xB7",
1964
2068
  " ",
1965
- /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "text-fail", children: [
2069
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "text-fail", children: [
1966
2070
  summary.failed,
1967
2071
  " failed"
1968
2072
  ] })
1969
2073
  ] }) : null,
1970
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "mb-2 flex flex-wrap gap-1.5", role: "group", "aria-label": "Filter by status", children: STATUS_FILTERS.map((f) => /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2074
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "mb-2 flex flex-wrap gap-1.5", role: "group", "aria-label": "Filter by status", children: STATUS_FILTERS.map((f) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
1971
2075
  "button",
1972
2076
  {
1973
2077
  type: "button",
@@ -1982,26 +2086,26 @@ function ScenarioExplorer({ scenarios, summary }) {
1982
2086
  },
1983
2087
  f.value
1984
2088
  )) }),
1985
- /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
2089
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
1986
2090
  Command,
1987
2091
  {
1988
2092
  className: "rounded-lg border border-border bg-card",
1989
2093
  filter: (value, search) => value.toLowerCase().includes(search.toLowerCase()) ? 1 : 0,
1990
2094
  children: [
1991
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(CommandInput, { placeholder: "Search scenarios\u2026" }),
1992
- /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(CommandList, { className: "max-h-none", children: [
1993
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(CommandEmpty, { children: "No scenarios match your filters." }),
1994
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(CommandGroup, { children: visible.map((s) => /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2095
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(CommandInput, { placeholder: "Search scenarios\u2026" }),
2096
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(CommandList, { className: "max-h-none", children: [
2097
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(CommandEmpty, { children: "No scenarios match your filters." }),
2098
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(CommandGroup, { children: visible.map((s) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
1995
2099
  CommandItem,
1996
2100
  {
1997
2101
  value: `${s.title} ${s.feature} ${s.tags.join(" ")}`,
1998
2102
  asChild: true,
1999
2103
  className: "cursor-pointer",
2000
- children: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("a", { href: s.href, className: "flex items-center gap-3", children: [
2001
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Badge, { variant: s.status, children: s.status }),
2002
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "min-w-0 flex-1 truncate font-medium", children: s.title }),
2003
- s.tags.slice(0, 3).map((t) => /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Badge, { variant: "tag", className: "hidden sm:inline-flex", children: t }, t)),
2004
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("code", { className: "hidden font-mono text-xs text-muted-foreground md:inline", children: s.feature })
2104
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("a", { href: s.href, className: "flex items-center gap-3", children: [
2105
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Badge, { variant: s.status, children: s.status }),
2106
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "min-w-0 flex-1 truncate font-medium", children: s.title }),
2107
+ s.tags.slice(0, 3).map((t) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Badge, { variant: "tag", className: "hidden sm:inline-flex", children: t }, t)),
2108
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("code", { className: "hidden font-mono text-xs text-muted-foreground md:inline", children: s.feature })
2005
2109
  ] })
2006
2110
  },
2007
2111
  s.id
@@ -2148,7 +2252,7 @@ var story_report_v1_default = {
2148
2252
  title: {
2149
2253
  type: "string",
2150
2254
  minLength: 1,
2151
- description: "Display title. Derived from describe block when present, otherwise the file basename."
2255
+ description: "Display title. Comes from story.feature(...) when declared, otherwise the describe block or file basename."
2152
2256
  },
2153
2257
  sourceFile: {
2154
2258
  type: "string",
@@ -2164,15 +2268,25 @@ var story_report_v1_default = {
2164
2268
  $ref: "#/$defs/Scenario"
2165
2269
  },
2166
2270
  description: "Scenarios in this feature, in declaration order."
2271
+ },
2272
+ kind: {
2273
+ type: "string",
2274
+ enum: ["feature", "ability", "business-need"],
2275
+ description: "How the feature was declared. Absent when the title was derived rather than declared."
2276
+ },
2277
+ narrative: {
2278
+ type: "string",
2279
+ description: "Markdown explaining why the feature exists and who it serves."
2280
+ },
2281
+ glossary: {
2282
+ type: "array",
2283
+ items: {
2284
+ $ref: "#/$defs/GlossaryTerm"
2285
+ },
2286
+ description: "Terms this feature defines, linked wherever they appear in scenario text."
2167
2287
  }
2168
2288
  },
2169
- required: [
2170
- "id",
2171
- "title",
2172
- "sourceFile",
2173
- "summary",
2174
- "scenarios"
2175
- ],
2289
+ required: ["id", "title", "sourceFile", "summary", "scenarios"],
2176
2290
  additionalProperties: false
2177
2291
  },
2178
2292
  Scenario: {
@@ -2302,6 +2416,11 @@ var story_report_v1_default = {
2302
2416
  type: "number",
2303
2417
  minimum: 0
2304
2418
  },
2419
+ assertions: {
2420
+ type: "integer",
2421
+ minimum: 0,
2422
+ description: "Assertions observed or declared for this step. Omit when the adapter cannot observe assertions; zero means observed none."
2423
+ },
2305
2424
  errorMessage: {
2306
2425
  type: "string"
2307
2426
  },
@@ -2329,33 +2448,15 @@ var story_report_v1_default = {
2329
2448
  },
2330
2449
  StepKeyword: {
2331
2450
  type: "string",
2332
- enum: [
2333
- "Given",
2334
- "When",
2335
- "Then",
2336
- "And",
2337
- "But"
2338
- ]
2451
+ enum: ["Given", "When", "Then", "And", "But"]
2339
2452
  },
2340
2453
  StepMode: {
2341
2454
  type: "string",
2342
- enum: [
2343
- "normal",
2344
- "skip",
2345
- "only",
2346
- "todo",
2347
- "fails",
2348
- "concurrent"
2349
- ]
2455
+ enum: ["normal", "skip", "only", "todo", "fails", "concurrent"]
2350
2456
  },
2351
2457
  TestStatus: {
2352
2458
  type: "string",
2353
- enum: [
2354
- "passed",
2355
- "failed",
2356
- "skipped",
2357
- "pending"
2358
- ]
2459
+ enum: ["passed", "failed", "skipped", "pending"]
2359
2460
  },
2360
2461
  Ticket: {
2361
2462
  type: "object",
@@ -2368,9 +2469,7 @@ var story_report_v1_default = {
2368
2469
  type: "string"
2369
2470
  }
2370
2471
  },
2371
- required: [
2372
- "id"
2373
- ],
2472
+ required: ["id"],
2374
2473
  additionalProperties: false
2375
2474
  },
2376
2475
  Attachment: {
@@ -2388,18 +2487,10 @@ var story_report_v1_default = {
2388
2487
  },
2389
2488
  contentEncoding: {
2390
2489
  type: "string",
2391
- enum: [
2392
- "BASE64",
2393
- "IDENTITY"
2394
- ]
2490
+ enum: ["BASE64", "IDENTITY"]
2395
2491
  }
2396
2492
  },
2397
- required: [
2398
- "name",
2399
- "mediaType",
2400
- "body",
2401
- "contentEncoding"
2402
- ],
2493
+ required: ["name", "mediaType", "body", "contentEncoding"],
2403
2494
  additionalProperties: false
2404
2495
  },
2405
2496
  CIInfo: {
@@ -2424,9 +2515,7 @@ var story_report_v1_default = {
2424
2515
  type: "string"
2425
2516
  }
2426
2517
  },
2427
- required: [
2428
- "name"
2429
- ],
2518
+ required: ["name"],
2430
2519
  additionalProperties: false
2431
2520
  },
2432
2521
  CoverageSummary: {
@@ -2457,10 +2546,7 @@ var story_report_v1_default = {
2457
2546
  },
2458
2547
  DocPhase: {
2459
2548
  type: "string",
2460
- enum: [
2461
- "static",
2462
- "runtime"
2463
- ]
2549
+ enum: ["static", "runtime"]
2464
2550
  },
2465
2551
  DocEntry: {
2466
2552
  oneOf: [
@@ -2524,11 +2610,7 @@ var story_report_v1_default = {
2524
2610
  }
2525
2611
  }
2526
2612
  },
2527
- required: [
2528
- "kind",
2529
- "text",
2530
- "phase"
2531
- ],
2613
+ required: ["kind", "text", "phase"],
2532
2614
  additionalProperties: false
2533
2615
  },
2534
2616
  DocTag: {
@@ -2553,11 +2635,7 @@ var story_report_v1_default = {
2553
2635
  }
2554
2636
  }
2555
2637
  },
2556
- required: [
2557
- "kind",
2558
- "names",
2559
- "phase"
2560
- ],
2638
+ required: ["kind", "names", "phase"],
2561
2639
  additionalProperties: false
2562
2640
  },
2563
2641
  DocKv: {
@@ -2580,12 +2658,7 @@ var story_report_v1_default = {
2580
2658
  }
2581
2659
  }
2582
2660
  },
2583
- required: [
2584
- "kind",
2585
- "label",
2586
- "value",
2587
- "phase"
2588
- ],
2661
+ required: ["kind", "label", "value", "phase"],
2589
2662
  additionalProperties: false
2590
2663
  },
2591
2664
  DocCode: {
@@ -2613,12 +2686,7 @@ var story_report_v1_default = {
2613
2686
  }
2614
2687
  }
2615
2688
  },
2616
- required: [
2617
- "kind",
2618
- "label",
2619
- "content",
2620
- "phase"
2621
- ],
2689
+ required: ["kind", "label", "content", "phase"],
2622
2690
  additionalProperties: false
2623
2691
  },
2624
2692
  DocTable: {
@@ -2655,13 +2723,7 @@ var story_report_v1_default = {
2655
2723
  }
2656
2724
  }
2657
2725
  },
2658
- required: [
2659
- "kind",
2660
- "label",
2661
- "columns",
2662
- "rows",
2663
- "phase"
2664
- ],
2726
+ required: ["kind", "label", "columns", "rows", "phase"],
2665
2727
  additionalProperties: false
2666
2728
  },
2667
2729
  DocLink: {
@@ -2686,12 +2748,7 @@ var story_report_v1_default = {
2686
2748
  }
2687
2749
  }
2688
2750
  },
2689
- required: [
2690
- "kind",
2691
- "label",
2692
- "url",
2693
- "phase"
2694
- ],
2751
+ required: ["kind", "label", "url", "phase"],
2695
2752
  additionalProperties: false
2696
2753
  },
2697
2754
  DocSection: {
@@ -2716,12 +2773,7 @@ var story_report_v1_default = {
2716
2773
  }
2717
2774
  }
2718
2775
  },
2719
- required: [
2720
- "kind",
2721
- "title",
2722
- "markdown",
2723
- "phase"
2724
- ],
2776
+ required: ["kind", "title", "markdown", "phase"],
2725
2777
  additionalProperties: false
2726
2778
  },
2727
2779
  DocMermaid: {
@@ -2746,11 +2798,7 @@ var story_report_v1_default = {
2746
2798
  }
2747
2799
  }
2748
2800
  },
2749
- required: [
2750
- "kind",
2751
- "code",
2752
- "phase"
2753
- ],
2801
+ required: ["kind", "code", "phase"],
2754
2802
  additionalProperties: false
2755
2803
  },
2756
2804
  DocScreenshot: {
@@ -2775,11 +2823,7 @@ var story_report_v1_default = {
2775
2823
  }
2776
2824
  }
2777
2825
  },
2778
- required: [
2779
- "kind",
2780
- "path",
2781
- "phase"
2782
- ],
2826
+ required: ["kind", "path", "phase"],
2783
2827
  additionalProperties: false
2784
2828
  },
2785
2829
  DocVideo: {
@@ -2807,11 +2851,7 @@ var story_report_v1_default = {
2807
2851
  }
2808
2852
  }
2809
2853
  },
2810
- required: [
2811
- "kind",
2812
- "path",
2813
- "phase"
2814
- ],
2854
+ required: ["kind", "path", "phase"],
2815
2855
  additionalProperties: false
2816
2856
  },
2817
2857
  DocHtml: {
@@ -2852,26 +2892,17 @@ var story_report_v1_default = {
2852
2892
  }
2853
2893
  }
2854
2894
  },
2855
- required: [
2856
- "kind",
2857
- "phase"
2858
- ],
2895
+ required: ["kind", "phase"],
2859
2896
  additionalProperties: false,
2860
2897
  oneOf: [
2861
2898
  {
2862
- required: [
2863
- "path"
2864
- ]
2899
+ required: ["path"]
2865
2900
  },
2866
2901
  {
2867
- required: [
2868
- "url"
2869
- ]
2902
+ required: ["url"]
2870
2903
  },
2871
2904
  {
2872
- required: [
2873
- "content"
2874
- ]
2905
+ required: ["content"]
2875
2906
  }
2876
2907
  ]
2877
2908
  },
@@ -2895,11 +2926,7 @@ var story_report_v1_default = {
2895
2926
  }
2896
2927
  }
2897
2928
  },
2898
- required: [
2899
- "kind",
2900
- "value",
2901
- "phase"
2902
- ],
2929
+ required: ["kind", "value", "phase"],
2903
2930
  additionalProperties: false
2904
2931
  },
2905
2932
  DocCustom: {
@@ -2923,21 +2950,12 @@ var story_report_v1_default = {
2923
2950
  }
2924
2951
  }
2925
2952
  },
2926
- required: [
2927
- "kind",
2928
- "type",
2929
- "data",
2930
- "phase"
2931
- ],
2953
+ required: ["kind", "type", "data", "phase"],
2932
2954
  additionalProperties: false
2933
2955
  },
2934
2956
  OtelSpan: {
2935
2957
  type: "object",
2936
- required: [
2937
- "spanId",
2938
- "name",
2939
- "status"
2940
- ],
2958
+ required: ["spanId", "name", "status"],
2941
2959
  properties: {
2942
2960
  spanId: {
2943
2961
  type: "string"
@@ -2961,11 +2979,7 @@ var story_report_v1_default = {
2961
2979
  type: "number"
2962
2980
  },
2963
2981
  status: {
2964
- enum: [
2965
- "ok",
2966
- "error",
2967
- "unset"
2968
- ]
2982
+ enum: ["ok", "error", "unset"]
2969
2983
  },
2970
2984
  statusMessage: {
2971
2985
  type: "string"
@@ -2975,6 +2989,22 @@ var story_report_v1_default = {
2975
2989
  }
2976
2990
  },
2977
2991
  additionalProperties: false
2992
+ },
2993
+ GlossaryTerm: {
2994
+ type: "object",
2995
+ description: "One glossary entry: a term the scenarios use and what it means.",
2996
+ properties: {
2997
+ term: {
2998
+ type: "string",
2999
+ minLength: 1
3000
+ },
3001
+ definition: {
3002
+ type: "string",
3003
+ minLength: 1
3004
+ }
3005
+ },
3006
+ required: ["term", "definition"],
3007
+ additionalProperties: false
2978
3008
  }
2979
3009
  }
2980
3010
  };
@@ -3077,6 +3107,7 @@ function parseStoryReport(input) {
3077
3107
  parseStoryReport,
3078
3108
  prUrl,
3079
3109
  reportLastRunMs,
3110
+ scenarioLastRunMs,
3080
3111
  storyReportSchema,
3081
3112
  useBuiltinRenderers,
3082
3113
  useCustomRenderers,