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.js CHANGED
@@ -290,23 +290,48 @@ function useReport() {
290
290
  // src/components/ReportScenario.tsx
291
291
  import { formatDuration as formatDuration3 } from "executable-stories-core/utils/duration";
292
292
 
293
+ // src/lib/step-params.tsx
294
+ import { jsx as jsx3 } from "react/jsx-runtime";
295
+ var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.-])\d+(?:\.\d+)?(?![\w.-])/g;
296
+ function highlightStepParams(text) {
297
+ const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
298
+ if (matches.length === 0) return text;
299
+ const nodes = [];
300
+ let lastIndex = 0;
301
+ matches.forEach((match, i) => {
302
+ const start = match.index;
303
+ const end = start + match[0].length;
304
+ if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
305
+ nodes.push(
306
+ /* @__PURE__ */ jsx3("span", { className: "font-medium text-step-param italic", children: match[0] }, i)
307
+ );
308
+ lastIndex = end;
309
+ });
310
+ if (lastIndex < text.length) nodes.push(text.slice(lastIndex));
311
+ return nodes;
312
+ }
313
+
293
314
  // src/components/ReportSteps.tsx
315
+ import {
316
+ assertionState,
317
+ assertiveSteps
318
+ } from "executable-stories-core/utils/assertive-steps";
294
319
  import { formatDuration } from "executable-stories-core/utils/duration";
295
320
 
296
321
  // src/components/doc/DocNote.tsx
297
- import { jsx as jsx3 } from "react/jsx-runtime";
322
+ import { jsx as jsx4 } from "react/jsx-runtime";
298
323
  function DocNote({ entry }) {
299
- return /* @__PURE__ */ jsx3("p", { className: "my-2 text-sm text-muted-foreground", children: entry.text });
324
+ return /* @__PURE__ */ jsx4("p", { className: "my-2 text-sm text-muted-foreground", children: entry.text });
300
325
  }
301
326
 
302
327
  // src/components/doc/DocTag.tsx
303
- import { jsx as jsx4 } from "react/jsx-runtime";
328
+ import { jsx as jsx5 } from "react/jsx-runtime";
304
329
  function DocTag({ entry }) {
305
- return /* @__PURE__ */ jsx4("ul", { className: "my-2 flex flex-wrap gap-1.5", "aria-label": "Tags", children: entry.names.map((n) => /* @__PURE__ */ jsx4("li", { children: /* @__PURE__ */ jsx4(Badge, { variant: "tag", children: n }) }, n)) });
330
+ return /* @__PURE__ */ jsx5("ul", { className: "my-2 flex flex-wrap gap-1.5", "aria-label": "Tags", children: entry.names.map((n) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsx5(Badge, { variant: "tag", children: n }) }, n)) });
306
331
  }
307
332
 
308
333
  // src/components/doc/DocKv.tsx
