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/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",
@@ -404,30 +426,38 @@ function DocLink({ entry }) {
404
426
  );
405
427
  }
406
428
 
407
- // src/components/doc/DocSection.tsx
429
+ // src/lib/markdown.ts
408
430
  var import_marked = require("marked");
409
431
 
410
432
  // src/lib/url.ts
411
433
  var import_url = require("executable-stories-core/utils/url");
412
434
 
413
- // src/components/doc/DocSection.tsx
414
- var import_jsx_runtime9 = require("react/jsx-runtime");
415
- function neutralizeUrl(value) {
416
- return (0, import_url.safeUrl)(value) ?? "#";
417
- }
435
+ // src/lib/markdown.ts
418
436
  function safeMarkdownHtml(markdown) {
419
437
  const raw = import_marked.marked.parse(markdown, { async: false });
420
438
  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)}'`);
421
439
  }
440
+ function dedent(text) {
441
+ const lines = text.replace(/\t/g, " ").split("\n");
442
+ const indents = lines.filter((line) => line.trim().length > 0).map((line) => line.length - line.trimStart().length);
443
+ const shortest = indents.length > 0 ? Math.min(...indents) : 0;
444
+ return lines.map((line) => line.slice(shortest)).join("\n").trim();
445
+ }
446
+ function neutralizeUrl(value) {
447
+ return (0, import_url.safeUrl)(value) ?? "#";
448
+ }
449
+
450
+ // src/components/doc/DocSection.tsx
451
+ var import_jsx_runtime10 = require("react/jsx-runtime");
422
452
  function DocSection({ entry }) {
423
453
  const renderers = useBuiltinRenderers();
424
454
  if (renderers.section) {
425
- 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) });
426
456
  }
427
457
  const html = safeMarkdownHtml(entry.markdown);
