executable-stories-react 0.11.0 → 0.13.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/ssr.cjs CHANGED
@@ -23,7 +23,7 @@ __export(ssr_entry_exports, {
23
23
  renderReportToHtml: () => renderReportToHtml
24
24
  });
25
25
  module.exports = __toCommonJS(ssr_entry_exports);
26
- var import_react8 = require("react");
26
+ var import_react10 = require("react");
27
27
  var import_server = require("react-dom/server");
28
28
 
29
29
  // src/result.ts
@@ -314,23 +314,45 @@ function useReport() {
314
314
  // src/components/ReportScenario.tsx
315
315
  var import_duration3 = require("executable-stories-core/utils/duration");
316
316
 
317
+ // src/lib/step-params.tsx
318
+ var import_jsx_runtime3 = require("react/jsx-runtime");
319
+ var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.-])\d+(?:\.\d+)?(?![\w.-])/g;
320
+ function highlightStepParams(text) {
321
+ const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
322
+ if (matches.length === 0) return text;
323
+ const nodes = [];
324
+ let lastIndex = 0;
325
+ matches.forEach((match, i) => {
326
+ const start = match.index;
327
+ const end = start + match[0].length;
328
+ if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
329
+ nodes.push(
330
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "font-medium text-step-param italic", children: match[0] }, i)
331
+ );
332
+ lastIndex = end;
333
+ });
334
+ if (lastIndex < text.length) nodes.push(text.slice(lastIndex));
335
+ return nodes;
336
+ }
337
+
317
338
  // src/components/ReportSteps.tsx
339
+ var import_assertive_steps = require("executable-stories-core/utils/assertive-steps");
318
340
  var import_duration = require("executable-stories-core/utils/duration");
319
341
 
320
342
  // src/components/doc/DocNote.tsx
321
- var import_jsx_runtime3 = require("react/jsx-runtime");
343
+ var import_jsx_runtime4 = require("react/jsx-runtime");
322
344
  function DocNote({ entry }) {
323
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "my-2 text-sm text-muted-foreground", children: entry.text });
345
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "my-2 text-sm text-muted-foreground", children: entry.text });
324
346
  }
325
347
 
326
348
  // src/components/doc/DocTag.tsx
327
- var import_jsx_runtime4 = require("react/jsx-runtime");
349
+ var import_jsx_runtime5 = require("react/jsx-runtime");
328
350
  function DocTag({ entry }) {
329
- 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)) });
351
+ 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)) });
330
352
  }
331
353
 
332
354
  // src/components/doc/DocKv.tsx
333
- var import_jsx_runtime5 = require("react/jsx-runtime");
355
+ var import_jsx_runtime6 = require("react/jsx-runtime");
334
356
  function formatValue(value) {
335
357
  if (value === null) return "null";
336
358
  if (typeof value === "string") return value;
@@ -342,9 +364,9 @@ function formatValue(value) {
342
364
  }
343
365
  }
344
366
  function DocKv({ entry }) {
345
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("dl", { className: "my-2 flex flex-wrap items-baseline gap-x-2 text-sm", children: [
346
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dt", { className: "font-semibold text-muted-foreground", children: entry.label }),
347
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dd", { className: "font-mono text-foreground", children: formatValue(entry.value) })
367
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("dl", { className: "my-2 flex flex-wrap items-baseline gap-x-2 text-sm", children: [
368
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { className: "font-semibold text-muted-foreground", children: entry.label }),
369
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dd", { className: "font-mono text-foreground", children: formatValue(entry.value) })
348
370
  ] });
349
371
  }
350
372
 
@@ -362,37 +384,37 @@ function useBuiltinRenderers() {
362
384
  }
363
385
 
364
386
  // src/components/doc/DocCode.tsx
365
- var import_jsx_runtime6 = require("react/jsx-runtime");
387
+ var import_jsx_runtime7 = require("react/jsx-runtime");
366
388
  function CodeFigure({ label, children }) {
367
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("figure", { className: "my-3", children: [
368
- label ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: label }) : null,
369
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3", children })
389
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("figure", { className: "my-3", children: [
390
+ label ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: label }) : null,
391
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3", children })
370
392
  ] });
371
393
  }
372
394
  function DocCode({ entry }) {
373
395
  const renderers = useBuiltinRenderers();
374
396
  if (renderers.code) {
375
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: renderers.code(entry) });
397
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: renderers.code(entry) });
376
398
  }
377
- 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 }) });
399
+ 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 }) });
378
400
  }
379
401
 
380
402
  // src/components/doc/DocTable.tsx