309
- import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
334
+ import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
310
335
  function formatValue(value) {
311
336
  if (value === null) return "null";
312
337
  if (typeof value === "string") return value;
@@ -319,8 +344,8 @@ function formatValue(value) {
319
344
  }
320
345
  function DocKv({ entry }) {
321
346
  return /* @__PURE__ */ jsxs2("dl", { className: "my-2 flex flex-wrap items-baseline gap-x-2 text-sm", children: [
322
- /* @__PURE__ */ jsx5("dt", { className: "font-semibold text-muted-foreground", children: entry.label }),
323
- /* @__PURE__ */ jsx5("dd", { className: "font-mono text-foreground", children: formatValue(entry.value) })
347
+ /* @__PURE__ */ jsx6("dt", { className: "font-semibold text-muted-foreground", children: entry.label }),
348
+ /* @__PURE__ */ jsx6("dd", { className: "font-mono text-foreground", children: formatValue(entry.value) })
324
349
  ] });
325
350
  }
326
351
 
@@ -338,37 +363,37 @@ function useBuiltinRenderers() {
338
363
  }
339
364
 
340
365
  // src/components/doc/DocCode.tsx
341
- import { Fragment, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
366
+ import { Fragment, jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
342
367
  function CodeFigure({ label, children }) {
343
368
  return /* @__PURE__ */ jsxs3("figure", { className: "my-3", children: [
344
- label ? /* @__PURE__ */ jsx6("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: label }) : null,
345
- /* @__PURE__ */ jsx6("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3", children })
369
+ label ? /* @__PURE__ */ jsx7("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: label }) : null,
370
+ /* @__PURE__ */ jsx7("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3", children })
346
371
  ] });
347
372
  }
348
373
  function DocCode({ entry }) {
349
374
  const renderers = useBuiltinRenderers();
350
375
  if (renderers.code) {
351
- return /* @__PURE__ */ jsx6(Fragment, { children: renderers.code(entry) });
376
+ return /* @__PURE__ */ jsx7(Fragment, { children: renderers.code(entry) });
352
377
  }
353
- return /* @__PURE__ */ jsx6(CodeFigure, { label: entry.label, children: /* @__PURE__ */ jsx6("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
378
+ return /* @__PURE__ */ jsx7(CodeFigure, { label: entry.label, children: /* @__PURE__ */ jsx7("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
354
379
  }
355
380
 
356
381
  // src/components/doc/DocTable.tsx
357
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
382
+ import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
358
383
  function DocTable({ entry }) {
359
384
  return /* @__PURE__ */ jsxs4("figure", { className: "my-3", children: [
360
- entry.label ? /* @__PURE__ */ jsx7("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
361
- /* @__PURE__ */ jsx7("div", { tabIndex: 0, className: "overflow-x-auto rounded-md border border-border", children: /* @__PURE__ */ jsxs4("table", { className: "w-full border-collapse text-sm", children: [
362
- /* @__PURE__ */ jsx7("thead", { children: /* @__PURE__ */ jsx7("tr", { className: "bg-muted/50", children: entry.columns.map((c) => /* @__PURE__ */ jsx7("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: c }, c)) }) }),
363
- /* @__PURE__ */ jsx7("tbody", { children: entry.rows.map((row, i) => /* @__PURE__ */ jsx7("tr", { children: row.map((cell, j) => /* @__PURE__ */ jsx7("td", { className: "border-b border-border px-3 py-2 text-foreground", children: cell }, j)) }, i)) })
385
+ entry.label ? /* @__PURE__ */ jsx8("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
386
+ /* @__PURE__ */ jsx8("div", { tabIndex: 0, className: "overflow-x-auto rounded-md border border-border", children: /* @__PURE__ */ jsxs4("table", { className: "w-full border-collapse text-sm", children: [
387
+ /* @__PURE__ */ jsx8("thead", { children: /* @__PURE__ */ jsx8("tr", { className: "bg-muted/50", children: entry.columns.map((c) => /* @__PURE__ */ jsx8("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: c }, c)) }) }),
388
+ /* @__PURE__ */ jsx8("tbody", { children: entry.rows.map((row, i) => /* @__PURE__ */ jsx8("tr", { children: row.map((cell, j) => /* @__PURE__ */ jsx8("td", { className: "border-b border-border px-3 py-2 text-foreground", children: cell }, j)) }, i)) })
364
389
  ] }) })
365
390
  ] });
366
391
  }
367
392
 
368
393
  // src/components/doc/DocLink.tsx
369
- import { jsx as jsx8 } from "react/jsx-runtime";
394
+ import { jsx as jsx9 } from "react/jsx-runtime";
370
395
  function DocLink({ entry }) {
371
- return /* @__PURE__ */ jsx8(
396
+ return /* @__PURE__ */ jsx9(
372
397
  "a",
373
398
  {
374
399
  className: "text-sm font-medium text-primary underline underline-offset-2 hover:no-underline",
@@ -380,30 +405,38 @@ function DocLink({ entry }) {
380
405
  );
381
406
  }
382
407
 
383
- // src/components/doc/DocSection.tsx
408
+ // src/lib/markdown.ts
384
409
  import { marked } from "marked";
385
410
 
386
411
  // src/lib/url.ts
387
412
  import { safeUrl, safeImageUrl, isLocalFsPath } from "executable-stories-core/utils/url";
388
413
 
389
- // src/components/doc/DocSection.tsx
390
- import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
391
- function neutralizeUrl(value) {
392
- return safeUrl(value) ?? "#";
393
- }
414
+ // src/lib/markdown.ts
394
415
  function safeMarkdownHtml(markdown) {
395
416
  const raw = marked.parse(markdown, { async: false });
396
417
  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)}'`);
397
418
  }
419
+ function dedent(text) {
420
+ const lines = text.replace(/\t/g, " ").split("\n");
421
+ const indents = lines.filter((line) => line.trim().length > 0).map((line) => line.length - line.trimStart().length);
422
+ const shortest = indents.length > 0 ? Math.min(...indents) : 0;
423
+ return lines.map((line) => line.slice(shortest)).join("\n").trim();
424
+ }
425
+ function neutralizeUrl(value) {
426
+ return safeUrl(value) ?? "#";
427
+ }
428
+
429
+ // src/components/doc/DocSection.tsx
430
+ import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
398
431
  function DocSection({ entry }) {
399
432
  const renderers = useBuiltinRenderers();
400
433
  if (renderers.section) {
401
- return /* @__PURE__ */ jsx9(Fragment2, { children: renderers.section(entry) });
434
+ return /* @__PURE__ */ jsx10(Fragment2, { children: renderers.section(entry) });
402
435
  }
403
436
  const html = safeMarkdownHtml(entry.markdown);
404
437
  return /* @__PURE__ */ jsxs5("section", { className: "my-3 text-sm", "aria-label": entry.title, children: [
405
- entry.title ? /* @__PURE__ */ jsx9("h4", { className: "mb-1 font-semibold text-foreground", children: entry.title }) : null,
406
- /* @__PURE__ */ jsx9(
438
+ entry.title ? /* @__PURE__ */ jsx10("h4", { className: "mb-1 font-semibold text-foreground", children: entry.title }) : null,
439
+ /* @__PURE__ */ jsx10(
407
440
  "div",
408
441
  {
409
442
  className: "es-doc-prose prose prose-sm max-w-none",
@@ -414,11 +447,11 @@ function DocSection({ entry }) {
414
447
  }
415
448
 
416
449
  // src/components/doc/DocMermaid.tsx
417
- import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
450
+ import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
418
451
  function MermaidSource({ entry, ref }) {
419
452
  return /* @__PURE__ */ jsxs6("figure", { ref, className: "my-3", "aria-label": entry.title ?? "Diagram", children: [
420
- entry.title ? /* @__PURE__ */ jsx10("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
421
- /* @__PURE__ */ jsx10(
453
+ entry.title ? /* @__PURE__ */ jsx11("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
454
+ /* @__PURE__ */ jsx11(
422
455
  "pre",
423
456
  {
424
457
  "data-mermaid": true,
@@ -432,24 +465,24 @@ function MermaidSource({ entry, ref }) {
432
465
  function DocMermaid({ entry }) {
433
466
  const renderers = useBuiltinRenderers();
434
467
  if (renderers.mermaid) {
435
- return /* @__PURE__ */ jsx10(Fragment3, { children: renderers.mermaid(entry) });
468
+ return /* @__PURE__ */ jsx11(Fragment3, { children: renderers.mermaid(entry) });
436
469
  }
437
- return /* @__PURE__ */ jsx10(MermaidSource, { entry });
470
+ return /* @__PURE__ */ jsx11(MermaidSource, { entry });
438
471
  }
439
472
 
440
473
  // src/components/doc/DocScreenshot.tsx
441
- import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
474
+ import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
442
475
  function DocScreenshot({ entry }) {
443
476
  const src = isLocalFsPath(entry.path) ? void 0 : safeImageUrl(entry.path);
444
477
  if (!src) {
445
478
  return /* @__PURE__ */ jsxs7("figure", { className: "my-3 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground", children: [
446
- /* @__PURE__ */ jsx11("div", { className: "font-medium", children: "Screenshot unavailable" }),
447
- /* @__PURE__ */ jsx11("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
448
- entry.alt ? /* @__PURE__ */ jsx11("figcaption", { className: "mt-1.5 text-xs", children: entry.alt }) : null
479
+ /* @__PURE__ */ jsx12("div", { className: "font-medium", children: "Screenshot unavailable" }),
480
+ /* @__PURE__ */ jsx12("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
481
+ entry.alt ? /* @__PURE__ */ jsx12("figcaption", { className: "mt-1.5 text-xs", children: entry.alt }) : null
449
482
  ] });
450
483
  }
451
484
  return /* @__PURE__ */ jsxs7("figure", { className: "my-3", children: [
452
- /* @__PURE__ */ jsx11(
485
+ /* @__PURE__ */ jsx12(
453
486
  "img",
454
487
  {
455
488
  src,
@@ -458,24 +491,24 @@ function DocScreenshot({ entry }) {
458
491
  className: "max-w-full rounded-md border border-border"
459
492
  }
460
493
  ),
461
- entry.alt ? /* @__PURE__ */ jsx11("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: entry.alt }) : null
494
+ entry.alt ? /* @__PURE__ */ jsx12("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: entry.alt }) : null
462
495
  ] });
463
496
  }
464
497
 
465
498
  // src/components/doc/DocVideo.tsx
466
- import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
499
+ import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
467
500
  function DocVideo({ entry }) {
468
501
  if (isLocalFsPath(entry.path)) {
469
502
  return /* @__PURE__ */ jsxs8("figure", { className: "my-3 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground", children: [
470
- /* @__PURE__ */ jsx12("div", { className: "font-medium", children: "Video unavailable" }),
471
- /* @__PURE__ */ jsx12("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
472
- entry.caption ? /* @__PURE__ */ jsx12("figcaption", { className: "mt-1.5 text-xs", children: entry.caption }) : null
503
+ /* @__PURE__ */ jsx13("div", { className: "font-medium", children: "Video unavailable" }),
504
+ /* @__PURE__ */ jsx13("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
505
+ entry.caption ? /* @__PURE__ */ jsx13("figcaption", { className: "mt-1.5 text-xs", children: entry.caption }) : null
473
506
  ] });
474
507
  }
475
508
  const src = safeUrl(entry.path);
476
509
  const poster = safeUrl(entry.poster);
477
510
  return /* @__PURE__ */ jsxs8("figure", { className: "my-3", children: [
478
- /* @__PURE__ */ jsx12(
511
+ /* @__PURE__ */ jsx13(
479
512
  "video",
480
513
  {
481
514
  controls: true,
@@ -485,12 +518,12 @@ function DocVideo({ entry }) {
485
518
  className: "max-w-full rounded-md border border-border"
486
519
  }
487
520
  ),
488
- entry.caption ? /* @__PURE__ */ jsx12("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: entry.caption }) : null
521
+ entry.caption ? /* @__PURE__ */ jsx13("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: entry.caption }) : null
489
522
  ] });
490
523
  }
491
524
 
492
525
  // src/components/doc/DocHtml.tsx
493
- import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
526
+ import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
494
527
  function DocHtml({ entry }) {
495
528
  const height = typeof entry.height === "number" ? `${entry.height}px` : entry.height ?? "400px";
496
529
  const title = entry.title ?? "Embedded HTML";
@@ -498,8 +531,8 @@ function DocHtml({ entry }) {
498
531
  const frameProps = entry.content !== void 0 ? { srcDoc: entry.content } : { src: safeHref ?? "" };
499
532
  return /* @__PURE__ */ jsxs9("figure", { className: "my-3 overflow-hidden rounded-md border border-border", children: [
500
533
  /* @__PURE__ */ jsxs9("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: [
501
- /* @__PURE__ */ jsx13("span", { children: entry.title ?? "HTML" }),
502
- safeHref ? /* @__PURE__ */ jsx13(
534
+ /* @__PURE__ */ jsx14("span", { children: entry.title ?? "HTML" }),
535
+ safeHref ? /* @__PURE__ */ jsx14(
503
536
  "a",
504
537
  {
505
538
  href: safeHref,
@@ -512,7 +545,7 @@ function DocHtml({ entry }) {
512
545
  }
513
546
  ) : null
514
547
  ] }),
515
- /* @__PURE__ */ jsx13(
548
+ /* @__PURE__ */ jsx14(
516
549
  "iframe",
517
550
  {
518
551
  ...frameProps,
@@ -527,7 +560,7 @@ function DocHtml({ entry }) {
527
560
  }
528
561
 
529
562
  // src/components/doc/DocState.tsx
530
- import { jsx as jsx14 } from "react/jsx-runtime";
563
+ import { jsx as jsx15 } from "react/jsx-runtime";
531
564
  function DocState({ entry }) {
532
565
  let json;
533
566
  try {
@@ -535,20 +568,20 @@ function DocState({ entry }) {
535
568
  } catch {
536
569
  json = String(entry.value);
537
570
  }
538
- return /* @__PURE__ */ jsx14(CodeFigure, { label: entry.label ?? "State", children: /* @__PURE__ */ jsx14("code", { className: "font-mono text-xs language-json", children: json }) });
571
+ return /* @__PURE__ */ jsx15(CodeFigure, { label: entry.label ?? "State", children: /* @__PURE__ */ jsx15("code", { className: "font-mono text-xs language-json", children: json }) });
539
572
  }
540
573
 
541
574
  // src/components/doc/DocCustom.tsx
542
- import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
575
+ import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
543
576
  function DocCustom({ entry }) {
544
577
  const renderers = useCustomRenderers();
545
578
  const renderer = renderers[entry.type];
546
579
  if (renderer) {
547
- return /* @__PURE__ */ jsx15(Fragment4, { children: renderer(entry) });
580
+ return /* @__PURE__ */ jsx16(Fragment4, { children: renderer(entry) });
548
581
  }
549
582
  return /* @__PURE__ */ jsxs10("div", { className: "my-2", "data-type": entry.type, children: [
550
- /* @__PURE__ */ jsx15("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: entry.type }),
551
- /* @__PURE__ */ jsx15("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3 font-mono text-xs text-foreground", children: safeStringify2(entry.data) })
583
+ /* @__PURE__ */ jsx16("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: entry.type }),
584
+ /* @__PURE__ */ jsx16("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3 font-mono text-xs text-foreground", children: safeStringify2(entry.data) })
552
585
  ] });
553
586
  }
554
587
  function safeStringify2(value) {
@@ -560,64 +593,43 @@ function safeStringify2(value) {
560
593
  }
561
594
 
562
595
  // src/components/doc/DocEntry.tsx
563
- import { jsx as jsx16 } from "react/jsx-runtime";
596
+ import { jsx as jsx17 } from "react/jsx-runtime";
564
597
  function DocEntry({ entry }) {
565
598
  switch (entry.kind) {
566
599
  case "note":
567
- return /* @__PURE__ */ jsx16(DocNote, { entry });
600
+ return /* @__PURE__ */ jsx17(DocNote, { entry });
568
601
  case "tag":
569
- return /* @__PURE__ */ jsx16(DocTag, { entry });
602
+ return /* @__PURE__ */ jsx17(DocTag, { entry });
570
603
  case "kv":
571
- return /* @__PURE__ */ jsx16(DocKv, { entry });
604
+ return /* @__PURE__ */ jsx17(DocKv, { entry });
572
605
  case "code":
573
- return /* @__PURE__ */ jsx16(DocCode, { entry });
606
+ return /* @__PURE__ */ jsx17(DocCode, { entry });
574
607
  case "table":
575
- return /* @__PURE__ */ jsx16(DocTable, { entry });
608
+ return /* @__PURE__ */ jsx17(DocTable, { entry });
576
609
  case "link":
577
- return /* @__PURE__ */ jsx16(DocLink, { entry });
610
+ return /* @__PURE__ */ jsx17(DocLink, { entry });
578
611
  case "section":
579
- return /* @__PURE__ */ jsx16(DocSection, { entry });
612
+ return /* @__PURE__ */ jsx17(DocSection, { entry });
580
613
  case "mermaid":
581
- return /* @__PURE__ */ jsx16(DocMermaid, { entry });
614
+ return /* @__PURE__ */ jsx17(DocMermaid, { entry });
582
615
  case "screenshot":
583
- return /* @__PURE__ */ jsx16(DocScreenshot, { entry });
616
+ return /* @__PURE__ */ jsx17(DocScreenshot, { entry });
584
617
  case "video":
585
- return /* @__PURE__ */ jsx16(DocVideo, { entry });
618
+ return /* @__PURE__ */ jsx17(DocVideo, { entry });
586
619
  case "html":
587
- return /* @__PURE__ */ jsx16(DocHtml, { entry });
620
+ return /* @__PURE__ */ jsx17(DocHtml, { entry });
588
621
  case "state":
589
- return /* @__PURE__ */ jsx16(DocState, { entry });
622
+ return /* @__PURE__ */ jsx17(DocState, { entry });
590
623
  case "custom":
591
- return /* @__PURE__ */ jsx16(DocCustom, { entry });
624
+ return /* @__PURE__ */ jsx17(DocCustom, { entry });
592
625
  }
593
626
  }
594
627
 
595
628
  // src/components/ReportDocEntries.tsx
596
- import { Fragment as Fragment5, jsx as jsx17 } from "react/jsx-runtime";
629
+ import { Fragment as Fragment5, jsx as jsx18 } from "react/jsx-runtime";
597
630
  function ReportDocEntries({ entries }) {
598
631
  if (entries.length === 0) return null;
599
- return /* @__PURE__ */ jsx17(Fragment5, { children: entries.map((entry, i) => /* @__PURE__ */ jsx17(DocEntry, { entry }, i)) });
600
- }
601
-
602
- // src/lib/step-params.tsx
603
- import { jsx as jsx18 } from "react/jsx-runtime";
604
- var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.-])\d+(?:\.\d+)?(?![\w.-])/g;
605
- function highlightStepParams(text) {
606
- const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
607
- if (matches.length === 0) return text;
608
- const nodes = [];
609
- let lastIndex = 0;
610
- matches.forEach((match, i) => {
611
- const start = match.index;
612
- const end = start + match[0].length;
613
- if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
614
- nodes.push(
615
- /* @__PURE__ */ jsx18("span", { className: "font-medium text-step-param italic", children: match[0] }, i)
616
- );
617
- lastIndex = end;
618
- });
619
- if (lastIndex < text.length) nodes.push(text.slice(lastIndex));
620
- return nodes;
632
+ return /* @__PURE__ */ jsx18(Fragment5, { children: entries.map((entry, i) => /* @__PURE__ */ jsx18(DocEntry, { entry }, i)) });
621
633
  }
622
634
 
623
635
  // src/components/ReportSteps.tsx
@@ -636,9 +648,22 @@ var STEP_GLYPH_COLOR = {
636
648
  };
637
649
  function ReportSteps({ scenario }) {
638
650
  if (scenario.steps.length === 0) return null;
639
- return /* @__PURE__ */ jsx19("ol", { "data-slot": "steps", className: "flex flex-col", children: scenario.steps.map((step) => /* @__PURE__ */ jsx19(ReportStepItem, { step }, step.id)) });
651
+ const unasserted = new Set(
652
+ assertionState(scenario.steps) === "unasserted" ? assertiveSteps(scenario.steps).map((step) => step.id) : []
653
+ );
654
+ return /* @__PURE__ */ jsx19("ol", { "data-slot": "steps", className: "flex flex-col", children: scenario.steps.map((step) => /* @__PURE__ */ jsx19(
655
+ ReportStepItem,
656
+ {
657
+ step,
658
+ unasserted: unasserted.has(step.id)
659
+ },
660
+ step.id
661
+ )) });
640
662
  }
641
- function ReportStepItem({ step }) {
663
+ function ReportStepItem({
664
+ step,
665
+ unasserted = false
666
+ }) {
642
667
  const kw = step.keyword.toLowerCase();
643
668
  const continuation = kw === "and" || kw === "but";
644
669
  return /* @__PURE__ */ jsxs11(
@@ -646,10 +671,23 @@ function ReportStepItem({ step }) {
646
671
  {
647
672
  id: step.id,
648
673
  "data-status": step.status,
649
- className: cn("py-1.5 text-[0.8125rem] leading-normal", continuation && "pl-5"),
674
+ className: cn(
675
+ "py-1.5 text-[0.8125rem] leading-normal",
676
+ continuation && "pl-5"
677
+ ),
650
678
  children: [
651
679
  /* @__PURE__ */ jsxs11("div", { className: "flex items-baseline gap-2", children: [
652
- /* @__PURE__ */ jsx19("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" }),
680
+ /* @__PURE__ */ jsx19(
681
+ "span",
682
+ {
683
+ "aria-hidden": true,
684
+ className: cn(
685
+ "w-4 shrink-0 text-center text-xs",
686
+ STEP_GLYPH_COLOR[step.status]
687
+ ),
688
+ children: STEP_GLYPH[step.status] ?? "\u25CB"
689
+ }
690
+ ),
653
691
  /* @__PURE__ */ jsx19(
654
692
  "span",
655
693
  {
@@ -661,6 +699,7 @@ function ReportStepItem({ step }) {
661
699
  }
662
700
  ),
663
701
  /* @__PURE__ */ jsx19("span", { className: "flex-1 text-foreground", children: highlightStepParams(step.text) }),
702
+ unasserted ? /* @__PURE__ */ jsx19("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,
664
703
  step.durationMs > 0 ? /* @__PURE__ */ jsx19("span", { className: "shrink-0 whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(step.durationMs) }) : null
665
704
  ] }),
666
705
  step.errorMessage ? /* @__PURE__ */ jsx19(
@@ -1056,21 +1095,95 @@ function ScenarioRunHistory({ scenarioId }) {
1056
1095
  ] });
1057
1096
  }
1058
1097
 
1098
+ // src/components/ScenarioStaleness.tsx
1099
+ import { useEffect, useState } from "react";
1100
+
1101
+ // src/lib/provenance.ts
1102
+ function formatRelativeAge(thenMs, nowMs) {
1103
+ const deltaMs = Math.max(0, nowMs - thenMs);
1104
+ const minutes = Math.floor(deltaMs / 6e4);
1105
+ if (minutes < 1) return "just now";
1106
+ if (minutes < 60) return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
1107
+ const hours = Math.floor(minutes / 60);
1108
+ if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
1109
+ const days = Math.floor(hours / 24);
1110
+ return `${days} day${days === 1 ? "" : "s"} ago`;
1111
+ }
1112
+ function isReportStale(lastRunMs, staleAfterDays, nowMs) {
1113
+ if (lastRunMs === void 0 || staleAfterDays <= 0) return false;
1114
+ return nowMs - lastRunMs >= staleAfterDays * 864e5;
1115
+ }
1116
+ var CI_DISPLAY_NAMES = {
1117
+ github: "GitHub Actions",
1118
+ gitlab: "GitLab CI",
1119
+ circleci: "CircleCI",
1120
+ jenkins: "Jenkins",
1121
+ azure: "Azure DevOps",
1122
+ buildkite: "Buildkite",
1123
+ travis: "Travis CI",
1124
+ ci: "CI"
1125
+ };
1126
+ function ciDisplayName(name) {
1127
+ return CI_DISPLAY_NAMES[name] ?? name;
1128
+ }
1129
+ function commitUrl(ci, sha) {
1130
+ const commitSha = ci?.commitSha ?? sha;
1131
+ if (!ci?.url || !commitSha) return void 0;
1132
+ const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1133
+ if (github) return `${github[1]}/commit/${commitSha}`;
1134
+ const gitlab = ci.url.match(/^(https?:\/\/.+)\/-\/pipelines\//);
1135
+ if (gitlab) return `${gitlab[1]}/-/commit/${commitSha}`;
1136
+ return void 0;
1137
+ }
1138
+ function prUrl(ci) {
1139
+ if (!ci?.url || !ci.prNumber) return void 0;
1140
+ const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1141
+ if (github) return `${github[1]}/pull/${ci.prNumber}`;
1142
+ return void 0;
1143
+ }
1144
+
1145
+ // src/interactive/scenario-freshness-context.tsx
1146
+ import { createContext as createContext3, useContext as useContext4 } from "react";
1147
+ var ScenarioFreshnessContext = createContext3(null);
1148
+ var ScenarioFreshnessProvider = ScenarioFreshnessContext.Provider;
1149
+ function useScenarioFreshness() {
1150
+ return useContext4(ScenarioFreshnessContext);
1151
+ }
1152
+
1153
+ // src/components/ScenarioStaleness.tsx
1154
+ import { jsxs as jsxs16 } from "react/jsx-runtime";
1155
+ function ScenarioStaleness({ scenario }) {
1156
+ const freshness = useScenarioFreshness();
1157
+ const fixedNow = freshness?.nowMs;
1158
+ const [now, setNow] = useState(fixedNow);
1159
+ useEffect(() => {
1160
+ if (fixedNow === void 0) setNow(Date.now());
1161
+ }, [fixedNow]);
1162
+ if (!freshness || now === void 0) return null;
1163
+ const lastRunAtMs = scenario.lastRunAtMs;
1164
+ if (!lastRunAtMs || lastRunAtMs <= 0) return null;
1165
+ if (!isReportStale(lastRunAtMs, freshness.staleAfterDays, now)) return null;
1166
+ return /* @__PURE__ */ jsxs16(Badge, { variant: "outline", "aria-label": "Scenario freshness", children: [
1167
+ "Last ran ",
1168
+ formatRelativeAge(lastRunAtMs, now)
1169
+ ] });
1170
+ }
1171
+
1059
1172
  // src/interactive/collapse-context.ts
1060
- import { createContext as createContext3, useCallback, useContext as useContext4, useState } from "react";
1061
- var CollapseContext = createContext3(null);
1173
+ import { createContext as createContext4, useCallback, useContext as useContext5, useState as useState2 } from "react";
1174
+ var CollapseContext = createContext4(null);
1062
1175
  var CollapseProvider = CollapseContext.Provider;
1063
1176
  function useCollapse() {
1064
- return useContext4(CollapseContext);
1177
+ return useContext5(CollapseContext);
1065
1178
  }
1066
1179
 
1067
1180
  // src/interactive/scenario-actions.tsx
1068
- import { createContext as createContext4, useContext as useContext5 } from "react";
1181
+ import { createContext as createContext5, useContext as useContext6 } from "react";
1069
1182
  import { scenarioToMarkdown as coreScenarioToMarkdown } from "executable-stories-core/utils/scenario-markdown";
1070
- var ScenarioActionsContext = createContext4(null);
1183
+ var ScenarioActionsContext = createContext5(null);
1071
1184
  var ScenarioActionsProvider = ScenarioActionsContext.Provider;
1072
1185
  function useScenarioActions() {
1073
- return useContext5(ScenarioActionsContext);
1186
+ return useContext6(ScenarioActionsContext);
1074
1187
  }
1075
1188
 
1076
1189
  // src/components/ui/card.tsx
@@ -1117,7 +1230,7 @@ function CardContent({ className, ...props }) {
1117
1230
  import "react";
1118
1231
  import { Menu as MenuPrimitive } from "@base-ui/react/menu";
1119
1232
  import { CheckIcon } from "lucide-react";
1120
- import { jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
1233
+ import { jsx as jsx25, jsxs as jsxs17 } from "react/jsx-runtime";
1121
1234
  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";
1122
1235
  function DropdownMenu(props) {
1123
1236
  return /* @__PURE__ */ jsx25(MenuPrimitive.Root, { "data-slot": "dropdown-menu", ...props });
@@ -1168,7 +1281,7 @@ function DropdownMenuItem({
1168
1281
 
1169
1282
  // src/components/ReportScenario.tsx
1170
1283
  import { LinkIcon, FileTextIcon, SparklesIcon, MoreHorizontalIcon } from "lucide-react";
1171
- import { jsx as jsx26, jsxs as jsxs17 } from "react/jsx-runtime";
1284
+ import { jsx as jsx26, jsxs as jsxs18 } from "react/jsx-runtime";
1172
1285
  var STATUS_META = {
1173
1286
  passed: { label: "Passed", glyph: "\u2713", glyphColor: "text-pass", badgeVariant: "passed", showPill: false },
1174
1287
  failed: { label: "Failed", glyph: "\u2717", glyphColor: "text-fail", badgeVariant: "failed", showPill: true },
@@ -1184,7 +1297,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
1184
1297
  const collapsible = collapse !== null && !hideTitle;
1185
1298
  const collapsed = collapsible ? collapse.isCollapsed(scenario.id) : false;
1186
1299
  const actions = useScenarioActions();
1187
- return /* @__PURE__ */ jsxs17(
1300
+ return /* @__PURE__ */ jsxs18(
1188
1301
  Card,
1189
1302
  {
1190
1303
  id: scenario.id,
@@ -1192,9 +1305,9 @@ function ReportScenario({ scenario, hideTitle = false }) {
1192
1305
  ...hideTitle ? { "aria-label": `${scenario.title}, ${label}` } : { "aria-labelledby": titleId },
1193
1306
  className: "gap-0 py-0",
1194
1307
  children: [
1195
- /* @__PURE__ */ jsxs17(CardHeader, { className: "px-4 py-3", children: [
1196
- /* @__PURE__ */ jsxs17("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
1197
- hideTitle ? null : /* @__PURE__ */ jsxs17("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1308
+ /* @__PURE__ */ jsxs18(CardHeader, { className: "px-4 py-3", children: [
1309
+ /* @__PURE__ */ jsxs18("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
1310
+ hideTitle ? null : /* @__PURE__ */ jsxs18("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1198
1311
  collapsible ? /* @__PURE__ */ jsx26(
1199
1312
  "button",
1200
1313
  {
@@ -1210,11 +1323,12 @@ function ReportScenario({ scenario, hideTitle = false }) {
1210
1323
  /* @__PURE__ */ jsx26("span", { "aria-hidden": true, className: cn("text-sm leading-none", meta.glyphColor), children: meta.glyph }),
1211
1324
  /* @__PURE__ */ jsx26("span", { children: scenario.title })
1212
1325
  ] }),
1213
- /* @__PURE__ */ jsxs17("div", { className: "flex shrink-0 items-center gap-2", children: [
1326
+ /* @__PURE__ */ jsxs18("div", { className: "flex shrink-0 items-center gap-2", children: [
1214
1327
  /* @__PURE__ */ jsx26(ScenarioRunHistory, { scenarioId: scenario.id }),
1328
+ /* @__PURE__ */ jsx26(ScenarioStaleness, { scenario }),
1215
1329
  scenario.durationMs > 0 ? /* @__PURE__ */ jsx26("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration3(scenario.durationMs) }) : null,
1216
1330
  meta.showPill ? /* @__PURE__ */ jsx26(Badge, { variant: meta.badgeVariant, "aria-label": `Status: ${label}`, children: label }) : /* @__PURE__ */ jsx26("span", { className: "sr-only", children: `Status: ${label}` }),
1217
- actions ? /* @__PURE__ */ jsxs17(DropdownMenu, { children: [
1331
+ actions ? /* @__PURE__ */ jsxs18(DropdownMenu, { children: [
1218
1332
  /* @__PURE__ */ jsx26(
1219
1333
  DropdownMenuTrigger,
1220
1334
  {
@@ -1223,16 +1337,16 @@ function ReportScenario({ scenario, hideTitle = false }) {
1223
1337
  children: /* @__PURE__ */ jsx26(MoreHorizontalIcon, { "aria-hidden": true })
1224
1338
  }
1225
1339
  ),
1226
- /* @__PURE__ */ jsxs17(DropdownMenuContent, { align: "end", children: [
1227
- /* @__PURE__ */ jsxs17(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
1340
+ /* @__PURE__ */ jsxs18(DropdownMenuContent, { align: "end", children: [
1341
+ /* @__PURE__ */ jsxs18(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
1228
1342
  /* @__PURE__ */ jsx26(LinkIcon, {}),
1229
1343
  " Copy link"
1230
1344
  ] }),
1231
- /* @__PURE__ */ jsxs17(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
1345
+ /* @__PURE__ */ jsxs18(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
1232
1346
  /* @__PURE__ */ jsx26(FileTextIcon, {}),
1233
1347
  " Copy as Markdown"
1234
1348
  ] }),
1235
- /* @__PURE__ */ jsxs17(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
1349
+ /* @__PURE__ */ jsxs18(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
1236
1350
  /* @__PURE__ */ jsx26(SparklesIcon, {}),
1237
1351
  " Explain with AI"
1238
1352
  ] })
@@ -1240,12 +1354,12 @@ function ReportScenario({ scenario, hideTitle = false }) {
1240
1354
  ] }) : null
1241
1355
  ] })
1242
1356
  ] }),
1243
- scenario.tags.length > 0 || (scenario.tickets?.length ?? 0) > 0 ? /* @__PURE__ */ jsxs17("ul", { "aria-label": "Tags and tickets", className: "flex flex-wrap gap-1.5", children: [
1357
+ scenario.tags.length > 0 || (scenario.tickets?.length ?? 0) > 0 ? /* @__PURE__ */ jsxs18("ul", { "aria-label": "Tags and tickets", className: "flex flex-wrap gap-1.5", children: [
1244
1358
  scenario.tags.map((t) => /* @__PURE__ */ jsx26("li", { children: /* @__PURE__ */ jsx26(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
1245
1359
  (scenario.tickets ?? []).map((ticket) => /* @__PURE__ */ jsx26("li", { children: ticket.url ? /* @__PURE__ */ jsx26("a", { href: ticket.url, target: "_blank", rel: "noreferrer noopener", children: /* @__PURE__ */ jsx26(Badge, { variant: "outline", children: ticket.id }) }) : /* @__PURE__ */ jsx26(Badge, { variant: "outline", children: ticket.id }) }, `ticket-${ticket.id}`))
1246
1360
  ] }) : null
1247
1361
  ] }),
1248
- /* @__PURE__ */ jsxs17(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
1362
+ /* @__PURE__ */ jsxs18(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
1249
1363
  scenario.errorMessage ? /* @__PURE__ */ jsx26(
1250
1364
  "pre",
1251
1365
  {
@@ -1271,8 +1385,35 @@ function ReportScenarioList({ feature }) {
1271
1385
  return /* @__PURE__ */ jsx27(Fragment8, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ jsx27(ReportScenario, { scenario }, scenario.id)) });
1272
1386
  }
1273
1387
 
1388
+ // src/components/ReportFeatureIntro.tsx
1389
+ import { jsx as jsx28, jsxs as jsxs19 } from "react/jsx-runtime";
1390
+ function ReportFeatureIntro({ feature }) {
1391
+ const hasNarrative = Boolean(feature.narrative?.trim());
1392
+ const hasGlossary = Boolean(feature.glossary?.length);
1393
+ if (!hasNarrative && !hasGlossary) return null;
1394
+ return /* @__PURE__ */ jsxs19("div", { className: "flex flex-col gap-3 border-l-2 border-border pl-4 text-sm", children: [
1395
+ hasNarrative ? /* @__PURE__ */ jsx28(
1396
+ "div",
1397
+ {
1398
+ className: "es-doc-prose prose prose-sm max-w-none text-muted-foreground",
1399
+ dangerouslySetInnerHTML: { __html: safeMarkdownHtml(dedent(feature.narrative)) }
1400
+ }
1401
+ ) : null,
1402
+ hasGlossary ? /* @__PURE__ */ jsx28("dl", { className: "grid gap-1.5", "aria-label": "Glossary", children: feature.glossary.map((entry) => /* @__PURE__ */ jsxs19("div", { className: "flex flex-wrap gap-x-2 text-xs", children: [
1403
+ /* @__PURE__ */ jsx28("dt", { className: "font-medium text-foreground", children: entry.term }),
1404
+ /* @__PURE__ */ jsx28("dd", { className: "flex-1 text-muted-foreground", children: entry.definition })
1405
+ ] }, entry.term)) }) : null
1406
+ ] });
1407
+ }
1408
+ function featureKindLabel(kind) {
1409
+ if (kind === "ability") return "Ability";
1410
+ if (kind === "business-need") return "Business need";
1411
+ if (kind === "feature") return "Feature";
1412
+ return null;
1413
+ }
1414
+
1274
1415
  // src/components/ReportFeature.tsx
1275
- import { Fragment as Fragment9, jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
1416
+ import { Fragment as Fragment9, jsx as jsx29, jsxs as jsxs20 } from "react/jsx-runtime";
1276
1417
  function ReportFeature({ feature }) {
1277
1418
  const titleId = `${feature.id}-title`;
1278
1419
  const bodyId = `${feature.id}-body`;
@@ -1280,10 +1421,11 @@ function ReportFeature({ feature }) {
1280
1421
  const skipped = s.skipped + s.pending;
1281
1422
  const collapse = useCollapse();
1282
1423
  const collapsed = collapse?.isCollapsed(feature.id) ?? false;
1424
+ const kindLabel = featureKindLabel(feature.kind);
1283
1425
  return (
1284
1426
  // Suite = a section heading + a divider, NOT another bordered card. Drops a
1285
1427
  // layer of nesting so the scenario cards below read as the primary unit.
1286
- /* @__PURE__ */ jsxs18(
1428
+ /* @__PURE__ */ jsxs20(
1287
1429
  "section",
1288
1430
  {
1289
1431
  id: feature.id,
@@ -1291,9 +1433,9 @@ function ReportFeature({ feature }) {
1291
1433
  "aria-labelledby": titleId,
1292
1434
  className: "flex flex-col gap-3",
1293
1435
  children: [
1294
- /* @__PURE__ */ jsxs18("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1295
- /* @__PURE__ */ jsxs18("div", { className: "min-w-0", children: [
1296
- /* @__PURE__ */ jsx28("h2", { id: titleId, className: "text-base font-semibold tracking-tight text-foreground", children: collapse ? /* @__PURE__ */ jsxs18(
1436
+ /* @__PURE__ */ jsxs20("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1437
+ /* @__PURE__ */ jsxs20("div", { className: "min-w-0", children: [
1438
+ /* @__PURE__ */ jsx29("h2", { id: titleId, className: "text-base font-semibold tracking-tight text-foreground", children: collapse ? /* @__PURE__ */ jsxs20(
1297
1439
  "button",
1298
1440
  {
1299
1441
  type: "button",
@@ -1302,31 +1444,34 @@ function ReportFeature({ feature }) {
1302
1444
  "aria-controls": bodyId,
1303
1445
  className: "flex w-full cursor-pointer items-center gap-2 text-left hover:text-foreground/80",
1304
1446
  children: [
1305
- /* @__PURE__ */ jsx28("span", { "aria-hidden": true, className: cn("shrink-0 text-xs text-muted-foreground transition-transform", !collapsed && "rotate-90"), children: "\u25B8" }),
1306
- /* @__PURE__ */ jsx28("span", { className: "truncate", children: feature.title })
1447
+ /* @__PURE__ */ jsx29("span", { "aria-hidden": true, className: cn("shrink-0 text-xs text-muted-foreground transition-transform", !collapsed && "rotate-90"), children: "\u25B8" }),
1448
+ /* @__PURE__ */ jsx29("span", { className: "truncate", children: feature.title })
1307
1449
  ]
1308
1450
  }
1309
1451
  ) : feature.title }),
1310
- /* @__PURE__ */ jsx28("p", { className: cn("mt-0.5 truncate font-mono text-xs text-muted-foreground", collapse && "pl-5"), children: feature.sourceFile })
1452
+ /* @__PURE__ */ jsxs20("p", { className: cn("mt-0.5 flex items-center gap-2 text-xs text-muted-foreground", collapse && "pl-5"), children: [
1453
+ kindLabel ? /* @__PURE__ */ jsx29("span", { className: "shrink-0 rounded-sm bg-muted px-1.5 py-0.5 font-medium text-foreground", children: kindLabel }) : null,
1454
+ /* @__PURE__ */ jsx29("span", { className: "truncate font-mono", children: feature.sourceFile })
1455
+ ] })
1311
1456
  ] }),
1312
- /* @__PURE__ */ jsxs18(
1457
+ /* @__PURE__ */ jsxs20(
1313
1458
  "p",
1314
1459
  {
1315
1460
  className: "flex shrink-0 items-center gap-1.5 text-xs text-muted-foreground",
1316
1461
  "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
1317
1462
  children: [
1318
- /* @__PURE__ */ jsxs18("span", { children: [
1463
+ /* @__PURE__ */ jsxs20("span", { children: [
1319
1464
  s.passed,
1320
1465
  " passed"
1321
1466
  ] }),
1322
- /* @__PURE__ */ jsx28("span", { "aria-hidden": true, children: "\xB7" }),
1323
- /* @__PURE__ */ jsxs18("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
1467
+ /* @__PURE__ */ jsx29("span", { "aria-hidden": true, children: "\xB7" }),
1468
+ /* @__PURE__ */ jsxs20("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
1324
1469
  s.failed,
1325
1470
  " failed"
1326
1471
  ] }),
1327
- skipped > 0 ? /* @__PURE__ */ jsxs18(Fragment9, { children: [
1328
- /* @__PURE__ */ jsx28("span", { "aria-hidden": true, children: "\xB7" }),
1329
- /* @__PURE__ */ jsxs18("span", { children: [
1472
+ skipped > 0 ? /* @__PURE__ */ jsxs20(Fragment9, { children: [
1473
+ /* @__PURE__ */ jsx29("span", { "aria-hidden": true, children: "\xB7" }),
1474
+ /* @__PURE__ */ jsxs20("span", { children: [
1330
1475
  skipped,
1331
1476
  " skipped"
1332
1477
  ] })
@@ -1335,7 +1480,10 @@ function ReportFeature({ feature }) {
1335
1480
  }
1336
1481
  )
1337
1482
  ] }),
1338
- /* @__PURE__ */ jsx28("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-3", children: /* @__PURE__ */ jsx28(ReportScenarioList, { feature }) })
1483
+ /* @__PURE__ */ jsxs20("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-3", children: [
1484
+ /* @__PURE__ */ jsx29(ReportFeatureIntro, { feature }),
1485
+ /* @__PURE__ */ jsx29(ReportScenarioList, { feature })
1486
+ ] })
1339
1487
  ]
1340
1488
  }
1341
1489
  )
@@ -1343,17 +1491,17 @@ function ReportFeature({ feature }) {
1343
1491
  }
1344
1492
 
1345
1493
  // src/components/ReportFeatureList.tsx
1346
- import { jsx as jsx29 } from "react/jsx-runtime";
1494
+ import { jsx as jsx30 } from "react/jsx-runtime";
1347
1495
  function ReportFeatureList() {
1348
1496
  const report = useReport();
1349
- return /* @__PURE__ */ jsx29("div", { className: "flex flex-col gap-8", children: report.features.map((feature) => /* @__PURE__ */ jsx29(ReportFeature, { feature }, feature.id)) });
1497
+ return /* @__PURE__ */ jsx30("div", { className: "flex flex-col gap-8", children: report.features.map((feature) => /* @__PURE__ */ jsx30(ReportFeature, { feature }, feature.id)) });
1350
1498
  }
1351
1499
 
1352
1500
  // src/components/ui/empty.tsx
1353
1501
  import { cva as cva2 } from "class-variance-authority";
1354
- import { jsx as jsx30 } from "react/jsx-runtime";
1502
+ import { jsx as jsx31 } from "react/jsx-runtime";
1355
1503
  function Empty({ className, ...props }) {
1356
- return /* @__PURE__ */ jsx30(
1504
+ return /* @__PURE__ */ jsx31(
1357
1505
  "div",
1358
1506
  {
1359
1507
  "data-slot": "empty",
@@ -1366,7 +1514,7 @@ function Empty({ className, ...props }) {
1366
1514
  );
1367
1515
  }
1368
1516
  function EmptyHeader({ className, ...props }) {
1369
- return /* @__PURE__ */ jsx30(
1517
+ return /* @__PURE__ */ jsx31(
1370
1518
  "div",
1371
1519
  {
1372
1520
  "data-slot": "empty-header",
@@ -1393,7 +1541,7 @@ var emptyMediaVariants = cva2(
1393
1541
  }
1394
1542
  );
1395
1543
  function EmptyTitle({ className, ...props }) {
1396
- return /* @__PURE__ */ jsx30(
1544
+ return /* @__PURE__ */ jsx31(
1397
1545
  "div",
1398
1546
  {
1399
1547
  "data-slot": "empty-title",
@@ -1404,16 +1552,16 @@ function EmptyTitle({ className, ...props }) {
1404
1552
  }
1405
1553
 
1406
1554
  // src/components/ReportEmpty.tsx
1407
- import { jsx as jsx31 } from "react/jsx-runtime";
1555
+ import { jsx as jsx32 } from "react/jsx-runtime";
1408
1556
  function ReportEmpty({ message }) {
1409
- return /* @__PURE__ */ jsx31(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ jsx31(EmptyHeader, { children: /* @__PURE__ */ jsx31(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1557
+ return /* @__PURE__ */ jsx32(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ jsx32(EmptyHeader, { children: /* @__PURE__ */ jsx32(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1410
1558
  }
1411
1559
 
1412
1560
  // src/components/ReportSummary.tsx
1413
- import { jsx as jsx32, jsxs as jsxs19 } from "react/jsx-runtime";
1561
+ import { jsx as jsx33, jsxs as jsxs21 } from "react/jsx-runtime";
1414
1562
  function ReportSummary({ className }) {
1415
1563
  const report = useReport();
1416
- return /* @__PURE__ */ jsx32(
1564
+ return /* @__PURE__ */ jsx33(
1417
1565
  ReportSummaryView,
1418
1566
  {
1419
1567
  summary: report.summary,
@@ -1439,7 +1587,7 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1439
1587
  if (summary.pending > 0) {
1440
1588
  cards.push({ status: "pending", label: "Pending", value: summary.pending });
1441
1589
  }
1442
- return /* @__PURE__ */ jsx32(
1590
+ return /* @__PURE__ */ jsx33(
1443
1591
  "dl",
1444
1592
  {
1445
1593
  className: cn(
@@ -1452,17 +1600,17 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1452
1600
  "aria-label": ariaLabel,
1453
1601
  children: cards.map((c) => {
1454
1602
  const tone = TONE[c.status];
1455
- return /* @__PURE__ */ jsxs19(
1603
+ return /* @__PURE__ */ jsxs21(
1456
1604
  "div",
1457
1605
  {
1458
1606
  "data-status": c.status,
1459
1607
  className: "rounded-lg border bg-card px-4 py-3",
1460
1608
  children: [
1461
- /* @__PURE__ */ jsxs19("dt", { className: "flex items-center gap-1.5 font-mono text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: [
1462
- /* @__PURE__ */ jsx32("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
1609
+ /* @__PURE__ */ jsxs21("dt", { className: "flex items-center gap-1.5 font-mono text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: [
1610
+ /* @__PURE__ */ jsx33("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
1463
1611
  c.label
1464
1612
  ] }),
1465
- /* @__PURE__ */ jsx32("dd", { className: cn("mt-2 text-3xl font-semibold leading-none tracking-tight tabular-nums", tone.num), children: c.value })
1613
+ /* @__PURE__ */ jsx33("dd", { className: cn("mt-2 text-3xl font-semibold leading-none tracking-tight tabular-nums", tone.num), children: c.value })
1466
1614
  ]
1467
1615
  },
1468
1616
  c.status
@@ -1474,39 +1622,7 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1474
1622
 
1475
1623
  // src/components/ReportMeta.tsx
1476
1624
  import { formatDuration as formatDuration4 } from "executable-stories-core/utils/duration";
1477
-
1478
- // src/lib/provenance.ts
1479
- var CI_DISPLAY_NAMES = {
1480
- github: "GitHub Actions",
1481
- gitlab: "GitLab CI",
1482
- circleci: "CircleCI",
1483
- jenkins: "Jenkins",
1484
- azure: "Azure DevOps",
1485
- buildkite: "Buildkite",
1486
- travis: "Travis CI",
1487
- ci: "CI"
1488
- };
1489
- function ciDisplayName(name) {
1490
- return CI_DISPLAY_NAMES[name] ?? name;
1491
- }
1492
- function commitUrl(ci, sha) {
1493
- const commitSha = ci?.commitSha ?? sha;
1494
- if (!ci?.url || !commitSha) return void 0;
1495
- const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1496
- if (github) return `${github[1]}/commit/${commitSha}`;
1497
- const gitlab = ci.url.match(/^(https?:\/\/.+)\/-\/pipelines\//);
1498
- if (gitlab) return `${gitlab[1]}/-/commit/${commitSha}`;
1499
- return void 0;
1500
- }
1501
- function prUrl(ci) {
1502
- if (!ci?.url || !ci.prNumber) return void 0;
1503
- const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1504
- if (github) return `${github[1]}/pull/${ci.prNumber}`;
1505
- return void 0;
1506
- }
1507
-
1508
- // src/components/ReportMeta.tsx
1509
- import { jsx as jsx33, jsxs as jsxs20 } from "react/jsx-runtime";
1625
+ import { jsx as jsx34, jsxs as jsxs22 } from "react/jsx-runtime";
1510
1626
  function ReportMeta() {
1511
1627
  const report = useReport();
1512
1628
  const items = [];
@@ -1529,13 +1645,13 @@ function ReportMeta() {
1529
1645
  const short = sha.slice(0, 8);
1530
1646
  items.push({
1531
1647
  label: "Commit",
1532
- value: url ? /* @__PURE__ */ jsx33("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1648
+ value: url ? /* @__PURE__ */ jsx34("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1533
1649
  });
1534
1650
  }
1535
1651
  if (report.ci) {
1536
1652
  items.push({
1537
1653
  label: "CI",
1538
- value: report.ci.url ? /* @__PURE__ */ jsxs20("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1654
+ value: report.ci.url ? /* @__PURE__ */ jsxs22("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1539
1655
  ciDisplayName(report.ci.name),
1540
1656
  report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
1541
1657
  ] }) : `${ciDisplayName(report.ci.name)}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
@@ -1546,7 +1662,7 @@ function ReportMeta() {
1546
1662
  const label = `#${report.ci.prNumber}`;
1547
1663
  items.push({
1548
1664
  label: "PR",
1549
- value: url ? /* @__PURE__ */ jsx33("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1665
+ value: url ? /* @__PURE__ */ jsx34("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1550
1666
  });
1551
1667
  }
1552
1668
  if (items.length === 0) return null;
@@ -1554,48 +1670,48 @@ function ReportMeta() {
1554
1670
  // Run metadata is useful but secondary to "what failed" — tuck it into a
1555
1671
  // collapsed disclosure so it doesn't push the result down. Native <details>
1556
1672
  // so it works with no JS (the SSR/no-hydration report still toggles).
1557
- /* @__PURE__ */ jsxs20("details", { "data-es-meta-card": true, className: "es-run-details group rounded-lg border border-border bg-card text-xs", children: [
1558
- /* @__PURE__ */ jsxs20("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: [
1559
- /* @__PURE__ */ jsx33("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
1673
+ /* @__PURE__ */ jsxs22("details", { "data-es-meta-card": true, className: "es-run-details group rounded-lg border border-border bg-card text-xs", children: [
1674
+ /* @__PURE__ */ jsxs22("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: [
1675
+ /* @__PURE__ */ jsx34("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
1560
1676
  "Run details"
1561
1677
  ] }),
1562
- /* @__PURE__ */ jsx33("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__ */ jsxs20("div", { className: "flex items-baseline gap-1.5", children: [
1563
- /* @__PURE__ */ jsx33("dt", { className: "font-medium", children: item.label }),
1564
- /* @__PURE__ */ jsx33("dd", { className: "font-mono text-foreground", children: item.value })
1678
+ /* @__PURE__ */ jsx34("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__ */ jsxs22("div", { className: "flex items-baseline gap-1.5", children: [
1679
+ /* @__PURE__ */ jsx34("dt", { className: "font-medium", children: item.label }),
1680
+ /* @__PURE__ */ jsx34("dd", { className: "font-mono text-foreground", children: item.value })
1565
1681
  ] }, item.label)) })
1566
1682
  ] })
1567
1683
  );
1568
1684
  }
1569
1685
 
1570
1686
  // src/components/ReportSchemaError.tsx
1571
- import { jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
1687
+ import { jsx as jsx35, jsxs as jsxs23 } from "react/jsx-runtime";
1572
1688
  function ReportSchemaError({ error }) {
1573
- return /* @__PURE__ */ jsxs21("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1574
- /* @__PURE__ */ jsx34("p", { children: /* @__PURE__ */ jsx34("strong", { children: "Report could not be displayed." }) }),
1575
- /* @__PURE__ */ jsx34("p", { children: error.message }),
1576
- error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs21("p", { children: [
1689
+ return /* @__PURE__ */ jsxs23("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1690
+ /* @__PURE__ */ jsx35("p", { children: /* @__PURE__ */ jsx35("strong", { children: "Report could not be displayed." }) }),
1691
+ /* @__PURE__ */ jsx35("p", { children: error.message }),
1692
+ error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs23("p", { children: [
1577
1693
  "The report bundle is newer than this version of ",
1578
- /* @__PURE__ */ jsx34("code", { children: "executable-stories-react" }),
1694
+ /* @__PURE__ */ jsx35("code", { children: "executable-stories-react" }),
1579
1695
  ". Upgrade the package, or regenerate the report with an older formatters CLI."
1580
1696
  ] }) : null,
1581
- error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs21("details", { children: [
1582
- /* @__PURE__ */ jsxs21("summary", { children: [
1697
+ error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs23("details", { children: [
1698
+ /* @__PURE__ */ jsxs23("summary", { children: [
1583
1699
  error.issues.length,
1584
1700
  " validation issue",
1585
1701
  error.issues.length === 1 ? "" : "s"
1586
1702
  ] }),
1587
- /* @__PURE__ */ jsx34("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1703
+ /* @__PURE__ */ jsx35("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1588
1704
  ] }) : null
1589
1705
  ] });
1590
1706
  }
1591
1707
 
1592
1708
  // src/components/ReportShell.tsx
1593
- import { Fragment as Fragment10, jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
1709
+ import { Fragment as Fragment10, jsx as jsx36, jsxs as jsxs24 } from "react/jsx-runtime";
1594
1710
  function ReportTitleBlock({ title }) {
1595
- return /* @__PURE__ */ jsxs22(Fragment10, { children: [
1596
- /* @__PURE__ */ jsx35("h1", { children: title ?? "Story Report" }),
1597
- /* @__PURE__ */ jsx35(ReportSummary, {}),
1598
- /* @__PURE__ */ jsx35(ReportMeta, {})
1711
+ return /* @__PURE__ */ jsxs24(Fragment10, { children: [
1712
+ /* @__PURE__ */ jsx36("h1", { children: title ?? "Story Report" }),
1713
+ /* @__PURE__ */ jsx36(ReportSummary, {}),
1714
+ /* @__PURE__ */ jsx36(ReportMeta, {})
1599
1715
  ] });
1600
1716
  }
1601
1717
  function ReportErrorShell({
@@ -1604,22 +1720,22 @@ function ReportErrorShell({
1604
1720
  title,
1605
1721
  dataTheme
1606
1722
  }) {
1607
- return /* @__PURE__ */ jsx35("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ jsx35(ReportSchemaError, { error }) });
1723
+ return /* @__PURE__ */ jsx36("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ jsx36(ReportSchemaError, { error }) });
1608
1724
  }
1609
1725
 
1610
1726
  // src/components/Report.tsx
1611
- import { jsx as jsx36, jsxs as jsxs23 } from "react/jsx-runtime";
1727
+ import { jsx as jsx37, jsxs as jsxs25 } from "react/jsx-runtime";
1612
1728
  function Report(props) {
1613
1729
  const { report, customRenderers, renderers, className, title, dataTheme } = props;
1614
1730
  const result = unwrapReport(report);
1615
1731
  if (!result.ok) {
1616
- return /* @__PURE__ */ jsx36(ReportErrorShell, { error: result.error, className, title, dataTheme });
1732
+ return /* @__PURE__ */ jsx37(ReportErrorShell, { error: result.error, className, title, dataTheme });
1617
1733
  }
1618
1734
  const data = result.data;
1619
1735
  const hasContent = data.features.length > 0;
1620
- return /* @__PURE__ */ jsx36(ReportRoot, { report: data, customRenderers, renderers, children: /* @__PURE__ */ jsxs23("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: [
1621
- /* @__PURE__ */ jsx36("header", { className: "es-report-header", children: /* @__PURE__ */ jsx36(ReportTitleBlock, { title }) }),
1622
- hasContent ? /* @__PURE__ */ jsx36(ReportFeatureList, {}) : /* @__PURE__ */ jsx36(ReportEmpty, {})
1736
+ return /* @__PURE__ */ jsx37(ReportRoot, { report: data, customRenderers, renderers, children: /* @__PURE__ */ jsxs25("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: [
1737
+ /* @__PURE__ */ jsx37("header", { className: "es-report-header", children: /* @__PURE__ */ jsx37(ReportTitleBlock, { title }) }),
1738
+ hasContent ? /* @__PURE__ */ jsx37(ReportFeatureList, {}) : /* @__PURE__ */ jsx37(ReportEmpty, {})
1623
1739
  ] }) });
1624
1740
  }
1625
1741