428
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("section", { className: "my-3 text-sm", "aria-label": entry.title, children: [
429
- entry.title ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h4", { className: "mb-1 font-semibold text-foreground", children: entry.title }) : null,
430
- /* @__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)(
431
461
  "div",
432
462
  {
433
463
  className: "es-doc-prose prose prose-sm max-w-none",
@@ -438,11 +468,11 @@ function DocSection({ entry }) {
438
468
  }
439
469
 
440
470
  // src/components/doc/DocMermaid.tsx
441
- var import_jsx_runtime10 = require("react/jsx-runtime");
471
+ var import_jsx_runtime11 = require("react/jsx-runtime");
442
472
  function MermaidSource({ entry, ref }) {
443
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("figure", { ref, className: "my-3", "aria-label": entry.title ?? "Diagram", children: [
444
- entry.title ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
445
- /* @__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)(
446
476
  "pre",
447
477
  {
448
478
  "data-mermaid": true,
@@ -456,24 +486,24 @@ function MermaidSource({ entry, ref }) {
456
486
  function DocMermaid({ entry }) {
457
487
  const renderers = useBuiltinRenderers();
458
488
  if (renderers.mermaid) {
459
- 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) });
460
490
  }
461
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MermaidSource, { entry });
491
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MermaidSource, { entry });
462
492
  }
463
493
 
464
494
  // src/components/doc/DocScreenshot.tsx
465
- var import_jsx_runtime11 = require("react/jsx-runtime");
495
+ var import_jsx_runtime12 = require("react/jsx-runtime");
466
496
  function DocScreenshot({ entry }) {
467
497
  const src = (0, import_url.isLocalFsPath)(entry.path) ? void 0 : (0, import_url.safeImageUrl)(entry.path);
468
498
  if (!src) {
469
- 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: [
470
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "font-medium", children: "Screenshot unavailable" }),
471
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
472
- 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
473
503
  ] });
474
504
  }
475
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("figure", { className: "my-3", children: [
476
- /* @__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)(
477
507
  "img",
478
508
  {
479
509
  src,
@@ -482,24 +512,24 @@ function DocScreenshot({ entry }) {
482
512
  className: "max-w-full rounded-md border border-border"
483
513
  }
484
514
  ),
485
- 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
486
516
  ] });
487
517
  }
488
518
 
489
519
  // src/components/doc/DocVideo.tsx
490
- var import_jsx_runtime12 = require("react/jsx-runtime");
520
+ var import_jsx_runtime13 = require("react/jsx-runtime");
491
521
  function DocVideo({ entry }) {
492
522
  if ((0, import_url.isLocalFsPath)(entry.path)) {
493
- 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: [
494
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "font-medium", children: "Video unavailable" }),
495
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
496
- 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
497
527
  ] });
498
528
  }
499
529
  const src = (0, import_url.safeUrl)(entry.path);
500
530
  const poster = (0, import_url.safeUrl)(entry.poster);
501
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("figure", { className: "my-3", children: [
502
- /* @__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)(
503
533
  "video",
504
534
  {
505
535
  controls: true,
@@ -509,21 +539,21 @@ function DocVideo({ entry }) {
509
539
  className: "max-w-full rounded-md border border-border"
510
540
  }
511
541
  ),
512
- 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
513
543
  ] });
514
544
  }
515
545
 
516
546
  // src/components/doc/DocHtml.tsx
517
- var import_jsx_runtime13 = require("react/jsx-runtime");
547
+ var import_jsx_runtime14 = require("react/jsx-runtime");
518
548
  function DocHtml({ entry }) {
519
549
  const height = typeof entry.height === "number" ? `${entry.height}px` : entry.height ?? "400px";
520
550
  const title = entry.title ?? "Embedded HTML";
521
551
  const safeHref = (0, import_url.safeUrl)(entry.url ?? entry.path);
522
552
  const frameProps = entry.content !== void 0 ? { srcDoc: entry.content } : { src: safeHref ?? "" };
523
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("figure", { className: "my-3 overflow-hidden rounded-md border border-border", children: [
524
- /* @__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: [
525
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: entry.title ?? "HTML" }),
526
- 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)(
527
557
  "a",
528
558
  {
529
559
  href: safeHref,
@@ -536,7 +566,7 @@ function DocHtml({ entry }) {
536
566
  }
537
567
  ) : null
538
568
  ] }),
539
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
569
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
540
570
  "iframe",
541
571
  {
542
572
  ...frameProps,
@@ -551,7 +581,7 @@ function DocHtml({ entry }) {
551
581
  }
552
582
 
553
583
  // src/components/doc/DocState.tsx
554
- var import_jsx_runtime14 = require("react/jsx-runtime");
584
+ var import_jsx_runtime15 = require("react/jsx-runtime");
555
585
  function DocState({ entry }) {
556
586
  let json;
557
587
  try {
@@ -559,20 +589,20 @@ function DocState({ entry }) {
559
589
  } catch {
560
590
  json = String(entry.value);
561
591
  }
562
- 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 }) });
563
593
  }
564
594
 
565
595
  // src/components/doc/DocCustom.tsx
566
- var import_jsx_runtime15 = require("react/jsx-runtime");
596
+ var import_jsx_runtime16 = require("react/jsx-runtime");
567
597
  function DocCustom({ entry }) {
568
598
  const renderers = useCustomRenderers();
569
599
  const renderer = renderers[entry.type];
570
600
  if (renderer) {
571
- 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) });
572
602
  }
573
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "my-2", "data-type": entry.type, children: [
574
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: entry.type }),
575
- /* @__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) })
576
606
  ] });
577
607
  }
578
608
  function safeStringify2(value) {
@@ -584,64 +614,43 @@ function safeStringify2(value) {
584
614
  }
585
615
 
586
616
  // src/components/doc/DocEntry.tsx
587
- var import_jsx_runtime16 = require("react/jsx-runtime");
617
+ var import_jsx_runtime17 = require("react/jsx-runtime");
588
618
  function DocEntry({ entry }) {
589
619
  switch (entry.kind) {
590
620
  case "note":
591
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocNote, { entry });
621
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocNote, { entry });
592
622
  case "tag":
593
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocTag, { entry });
623
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocTag, { entry });
594
624
  case "kv":
595
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocKv, { entry });
625
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocKv, { entry });
596
626
  case "code":
597
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocCode, { entry });
627
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocCode, { entry });
598
628
  case "table":
599
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocTable, { entry });
629
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocTable, { entry });
600
630
  case "link":
601
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocLink, { entry });
631
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocLink, { entry });
602
632
  case "section":
603
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocSection, { entry });
633
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocSection, { entry });
604
634
  case "mermaid":
605
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocMermaid, { entry });
635
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocMermaid, { entry });
606
636
  case "screenshot":
607
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocScreenshot, { entry });
637
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocScreenshot, { entry });
608
638
  case "video":
609
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocVideo, { entry });
639
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocVideo, { entry });
610
640
  case "html":
611
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocHtml, { entry });
641
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocHtml, { entry });
612
642
  case "state":
613
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocState, { entry });
643
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocState, { entry });
614
644
  case "custom":
615
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocCustom, { entry });
645
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocCustom, { entry });
616
646
  }
617
647
  }
618
648
 
619
649
  // src/components/ReportDocEntries.tsx
620
- var import_jsx_runtime17 = require("react/jsx-runtime");
650
+ var import_jsx_runtime18 = require("react/jsx-runtime");
621
651
  function ReportDocEntries({ entries }) {
622
652
  if (entries.length === 0) return null;
623
- 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)) });
624
- }
625
-
626
- // src/lib/step-params.tsx
627
- var import_jsx_runtime18 = require("react/jsx-runtime");
628
- var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.-])\d+(?:\.\d+)?(?![\w.-])/g;
629
- function highlightStepParams(text) {
630
- const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
631
- if (matches.length === 0) return text;
632
- const nodes = [];
633
- let lastIndex = 0;
634
- matches.forEach((match, i) => {
635
- const start = match.index;
636
- const end = start + match[0].length;
637
- if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
638
- nodes.push(
639
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "font-medium text-step-param italic", children: match[0] }, i)
640
- );
641
- lastIndex = end;
642
- });
643
- if (lastIndex < text.length) nodes.push(text.slice(lastIndex));
644
- 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)) });
645
654
  }
646
655
 
647
656
  // src/components/ReportSteps.tsx
@@ -660,9 +669,22 @@ var STEP_GLYPH_COLOR = {
660
669
  };
661
670
  function ReportSteps({ scenario }) {
662
671
  if (scenario.steps.length === 0) return null;
663
- 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
+ )) });
664
683
  }
665
- function ReportStepItem({ step }) {
684
+ function ReportStepItem({
685
+ step,
686
+ unasserted = false
687
+ }) {
666
688
  const kw = step.keyword.toLowerCase();
667
689
  const continuation = kw === "and" || kw === "but";
668
690
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
@@ -670,10 +692,23 @@ function ReportStepItem({ step }) {
670
692
  {
671
693
  id: step.id,
672
694
  "data-status": step.status,
673
- 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
+ ),
674
699
  children: [
675
700
  /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-baseline gap-2", children: [
676
- /* @__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
+ ),
677
712
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
678
713
  "span",
679
714
  {
@@ -685,6 +720,7 @@ function ReportStepItem({ step }) {
685
720
  }
686
721
  ),
687
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,
688
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
689
725
  ] }),
690
726
  step.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
@@ -1080,28 +1116,102 @@ function ScenarioRunHistory({ scenarioId }) {
1080
1116
  ] });
1081
1117
  }
1082
1118
 
1083
- // 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
1084
1167
  var import_react6 = require("react");
1085
- 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);
1086
1196
  var CollapseProvider = CollapseContext.Provider;
1087
1197
  function useCollapse() {
1088
- return (0, import_react6.useContext)(CollapseContext);
1198
+ return (0, import_react8.useContext)(CollapseContext);
1089
1199
  }
1090
1200
 
1091
1201
  // src/interactive/scenario-actions.tsx
1092
- var import_react7 = require("react");
1202
+ var import_react9 = require("react");
1093
1203
  var import_scenario_markdown = require("executable-stories-core/utils/scenario-markdown");
1094
- var ScenarioActionsContext = (0, import_react7.createContext)(null);
1204
+ var ScenarioActionsContext = (0, import_react9.createContext)(null);
1095
1205
  var ScenarioActionsProvider = ScenarioActionsContext.Provider;
1096
1206
  function useScenarioActions() {
1097
- return (0, import_react7.useContext)(ScenarioActionsContext);
1207
+ return (0, import_react9.useContext)(ScenarioActionsContext);
1098
1208
  }
1099
1209
 
1100
1210
  // src/components/ui/card.tsx
1101
1211
  var React2 = require("react");
1102
- var import_jsx_runtime24 = require("react/jsx-runtime");
1212
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1103
1213
  function Card({ className, ...props }) {
1104
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1214
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1105
1215
  "div",
1106
1216
  {
1107
1217
  "data-slot": "card",
@@ -1114,7 +1224,7 @@ function Card({ className, ...props }) {
1114
1224
  );
1115
1225
  }
1116
1226
  function CardHeader({ className, ...props }) {
1117
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1227
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1118
1228
  "div",
1119
1229
  {
1120
1230
  "data-slot": "card-header",
@@ -1127,7 +1237,7 @@ function CardHeader({ className, ...props }) {
1127
1237
  );
1128
1238
  }
1129
1239
  function CardContent({ className, ...props }) {
1130
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1240
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1131
1241
  "div",
1132
1242
  {
1133
1243
  "data-slot": "card-content",
@@ -1141,13 +1251,13 @@ function CardContent({ className, ...props }) {
1141
1251
  var React3 = require("react");
1142
1252
  var import_menu = require("@base-ui/react/menu");
1143
1253
  var import_lucide_react = require("lucide-react");
1144
- var import_jsx_runtime25 = require("react/jsx-runtime");
1254
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1145
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";
1146
1256
  function DropdownMenu(props) {
1147
- 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 });
1148
1258
  }
1149
1259
  function DropdownMenuTrigger(props) {
1150
- 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 });
1151
1261
  }
1152
1262
  function DropdownMenuContent({
1153
1263
  className,
@@ -1155,14 +1265,14 @@ function DropdownMenuContent({
1155
1265
  sideOffset = 4,
1156
1266
  ...props
1157
1267
  }) {
1158
- 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)(
1159
1269
  import_menu.Menu.Positioner,
1160
1270
  {
1161
1271
  "data-slot": "dropdown-menu-positioner",
1162
1272
  className: "z-50",
1163
1273
  align,
1164
1274
  sideOffset,
1165
- children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1275
+ children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1166
1276
  import_menu.Menu.Popup,
1167
1277
  {
1168
1278
  "data-slot": "dropdown-menu-content",
@@ -1180,7 +1290,7 @@ function DropdownMenuItem({
1180
1290
  className,
1181
1291
  ...props
1182
1292
  }) {
1183
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1293
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1184
1294
  import_menu.Menu.Item,
1185
1295
  {
1186
1296
  "data-slot": "dropdown-menu-item",
@@ -1192,7 +1302,7 @@ function DropdownMenuItem({
1192
1302
 
1193
1303
  // src/components/ReportScenario.tsx
1194
1304
  var import_lucide_react2 = require("lucide-react");
1195
- var import_jsx_runtime26 = require("react/jsx-runtime");
1305
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1196
1306
  var STATUS_META = {
1197
1307
  passed: { label: "Passed", glyph: "\u2713", glyphColor: "text-pass", badgeVariant: "passed", showPill: false },
1198
1308
  failed: { label: "Failed", glyph: "\u2717", glyphColor: "text-fail", badgeVariant: "failed", showPill: true },
@@ -1208,7 +1318,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
1208
1318
  const collapsible = collapse !== null && !hideTitle;
1209
1319
  const collapsed = collapsible ? collapse.isCollapsed(scenario.id) : false;
1210
1320
  const actions = useScenarioActions();
1211
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
1321
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
1212
1322
  Card,
1213
1323
  {
1214
1324
  id: scenario.id,
@@ -1216,10 +1326,10 @@ function ReportScenario({ scenario, hideTitle = false }) {
1216
1326
  ...hideTitle ? { "aria-label": `${scenario.title}, ${label}` } : { "aria-labelledby": titleId },
1217
1327
  className: "gap-0 py-0",
1218
1328
  children: [
1219
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(CardHeader, { className: "px-4 py-3", children: [
1220
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
1221
- hideTitle ? null : /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1222
- 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)(
1223
1333
  "button",
1224
1334
  {
1225
1335
  type: "button",
@@ -1228,49 +1338,50 @@ function ReportScenario({ scenario, hideTitle = false }) {
1228
1338
  "aria-controls": bodyId,
1229
1339
  "aria-label": `Toggle ${scenario.title}`,
1230
1340
  className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
1231
- 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" })
1232
1342
  }
1233
1343
  ) : null,
1234
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { "aria-hidden": true, className: cn("text-sm leading-none", meta.glyphColor), children: meta.glyph }),
1235
- /* @__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 })
1236
1346
  ] }),
1237
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex shrink-0 items-center gap-2", children: [
1238
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ScenarioRunHistory, { scenarioId: scenario.id }),
1239
- 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,
1240
- 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}` }),
1241
- actions ? /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenu, { children: [
1242
- /* @__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)(
1243
1354
  DropdownMenuTrigger,
1244
1355
  {
1245
1356
  "aria-label": `Actions for ${scenario.title}`,
1246
1357
  className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
1247
- 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 })
1248
1359
  }
1249
1360
  ),
1250
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenuContent, { align: "end", children: [
1251
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
1252
- /* @__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, {}),
1253
1364
  " Copy link"
1254
1365
  ] }),
1255
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
1256
- /* @__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, {}),
1257
1368
  " Copy as Markdown"
1258
1369
  ] }),
1259
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
1260
- /* @__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, {}),
1261
1372
  " Explain with AI"
1262
1373
  ] })
1263
1374
  ] })
1264
1375
  ] }) : null
1265
1376
  ] })
1266
1377
  ] }),
1267
- 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: [
1268
- 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}`)),
1269
- (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}`))
1270
1381
  ] }) : null
1271
1382
  ] }),
1272
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
1273
- 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)(
1274
1385
  "pre",
1275
1386
  {
1276
1387
  role: "alert",
@@ -1278,11 +1389,11 @@ function ReportScenario({ scenario, hideTitle = false }) {
1278
1389
  children: scenario.errorMessage
1279
1390
  }
1280
1391
  ) : null,
1281
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ReportStoryboard, { scenario }),
1282
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ReportSteps, { scenario }),
1283
- 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,
1284
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ReportAttachments, { attachments: scenario.attachments }),
1285
- /* @__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 })
1286
1397
  ] })
1287
1398
  ]
1288
1399
  }
@@ -1290,13 +1401,40 @@ function ReportScenario({ scenario, hideTitle = false }) {
1290
1401
  }
1291
1402
 
1292
1403
  // src/components/ReportScenarioList.tsx
1293
- var import_jsx_runtime27 = require("react/jsx-runtime");
1404
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1294
1405
  function ReportScenarioList({ feature }) {
1295
- 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)) });
1407
+ }
1408
+
1409
+ // src/components/ReportFeatureIntro.tsx
1410
+ var import_jsx_runtime29 = require("react/jsx-runtime");
1411
+ function ReportFeatureIntro({ feature }) {
1412
+ const hasNarrative = Boolean(feature.narrative?.trim());
1413
+ const hasGlossary = Boolean(feature.glossary?.length);
1414
+ if (!hasNarrative && !hasGlossary) return null;
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)(
1417
+ "div",
1418
+ {
1419
+ className: "es-doc-prose prose prose-sm max-w-none text-muted-foreground",
1420
+ dangerouslySetInnerHTML: { __html: safeMarkdownHtml(dedent(feature.narrative)) }
1421
+ }
1422
+ ) : null,
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 })
1426
+ ] }, entry.term)) }) : null
1427
+ ] });
1428
+ }
1429
+ function featureKindLabel(kind) {
1430
+ if (kind === "ability") return "Ability";
1431
+ if (kind === "business-need") return "Business need";
1432
+ if (kind === "feature") return "Feature";
1433
+ return null;
1296
1434
  }
1297
1435
 
1298
1436
  // src/components/ReportFeature.tsx
1299
- var import_jsx_runtime28 = require("react/jsx-runtime");
1437
+ var import_jsx_runtime30 = require("react/jsx-runtime");
1300
1438
  function ReportFeature({ feature }) {
1301
1439
  const titleId = `${feature.id}-title`;
1302
1440
  const bodyId = `${feature.id}-body`;
@@ -1304,10 +1442,11 @@ function ReportFeature({ feature }) {
1304
1442
  const skipped = s.skipped + s.pending;
1305
1443
  const collapse = useCollapse();
1306
1444
  const collapsed = collapse?.isCollapsed(feature.id) ?? false;
1445
+ const kindLabel = featureKindLabel(feature.kind);
1307
1446
  return (
1308
1447
  // Suite = a section heading + a divider, NOT another bordered card. Drops a
1309
1448
  // layer of nesting so the scenario cards below read as the primary unit.
1310
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1449
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
1311
1450
  "section",
1312
1451
  {
1313
1452
  id: feature.id,
@@ -1315,9 +1454,9 @@ function ReportFeature({ feature }) {
1315
1454
  "aria-labelledby": titleId,
1316
1455
  className: "flex flex-col gap-3",
1317
1456
  children: [
1318
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1319
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "min-w-0", children: [
1320
- /* @__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)(
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)(
1321
1460
  "button",
1322
1461
  {
1323
1462
  type: "button",
@@ -1326,31 +1465,34 @@ function ReportFeature({ feature }) {
1326
1465
  "aria-controls": bodyId,
1327
1466
  className: "flex w-full cursor-pointer items-center gap-2 text-left hover:text-foreground/80",
1328
1467
  children: [
1329
- /* @__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" }),
1330
- /* @__PURE__ */ (0, import_jsx_runtime28.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 })
1331
1470
  ]
1332
1471
  }