381
- var import_jsx_runtime7 = require("react/jsx-runtime");
403
+ var import_jsx_runtime8 = require("react/jsx-runtime");
382
404
  function DocTable({ entry }) {
383
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("figure", { className: "my-3", children: [
384
- entry.label ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
385
- /* @__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: [
386
- /* @__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)) }) }),
387
- /* @__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)) })
405
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("figure", { className: "my-3", children: [
406
+ entry.label ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
407
+ /* @__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: [
408
+ /* @__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)) }) }),
409
+ /* @__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)) })
388
410
  ] }) })
389
411
  ] });
390
412
  }
391
413
 
392
414
  // src/components/doc/DocLink.tsx
393
- var import_jsx_runtime8 = require("react/jsx-runtime");
415
+ var import_jsx_runtime9 = require("react/jsx-runtime");
394
416
  function DocLink({ entry }) {
395
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
417
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
396
418
  "a",
397
419
  {
398
420
  className: "text-sm font-medium text-primary underline underline-offset-2 hover:no-underline",
@@ -426,16 +448,16 @@ function neutralizeUrl(value) {
426
448
  }
427
449
 
428
450
  // src/components/doc/DocSection.tsx
429
- var import_jsx_runtime9 = require("react/jsx-runtime");
451
+ var import_jsx_runtime10 = require("react/jsx-runtime");
430
452
  function DocSection({ entry }) {
431
453
  const renderers = useBuiltinRenderers();
432
454
  if (renderers.section) {
433
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_jsx_runtime9.Fragment, { children: renderers.section(entry) });
455
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_jsx_runtime10.Fragment, { children: renderers.section(entry) });
434
456
  }
435
457
  const html = safeMarkdownHtml(entry.markdown);
436
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("section", { className: "my-3 text-sm", "aria-label": entry.title, children: [
437
- entry.title ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h4", { className: "mb-1 font-semibold text-foreground", children: entry.title }) : null,
438
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
458
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: "my-3 text-sm", "aria-label": entry.title, children: [
459
+ entry.title ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h4", { className: "mb-1 font-semibold text-foreground", children: entry.title }) : null,
460
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
439
461
  "div",
440
462
  {
441
463
  className: "es-doc-prose prose prose-sm max-w-none",
@@ -446,11 +468,11 @@ function DocSection({ entry }) {
446
468
  }
447
469
 
448
470
  // src/components/doc/DocMermaid.tsx
449
- var import_jsx_runtime10 = require("react/jsx-runtime");
471
+ var import_jsx_runtime11 = require("react/jsx-runtime");
450
472
  function MermaidSource({ entry, ref }) {
451
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("figure", { ref, className: "my-3", "aria-label": entry.title ?? "Diagram", children: [
452
- entry.title ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
453
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
473
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("figure", { ref, className: "my-3", "aria-label": entry.title ?? "Diagram", children: [
474
+ entry.title ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
475
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
454
476
  "pre",
455
477
  {
456
478
  "data-mermaid": true,
@@ -464,24 +486,24 @@ function MermaidSource({ entry, ref }) {
464
486
  function DocMermaid({ entry }) {
465
487
  const renderers = useBuiltinRenderers();
466
488
  if (renderers.mermaid) {
467
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_jsx_runtime10.Fragment, { children: renderers.mermaid(entry) });
489
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_jsx_runtime11.Fragment, { children: renderers.mermaid(entry) });
468
490
  }
469
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MermaidSource, { entry });
491
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MermaidSource, { entry });
470
492
  }
471
493
 
472
494
  // src/components/doc/DocScreenshot.tsx
473
- var import_jsx_runtime11 = require("react/jsx-runtime");
495
+ var import_jsx_runtime12 = require("react/jsx-runtime");
474
496
  function DocScreenshot({ entry }) {
475
497
  const src = (0, import_url.isLocalFsPath)(entry.path) ? void 0 : (0, import_url.safeImageUrl)(entry.path);
476
498
  if (!src) {
477
- 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: [
478
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "font-medium", children: "Screenshot unavailable" }),
479
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
480
- entry.alt ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("figcaption", { className: "mt-1.5 text-xs", children: entry.alt }) : null
499
+ 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: [
500
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "font-medium", children: "Screenshot unavailable" }),
501
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
502
+ entry.alt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("figcaption", { className: "mt-1.5 text-xs", children: entry.alt }) : null
481
503
  ] });
482
504
  }
483
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("figure", { className: "my-3", children: [
484
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
505
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("figure", { className: "my-3", children: [
506
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
485
507
  "img",
486
508
  {
487
509
  src,
@@ -490,24 +512,24 @@ function DocScreenshot({ entry }) {
490
512
  className: "max-w-full rounded-md border border-border"
491
513
  }
492
514
  ),
493
- entry.alt ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: entry.alt }) : null
515
+ entry.alt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: entry.alt }) : null
494
516
  ] });
495
517
  }
496
518
 
497
519
  // src/components/doc/DocVideo.tsx
498
- var import_jsx_runtime12 = require("react/jsx-runtime");
520
+ var import_jsx_runtime13 = require("react/jsx-runtime");
499
521
  function DocVideo({ entry }) {
500
522
  if ((0, import_url.isLocalFsPath)(entry.path)) {
501
- 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: [
502
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "font-medium", children: "Video unavailable" }),
503
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
504
- entry.caption ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("figcaption", { className: "mt-1.5 text-xs", children: entry.caption }) : null
523
+ 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: [
524
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "font-medium", children: "Video unavailable" }),
525
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
526
+ entry.caption ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("figcaption", { className: "mt-1.5 text-xs", children: entry.caption }) : null
505
527
  ] });
506
528
  }
507
529
  const src = (0, import_url.safeUrl)(entry.path);
508
530
  const poster = (0, import_url.safeUrl)(entry.poster);
509
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("figure", { className: "my-3", children: [
510
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
531
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("figure", { className: "my-3", children: [
532
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
511
533
  "video",
512
534
  {
513
535
  controls: true,
@@ -517,21 +539,21 @@ function DocVideo({ entry }) {
517
539
  className: "max-w-full rounded-md border border-border"
518
540
  }
519
541
  ),
520
- entry.caption ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: entry.caption }) : null
542
+ entry.caption ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: entry.caption }) : null
521
543
  ] });
522
544
  }
523
545
 
524
546
  // src/components/doc/DocHtml.tsx
525
- var import_jsx_runtime13 = require("react/jsx-runtime");
547
+ var import_jsx_runtime14 = require("react/jsx-runtime");
526
548
  function DocHtml({ entry }) {
527
549
  const height = typeof entry.height === "number" ? `${entry.height}px` : entry.height ?? "400px";
528
550
  const title = entry.title ?? "Embedded HTML";
529
551
  const safeHref = (0, import_url.safeUrl)(entry.url ?? entry.path);
530
552
  const frameProps = entry.content !== void 0 ? { srcDoc: entry.content } : { src: safeHref ?? "" };
531
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("figure", { className: "my-3 overflow-hidden rounded-md border border-border", children: [
532
- /* @__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: [
533
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: entry.title ?? "HTML" }),
534
- safeHref ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
553
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("figure", { className: "my-3 overflow-hidden rounded-md border border-border", children: [
554
+ /* @__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: [
555
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { children: entry.title ?? "HTML" }),
556
+ safeHref ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
535
557
  "a",
536
558
  {
537
559
  href: safeHref,
@@ -544,7 +566,7 @@ function DocHtml({ entry }) {
544
566
  }
545
567
  ) : null
546
568
  ] }),
547
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
569
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
548
570
  "iframe",
549
571
  {
550
572
  ...frameProps,
@@ -559,7 +581,7 @@ function DocHtml({ entry }) {
559
581
  }
560
582
 
561
583
  // src/components/doc/DocState.tsx
562
- var import_jsx_runtime14 = require("react/jsx-runtime");
584
+ var import_jsx_runtime15 = require("react/jsx-runtime");
563
585
  function DocState({ entry }) {
564
586
  let json;
565
587
  try {
@@ -567,20 +589,20 @@ function DocState({ entry }) {
567
589
  } catch {
568
590
  json = String(entry.value);
569
591
  }
570
- 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 }) });
592
+ 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 }) });
571
593
  }
572
594
 
573
595
  // src/components/doc/DocCustom.tsx
574
- var import_jsx_runtime15 = require("react/jsx-runtime");
596
+ var import_jsx_runtime16 = require("react/jsx-runtime");
575
597
  function DocCustom({ entry }) {
576
598
  const renderers = useCustomRenderers();
577
599
  const renderer = renderers[entry.type];
578
600
  if (renderer) {
579
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_jsx_runtime15.Fragment, { children: renderer(entry) });
601
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_jsx_runtime16.Fragment, { children: renderer(entry) });
580
602
  }
581
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "my-2", "data-type": entry.type, children: [
582
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: entry.type }),
583
- /* @__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) })
603
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "my-2", "data-type": entry.type, children: [
604
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: entry.type }),
605
+ /* @__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) })
584
606
  ] });
585
607
  }
586
608
  function safeStringify2(value) {
@@ -592,64 +614,43 @@ function safeStringify2(value) {
592
614
  }
593
615
 
594
616
  // src/components/doc/DocEntry.tsx
595
- var import_jsx_runtime16 = require("react/jsx-runtime");
617
+ var import_jsx_runtime17 = require("react/jsx-runtime");
596
618
  function DocEntry({ entry }) {
597
619
  switch (entry.kind) {
598
620
  case "note":
599
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocNote, { entry });
621
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocNote, { entry });
600
622
  case "tag":
601
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocTag, { entry });
623
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocTag, { entry });
602
624
  case "kv":
603
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocKv, { entry });
625
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocKv, { entry });
604
626
  case "code":
605
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocCode, { entry });
627
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocCode, { entry });
606
628
  case "table":
607
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocTable, { entry });
629
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocTable, { entry });
608
630
  case "link":
609
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocLink, { entry });
631
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocLink, { entry });
610
632
  case "section":
611
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocSection, { entry });
633
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocSection, { entry });
612
634
  case "mermaid":
613
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocMermaid, { entry });
635
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocMermaid, { entry });
614
636
  case "screenshot":
615
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocScreenshot, { entry });
637
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocScreenshot, { entry });
616
638
  case "video":
617
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocVideo, { entry });
639
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocVideo, { entry });
618
640
  case "html":
619
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocHtml, { entry });
641
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocHtml, { entry });
620
642
  case "state":
621
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocState, { entry });
643
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocState, { entry });
622
644
  case "custom":
623
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocCustom, { entry });
645
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocCustom, { entry });
624
646
  }
625
647
  }
626
648
 
627
649
  // src/components/ReportDocEntries.tsx
628
- var import_jsx_runtime17 = require("react/jsx-runtime");
650
+ var import_jsx_runtime18 = require("react/jsx-runtime");
629
651
  function ReportDocEntries({ entries }) {
630
652
  if (entries.length === 0) return null;
631
- 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)) });
632
- }
633
-
634
- // src/lib/step-params.tsx
635
- var import_jsx_runtime18 = require("react/jsx-runtime");
636
- var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.-])\d+(?:\.\d+)?(?![\w.-])/g;
637
- function highlightStepParams(text) {
638
- const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
639
- if (matches.length === 0) return text;
640
- const nodes = [];
641
- let lastIndex = 0;
642
- matches.forEach((match, i) => {
643
- const start = match.index;
644
- const end = start + match[0].length;
645
- if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
646
- nodes.push(
647
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "font-medium text-step-param italic", children: match[0] }, i)
648
- );
649
- lastIndex = end;
650
- });
651
- if (lastIndex < text.length) nodes.push(text.slice(lastIndex));
652
- return nodes;
653
+ 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)) });
653
654
  }
654
655
 
655
656
  // src/components/ReportSteps.tsx
@@ -668,9 +669,22 @@ var STEP_GLYPH_COLOR = {
668
669
  };
669
670
  function ReportSteps({ scenario }) {
670
671
  if (scenario.steps.length === 0) return null;
671
- 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)) });
672
+ const unasserted = new Set(
673
+ (0, import_assertive_steps.assertionState)(scenario.steps) === "unasserted" ? (0, import_assertive_steps.assertiveSteps)(scenario.steps).map((step) => step.id) : []
674
+ );
675
+ 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)(
676
+ ReportStepItem,
677
+ {
678
+ step,
679
+ unasserted: unasserted.has(step.id)
680
+ },
681
+ step.id
682
+ )) });
672
683
  }
673
- function ReportStepItem({ step }) {
684
+ function ReportStepItem({
685
+ step,
686
+ unasserted = false
687
+ }) {
674
688
  const kw = step.keyword.toLowerCase();
675
689
  const continuation = kw === "and" || kw === "but";
676
690
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
@@ -678,10 +692,23 @@ function ReportStepItem({ step }) {
678
692
  {
679
693
  id: step.id,
680
694
  "data-status": step.status,
681
- className: cn("py-1.5 text-[0.8125rem] leading-normal", continuation && "pl-5"),
695
+ className: cn(
696
+ "py-1.5 text-[0.8125rem] leading-normal",
697
+ continuation && "pl-5"
698
+ ),
682
699
  children: [
683
700
  /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-baseline gap-2", children: [
684
- /* @__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" }),
701
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
702
+ "span",
703
+ {
704
+ "aria-hidden": true,
705
+ className: cn(
706
+ "w-4 shrink-0 text-center text-xs",
707
+ STEP_GLYPH_COLOR[step.status]
708
+ ),
709
+ children: STEP_GLYPH[step.status] ?? "\u25CB"
710
+ }
711
+ ),
685
712
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
686
713
  "span",
687
714
  {
@@ -693,6 +720,7 @@ function ReportStepItem({ step }) {
693
720
  }
694
721
  ),
695
722
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "flex-1 text-foreground", children: highlightStepParams(step.text) }),
723
+ 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,
696
724
  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
697
725
  ] }),
698
726
  step.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
@@ -1088,28 +1116,102 @@ function ScenarioRunHistory({ scenarioId }) {
1088
1116
  ] });
1089
1117
  }
1090
1118
 
1091
- // src/interactive/collapse-context.ts
1119
+ // src/components/ScenarioStaleness.tsx
1120
+ var import_react7 = require("react");
1121
+
1122
+ // src/lib/provenance.ts
1123
+ function formatRelativeAge(thenMs, nowMs) {
1124
+ const deltaMs = Math.max(0, nowMs - thenMs);
1125
+ const minutes = Math.floor(deltaMs / 6e4);
1126
+ if (minutes < 1) return "just now";
1127
+ if (minutes < 60) return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
1128
+ const hours = Math.floor(minutes / 60);
1129
+ if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
1130
+ const days = Math.floor(hours / 24);
1131
+ return `${days} day${days === 1 ? "" : "s"} ago`;
1132
+ }
1133
+ function isReportStale(lastRunMs, staleAfterDays, nowMs) {
1134
+ if (lastRunMs === void 0 || staleAfterDays <= 0) return false;
1135
+ return nowMs - lastRunMs >= staleAfterDays * 864e5;
1136
+ }
1137
+ var CI_DISPLAY_NAMES = {
1138
+ github: "GitHub Actions",
1139
+ gitlab: "GitLab CI",
1140
+ circleci: "CircleCI",
1141
+ jenkins: "Jenkins",
1142
+ azure: "Azure DevOps",
1143
+ buildkite: "Buildkite",
1144
+ travis: "Travis CI",
1145
+ ci: "CI"
1146
+ };
1147
+ function ciDisplayName(name) {
1148
+ return CI_DISPLAY_NAMES[name] ?? name;
1149
+ }
1150
+ function commitUrl(ci, sha) {
1151
+ const commitSha = ci?.commitSha ?? sha;
1152
+ if (!ci?.url || !commitSha) return void 0;
1153
+ const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1154
+ if (github) return `${github[1]}/commit/${commitSha}`;
1155
+ const gitlab = ci.url.match(/^(https?:\/\/.+)\/-\/pipelines\//);
1156
+ if (gitlab) return `${gitlab[1]}/-/commit/${commitSha}`;
1157
+ return void 0;
1158
+ }
1159
+ function prUrl(ci) {
1160
+ if (!ci?.url || !ci.prNumber) return void 0;
1161
+ const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1162
+ if (github) return `${github[1]}/pull/${ci.prNumber}`;
1163
+ return void 0;
1164
+ }
1165
+
1166
+ // src/interactive/scenario-freshness-context.tsx
1092
1167
  var import_react6 = require("react");
1093
- var CollapseContext = (0, import_react6.createContext)(null);
1168
+ var ScenarioFreshnessContext = (0, import_react6.createContext)(null);
1169
+ var ScenarioFreshnessProvider = ScenarioFreshnessContext.Provider;
1170
+ function useScenarioFreshness() {
1171
+ return (0, import_react6.useContext)(ScenarioFreshnessContext);
1172
+ }
1173
+
1174
+ // src/components/ScenarioStaleness.tsx
1175
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1176
+ function ScenarioStaleness({ scenario }) {
1177
+ const freshness = useScenarioFreshness();
1178
+ const fixedNow = freshness?.nowMs;
1179
+ const [now, setNow] = (0, import_react7.useState)(fixedNow);
1180
+ (0, import_react7.useEffect)(() => {
1181
+ if (fixedNow === void 0) setNow(Date.now());
1182
+ }, [fixedNow]);
1183
+ if (!freshness || now === void 0) return null;
1184
+ const lastRunAtMs = scenario.lastRunAtMs;
1185
+ if (!lastRunAtMs || lastRunAtMs <= 0) return null;
1186
+ if (!isReportStale(lastRunAtMs, freshness.staleAfterDays, now)) return null;
1187
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(Badge, { variant: "outline", "aria-label": "Scenario freshness", children: [
1188
+ "Last ran ",
1189
+ formatRelativeAge(lastRunAtMs, now)
1190
+ ] });
1191
+ }
1192
+
1193
+ // src/interactive/collapse-context.ts
1194
+ var import_react8 = require("react");
1195
+ var CollapseContext = (0, import_react8.createContext)(null);
1094
1196
  var CollapseProvider = CollapseContext.Provider;
1095
1197
  function useCollapse() {
1096
- return (0, import_react6.useContext)(CollapseContext);
1198
+ return (0, import_react8.useContext)(CollapseContext);
1097
1199
  }
1098
1200
 
1099
1201
  // src/interactive/scenario-actions.tsx
1100
- var import_react7 = require("react");
1202
+ var import_react9 = require("react");
1101
1203
  var import_scenario_markdown = require("executable-stories-core/utils/scenario-markdown");
1102
- var ScenarioActionsContext = (0, import_react7.createContext)(null);
1204
+ var ScenarioActionsContext = (0, import_react9.createContext)(null);
1103
1205
  var ScenarioActionsProvider = ScenarioActionsContext.Provider;
1104
1206
  function useScenarioActions() {
1105
- return (0, import_react7.useContext)(ScenarioActionsContext);
1207
+ return (0, import_react9.useContext)(ScenarioActionsContext);
1106
1208
  }
1107
1209
 
1108
1210
  // src/components/ui/card.tsx
1109
1211
  var React2 = require("react");
1110
- var import_jsx_runtime24 = require("react/jsx-runtime");
1212
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1111
1213
  function Card({ className, ...props }) {
1112
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1214
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1113
1215
  "div",
1114
1216
  {
1115
1217
  "data-slot": "card",
@@ -1122,7 +1224,7 @@ function Card({ className, ...props }) {
1122
1224
  );
1123
1225
  }
1124
1226
  function CardHeader({ className, ...props }) {
1125
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1227
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1126
1228
  "div",
1127
1229
  {
1128
1230
  "data-slot": "card-header",
@@ -1135,7 +1237,7 @@ function CardHeader({ className, ...props }) {
1135
1237
  );
1136
1238
  }
1137
1239
  function CardContent({ className, ...props }) {
1138
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1240
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1139
1241
  "div",
1140
1242
  {
1141
1243
  "data-slot": "card-content",
@@ -1149,13 +1251,13 @@ function CardContent({ className, ...props }) {
1149
1251
  var React3 = require("react");
1150
1252
  var import_menu = require("@base-ui/react/menu");
1151
1253
  var import_lucide_react = require("lucide-react");
1152
- var import_jsx_runtime25 = require("react/jsx-runtime");
1254
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1153
1255
  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";
1154
1256
  function DropdownMenu(props) {
1155
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_menu.Menu.Root, { "data-slot": "dropdown-menu", ...props });
1257
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_menu.Menu.Root, { "data-slot": "dropdown-menu", ...props });
1156
1258
  }
1157
1259
  function DropdownMenuTrigger(props) {
1158
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_menu.Menu.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
1260
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_menu.Menu.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
1159
1261
  }
1160
1262
  function DropdownMenuContent({
1161
1263
  className,
@@ -1163,14 +1265,14 @@ function DropdownMenuContent({
1163
1265
  sideOffset = 4,
1164
1266
  ...props
1165
1267
  }) {
1166
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_menu.Menu.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1268
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_menu.Menu.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1167
1269
  import_menu.Menu.Positioner,
1168
1270
  {
1169
1271
  "data-slot": "dropdown-menu-positioner",
1170
1272
  className: "z-50",
1171
1273
  align,
1172
1274
  sideOffset,
1173
- children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1275
+ children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1174
1276
  import_menu.Menu.Popup,
1175
1277
  {
1176
1278
  "data-slot": "dropdown-menu-content",
@@ -1188,7 +1290,7 @@ function DropdownMenuItem({
1188
1290
  className,
1189
1291
  ...props
1190
1292
  }) {
1191
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1293
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1192
1294
  import_menu.Menu.Item,
1193
1295
  {
1194
1296
  "data-slot": "dropdown-menu-item",
@@ -1200,7 +1302,7 @@ function DropdownMenuItem({
1200
1302
 
1201
1303
  // src/components/ReportScenario.tsx
1202
1304
  var import_lucide_react2 = require("lucide-react");
1203
- var import_jsx_runtime26 = require("react/jsx-runtime");
1305
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1204
1306
  var STATUS_META = {
1205
1307
  passed: { label: "Passed", glyph: "\u2713", glyphColor: "text-pass", badgeVariant: "passed", showPill: false },
1206
1308
  failed: { label: "Failed", glyph: "\u2717", glyphColor: "text-fail", badgeVariant: "failed", showPill: true },
@@ -1216,7 +1318,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
1216
1318
  const collapsible = collapse !== null && !hideTitle;
1217
1319
  const collapsed = collapsible ? collapse.isCollapsed(scenario.id) : false;
1218
1320
  const actions = useScenarioActions();
1219
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
1321
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
1220
1322
  Card,
1221
1323
  {
1222
1324
  id: scenario.id,
@@ -1224,10 +1326,10 @@ function ReportScenario({ scenario, hideTitle = false }) {
1224
1326
  ...hideTitle ? { "aria-label": `${scenario.title}, ${label}` } : { "aria-labelledby": titleId },
1225
1327
  className: "gap-0 py-0",
1226
1328
  children: [
1227
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(CardHeader, { className: "px-4 py-3", children: [
1228
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
1229
- hideTitle ? null : /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1230
- collapsible ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1329
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(CardHeader, { className: "px-4 py-3", children: [
1330
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
1331
+ hideTitle ? null : /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1332
+ collapsible ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1231
1333
  "button",
1232
1334
  {
1233
1335
  type: "button",
@@ -1236,49 +1338,50 @@ function ReportScenario({ scenario, hideTitle = false }) {
1236
1338
  "aria-controls": bodyId,
1237
1339
  "aria-label": `Toggle ${scenario.title}`,
1238
1340
  className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
1239
- children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1341
+ children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1240
1342
  }
1241
1343
  ) : null,
1242
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { "aria-hidden": true, className: cn("text-sm leading-none", meta.glyphColor), children: meta.glyph }),
1243
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { children: scenario.title })
1344
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { "aria-hidden": true, className: cn("text-sm leading-none", meta.glyphColor), children: meta.glyph }),
1345
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { children: scenario.title })
1244
1346
  ] }),
1245
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex shrink-0 items-center gap-2", children: [
1246
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ScenarioRunHistory, { scenarioId: scenario.id }),
1247
- 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,
1248
- 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}` }),
1249
- actions ? /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenu, { children: [
1250
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1347
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex shrink-0 items-center gap-2", children: [
1348
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ScenarioRunHistory, { scenarioId: scenario.id }),
1349
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ScenarioStaleness, { scenario }),
1350
+ 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,
1351
+ 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}` }),
1352
+ actions ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenu, { children: [
1353
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1251
1354
  DropdownMenuTrigger,
1252
1355
  {
1253
1356
  "aria-label": `Actions for ${scenario.title}`,
1254
1357
  className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
1255
- children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react2.MoreHorizontalIcon, { "aria-hidden": true })
1358
+ children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.MoreHorizontalIcon, { "aria-hidden": true })
1256
1359
  }
1257
1360
  ),
1258
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenuContent, { align: "end", children: [
1259
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
1260
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react2.LinkIcon, {}),
1361
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuContent, { align: "end", children: [
1362
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
1363
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.LinkIcon, {}),
1261
1364
  " Copy link"
1262
1365
  ] }),
1263
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
1264
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react2.FileTextIcon, {}),
1366
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
1367
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.FileTextIcon, {}),
1265
1368
  " Copy as Markdown"
1266
1369
  ] }),
1267
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
1268
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react2.SparklesIcon, {}),
1370
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
1371
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.SparklesIcon, {}),
1269
1372
  " Explain with AI"
1270
1373
  ] })
1271
1374
  ] })
1272
1375
  ] }) : null
1273
1376
  ] })
1274
1377
  ] }),
1275
- 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: [
1276
- 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}`)),
1277
- (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}`))
1378
+ 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: [
1379
+ 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}`)),
1380
+ (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}`))
1278
1381
  ] }) : null
1279
1382
  ] }),
1280
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
1281
- scenario.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1383
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
1384
+ scenario.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1282
1385
  "pre",
1283
1386
  {
1284
1387
  role: "alert",
@@ -1286,11 +1389,11 @@ function ReportScenario({ scenario, hideTitle = false }) {
1286
1389
  children: scenario.errorMessage
1287
1390
  }
1288
1391
  ) : null,
1289
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ReportStoryboard, { scenario }),
1290
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ReportSteps, { scenario }),
1291
- 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,
1292
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ReportAttachments, { attachments: scenario.attachments }),
1293
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ReportTrace, { spans: scenario.otelSpans })
1392
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ReportStoryboard, { scenario }),
1393
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ReportSteps, { scenario }),
1394
+ 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,
1395
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ReportAttachments, { attachments: scenario.attachments }),
1396
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ReportTrace, { spans: scenario.otelSpans })
1294
1397
  ] })
1295
1398
  ]
1296
1399
  }
@@ -1298,28 +1401,28 @@ function ReportScenario({ scenario, hideTitle = false }) {
1298
1401
  }
1299
1402
 
1300
1403
  // src/components/ReportScenarioList.tsx
1301
- var import_jsx_runtime27 = require("react/jsx-runtime");
1404
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1302
1405
  function ReportScenarioList({ feature }) {
1303
- 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)) });
1406
+ 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)) });
1304
1407
  }
1305
1408
 
1306
1409
  // src/components/ReportFeatureIntro.tsx
1307
- var import_jsx_runtime28 = require("react/jsx-runtime");
1410
+ var import_jsx_runtime29 = require("react/jsx-runtime");
1308
1411
  function ReportFeatureIntro({ feature }) {
1309
1412
  const hasNarrative = Boolean(feature.narrative?.trim());
1310
1413
  const hasGlossary = Boolean(feature.glossary?.length);
1311
1414
  if (!hasNarrative && !hasGlossary) return null;
1312
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex flex-col gap-3 border-l-2 border-border pl-4 text-sm", children: [
1313
- hasNarrative ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1415
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-col gap-3 border-l-2 border-border pl-4 text-sm", children: [
1416
+ hasNarrative ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1314
1417
  "div",
1315
1418
  {
1316
1419
  className: "es-doc-prose prose prose-sm max-w-none text-muted-foreground",
1317
1420
  dangerouslySetInnerHTML: { __html: safeMarkdownHtml(dedent(feature.narrative)) }
1318
1421
  }
1319
1422
  ) : null,
1320
- hasGlossary ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("dl", { className: "grid gap-1.5", "aria-label": "Glossary", children: feature.glossary.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex flex-wrap gap-x-2 text-xs", children: [
1321
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("dt", { className: "font-medium text-foreground", children: entry.term }),
1322
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("dd", { className: "flex-1 text-muted-foreground", children: entry.definition })
1423
+ 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: [
1424
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("dt", { className: "font-medium text-foreground", children: entry.term }),
1425
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("dd", { className: "flex-1 text-muted-foreground", children: entry.definition })
1323
1426
  ] }, entry.term)) }) : null
1324
1427
  ] });
1325
1428
  }
@@ -1331,7 +1434,7 @@ function featureKindLabel(kind) {
1331
1434
  }
1332
1435
 
1333
1436
  // src/components/ReportFeature.tsx
1334
- var import_jsx_runtime29 = require("react/jsx-runtime");
1437
+ var import_jsx_runtime30 = require("react/jsx-runtime");
1335
1438
  function ReportFeature({ feature }) {
1336
1439
  const titleId = `${feature.id}-title`;
1337
1440
  const bodyId = `${feature.id}-body`;
@@ -1343,7 +1446,7 @@ function ReportFeature({ feature }) {
1343
1446
  return (
1344
1447
  // Suite = a section heading + a divider, NOT another bordered card. Drops a
1345
1448
  // layer of nesting so the scenario cards below read as the primary unit.
1346
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
1449
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
1347
1450
  "section",
1348
1451
  {
1349
1452
  id: feature.id,
@@ -1351,9 +1454,9 @@ function ReportFeature({ feature }) {
1351
1454
  "aria-labelledby": titleId,
1352
1455
  className: "flex flex-col gap-3",
1353
1456
  children: [
1354
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1355
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "min-w-0", children: [
1356
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("h2", { id: titleId, className: "text-base font-semibold tracking-tight text-foreground", children: collapse ? /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
1457
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1458
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "min-w-0", children: [
1459
+ /* @__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)(
1357
1460
  "button",
1358
1461
  {
1359
1462
  type: "button",
@@ -1362,34 +1465,34 @@ function ReportFeature({ feature }) {
1362
1465
  "aria-controls": bodyId,
1363
1466
  className: "flex w-full cursor-pointer items-center gap-2 text-left hover:text-foreground/80",
1364
1467
  children: [
1365
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { "aria-hidden": true, className: cn("shrink-0 text-xs text-muted-foreground transition-transform", !collapsed && "rotate-90"), children: "\u25B8" }),
1366
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "truncate", children: feature.title })
1468
+ /* @__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" }),
1469
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "truncate", children: feature.title })
1367
1470
  ]
1368
1471
  }
1369
1472
  ) : feature.title }),
1370
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("p", { className: cn("mt-0.5 flex items-center gap-2 text-xs text-muted-foreground", collapse && "pl-5"), children: [
1371
- kindLabel ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "shrink-0 rounded-sm bg-muted px-1.5 py-0.5 font-medium text-foreground", children: kindLabel }) : null,
1372
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "truncate font-mono", children: feature.sourceFile })
1473
+ /* @__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: [
1474
+ 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,
1475
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "truncate font-mono", children: feature.sourceFile })
1373
1476
  ] })
1374
1477
  ] }),
1375
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
1478
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
1376
1479
  "p",
1377
1480
  {
1378
1481
  className: "flex shrink-0 items-center gap-1.5 text-xs text-muted-foreground",
1379
1482
  "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
1380
1483
  children: [
1381
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("span", { children: [
1484
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("span", { children: [
1382
1485
  s.passed,
1383
1486
  " passed"
1384
1487
  ] }),
1385
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1386
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
1488
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1489
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
1387
1490
  s.failed,
1388
1491
  " failed"
1389
1492
  ] }),
1390
- skipped > 0 ? /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_jsx_runtime29.Fragment, { children: [
1391
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1392
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("span", { children: [
1493
+ skipped > 0 ? /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
1494
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1495
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("span", { children: [
1393
1496
  skipped,
1394
1497
  " skipped"
1395
1498
  ] })
@@ -1398,9 +1501,9 @@ function ReportFeature({ feature }) {
1398
1501
  }
1399
1502
  )
1400
1503
  ] }),
1401
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-3", children: [
1402
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(ReportFeatureIntro, { feature }),
1403
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(ReportScenarioList, { feature })
1504
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-3", children: [
1505
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ReportFeatureIntro, { feature }),
1506
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ReportScenarioList, { feature })
1404
1507
  ] })
1405
1508
  ]
1406
1509
  }
@@ -1409,17 +1512,17 @@ function ReportFeature({ feature }) {
1409
1512
  }
1410
1513
 
1411
1514
  // src/components/ReportFeatureList.tsx
1412
- var import_jsx_runtime30 = require("react/jsx-runtime");
1515
+ var import_jsx_runtime31 = require("react/jsx-runtime");
1413
1516
  function ReportFeatureList() {
1414
1517
  const report = useReport();
1415
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "flex flex-col gap-8", children: report.features.map((feature) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ReportFeature, { feature }, feature.id)) });
1518
+ 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)) });
1416
1519
  }
1417
1520
 
1418
1521
  // src/components/ui/empty.tsx
1419
1522
  var import_class_variance_authority2 = require("class-variance-authority");
1420
- var import_jsx_runtime31 = require("react/jsx-runtime");
1523
+ var import_jsx_runtime32 = require("react/jsx-runtime");
1421
1524
  function Empty({ className, ...props }) {
1422
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1525
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1423
1526
  "div",
1424
1527
  {
1425
1528
  "data-slot": "empty",
@@ -1432,7 +1535,7 @@ function Empty({ className, ...props }) {
1432
1535
  );
1433
1536
  }
1434
1537
  function EmptyHeader({ className, ...props }) {
1435
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1538
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1436
1539
  "div",
1437
1540
  {
1438
1541
  "data-slot": "empty-header",
@@ -1459,7 +1562,7 @@ var emptyMediaVariants = (0, import_class_variance_authority2.cva)(
1459
1562
  }
1460
1563
  );
1461
1564
  function EmptyTitle({ className, ...props }) {
1462
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1565
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1463
1566
  "div",
1464
1567
  {
1465
1568
  "data-slot": "empty-title",
@@ -1470,16 +1573,16 @@ function EmptyTitle({ className, ...props }) {
1470
1573
  }
1471
1574
 
1472
1575
  // src/components/ReportEmpty.tsx
1473
- var import_jsx_runtime32 = require("react/jsx-runtime");
1576
+ var import_jsx_runtime33 = require("react/jsx-runtime");
1474
1577
  function ReportEmpty({ message }) {
1475
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(EmptyHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1578
+ 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." }) }) });
1476
1579
  }
1477
1580
 
1478
1581
  // src/components/ReportSummary.tsx
1479
- var import_jsx_runtime33 = require("react/jsx-runtime");
1582
+ var import_jsx_runtime34 = require("react/jsx-runtime");
1480
1583
  function ReportSummary({ className }) {
1481
1584
  const report = useReport();
1482
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
1585
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
1483
1586
  ReportSummaryView,
1484
1587
  {
1485
1588
  summary: report.summary,
@@ -1505,7 +1608,7 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1505
1608
  if (summary.pending > 0) {
1506
1609
  cards.push({ status: "pending", label: "Pending", value: summary.pending });
1507
1610
  }
1508
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
1611
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
1509
1612
  "dl",
1510
1613
  {
1511
1614
  className: cn(
@@ -1518,17 +1621,17 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1518
1621
  "aria-label": ariaLabel,
1519
1622
  children: cards.map((c) => {
1520
1623
  const tone = TONE[c.status];
1521
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
1624
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
1522
1625
  "div",
1523
1626
  {
1524
1627
  "data-status": c.status,
1525
1628
  className: "rounded-lg border bg-card px-4 py-3",
1526
1629
  children: [
1527
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("dt", { className: "flex items-center gap-1.5 font-mono text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: [
1528
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
1630
+ /* @__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: [
1631
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
1529
1632
  c.label
1530
1633
  ] }),
1531
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("dd", { className: cn("mt-2 text-3xl font-semibold leading-none tracking-tight tabular-nums", tone.num), children: c.value })
1634
+ /* @__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 })
1532
1635
  ]
1533
1636
  },
1534
1637
  c.status
@@ -1540,39 +1643,7 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1540
1643
 
1541
1644
  // src/components/ReportMeta.tsx
1542
1645
  var import_duration4 = require("executable-stories-core/utils/duration");
1543
-
1544
- // src/lib/provenance.ts
1545
- var CI_DISPLAY_NAMES = {
1546
- github: "GitHub Actions",
1547
- gitlab: "GitLab CI",
1548
- circleci: "CircleCI",
1549
- jenkins: "Jenkins",
1550
- azure: "Azure DevOps",
1551
- buildkite: "Buildkite",
1552
- travis: "Travis CI",
1553
- ci: "CI"
1554
- };
1555
- function ciDisplayName(name) {
1556
- return CI_DISPLAY_NAMES[name] ?? name;
1557
- }
1558
- function commitUrl(ci, sha) {
1559
- const commitSha = ci?.commitSha ?? sha;
1560
- if (!ci?.url || !commitSha) return void 0;
1561
- const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1562
- if (github) return `${github[1]}/commit/${commitSha}`;
1563
- const gitlab = ci.url.match(/^(https?:\/\/.+)\/-\/pipelines\//);
1564
- if (gitlab) return `${gitlab[1]}/-/commit/${commitSha}`;
1565
- return void 0;
1566
- }
1567
- function prUrl(ci) {
1568
- if (!ci?.url || !ci.prNumber) return void 0;
1569
- const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1570
- if (github) return `${github[1]}/pull/${ci.prNumber}`;
1571
- return void 0;
1572
- }
1573
-
1574
- // src/components/ReportMeta.tsx
1575
- var import_jsx_runtime34 = require("react/jsx-runtime");
1646
+ var import_jsx_runtime35 = require("react/jsx-runtime");
1576
1647
  function ReportMeta() {
1577
1648
  const report = useReport();
1578
1649
  const items = [];
@@ -1595,13 +1666,13 @@ function ReportMeta() {
1595
1666
  const short = sha.slice(0, 8);
1596
1667
  items.push({
1597
1668
  label: "Commit",
1598
- value: url ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1669
+ value: url ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1599
1670
  });
1600
1671
  }
1601
1672
  if (report.ci) {
1602
1673
  items.push({
1603
1674
  label: "CI",
1604
- value: report.ci.url ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1675
+ value: report.ci.url ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1605
1676
  ciDisplayName(report.ci.name),
1606
1677
  report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
1607
1678
  ] }) : `${ciDisplayName(report.ci.name)}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
@@ -1612,7 +1683,7 @@ function ReportMeta() {
1612
1683
  const label = `#${report.ci.prNumber}`;
1613
1684
  items.push({
1614
1685
  label: "PR",
1615
- value: url ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1686
+ value: url ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1616
1687
  });
1617
1688
  }
1618
1689
  if (items.length === 0) return null;
@@ -1620,48 +1691,48 @@ function ReportMeta() {
1620
1691
  // Run metadata is useful but secondary to "what failed" — tuck it into a
1621
1692
  // collapsed disclosure so it doesn't push the result down. Native <details>
1622
1693
  // so it works with no JS (the SSR/no-hydration report still toggles).
1623
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("details", { "data-es-meta-card": true, className: "es-run-details group rounded-lg border border-border bg-card text-xs", children: [
1624
- /* @__PURE__ */ (0, import_jsx_runtime34.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: [
1625
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
1694
+ /* @__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: [
1695
+ /* @__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: [
1696
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
1626
1697
  "Run details"
1627
1698
  ] }),
1628
- /* @__PURE__ */ (0, import_jsx_runtime34.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_runtime34.jsxs)("div", { className: "flex items-baseline gap-1.5", children: [
1629
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("dt", { className: "font-medium", children: item.label }),
1630
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("dd", { className: "font-mono text-foreground", children: item.value })
1699
+ /* @__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: [
1700
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("dt", { className: "font-medium", children: item.label }),
1701
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("dd", { className: "font-mono text-foreground", children: item.value })
1631
1702
  ] }, item.label)) })
1632
1703
  ] })
1633
1704
  );
1634
1705
  }
1635
1706
 
1636
1707
  // src/components/ReportSchemaError.tsx
1637
- var import_jsx_runtime35 = require("react/jsx-runtime");
1708
+ var import_jsx_runtime36 = require("react/jsx-runtime");
1638
1709
  function ReportSchemaError({ error }) {
1639
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1640
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("strong", { children: "Report could not be displayed." }) }),
1641
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { children: error.message }),
1642
- error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("p", { children: [
1710
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1711
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("strong", { children: "Report could not be displayed." }) }),
1712
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { children: error.message }),
1713
+ error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("p", { children: [
1643
1714
  "The report bundle is newer than this version of ",
1644
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("code", { children: "executable-stories-react" }),
1715
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("code", { children: "executable-stories-react" }),
1645
1716
  ". Upgrade the package, or regenerate the report with an older formatters CLI."
1646
1717
  ] }) : null,
1647
- error.issues && error.issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("details", { children: [
1648
- /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("summary", { children: [
1718
+ error.issues && error.issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("details", { children: [
1719
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("summary", { children: [
1649
1720
  error.issues.length,
1650
1721
  " validation issue",
1651
1722
  error.issues.length === 1 ? "" : "s"
1652
1723
  ] }),
1653
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1724
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1654
1725
  ] }) : null
1655
1726
  ] });
1656
1727
  }
1657
1728
 
1658
1729
  // src/components/ReportShell.tsx
1659
- var import_jsx_runtime36 = require("react/jsx-runtime");
1730
+ var import_jsx_runtime37 = require("react/jsx-runtime");
1660
1731
  function ReportTitleBlock({ title }) {
1661
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
1662
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("h1", { children: title ?? "Story Report" }),
1663
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportSummary, {}),
1664
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportMeta, {})
1732
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
1733
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("h1", { children: title ?? "Story Report" }),
1734
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ReportSummary, {}),
1735
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ReportMeta, {})
1665
1736
  ] });
1666
1737
  }
1667
1738
  function ReportErrorShell({
@@ -1670,22 +1741,22 @@ function ReportErrorShell({
1670
1741
  title,
1671
1742
  dataTheme
1672
1743
  }) {
1673
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportSchemaError, { error }) });
1744
+ 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 }) });
1674
1745
  }
1675
1746
 
1676
1747
  // src/components/Report.tsx
1677
- var import_jsx_runtime37 = require("react/jsx-runtime");
1748
+ var import_jsx_runtime38 = require("react/jsx-runtime");
1678
1749
  function Report(props) {
1679
1750
  const { report, customRenderers, renderers, className, title, dataTheme } = props;
1680
1751
  const result = unwrapReport(report);
1681
1752
  if (!result.ok) {
1682
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ReportErrorShell, { error: result.error, className, title, dataTheme });
1753
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ReportErrorShell, { error: result.error, className, title, dataTheme });
1683
1754
  }
1684
1755
  const data = result.data;
1685
1756
  const hasContent = data.features.length > 0;
1686
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ReportRoot, { report: data, customRenderers, renderers, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: [
1687
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("header", { className: "es-report-header", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ReportTitleBlock, { title }) }),
1688
- hasContent ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ReportFeatureList, {}) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ReportEmpty, {})
1757
+ 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: [
1758
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("header", { className: "es-report-header", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ReportTitleBlock, { title }) }),
1759
+ hasContent ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ReportFeatureList, {}) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ReportEmpty, {})
1689
1760
  ] }) });
1690
1761
  }
1691
1762
 
@@ -1746,13 +1817,13 @@ function renderReportToHtml(report, options = {}) {
1746
1817
  scenarioHistory
1747
1818
  } = options;
1748
1819
  const markup = (0, import_server.renderToStaticMarkup)(
1749
- (0, import_react8.createElement)(Report, { report, title })
1820
+ (0, import_react10.createElement)(Report, { report, title })
1750
1821
  );
1751
1822
  const cdn = cdnAssets(syntaxHighlighting, mermaid);
1752
1823
  const parsed = unwrapReport(report);
1753
1824
  const rawReport = parsed.ok ? parsed.data : void 0;
1754
1825
  const interactive = islandScript.length > 0 && rawReport !== void 0;
1755
- const dataScript = interactive ? `<script type="application/json" id="${DATA_ID}">${escapeJsonForScript(JSON.stringify(rawReport))}</script>` : "";
1826
+ const dataScript = rawReport !== void 0 ? `<script type="application/json" id="${DATA_ID}">${escapeJsonForScript(JSON.stringify(rawReport))}</script>` : "";
1756
1827
  const historyScript = interactive && scenarioHistory && Object.keys(scenarioHistory).length > 0 ? `<script type="application/json" id="${HISTORY_ID}">${escapeJsonForScript(JSON.stringify(scenarioHistory))}</script>` : "";
1757
1828
  const islandTag = interactive ? `<script>${islandScript}</script>` : "";
1758
1829
  const cdnBody = interactive ? "" : cdn.body;