1333
1472
  ) : feature.title }),
1334
- /* @__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 })
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 })
1476
+ ] })
1335
1477
  ] }),
1336
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1478
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
1337
1479
  "p",
1338
1480
  {
1339
1481
  className: "flex shrink-0 items-center gap-1.5 text-xs text-muted-foreground",
1340
1482
  "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
1341
1483
  children: [
1342
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { children: [
1484
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("span", { children: [
1343
1485
  s.passed,
1344
1486
  " passed"
1345
1487
  ] }),
1346
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1347
- /* @__PURE__ */ (0, import_jsx_runtime28.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: [
1348
1490
  s.failed,
1349
1491
  " failed"
1350
1492
  ] }),
1351
- skipped > 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
1352
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1353
- /* @__PURE__ */ (0, import_jsx_runtime28.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: [
1354
1496
  skipped,
1355
1497
  " skipped"
1356
1498
  ] })
@@ -1359,7 +1501,10 @@ function ReportFeature({ feature }) {
1359
1501
  }
1360
1502
  )
1361
1503
  ] }),
1362
- /* @__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 }) })
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 })
1507
+ ] })
1363
1508
  ]
1364
1509
  }
1365
1510
  )
@@ -1367,17 +1512,17 @@ function ReportFeature({ feature }) {
1367
1512
  }
1368
1513
 
1369
1514
  // src/components/ReportFeatureList.tsx
1370
- var import_jsx_runtime29 = require("react/jsx-runtime");
1515
+ var import_jsx_runtime31 = require("react/jsx-runtime");
1371
1516
  function ReportFeatureList() {
1372
1517
  const report = useReport();
1373
- 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)) });
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)) });
1374
1519
  }
1375
1520
 
1376
1521
  // src/components/ui/empty.tsx
1377
1522
  var import_class_variance_authority2 = require("class-variance-authority");
1378
- var import_jsx_runtime30 = require("react/jsx-runtime");
1523
+ var import_jsx_runtime32 = require("react/jsx-runtime");
1379
1524
  function Empty({ className, ...props }) {
1380
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1525
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1381
1526
  "div",
1382
1527
  {
1383
1528
  "data-slot": "empty",
@@ -1390,7 +1535,7 @@ function Empty({ className, ...props }) {
1390
1535
  );
1391
1536
  }
1392
1537
  function EmptyHeader({ className, ...props }) {
1393
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1538
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1394
1539
  "div",
1395
1540
  {
1396
1541
  "data-slot": "empty-header",
@@ -1417,7 +1562,7 @@ var emptyMediaVariants = (0, import_class_variance_authority2.cva)(
1417
1562
  }
1418
1563
  );
1419
1564
  function EmptyTitle({ className, ...props }) {
1420
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1565
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1421
1566
  "div",
1422
1567
  {
1423
1568
  "data-slot": "empty-title",
@@ -1428,16 +1573,16 @@ function EmptyTitle({ className, ...props }) {
1428
1573
  }
1429
1574
 
1430
1575
  // src/components/ReportEmpty.tsx
1431
- var import_jsx_runtime31 = require("react/jsx-runtime");
1576
+ var import_jsx_runtime33 = require("react/jsx-runtime");
1432
1577
  function ReportEmpty({ message }) {
1433
- 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." }) }) });
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." }) }) });
1434
1579
  }
1435
1580
 
1436
1581
  // src/components/ReportSummary.tsx
1437
- var import_jsx_runtime32 = require("react/jsx-runtime");
1582
+ var import_jsx_runtime34 = require("react/jsx-runtime");
1438
1583
  function ReportSummary({ className }) {
1439
1584
  const report = useReport();
1440
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1585
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
1441
1586
  ReportSummaryView,
1442
1587
  {
1443
1588
  summary: report.summary,
@@ -1463,7 +1608,7 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1463
1608
  if (summary.pending > 0) {
1464
1609
  cards.push({ status: "pending", label: "Pending", value: summary.pending });
1465
1610
  }
1466
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1611
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
1467
1612
  "dl",
1468
1613
  {
1469
1614
  className: cn(
@@ -1476,17 +1621,17 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1476
1621
  "aria-label": ariaLabel,
1477
1622
  children: cards.map((c) => {
1478
1623
  const tone = TONE[c.status];
1479
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
1624
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
1480
1625
  "div",
1481
1626
  {
1482
1627
  "data-status": c.status,
1483
1628
  className: "rounded-lg border bg-card px-4 py-3",
1484
1629
  children: [
1485
- /* @__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: [
1486
- /* @__PURE__ */ (0, import_jsx_runtime32.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" }),
1487
1632
  c.label
1488
1633
  ] }),
1489
- /* @__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 })
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 })
1490
1635
  ]
1491
1636
  },
1492
1637
  c.status
@@ -1498,39 +1643,7 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1498
1643
 
1499
1644
  // src/components/ReportMeta.tsx
1500
1645
  var import_duration4 = require("executable-stories-core/utils/duration");
1501
-
1502
- // src/lib/provenance.ts
1503
- var CI_DISPLAY_NAMES = {
1504
- github: "GitHub Actions",
1505
- gitlab: "GitLab CI",
1506
- circleci: "CircleCI",
1507
- jenkins: "Jenkins",
1508
- azure: "Azure DevOps",
1509
- buildkite: "Buildkite",
1510
- travis: "Travis CI",
1511
- ci: "CI"
1512
- };
1513
- function ciDisplayName(name) {
1514
- return CI_DISPLAY_NAMES[name] ?? name;
1515
- }
1516
- function commitUrl(ci, sha) {
1517
- const commitSha = ci?.commitSha ?? sha;
1518
- if (!ci?.url || !commitSha) return void 0;
1519
- const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1520
- if (github) return `${github[1]}/commit/${commitSha}`;
1521
- const gitlab = ci.url.match(/^(https?:\/\/.+)\/-\/pipelines\//);
1522
- if (gitlab) return `${gitlab[1]}/-/commit/${commitSha}`;
1523
- return void 0;
1524
- }
1525
- function prUrl(ci) {
1526
- if (!ci?.url || !ci.prNumber) return void 0;
1527
- const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1528
- if (github) return `${github[1]}/pull/${ci.prNumber}`;
1529
- return void 0;
1530
- }
1531
-
1532
- // src/components/ReportMeta.tsx
1533
- var import_jsx_runtime33 = require("react/jsx-runtime");
1646
+ var import_jsx_runtime35 = require("react/jsx-runtime");
1534
1647
  function ReportMeta() {
1535
1648
  const report = useReport();
1536
1649
  const items = [];
@@ -1553,13 +1666,13 @@ function ReportMeta() {
1553
1666
  const short = sha.slice(0, 8);
1554
1667
  items.push({
1555
1668
  label: "Commit",
1556
- value: url ? /* @__PURE__ */ (0, import_jsx_runtime33.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
1557
1670
  });
1558
1671
  }
1559
1672
  if (report.ci) {
1560
1673
  items.push({
1561
1674
  label: "CI",
1562
- value: report.ci.url ? /* @__PURE__ */ (0, import_jsx_runtime33.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: [
1563
1676
  ciDisplayName(report.ci.name),
1564
1677
  report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
1565
1678
  ] }) : `${ciDisplayName(report.ci.name)}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
@@ -1570,7 +1683,7 @@ function ReportMeta() {
1570
1683
  const label = `#${report.ci.prNumber}`;
1571
1684
  items.push({
1572
1685
  label: "PR",
1573
- value: url ? /* @__PURE__ */ (0, import_jsx_runtime33.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
1574
1687
  });
1575
1688
  }
1576
1689
  if (items.length === 0) return null;
@@ -1578,48 +1691,48 @@ function ReportMeta() {
1578
1691
  // Run metadata is useful but secondary to "what failed" — tuck it into a
1579
1692
  // collapsed disclosure so it doesn't push the result down. Native <details>
1580
1693
  // so it works with no JS (the SSR/no-hydration report still toggles).
1581
- /* @__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: [
1582
- /* @__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: [
1583
- /* @__PURE__ */ (0, import_jsx_runtime33.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" }),
1584
1697
  "Run details"
1585
1698
  ] }),
1586
- /* @__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: [
1587
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("dt", { className: "font-medium", children: item.label }),
1588
- /* @__PURE__ */ (0, import_jsx_runtime33.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 })
1589
1702
  ] }, item.label)) })
1590
1703
  ] })
1591
1704
  );
1592
1705
  }
1593
1706
 
1594
1707
  // src/components/ReportSchemaError.tsx
1595
- var import_jsx_runtime34 = require("react/jsx-runtime");
1708
+ var import_jsx_runtime36 = require("react/jsx-runtime");
1596
1709
  function ReportSchemaError({ error }) {
1597
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1598
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("strong", { children: "Report could not be displayed." }) }),
1599
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { children: error.message }),
1600
- error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ (0, import_jsx_runtime34.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: [
1601
1714
  "The report bundle is newer than this version of ",
1602
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("code", { children: "executable-stories-react" }),
1715
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("code", { children: "executable-stories-react" }),
1603
1716
  ". Upgrade the package, or regenerate the report with an older formatters CLI."
1604
1717
  ] }) : null,
1605
- error.issues && error.issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("details", { children: [
1606
- /* @__PURE__ */ (0, import_jsx_runtime34.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: [
1607
1720
  error.issues.length,
1608
1721
  " validation issue",
1609
1722
  error.issues.length === 1 ? "" : "s"
1610
1723
  ] }),
1611
- /* @__PURE__ */ (0, import_jsx_runtime34.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") })
1612
1725
  ] }) : null
1613
1726
  ] });
1614
1727
  }
1615
1728
 
1616
1729
  // src/components/ReportShell.tsx
1617
- var import_jsx_runtime35 = require("react/jsx-runtime");
1730
+ var import_jsx_runtime37 = require("react/jsx-runtime");
1618
1731
  function ReportTitleBlock({ title }) {
1619
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
1620
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("h1", { children: title ?? "Story Report" }),
1621
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ReportSummary, {}),
1622
- /* @__PURE__ */ (0, import_jsx_runtime35.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, {})
1623
1736
  ] });
1624
1737
  }
1625
1738
  function ReportErrorShell({
@@ -1628,22 +1741,22 @@ function ReportErrorShell({
1628
1741
  title,
1629
1742
  dataTheme
1630
1743
  }) {
1631
- 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 }) });
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 }) });
1632
1745
  }
1633
1746
 
1634
1747
  // src/components/Report.tsx
1635
- var import_jsx_runtime36 = require("react/jsx-runtime");
1748
+ var import_jsx_runtime38 = require("react/jsx-runtime");
1636
1749
  function Report(props) {
1637
1750
  const { report, customRenderers, renderers, className, title, dataTheme } = props;
1638
1751
  const result = unwrapReport(report);
1639
1752
  if (!result.ok) {
1640
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportErrorShell, { error: result.error, className, title, dataTheme });
1753
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ReportErrorShell, { error: result.error, className, title, dataTheme });
1641
1754
  }
1642
1755
  const data = result.data;
1643
1756
  const hasContent = data.features.length > 0;
1644
- 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: [
1645
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("header", { className: "es-report-header", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportTitleBlock, { title }) }),
1646
- hasContent ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportFeatureList, {}) : /* @__PURE__ */ (0, import_jsx_runtime36.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, {})
1647
1760
  ] }) });
1648
1761
  }
1649
1762
 
@@ -1704,7 +1817,7 @@ function renderReportToHtml(report, options = {}) {
1704
1817
  scenarioHistory
1705
1818
  } = options;
1706
1819
  const markup = (0, import_server.renderToStaticMarkup)(
1707
- (0, import_react8.createElement)(Report, { report, title })
1820
+ (0, import_react10.createElement)(Report, { report, title })
1708
1821
  );
1709
1822
  const cdn = cdnAssets(syntaxHighlighting, mermaid);
1710
1823
  const parsed = unwrapReport(report);