executable-stories-react 0.10.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -289,23 +289,48 @@ function useReport() {
289
289
  // src/components/ReportScenario.tsx
290
290
  import { formatDuration as formatDuration3 } from "executable-stories-core/utils/duration";
291
291
 
292
+ // src/lib/step-params.tsx
293
+ import { jsx as jsx3 } from "react/jsx-runtime";
294
+ var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.-])\d+(?:\.\d+)?(?![\w.-])/g;
295
+ function highlightStepParams(text) {
296
+ const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
297
+ if (matches.length === 0) return text;
298
+ const nodes = [];
299
+ let lastIndex = 0;
300
+ matches.forEach((match, i) => {
301
+ const start = match.index;
302
+ const end = start + match[0].length;
303
+ if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
304
+ nodes.push(
305
+ /* @__PURE__ */ jsx3("span", { className: "font-medium text-step-param italic", children: match[0] }, i)
306
+ );
307
+ lastIndex = end;
308
+ });
309
+ if (lastIndex < text.length) nodes.push(text.slice(lastIndex));
310
+ return nodes;
311
+ }
312
+
292
313
  // src/components/ReportSteps.tsx
314
+ import {
315
+ assertionState,
316
+ assertiveSteps
317
+ } from "executable-stories-core/utils/assertive-steps";
293
318
  import { formatDuration } from "executable-stories-core/utils/duration";
294
319
 
295
320
  // src/components/doc/DocNote.tsx
296
- import { jsx as jsx3 } from "react/jsx-runtime";
321
+ import { jsx as jsx4 } from "react/jsx-runtime";
297
322
  function DocNote({ entry }) {
298
- return /* @__PURE__ */ jsx3("p", { className: "my-2 text-sm text-muted-foreground", children: entry.text });
323
+ return /* @__PURE__ */ jsx4("p", { className: "my-2 text-sm text-muted-foreground", children: entry.text });
299
324
  }
300
325
 
301
326
  // src/components/doc/DocTag.tsx
302
- import { jsx as jsx4 } from "react/jsx-runtime";
327
+ import { jsx as jsx5 } from "react/jsx-runtime";
303
328
  function DocTag({ entry }) {
304
- 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)) });
329
+ 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)) });
305
330
  }
306
331
 
307
332
  // src/components/doc/DocKv.tsx
308
- import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
333
+ import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
309
334
  function formatValue(value) {
310
335
  if (value === null) return "null";
311
336
  if (typeof value === "string") return value;
@@ -318,8 +343,8 @@ function formatValue(value) {
318
343
  }
319
344
  function DocKv({ entry }) {
320
345
  return /* @__PURE__ */ jsxs2("dl", { className: "my-2 flex flex-wrap items-baseline gap-x-2 text-sm", children: [
321
- /* @__PURE__ */ jsx5("dt", { className: "font-semibold text-muted-foreground", children: entry.label }),
322
- /* @__PURE__ */ jsx5("dd", { className: "font-mono text-foreground", children: formatValue(entry.value) })
346
+ /* @__PURE__ */ jsx6("dt", { className: "font-semibold text-muted-foreground", children: entry.label }),
347
+ /* @__PURE__ */ jsx6("dd", { className: "font-mono text-foreground", children: formatValue(entry.value) })
323
348
  ] });
324
349
  }
325
350
 
@@ -337,37 +362,37 @@ function useBuiltinRenderers() {
337
362
  }
338
363
 
339
364
  // src/components/doc/DocCode.tsx
340
- import { Fragment, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
365
+ import { Fragment, jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
341
366
  function CodeFigure({ label, children }) {
342
367
  return /* @__PURE__ */ jsxs3("figure", { className: "my-3", children: [
343
- label ? /* @__PURE__ */ jsx6("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: label }) : null,
344
- /* @__PURE__ */ jsx6("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3", children })
368
+ label ? /* @__PURE__ */ jsx7("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: label }) : null,
369
+ /* @__PURE__ */ jsx7("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3", children })
345
370
  ] });
346
371
  }
347
372
  function DocCode({ entry }) {
348
373
  const renderers = useBuiltinRenderers();
349
374
  if (renderers.code) {
350
- return /* @__PURE__ */ jsx6(Fragment, { children: renderers.code(entry) });
375
+ return /* @__PURE__ */ jsx7(Fragment, { children: renderers.code(entry) });
351
376
  }
352
- 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 }) });
377
+ 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 }) });
353
378
  }
354
379
 
355
380
  // src/components/doc/DocTable.tsx
356
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
381
+ import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
357
382
  function DocTable({ entry }) {
358
383
  return /* @__PURE__ */ jsxs4("figure", { className: "my-3", children: [
359
- entry.label ? /* @__PURE__ */ jsx7("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
360
- /* @__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: [
361
- /* @__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)) }) }),
362
- /* @__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)) })
384
+ entry.label ? /* @__PURE__ */ jsx8("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
385
+ /* @__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: [
386
+ /* @__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)) }) }),
387
+ /* @__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)) })
363
388
  ] }) })
364
389
  ] });
365
390
  }
366
391
 
367
392
  // src/components/doc/DocLink.tsx
368
- import { jsx as jsx8 } from "react/jsx-runtime";
393
+ import { jsx as jsx9 } from "react/jsx-runtime";
369
394
  function DocLink({ entry }) {
370
- return /* @__PURE__ */ jsx8(
395
+ return /* @__PURE__ */ jsx9(
371
396
  "a",
372
397
  {
373
398
  className: "text-sm font-medium text-primary underline underline-offset-2 hover:no-underline",
@@ -379,30 +404,38 @@ function DocLink({ entry }) {
379
404
  );
380
405
  }
381
406
 
382
- // src/components/doc/DocSection.tsx
407
+ // src/lib/markdown.ts
383
408
  import { marked } from "marked";
384
409
 
385
410
  // src/lib/url.ts
386
411
  import { safeUrl, safeImageUrl, isLocalFsPath } from "executable-stories-core/utils/url";
387
412
 
388
- // src/components/doc/DocSection.tsx
389
- import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
390
- function neutralizeUrl(value) {
391
- return safeUrl(value) ?? "#";
392
- }
413
+ // src/lib/markdown.ts
393
414
  function safeMarkdownHtml(markdown) {
394
415
  const raw = marked.parse(markdown, { async: false });
395
416
  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)}'`);
396
417
  }
418
+ function dedent(text) {
419
+ const lines = text.replace(/\t/g, " ").split("\n");
420
+ const indents = lines.filter((line) => line.trim().length > 0).map((line) => line.length - line.trimStart().length);
421
+ const shortest = indents.length > 0 ? Math.min(...indents) : 0;
422
+ return lines.map((line) => line.slice(shortest)).join("\n").trim();
423
+ }
424
+ function neutralizeUrl(value) {
425
+ return safeUrl(value) ?? "#";
426
+ }
427
+
428
+ // src/components/doc/DocSection.tsx
429
+ import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
397
430
  function DocSection({ entry }) {
398
431
  const renderers = useBuiltinRenderers();
399
432
  if (renderers.section) {
400
- return /* @__PURE__ */ jsx9(Fragment2, { children: renderers.section(entry) });
433
+ return /* @__PURE__ */ jsx10(Fragment2, { children: renderers.section(entry) });
401
434
  }
402
435
  const html = safeMarkdownHtml(entry.markdown);
403
436
  return /* @__PURE__ */ jsxs5("section", { className: "my-3 text-sm", "aria-label": entry.title, children: [
404
- entry.title ? /* @__PURE__ */ jsx9("h4", { className: "mb-1 font-semibold text-foreground", children: entry.title }) : null,
405
- /* @__PURE__ */ jsx9(
437
+ entry.title ? /* @__PURE__ */ jsx10("h4", { className: "mb-1 font-semibold text-foreground", children: entry.title }) : null,
438
+ /* @__PURE__ */ jsx10(
406
439
  "div",
407
440
  {
408
441
  className: "es-doc-prose prose prose-sm max-w-none",
@@ -413,11 +446,11 @@ function DocSection({ entry }) {
413
446
  }
414
447
 
415
448
  // src/components/doc/DocMermaid.tsx
416
- import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
449
+ import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
417
450
  function MermaidSource({ entry, ref }) {
418
451
  return /* @__PURE__ */ jsxs6("figure", { ref, className: "my-3", "aria-label": entry.title ?? "Diagram", children: [
419
- entry.title ? /* @__PURE__ */ jsx10("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
420
- /* @__PURE__ */ jsx10(
452
+ entry.title ? /* @__PURE__ */ jsx11("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
453
+ /* @__PURE__ */ jsx11(
421
454
  "pre",
422
455
  {
423
456
  "data-mermaid": true,
@@ -431,24 +464,24 @@ function MermaidSource({ entry, ref }) {
431
464
  function DocMermaid({ entry }) {
432
465
  const renderers = useBuiltinRenderers();
433
466
  if (renderers.mermaid) {
434
- return /* @__PURE__ */ jsx10(Fragment3, { children: renderers.mermaid(entry) });
467
+ return /* @__PURE__ */ jsx11(Fragment3, { children: renderers.mermaid(entry) });
435
468
  }
436
- return /* @__PURE__ */ jsx10(MermaidSource, { entry });
469
+ return /* @__PURE__ */ jsx11(MermaidSource, { entry });
437
470
  }
438
471
 
439
472
  // src/components/doc/DocScreenshot.tsx
440
- import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
473
+ import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
441
474
  function DocScreenshot({ entry }) {
442
475
  const src = isLocalFsPath(entry.path) ? void 0 : safeImageUrl(entry.path);
443
476
  if (!src) {
444
477
  return /* @__PURE__ */ jsxs7("figure", { className: "my-3 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground", children: [
445
- /* @__PURE__ */ jsx11("div", { className: "font-medium", children: "Screenshot unavailable" }),
446
- /* @__PURE__ */ jsx11("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
447
- entry.alt ? /* @__PURE__ */ jsx11("figcaption", { className: "mt-1.5 text-xs", children: entry.alt }) : null
478
+ /* @__PURE__ */ jsx12("div", { className: "font-medium", children: "Screenshot unavailable" }),
479
+ /* @__PURE__ */ jsx12("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
480
+ entry.alt ? /* @__PURE__ */ jsx12("figcaption", { className: "mt-1.5 text-xs", children: entry.alt }) : null
448
481
  ] });
449
482
  }
450
483
  return /* @__PURE__ */ jsxs7("figure", { className: "my-3", children: [
451
- /* @__PURE__ */ jsx11(
484
+ /* @__PURE__ */ jsx12(
452
485
  "img",
453
486
  {
454
487
  src,
@@ -457,24 +490,24 @@ function DocScreenshot({ entry }) {
457
490
  className: "max-w-full rounded-md border border-border"
458
491
  }
459
492
  ),
460
- entry.alt ? /* @__PURE__ */ jsx11("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: entry.alt }) : null
493
+ entry.alt ? /* @__PURE__ */ jsx12("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: entry.alt }) : null
461
494
  ] });
462
495
  }
463
496
 
464
497
  // src/components/doc/DocVideo.tsx
465
- import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
498
+ import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
466
499
  function DocVideo({ entry }) {
467
500
  if (isLocalFsPath(entry.path)) {
468
501
  return /* @__PURE__ */ jsxs8("figure", { className: "my-3 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground", children: [
469
- /* @__PURE__ */ jsx12("div", { className: "font-medium", children: "Video unavailable" }),
470
- /* @__PURE__ */ jsx12("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
471
- entry.caption ? /* @__PURE__ */ jsx12("figcaption", { className: "mt-1.5 text-xs", children: entry.caption }) : null
502
+ /* @__PURE__ */ jsx13("div", { className: "font-medium", children: "Video unavailable" }),
503
+ /* @__PURE__ */ jsx13("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
504
+ entry.caption ? /* @__PURE__ */ jsx13("figcaption", { className: "mt-1.5 text-xs", children: entry.caption }) : null
472
505
  ] });
473
506
  }
474
507
  const src = safeUrl(entry.path);
475
508
  const poster = safeUrl(entry.poster);
476
509
  return /* @__PURE__ */ jsxs8("figure", { className: "my-3", children: [
477
- /* @__PURE__ */ jsx12(
510
+ /* @__PURE__ */ jsx13(
478
511
  "video",
479
512
  {
480
513
  controls: true,
@@ -484,12 +517,12 @@ function DocVideo({ entry }) {
484
517
  className: "max-w-full rounded-md border border-border"
485
518
  }
486
519
  ),
487
- entry.caption ? /* @__PURE__ */ jsx12("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: entry.caption }) : null
520
+ entry.caption ? /* @__PURE__ */ jsx13("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: entry.caption }) : null
488
521
  ] });
489
522
  }
490
523
 
491
524
  // src/components/doc/DocHtml.tsx
492
- import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
525
+ import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
493
526
  function DocHtml({ entry }) {
494
527
  const height = typeof entry.height === "number" ? `${entry.height}px` : entry.height ?? "400px";
495
528
  const title = entry.title ?? "Embedded HTML";
@@ -497,8 +530,8 @@ function DocHtml({ entry }) {
497
530
  const frameProps = entry.content !== void 0 ? { srcDoc: entry.content } : { src: safeHref ?? "" };
498
531
  return /* @__PURE__ */ jsxs9("figure", { className: "my-3 overflow-hidden rounded-md border border-border", children: [
499
532
  /* @__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: [
500
- /* @__PURE__ */ jsx13("span", { children: entry.title ?? "HTML" }),
501
- safeHref ? /* @__PURE__ */ jsx13(
533
+ /* @__PURE__ */ jsx14("span", { children: entry.title ?? "HTML" }),
534
+ safeHref ? /* @__PURE__ */ jsx14(
502
535
  "a",
503
536
  {
504
537
  href: safeHref,
@@ -511,7 +544,7 @@ function DocHtml({ entry }) {
511
544
  }
512
545
  ) : null
513
546
  ] }),
514
- /* @__PURE__ */ jsx13(
547
+ /* @__PURE__ */ jsx14(
515
548
  "iframe",
516
549
  {
517
550
  ...frameProps,
@@ -526,7 +559,7 @@ function DocHtml({ entry }) {
526
559
  }
527
560
 
528
561
  // src/components/doc/DocState.tsx
529
- import { jsx as jsx14 } from "react/jsx-runtime";
562
+ import { jsx as jsx15 } from "react/jsx-runtime";
530
563
  function DocState({ entry }) {
531
564
  let json;
532
565
  try {
@@ -534,20 +567,20 @@ function DocState({ entry }) {
534
567
  } catch {
535
568
  json = String(entry.value);
536
569
  }
537
- return /* @__PURE__ */ jsx14(CodeFigure, { label: entry.label ?? "State", children: /* @__PURE__ */ jsx14("code", { className: "font-mono text-xs language-json", children: json }) });
570
+ return /* @__PURE__ */ jsx15(CodeFigure, { label: entry.label ?? "State", children: /* @__PURE__ */ jsx15("code", { className: "font-mono text-xs language-json", children: json }) });
538
571
  }
539
572
 
540
573
  // src/components/doc/DocCustom.tsx
541
- import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
574
+ import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
542
575
  function DocCustom({ entry }) {
543
576
  const renderers = useCustomRenderers();
544
577
  const renderer = renderers[entry.type];
545
578
  if (renderer) {
546
- return /* @__PURE__ */ jsx15(Fragment4, { children: renderer(entry) });
579
+ return /* @__PURE__ */ jsx16(Fragment4, { children: renderer(entry) });
547
580
  }
548
581
  return /* @__PURE__ */ jsxs10("div", { className: "my-2", "data-type": entry.type, children: [
549
- /* @__PURE__ */ jsx15("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: entry.type }),
550
- /* @__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) })
582
+ /* @__PURE__ */ jsx16("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: entry.type }),
583
+ /* @__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) })
551
584
  ] });
552
585
  }
553
586
  function safeStringify2(value) {
@@ -559,64 +592,43 @@ function safeStringify2(value) {
559
592
  }
560
593
 
561
594
  // src/components/doc/DocEntry.tsx
562
- import { jsx as jsx16 } from "react/jsx-runtime";
595
+ import { jsx as jsx17 } from "react/jsx-runtime";
563
596
  function DocEntry({ entry }) {
564
597
  switch (entry.kind) {
565
598
  case "note":
566
- return /* @__PURE__ */ jsx16(DocNote, { entry });
599
+ return /* @__PURE__ */ jsx17(DocNote, { entry });
567
600
  case "tag":
568
- return /* @__PURE__ */ jsx16(DocTag, { entry });
601
+ return /* @__PURE__ */ jsx17(DocTag, { entry });
569
602
  case "kv":
570
- return /* @__PURE__ */ jsx16(DocKv, { entry });
603
+ return /* @__PURE__ */ jsx17(DocKv, { entry });
571
604
  case "code":
572
- return /* @__PURE__ */ jsx16(DocCode, { entry });
605
+ return /* @__PURE__ */ jsx17(DocCode, { entry });
573
606
  case "table":
574
- return /* @__PURE__ */ jsx16(DocTable, { entry });
607
+ return /* @__PURE__ */ jsx17(DocTable, { entry });
575
608
  case "link":
576
- return /* @__PURE__ */ jsx16(DocLink, { entry });
609
+ return /* @__PURE__ */ jsx17(DocLink, { entry });
577
610
  case "section":
578
- return /* @__PURE__ */ jsx16(DocSection, { entry });
611
+ return /* @__PURE__ */ jsx17(DocSection, { entry });
579
612
  case "mermaid":
580
- return /* @__PURE__ */ jsx16(DocMermaid, { entry });
613
+ return /* @__PURE__ */ jsx17(DocMermaid, { entry });
581
614
  case "screenshot":
582
- return /* @__PURE__ */ jsx16(DocScreenshot, { entry });
615
+ return /* @__PURE__ */ jsx17(DocScreenshot, { entry });
583
616
  case "video":
584
- return /* @__PURE__ */ jsx16(DocVideo, { entry });
617
+ return /* @__PURE__ */ jsx17(DocVideo, { entry });
585
618
  case "html":
586
- return /* @__PURE__ */ jsx16(DocHtml, { entry });
619
+ return /* @__PURE__ */ jsx17(DocHtml, { entry });
587
620
  case "state":
588
- return /* @__PURE__ */ jsx16(DocState, { entry });
621
+ return /* @__PURE__ */ jsx17(DocState, { entry });
589
622
  case "custom":
590
- return /* @__PURE__ */ jsx16(DocCustom, { entry });
623
+ return /* @__PURE__ */ jsx17(DocCustom, { entry });
591
624
  }
592
625
  }
593
626
 
594
627
  // src/components/ReportDocEntries.tsx
595
- import { Fragment as Fragment5, jsx as jsx17 } from "react/jsx-runtime";
628
+ import { Fragment as Fragment5, jsx as jsx18 } from "react/jsx-runtime";
596
629
  function ReportDocEntries({ entries }) {
597
630
  if (entries.length === 0) return null;
598
- return /* @__PURE__ */ jsx17(Fragment5, { children: entries.map((entry, i) => /* @__PURE__ */ jsx17(DocEntry, { entry }, i)) });
599
- }
600
-
601
- // src/lib/step-params.tsx
602
- import { jsx as jsx18 } from "react/jsx-runtime";
603
- var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.-])\d+(?:\.\d+)?(?![\w.-])/g;
604
- function highlightStepParams(text) {
605
- const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
606
- if (matches.length === 0) return text;
607
- const nodes = [];
608
- let lastIndex = 0;
609
- matches.forEach((match, i) => {
610
- const start = match.index;
611
- const end = start + match[0].length;
612
- if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
613
- nodes.push(
614
- /* @__PURE__ */ jsx18("span", { className: "font-medium text-step-param italic", children: match[0] }, i)
615
- );
616
- lastIndex = end;
617
- });
618
- if (lastIndex < text.length) nodes.push(text.slice(lastIndex));
619
- return nodes;
631
+ return /* @__PURE__ */ jsx18(Fragment5, { children: entries.map((entry, i) => /* @__PURE__ */ jsx18(DocEntry, { entry }, i)) });
620
632
  }
621
633
 
622
634
  // src/components/ReportSteps.tsx
@@ -635,9 +647,22 @@ var STEP_GLYPH_COLOR = {
635
647
  };
636
648
  function ReportSteps({ scenario }) {
637
649
  if (scenario.steps.length === 0) return null;
638
- return /* @__PURE__ */ jsx19("ol", { "data-slot": "steps", className: "flex flex-col", children: scenario.steps.map((step) => /* @__PURE__ */ jsx19(ReportStepItem, { step }, step.id)) });
650
+ const unasserted = new Set(
651
+ assertionState(scenario.steps) === "unasserted" ? assertiveSteps(scenario.steps).map((step) => step.id) : []
652
+ );
653
+ return /* @__PURE__ */ jsx19("ol", { "data-slot": "steps", className: "flex flex-col", children: scenario.steps.map((step) => /* @__PURE__ */ jsx19(
654
+ ReportStepItem,
655
+ {
656
+ step,
657
+ unasserted: unasserted.has(step.id)
658
+ },
659
+ step.id
660
+ )) });
639
661
  }
640
- function ReportStepItem({ step }) {
662
+ function ReportStepItem({
663
+ step,
664
+ unasserted = false
665
+ }) {
641
666
  const kw = step.keyword.toLowerCase();
642
667
  const continuation = kw === "and" || kw === "but";
643
668
  return /* @__PURE__ */ jsxs11(
@@ -645,10 +670,23 @@ function ReportStepItem({ step }) {
645
670
  {
646
671
  id: step.id,
647
672
  "data-status": step.status,
648
- className: cn("py-1.5 text-[0.8125rem] leading-normal", continuation && "pl-5"),
673
+ className: cn(
674
+ "py-1.5 text-[0.8125rem] leading-normal",
675
+ continuation && "pl-5"
676
+ ),
649
677
  children: [
650
678
  /* @__PURE__ */ jsxs11("div", { className: "flex items-baseline gap-2", children: [
651
- /* @__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" }),
679
+ /* @__PURE__ */ jsx19(
680
+ "span",
681
+ {
682
+ "aria-hidden": true,
683
+ className: cn(
684
+ "w-4 shrink-0 text-center text-xs",
685
+ STEP_GLYPH_COLOR[step.status]
686
+ ),
687
+ children: STEP_GLYPH[step.status] ?? "\u25CB"
688
+ }
689
+ ),
652
690
  /* @__PURE__ */ jsx19(
653
691
  "span",
654
692
  {
@@ -660,6 +698,7 @@ function ReportStepItem({ step }) {
660
698
  }
661
699
  ),
662
700
  /* @__PURE__ */ jsx19("span", { className: "flex-1 text-foreground", children: highlightStepParams(step.text) }),
701
+ 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,
663
702
  step.durationMs > 0 ? /* @__PURE__ */ jsx19("span", { className: "shrink-0 whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(step.durationMs) }) : null
664
703
  ] }),
665
704
  step.errorMessage ? /* @__PURE__ */ jsx19(
@@ -1055,21 +1094,104 @@ function ScenarioRunHistory({ scenarioId }) {
1055
1094
  ] });
1056
1095
  }
1057
1096
 
1097
+ // src/components/ScenarioStaleness.tsx
1098
+ import { useEffect, useState } from "react";
1099
+
1100
+ // src/lib/provenance.ts
1101
+ function reportLastRunMs(report) {
1102
+ if (report.finishedAtMs && report.finishedAtMs > 0) return report.finishedAtMs;
1103
+ if (report.startedAtMs && report.startedAtMs > 0) return report.startedAtMs;
1104
+ return void 0;
1105
+ }
1106
+ function scenarioLastRunMs(scenario, report) {
1107
+ if (scenario.lastRunAtMs && scenario.lastRunAtMs > 0) return scenario.lastRunAtMs;
1108
+ return reportLastRunMs(report);
1109
+ }
1110
+ function formatRelativeAge(thenMs, nowMs) {
1111
+ const deltaMs = Math.max(0, nowMs - thenMs);
1112
+ const minutes = Math.floor(deltaMs / 6e4);
1113
+ if (minutes < 1) return "just now";
1114
+ if (minutes < 60) return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
1115
+ const hours = Math.floor(minutes / 60);
1116
+ if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
1117
+ const days = Math.floor(hours / 24);
1118
+ return `${days} day${days === 1 ? "" : "s"} ago`;
1119
+ }
1120
+ function isReportStale(lastRunMs, staleAfterDays, nowMs) {
1121
+ if (lastRunMs === void 0 || staleAfterDays <= 0) return false;
1122
+ return nowMs - lastRunMs >= staleAfterDays * 864e5;
1123
+ }
1124
+ var CI_DISPLAY_NAMES = {
1125
+ github: "GitHub Actions",
1126
+ gitlab: "GitLab CI",
1127
+ circleci: "CircleCI",
1128
+ jenkins: "Jenkins",
1129
+ azure: "Azure DevOps",
1130
+ buildkite: "Buildkite",
1131
+ travis: "Travis CI",
1132
+ ci: "CI"
1133
+ };
1134
+ function ciDisplayName(name) {
1135
+ return CI_DISPLAY_NAMES[name] ?? name;
1136
+ }
1137
+ function commitUrl(ci, sha) {
1138
+ const commitSha = ci?.commitSha ?? sha;
1139
+ if (!ci?.url || !commitSha) return void 0;
1140
+ const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1141
+ if (github) return `${github[1]}/commit/${commitSha}`;
1142
+ const gitlab = ci.url.match(/^(https?:\/\/.+)\/-\/pipelines\//);
1143
+ if (gitlab) return `${gitlab[1]}/-/commit/${commitSha}`;
1144
+ return void 0;
1145
+ }
1146
+ function prUrl(ci) {
1147
+ if (!ci?.url || !ci.prNumber) return void 0;
1148
+ const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1149
+ if (github) return `${github[1]}/pull/${ci.prNumber}`;
1150
+ return void 0;
1151
+ }
1152
+
1153
+ // src/interactive/scenario-freshness-context.tsx
1154
+ import { createContext as createContext3, useContext as useContext4 } from "react";
1155
+ var ScenarioFreshnessContext = createContext3(null);
1156
+ var ScenarioFreshnessProvider = ScenarioFreshnessContext.Provider;
1157
+ function useScenarioFreshness() {
1158
+ return useContext4(ScenarioFreshnessContext);
1159
+ }
1160
+
1161
+ // src/components/ScenarioStaleness.tsx
1162
+ import { jsxs as jsxs16 } from "react/jsx-runtime";
1163
+ function ScenarioStaleness({ scenario }) {
1164
+ const freshness = useScenarioFreshness();
1165
+ const fixedNow = freshness?.nowMs;
1166
+ const [now, setNow] = useState(fixedNow);
1167
+ useEffect(() => {
1168
+ if (fixedNow === void 0) setNow(Date.now());
1169
+ }, [fixedNow]);
1170
+ if (!freshness || now === void 0) return null;
1171
+ const lastRunAtMs = scenario.lastRunAtMs;
1172
+ if (!lastRunAtMs || lastRunAtMs <= 0) return null;
1173
+ if (!isReportStale(lastRunAtMs, freshness.staleAfterDays, now)) return null;
1174
+ return /* @__PURE__ */ jsxs16(Badge, { variant: "outline", "aria-label": "Scenario freshness", children: [
1175
+ "Last ran ",
1176
+ formatRelativeAge(lastRunAtMs, now)
1177
+ ] });
1178
+ }
1179
+
1058
1180
  // src/interactive/collapse-context.ts
1059
- import { createContext as createContext3, useCallback, useContext as useContext4, useState } from "react";
1060
- var CollapseContext = createContext3(null);
1181
+ import { createContext as createContext4, useCallback, useContext as useContext5, useState as useState2 } from "react";
1182
+ var CollapseContext = createContext4(null);
1061
1183
  var CollapseProvider = CollapseContext.Provider;
1062
1184
  function useCollapse() {
1063
- return useContext4(CollapseContext);
1185
+ return useContext5(CollapseContext);
1064
1186
  }
1065
1187
 
1066
1188
  // src/interactive/scenario-actions.tsx
1067
- import { createContext as createContext4, useContext as useContext5 } from "react";
1189
+ import { createContext as createContext5, useContext as useContext6 } from "react";
1068
1190
  import { scenarioToMarkdown as coreScenarioToMarkdown } from "executable-stories-core/utils/scenario-markdown";
1069
- var ScenarioActionsContext = createContext4(null);
1191
+ var ScenarioActionsContext = createContext5(null);
1070
1192
  var ScenarioActionsProvider = ScenarioActionsContext.Provider;
1071
1193
  function useScenarioActions() {
1072
- return useContext5(ScenarioActionsContext);
1194
+ return useContext6(ScenarioActionsContext);
1073
1195
  }
1074
1196
 
1075
1197
  // src/components/ui/card.tsx
@@ -1116,7 +1238,7 @@ function CardContent({ className, ...props }) {
1116
1238
  import "react";
1117
1239
  import { Menu as MenuPrimitive } from "@base-ui/react/menu";
1118
1240
  import { CheckIcon } from "lucide-react";
1119
- import { jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
1241
+ import { jsx as jsx25, jsxs as jsxs17 } from "react/jsx-runtime";
1120
1242
  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";
1121
1243
  function DropdownMenu(props) {
1122
1244
  return /* @__PURE__ */ jsx25(MenuPrimitive.Root, { "data-slot": "dropdown-menu", ...props });
@@ -1167,7 +1289,7 @@ function DropdownMenuItem({
1167
1289
 
1168
1290
  // src/components/ReportScenario.tsx
1169
1291
  import { LinkIcon, FileTextIcon, SparklesIcon, MoreHorizontalIcon } from "lucide-react";
1170
- import { jsx as jsx26, jsxs as jsxs17 } from "react/jsx-runtime";
1292
+ import { jsx as jsx26, jsxs as jsxs18 } from "react/jsx-runtime";
1171
1293
  var STATUS_META = {
1172
1294
  passed: { label: "Passed", glyph: "\u2713", glyphColor: "text-pass", badgeVariant: "passed", showPill: false },
1173
1295
  failed: { label: "Failed", glyph: "\u2717", glyphColor: "text-fail", badgeVariant: "failed", showPill: true },
@@ -1183,7 +1305,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
1183
1305
  const collapsible = collapse !== null && !hideTitle;
1184
1306
  const collapsed = collapsible ? collapse.isCollapsed(scenario.id) : false;
1185
1307
  const actions = useScenarioActions();
1186
- return /* @__PURE__ */ jsxs17(
1308
+ return /* @__PURE__ */ jsxs18(
1187
1309
  Card,
1188
1310
  {
1189
1311
  id: scenario.id,
@@ -1191,9 +1313,9 @@ function ReportScenario({ scenario, hideTitle = false }) {
1191
1313
  ...hideTitle ? { "aria-label": `${scenario.title}, ${label}` } : { "aria-labelledby": titleId },
1192
1314
  className: "gap-0 py-0",
1193
1315
  children: [
1194
- /* @__PURE__ */ jsxs17(CardHeader, { className: "px-4 py-3", children: [
1195
- /* @__PURE__ */ jsxs17("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
1196
- hideTitle ? null : /* @__PURE__ */ jsxs17("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1316
+ /* @__PURE__ */ jsxs18(CardHeader, { className: "px-4 py-3", children: [
1317
+ /* @__PURE__ */ jsxs18("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
1318
+ hideTitle ? null : /* @__PURE__ */ jsxs18("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1197
1319
  collapsible ? /* @__PURE__ */ jsx26(
1198
1320
  "button",
1199
1321
  {
@@ -1209,11 +1331,12 @@ function ReportScenario({ scenario, hideTitle = false }) {
1209
1331
  /* @__PURE__ */ jsx26("span", { "aria-hidden": true, className: cn("text-sm leading-none", meta.glyphColor), children: meta.glyph }),
1210
1332
  /* @__PURE__ */ jsx26("span", { children: scenario.title })
1211
1333
  ] }),
1212
- /* @__PURE__ */ jsxs17("div", { className: "flex shrink-0 items-center gap-2", children: [
1334
+ /* @__PURE__ */ jsxs18("div", { className: "flex shrink-0 items-center gap-2", children: [
1213
1335
  /* @__PURE__ */ jsx26(ScenarioRunHistory, { scenarioId: scenario.id }),
1336
+ /* @__PURE__ */ jsx26(ScenarioStaleness, { scenario }),
1214
1337
  scenario.durationMs > 0 ? /* @__PURE__ */ jsx26("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration3(scenario.durationMs) }) : null,
1215
1338
  meta.showPill ? /* @__PURE__ */ jsx26(Badge, { variant: meta.badgeVariant, "aria-label": `Status: ${label}`, children: label }) : /* @__PURE__ */ jsx26("span", { className: "sr-only", children: `Status: ${label}` }),
1216
- actions ? /* @__PURE__ */ jsxs17(DropdownMenu, { children: [
1339
+ actions ? /* @__PURE__ */ jsxs18(DropdownMenu, { children: [
1217
1340
  /* @__PURE__ */ jsx26(
1218
1341
  DropdownMenuTrigger,
1219
1342
  {
@@ -1222,16 +1345,16 @@ function ReportScenario({ scenario, hideTitle = false }) {
1222
1345
  children: /* @__PURE__ */ jsx26(MoreHorizontalIcon, { "aria-hidden": true })
1223
1346
  }
1224
1347
  ),
1225
- /* @__PURE__ */ jsxs17(DropdownMenuContent, { align: "end", children: [
1226
- /* @__PURE__ */ jsxs17(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
1348
+ /* @__PURE__ */ jsxs18(DropdownMenuContent, { align: "end", children: [
1349
+ /* @__PURE__ */ jsxs18(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
1227
1350
  /* @__PURE__ */ jsx26(LinkIcon, {}),
1228
1351
  " Copy link"
1229
1352
  ] }),
1230
- /* @__PURE__ */ jsxs17(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
1353
+ /* @__PURE__ */ jsxs18(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
1231
1354
  /* @__PURE__ */ jsx26(FileTextIcon, {}),
1232
1355
  " Copy as Markdown"
1233
1356
  ] }),
1234
- /* @__PURE__ */ jsxs17(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
1357
+ /* @__PURE__ */ jsxs18(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
1235
1358
  /* @__PURE__ */ jsx26(SparklesIcon, {}),
1236
1359
  " Explain with AI"
1237
1360
  ] })
@@ -1239,12 +1362,12 @@ function ReportScenario({ scenario, hideTitle = false }) {
1239
1362
  ] }) : null
1240
1363
  ] })
1241
1364
  ] }),
1242
- 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: [
1365
+ 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: [
1243
1366
  scenario.tags.map((t) => /* @__PURE__ */ jsx26("li", { children: /* @__PURE__ */ jsx26(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
1244
1367
  (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}`))
1245
1368
  ] }) : null
1246
1369
  ] }),
1247
- /* @__PURE__ */ jsxs17(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
1370
+ /* @__PURE__ */ jsxs18(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
1248
1371
  scenario.errorMessage ? /* @__PURE__ */ jsx26(
1249
1372
  "pre",
1250
1373
  {
@@ -1270,8 +1393,35 @@ function ReportScenarioList({ feature }) {
1270
1393
  return /* @__PURE__ */ jsx27(Fragment8, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ jsx27(ReportScenario, { scenario }, scenario.id)) });
1271
1394
  }
1272
1395
 
1396
+ // src/components/ReportFeatureIntro.tsx
1397
+ import { jsx as jsx28, jsxs as jsxs19 } from "react/jsx-runtime";
1398
+ function ReportFeatureIntro({ feature }) {
1399
+ const hasNarrative = Boolean(feature.narrative?.trim());
1400
+ const hasGlossary = Boolean(feature.glossary?.length);
1401
+ if (!hasNarrative && !hasGlossary) return null;
1402
+ return /* @__PURE__ */ jsxs19("div", { className: "flex flex-col gap-3 border-l-2 border-border pl-4 text-sm", children: [
1403
+ hasNarrative ? /* @__PURE__ */ jsx28(
1404
+ "div",
1405
+ {
1406
+ className: "es-doc-prose prose prose-sm max-w-none text-muted-foreground",
1407
+ dangerouslySetInnerHTML: { __html: safeMarkdownHtml(dedent(feature.narrative)) }
1408
+ }
1409
+ ) : null,
1410
+ 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: [
1411
+ /* @__PURE__ */ jsx28("dt", { className: "font-medium text-foreground", children: entry.term }),
1412
+ /* @__PURE__ */ jsx28("dd", { className: "flex-1 text-muted-foreground", children: entry.definition })
1413
+ ] }, entry.term)) }) : null
1414
+ ] });
1415
+ }
1416
+ function featureKindLabel(kind) {
1417
+ if (kind === "ability") return "Ability";
1418
+ if (kind === "business-need") return "Business need";
1419
+ if (kind === "feature") return "Feature";
1420
+ return null;
1421
+ }
1422
+
1273
1423
  // src/components/ReportFeature.tsx
1274
- import { Fragment as Fragment9, jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
1424
+ import { Fragment as Fragment9, jsx as jsx29, jsxs as jsxs20 } from "react/jsx-runtime";
1275
1425
  function ReportFeature({ feature }) {
1276
1426
  const titleId = `${feature.id}-title`;
1277
1427
  const bodyId = `${feature.id}-body`;
@@ -1279,10 +1429,11 @@ function ReportFeature({ feature }) {
1279
1429
  const skipped = s.skipped + s.pending;
1280
1430
  const collapse = useCollapse();
1281
1431
  const collapsed = collapse?.isCollapsed(feature.id) ?? false;
1432
+ const kindLabel = featureKindLabel(feature.kind);
1282
1433
  return (
1283
1434
  // Suite = a section heading + a divider, NOT another bordered card. Drops a
1284
1435
  // layer of nesting so the scenario cards below read as the primary unit.
1285
- /* @__PURE__ */ jsxs18(
1436
+ /* @__PURE__ */ jsxs20(
1286
1437
  "section",
1287
1438
  {
1288
1439
  id: feature.id,
@@ -1290,9 +1441,9 @@ function ReportFeature({ feature }) {
1290
1441
  "aria-labelledby": titleId,
1291
1442
  className: "flex flex-col gap-3",
1292
1443
  children: [
1293
- /* @__PURE__ */ jsxs18("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1294
- /* @__PURE__ */ jsxs18("div", { className: "min-w-0", children: [
1295
- /* @__PURE__ */ jsx28("h2", { id: titleId, className: "text-base font-semibold tracking-tight text-foreground", children: collapse ? /* @__PURE__ */ jsxs18(
1444
+ /* @__PURE__ */ jsxs20("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1445
+ /* @__PURE__ */ jsxs20("div", { className: "min-w-0", children: [
1446
+ /* @__PURE__ */ jsx29("h2", { id: titleId, className: "text-base font-semibold tracking-tight text-foreground", children: collapse ? /* @__PURE__ */ jsxs20(
1296
1447
  "button",
1297
1448
  {
1298
1449
  type: "button",
@@ -1301,31 +1452,34 @@ function ReportFeature({ feature }) {
1301
1452
  "aria-controls": bodyId,
1302
1453
  className: "flex w-full cursor-pointer items-center gap-2 text-left hover:text-foreground/80",
1303
1454
  children: [
1304
- /* @__PURE__ */ jsx28("span", { "aria-hidden": true, className: cn("shrink-0 text-xs text-muted-foreground transition-transform", !collapsed && "rotate-90"), children: "\u25B8" }),
1305
- /* @__PURE__ */ jsx28("span", { className: "truncate", children: feature.title })
1455
+ /* @__PURE__ */ jsx29("span", { "aria-hidden": true, className: cn("shrink-0 text-xs text-muted-foreground transition-transform", !collapsed && "rotate-90"), children: "\u25B8" }),
1456
+ /* @__PURE__ */ jsx29("span", { className: "truncate", children: feature.title })
1306
1457
  ]
1307
1458
  }
1308
1459
  ) : feature.title }),
1309
- /* @__PURE__ */ jsx28("p", { className: cn("mt-0.5 truncate font-mono text-xs text-muted-foreground", collapse && "pl-5"), children: feature.sourceFile })
1460
+ /* @__PURE__ */ jsxs20("p", { className: cn("mt-0.5 flex items-center gap-2 text-xs text-muted-foreground", collapse && "pl-5"), children: [
1461
+ kindLabel ? /* @__PURE__ */ jsx29("span", { className: "shrink-0 rounded-sm bg-muted px-1.5 py-0.5 font-medium text-foreground", children: kindLabel }) : null,
1462
+ /* @__PURE__ */ jsx29("span", { className: "truncate font-mono", children: feature.sourceFile })
1463
+ ] })
1310
1464
  ] }),
1311
- /* @__PURE__ */ jsxs18(
1465
+ /* @__PURE__ */ jsxs20(
1312
1466
  "p",
1313
1467
  {
1314
1468
  className: "flex shrink-0 items-center gap-1.5 text-xs text-muted-foreground",
1315
1469
  "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
1316
1470
  children: [
1317
- /* @__PURE__ */ jsxs18("span", { children: [
1471
+ /* @__PURE__ */ jsxs20("span", { children: [
1318
1472
  s.passed,
1319
1473
  " passed"
1320
1474
  ] }),
1321
- /* @__PURE__ */ jsx28("span", { "aria-hidden": true, children: "\xB7" }),
1322
- /* @__PURE__ */ jsxs18("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
1475
+ /* @__PURE__ */ jsx29("span", { "aria-hidden": true, children: "\xB7" }),
1476
+ /* @__PURE__ */ jsxs20("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
1323
1477
  s.failed,
1324
1478
  " failed"
1325
1479
  ] }),
1326
- skipped > 0 ? /* @__PURE__ */ jsxs18(Fragment9, { children: [
1327
- /* @__PURE__ */ jsx28("span", { "aria-hidden": true, children: "\xB7" }),
1328
- /* @__PURE__ */ jsxs18("span", { children: [
1480
+ skipped > 0 ? /* @__PURE__ */ jsxs20(Fragment9, { children: [
1481
+ /* @__PURE__ */ jsx29("span", { "aria-hidden": true, children: "\xB7" }),
1482
+ /* @__PURE__ */ jsxs20("span", { children: [
1329
1483
  skipped,
1330
1484
  " skipped"
1331
1485
  ] })
@@ -1334,7 +1488,10 @@ function ReportFeature({ feature }) {
1334
1488
  }
1335
1489
  )
1336
1490
  ] }),
1337
- /* @__PURE__ */ jsx28("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-3", children: /* @__PURE__ */ jsx28(ReportScenarioList, { feature }) })
1491
+ /* @__PURE__ */ jsxs20("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-3", children: [
1492
+ /* @__PURE__ */ jsx29(ReportFeatureIntro, { feature }),
1493
+ /* @__PURE__ */ jsx29(ReportScenarioList, { feature })
1494
+ ] })
1338
1495
  ]
1339
1496
  }
1340
1497
  )
@@ -1342,17 +1499,17 @@ function ReportFeature({ feature }) {
1342
1499
  }
1343
1500
 
1344
1501
  // src/components/ReportFeatureList.tsx
1345
- import { jsx as jsx29 } from "react/jsx-runtime";
1502
+ import { jsx as jsx30 } from "react/jsx-runtime";
1346
1503
  function ReportFeatureList() {
1347
1504
  const report = useReport();
1348
- return /* @__PURE__ */ jsx29("div", { className: "flex flex-col gap-8", children: report.features.map((feature) => /* @__PURE__ */ jsx29(ReportFeature, { feature }, feature.id)) });
1505
+ return /* @__PURE__ */ jsx30("div", { className: "flex flex-col gap-8", children: report.features.map((feature) => /* @__PURE__ */ jsx30(ReportFeature, { feature }, feature.id)) });
1349
1506
  }
1350
1507
 
1351
1508
  // src/components/ui/empty.tsx
1352
1509
  import { cva as cva2 } from "class-variance-authority";
1353
- import { jsx as jsx30 } from "react/jsx-runtime";
1510
+ import { jsx as jsx31 } from "react/jsx-runtime";
1354
1511
  function Empty({ className, ...props }) {
1355
- return /* @__PURE__ */ jsx30(
1512
+ return /* @__PURE__ */ jsx31(
1356
1513
  "div",
1357
1514
  {
1358
1515
  "data-slot": "empty",
@@ -1365,7 +1522,7 @@ function Empty({ className, ...props }) {
1365
1522
  );
1366
1523
  }
1367
1524
  function EmptyHeader({ className, ...props }) {
1368
- return /* @__PURE__ */ jsx30(
1525
+ return /* @__PURE__ */ jsx31(
1369
1526
  "div",
1370
1527
  {
1371
1528
  "data-slot": "empty-header",
@@ -1392,7 +1549,7 @@ var emptyMediaVariants = cva2(
1392
1549
  }
1393
1550
  );
1394
1551
  function EmptyTitle({ className, ...props }) {
1395
- return /* @__PURE__ */ jsx30(
1552
+ return /* @__PURE__ */ jsx31(
1396
1553
  "div",
1397
1554
  {
1398
1555
  "data-slot": "empty-title",
@@ -1403,16 +1560,16 @@ function EmptyTitle({ className, ...props }) {
1403
1560
  }
1404
1561
 
1405
1562
  // src/components/ReportEmpty.tsx
1406
- import { jsx as jsx31 } from "react/jsx-runtime";
1563
+ import { jsx as jsx32 } from "react/jsx-runtime";
1407
1564
  function ReportEmpty({ message }) {
1408
- return /* @__PURE__ */ jsx31(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ jsx31(EmptyHeader, { children: /* @__PURE__ */ jsx31(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1565
+ return /* @__PURE__ */ jsx32(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ jsx32(EmptyHeader, { children: /* @__PURE__ */ jsx32(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1409
1566
  }
1410
1567
 
1411
1568
  // src/components/ReportSummary.tsx
1412
- import { jsx as jsx32, jsxs as jsxs19 } from "react/jsx-runtime";
1569
+ import { jsx as jsx33, jsxs as jsxs21 } from "react/jsx-runtime";
1413
1570
  function ReportSummary({ className }) {
1414
1571
  const report = useReport();
1415
- return /* @__PURE__ */ jsx32(
1572
+ return /* @__PURE__ */ jsx33(
1416
1573
  ReportSummaryView,
1417
1574
  {
1418
1575
  summary: report.summary,
@@ -1438,7 +1595,7 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1438
1595
  if (summary.pending > 0) {
1439
1596
  cards.push({ status: "pending", label: "Pending", value: summary.pending });
1440
1597
  }
1441
- return /* @__PURE__ */ jsx32(
1598
+ return /* @__PURE__ */ jsx33(
1442
1599
  "dl",
1443
1600
  {
1444
1601
  className: cn(
@@ -1451,17 +1608,17 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1451
1608
  "aria-label": ariaLabel,
1452
1609
  children: cards.map((c) => {
1453
1610
  const tone = TONE[c.status];
1454
- return /* @__PURE__ */ jsxs19(
1611
+ return /* @__PURE__ */ jsxs21(
1455
1612
  "div",
1456
1613
  {
1457
1614
  "data-status": c.status,
1458
1615
  className: "rounded-lg border bg-card px-4 py-3",
1459
1616
  children: [
1460
- /* @__PURE__ */ jsxs19("dt", { className: "flex items-center gap-1.5 font-mono text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: [
1461
- /* @__PURE__ */ jsx32("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
1617
+ /* @__PURE__ */ jsxs21("dt", { className: "flex items-center gap-1.5 font-mono text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: [
1618
+ /* @__PURE__ */ jsx33("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
1462
1619
  c.label
1463
1620
  ] }),
1464
- /* @__PURE__ */ jsx32("dd", { className: cn("mt-2 text-3xl font-semibold leading-none tracking-tight tabular-nums", tone.num), children: c.value })
1621
+ /* @__PURE__ */ jsx33("dd", { className: cn("mt-2 text-3xl font-semibold leading-none tracking-tight tabular-nums", tone.num), children: c.value })
1465
1622
  ]
1466
1623
  },
1467
1624
  c.status
@@ -1473,58 +1630,7 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1473
1630
 
1474
1631
  // src/components/ReportMeta.tsx
1475
1632
  import { formatDuration as formatDuration4 } from "executable-stories-core/utils/duration";
1476
-
1477
- // src/lib/provenance.ts
1478
- function reportLastRunMs(report) {
1479
- if (report.finishedAtMs && report.finishedAtMs > 0) return report.finishedAtMs;
1480
- if (report.startedAtMs && report.startedAtMs > 0) return report.startedAtMs;
1481
- return void 0;
1482
- }
1483
- function formatRelativeAge(thenMs, nowMs) {
1484
- const deltaMs = Math.max(0, nowMs - thenMs);
1485
- const minutes = Math.floor(deltaMs / 6e4);
1486
- if (minutes < 1) return "just now";
1487
- if (minutes < 60) return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
1488
- const hours = Math.floor(minutes / 60);
1489
- if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
1490
- const days = Math.floor(hours / 24);
1491
- return `${days} day${days === 1 ? "" : "s"} ago`;
1492
- }
1493
- function isReportStale(lastRunMs, staleAfterDays, nowMs) {
1494
- if (lastRunMs === void 0 || staleAfterDays <= 0) return false;
1495
- return nowMs - lastRunMs >= staleAfterDays * 864e5;
1496
- }
1497
- var CI_DISPLAY_NAMES = {
1498
- github: "GitHub Actions",
1499
- gitlab: "GitLab CI",
1500
- circleci: "CircleCI",
1501
- jenkins: "Jenkins",
1502
- azure: "Azure DevOps",
1503
- buildkite: "Buildkite",
1504
- travis: "Travis CI",
1505
- ci: "CI"
1506
- };
1507
- function ciDisplayName(name) {
1508
- return CI_DISPLAY_NAMES[name] ?? name;
1509
- }
1510
- function commitUrl(ci, sha) {
1511
- const commitSha = ci?.commitSha ?? sha;
1512
- if (!ci?.url || !commitSha) return void 0;
1513
- const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1514
- if (github) return `${github[1]}/commit/${commitSha}`;
1515
- const gitlab = ci.url.match(/^(https?:\/\/.+)\/-\/pipelines\//);
1516
- if (gitlab) return `${gitlab[1]}/-/commit/${commitSha}`;
1517
- return void 0;
1518
- }
1519
- function prUrl(ci) {
1520
- if (!ci?.url || !ci.prNumber) return void 0;
1521
- const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1522
- if (github) return `${github[1]}/pull/${ci.prNumber}`;
1523
- return void 0;
1524
- }
1525
-
1526
- // src/components/ReportMeta.tsx
1527
- import { jsx as jsx33, jsxs as jsxs20 } from "react/jsx-runtime";
1633
+ import { jsx as jsx34, jsxs as jsxs22 } from "react/jsx-runtime";
1528
1634
  function ReportMeta() {
1529
1635
  const report = useReport();
1530
1636
  const items = [];
@@ -1547,13 +1653,13 @@ function ReportMeta() {
1547
1653
  const short = sha.slice(0, 8);
1548
1654
  items.push({
1549
1655
  label: "Commit",
1550
- value: url ? /* @__PURE__ */ jsx33("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1656
+ value: url ? /* @__PURE__ */ jsx34("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1551
1657
  });
1552
1658
  }
1553
1659
  if (report.ci) {
1554
1660
  items.push({
1555
1661
  label: "CI",
1556
- value: report.ci.url ? /* @__PURE__ */ jsxs20("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1662
+ value: report.ci.url ? /* @__PURE__ */ jsxs22("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1557
1663
  ciDisplayName(report.ci.name),
1558
1664
  report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
1559
1665
  ] }) : `${ciDisplayName(report.ci.name)}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
@@ -1564,7 +1670,7 @@ function ReportMeta() {
1564
1670
  const label = `#${report.ci.prNumber}`;
1565
1671
  items.push({
1566
1672
  label: "PR",
1567
- value: url ? /* @__PURE__ */ jsx33("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1673
+ value: url ? /* @__PURE__ */ jsx34("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1568
1674
  });
1569
1675
  }
1570
1676
  if (items.length === 0) return null;
@@ -1572,48 +1678,48 @@ function ReportMeta() {
1572
1678
  // Run metadata is useful but secondary to "what failed" — tuck it into a
1573
1679
  // collapsed disclosure so it doesn't push the result down. Native <details>
1574
1680
  // so it works with no JS (the SSR/no-hydration report still toggles).
1575
- /* @__PURE__ */ jsxs20("details", { "data-es-meta-card": true, className: "es-run-details group rounded-lg border border-border bg-card text-xs", children: [
1576
- /* @__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: [
1577
- /* @__PURE__ */ jsx33("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
1681
+ /* @__PURE__ */ jsxs22("details", { "data-es-meta-card": true, className: "es-run-details group rounded-lg border border-border bg-card text-xs", children: [
1682
+ /* @__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: [
1683
+ /* @__PURE__ */ jsx34("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
1578
1684
  "Run details"
1579
1685
  ] }),
1580
- /* @__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: [
1581
- /* @__PURE__ */ jsx33("dt", { className: "font-medium", children: item.label }),
1582
- /* @__PURE__ */ jsx33("dd", { className: "font-mono text-foreground", children: item.value })
1686
+ /* @__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: [
1687
+ /* @__PURE__ */ jsx34("dt", { className: "font-medium", children: item.label }),
1688
+ /* @__PURE__ */ jsx34("dd", { className: "font-mono text-foreground", children: item.value })
1583
1689
  ] }, item.label)) })
1584
1690
  ] })
1585
1691
  );
1586
1692
  }
1587
1693
 
1588
1694
  // src/components/ReportSchemaError.tsx
1589
- import { jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
1695
+ import { jsx as jsx35, jsxs as jsxs23 } from "react/jsx-runtime";
1590
1696
  function ReportSchemaError({ error }) {
1591
- return /* @__PURE__ */ jsxs21("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1592
- /* @__PURE__ */ jsx34("p", { children: /* @__PURE__ */ jsx34("strong", { children: "Report could not be displayed." }) }),
1593
- /* @__PURE__ */ jsx34("p", { children: error.message }),
1594
- error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs21("p", { children: [
1697
+ return /* @__PURE__ */ jsxs23("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1698
+ /* @__PURE__ */ jsx35("p", { children: /* @__PURE__ */ jsx35("strong", { children: "Report could not be displayed." }) }),
1699
+ /* @__PURE__ */ jsx35("p", { children: error.message }),
1700
+ error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs23("p", { children: [
1595
1701
  "The report bundle is newer than this version of ",
1596
- /* @__PURE__ */ jsx34("code", { children: "executable-stories-react" }),
1702
+ /* @__PURE__ */ jsx35("code", { children: "executable-stories-react" }),
1597
1703
  ". Upgrade the package, or regenerate the report with an older formatters CLI."
1598
1704
  ] }) : null,
1599
- error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs21("details", { children: [
1600
- /* @__PURE__ */ jsxs21("summary", { children: [
1705
+ error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs23("details", { children: [
1706
+ /* @__PURE__ */ jsxs23("summary", { children: [
1601
1707
  error.issues.length,
1602
1708
  " validation issue",
1603
1709
  error.issues.length === 1 ? "" : "s"
1604
1710
  ] }),
1605
- /* @__PURE__ */ jsx34("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1711
+ /* @__PURE__ */ jsx35("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1606
1712
  ] }) : null
1607
1713
  ] });
1608
1714
  }
1609
1715
 
1610
1716
  // src/components/ReportShell.tsx
1611
- import { Fragment as Fragment10, jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
1717
+ import { Fragment as Fragment10, jsx as jsx36, jsxs as jsxs24 } from "react/jsx-runtime";
1612
1718
  function ReportTitleBlock({ title }) {
1613
- return /* @__PURE__ */ jsxs22(Fragment10, { children: [
1614
- /* @__PURE__ */ jsx35("h1", { children: title ?? "Story Report" }),
1615
- /* @__PURE__ */ jsx35(ReportSummary, {}),
1616
- /* @__PURE__ */ jsx35(ReportMeta, {})
1719
+ return /* @__PURE__ */ jsxs24(Fragment10, { children: [
1720
+ /* @__PURE__ */ jsx36("h1", { children: title ?? "Story Report" }),
1721
+ /* @__PURE__ */ jsx36(ReportSummary, {}),
1722
+ /* @__PURE__ */ jsx36(ReportMeta, {})
1617
1723
  ] });
1618
1724
  }
1619
1725
  function ReportErrorShell({
@@ -1622,28 +1728,28 @@ function ReportErrorShell({
1622
1728
  title,
1623
1729
  dataTheme
1624
1730
  }) {
1625
- return /* @__PURE__ */ jsx35("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ jsx35(ReportSchemaError, { error }) });
1731
+ return /* @__PURE__ */ jsx36("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ jsx36(ReportSchemaError, { error }) });
1626
1732
  }
1627
1733
 
1628
1734
  // src/components/Report.tsx
1629
- import { jsx as jsx36, jsxs as jsxs23 } from "react/jsx-runtime";
1735
+ import { jsx as jsx37, jsxs as jsxs25 } from "react/jsx-runtime";
1630
1736
  function Report(props) {
1631
1737
  const { report, customRenderers, renderers, className, title, dataTheme } = props;
1632
1738
  const result = unwrapReport(report);
1633
1739
  if (!result.ok) {
1634
- return /* @__PURE__ */ jsx36(ReportErrorShell, { error: result.error, className, title, dataTheme });
1740
+ return /* @__PURE__ */ jsx37(ReportErrorShell, { error: result.error, className, title, dataTheme });
1635
1741
  }
1636
1742
  const data = result.data;
1637
1743
  const hasContent = data.features.length > 0;
1638
- 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: [
1639
- /* @__PURE__ */ jsx36("header", { className: "es-report-header", children: /* @__PURE__ */ jsx36(ReportTitleBlock, { title }) }),
1640
- hasContent ? /* @__PURE__ */ jsx36(ReportFeatureList, {}) : /* @__PURE__ */ jsx36(ReportEmpty, {})
1744
+ 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: [
1745
+ /* @__PURE__ */ jsx37("header", { className: "es-report-header", children: /* @__PURE__ */ jsx37(ReportTitleBlock, { title }) }),
1746
+ hasContent ? /* @__PURE__ */ jsx37(ReportFeatureList, {}) : /* @__PURE__ */ jsx37(ReportEmpty, {})
1641
1747
  ] }) });
1642
1748
  }
1643
1749
 
1644
1750
  // src/components/doc/MermaidView.tsx
1645
- import { useEffect, useRef, useState as useState2 } from "react";
1646
- import { Fragment as Fragment11, jsx as jsx37, jsxs as jsxs24 } from "react/jsx-runtime";
1751
+ import { useEffect as useEffect2, useRef, useState as useState3 } from "react";
1752
+ import { Fragment as Fragment11, jsx as jsx38, jsxs as jsxs26 } from "react/jsx-runtime";
1647
1753
  function prefersDark(el) {
1648
1754
  const themed = el?.closest("[data-theme]");
1649
1755
  if (themed?.dataset.theme === "dark") return true;
@@ -1652,9 +1758,9 @@ function prefersDark(el) {
1652
1758
  }
1653
1759
  function MermaidView({ entry, load }) {
1654
1760
  const ref = useRef(null);
1655
- const [svg, setSvg] = useState2(null);
1656
- const [error, setError] = useState2(null);
1657
- useEffect(() => {
1761
+ const [svg, setSvg] = useState3(null);
1762
+ const [error, setError] = useState3(null);
1763
+ useEffect2(() => {
1658
1764
  let cancelled = false;
1659
1765
  const draw = async () => {
1660
1766
  let mermaid;
@@ -1697,9 +1803,9 @@ function MermaidView({ entry, load }) {
1697
1803
  };
1698
1804
  }, [entry.code, load]);
1699
1805
  if (svg) {
1700
- return /* @__PURE__ */ jsxs24("figure", { ref, className: "my-3", children: [
1701
- entry.title ? /* @__PURE__ */ jsx37("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
1702
- /* @__PURE__ */ jsx37(
1806
+ return /* @__PURE__ */ jsxs26("figure", { ref, className: "my-3", children: [
1807
+ entry.title ? /* @__PURE__ */ jsx38("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
1808
+ /* @__PURE__ */ jsx38(
1703
1809
  "div",
1704
1810
  {
1705
1811
  role: "img",
@@ -1710,8 +1816,8 @@ function MermaidView({ entry, load }) {
1710
1816
  )
1711
1817
  ] });
1712
1818
  }
1713
- return /* @__PURE__ */ jsxs24(Fragment11, { children: [
1714
- error ? /* @__PURE__ */ jsxs24(
1819
+ return /* @__PURE__ */ jsxs26(Fragment11, { children: [
1820
+ error ? /* @__PURE__ */ jsxs26(
1715
1821
  "p",
1716
1822
  {
1717
1823
  role: "alert",
@@ -1723,34 +1829,34 @@ function MermaidView({ entry, load }) {
1723
1829
  ]
1724
1830
  }
1725
1831
  ) : null,
1726
- /* @__PURE__ */ jsx37(MermaidSource, { entry, ref })
1832
+ /* @__PURE__ */ jsx38(MermaidSource, { entry, ref })
1727
1833
  ] });
1728
1834
  }
1729
1835
 
1730
1836
  // src/components/doc/MermaidDiagram.tsx
1731
- import { jsx as jsx38 } from "react/jsx-runtime";
1837
+ import { jsx as jsx39 } from "react/jsx-runtime";
1732
1838
  var mermaidLoader;
1733
1839
  function loadMermaid() {
1734
1840
  mermaidLoader ??= import("mermaid").then((m) => m.default);
1735
1841
  return mermaidLoader;
1736
1842
  }
1737
1843
  function MermaidDiagram({ entry, load = loadMermaid }) {
1738
- return /* @__PURE__ */ jsx38(MermaidView, { entry, load });
1844
+ return /* @__PURE__ */ jsx39(MermaidView, { entry, load });
1739
1845
  }
1740
1846
 
1741
1847
  // src/components/ScenarioExplorer.tsx
1742
- import { useState as useState3 } from "react";
1848
+ import { useState as useState4 } from "react";
1743
1849
 
1744
1850
  // src/components/ui/command.tsx
1745
1851
  import "react";
1746
1852
  import { Command as CommandPrimitive } from "cmdk";
1747
1853
  import { SearchIcon } from "lucide-react";
1748
- import { jsx as jsx39, jsxs as jsxs25 } from "react/jsx-runtime";
1854
+ import { jsx as jsx40, jsxs as jsxs27 } from "react/jsx-runtime";
1749
1855
  function Command({
1750
1856
  className,
1751
1857
  ...props
1752
1858
  }) {
1753
- return /* @__PURE__ */ jsx39(
1859
+ return /* @__PURE__ */ jsx40(
1754
1860
  CommandPrimitive,
1755
1861
  {
1756
1862
  "data-slot": "command",
@@ -1766,14 +1872,14 @@ function CommandInput({
1766
1872
  className,
1767
1873
  ...props
1768
1874
  }) {
1769
- return /* @__PURE__ */ jsxs25(
1875
+ return /* @__PURE__ */ jsxs27(
1770
1876
  "div",
1771
1877
  {
1772
1878
  "data-slot": "command-input-wrapper",
1773
1879
  className: "flex h-9 items-center gap-2 border-b px-3",
1774
1880
  children: [
1775
- /* @__PURE__ */ jsx39(SearchIcon, { className: "size-4 shrink-0 opacity-50" }),
1776
- /* @__PURE__ */ jsx39(
1881
+ /* @__PURE__ */ jsx40(SearchIcon, { className: "size-4 shrink-0 opacity-50" }),
1882
+ /* @__PURE__ */ jsx40(
1777
1883
  CommandPrimitive.Input,
1778
1884
  {
1779
1885
  "data-slot": "command-input",
@@ -1792,7 +1898,7 @@ function CommandList({
1792
1898
  className,
1793
1899
  ...props
1794
1900
  }) {
1795
- return /* @__PURE__ */ jsx39(
1901
+ return /* @__PURE__ */ jsx40(
1796
1902
  CommandPrimitive.List,
1797
1903
  {
1798
1904
  "data-slot": "command-list",
@@ -1807,7 +1913,7 @@ function CommandList({
1807
1913
  function CommandEmpty({
1808
1914
  ...props
1809
1915
  }) {
1810
- return /* @__PURE__ */ jsx39(
1916
+ return /* @__PURE__ */ jsx40(
1811
1917
  CommandPrimitive.Empty,
1812
1918
  {
1813
1919
  "data-slot": "command-empty",
@@ -1820,7 +1926,7 @@ function CommandGroup({
1820
1926
  className,
1821
1927
  ...props
1822
1928
  }) {
1823
- return /* @__PURE__ */ jsx39(
1929
+ return /* @__PURE__ */ jsx40(
1824
1930
  CommandPrimitive.Group,
1825
1931
  {
1826
1932
  "data-slot": "command-group",
@@ -1836,7 +1942,7 @@ function CommandItem({
1836
1942
  className,
1837
1943
  ...props
1838
1944
  }) {
1839
- return /* @__PURE__ */ jsx39(
1945
+ return /* @__PURE__ */ jsx40(
1840
1946
  CommandPrimitive.Item,
1841
1947
  {
1842
1948
  "data-slot": "command-item",
@@ -1850,7 +1956,7 @@ function CommandItem({
1850
1956
  }
1851
1957
 
1852
1958
  // src/components/ScenarioExplorer.tsx
1853
- import { jsx as jsx40, jsxs as jsxs26 } from "react/jsx-runtime";
1959
+ import { jsx as jsx41, jsxs as jsxs28 } from "react/jsx-runtime";
1854
1960
  var STATUS_FILTERS = [
1855
1961
  { value: "all", label: "All" },
1856
1962
  { value: "passed", label: "Passed" },
@@ -1859,24 +1965,24 @@ var STATUS_FILTERS = [
1859
1965
  { value: "pending", label: "Pending" }
1860
1966
  ];
1861
1967
  function ScenarioExplorer({ scenarios, summary }) {
1862
- const [status, setStatus] = useState3("all");
1968
+ const [status, setStatus] = useState4("all");
1863
1969
  const visible = status === "all" ? scenarios : scenarios.filter((s) => s.status === status);
1864
- return /* @__PURE__ */ jsxs26("div", { className: "font-sans text-foreground", children: [
1865
- summary ? /* @__PURE__ */ jsxs26("p", { className: "mb-3 text-sm text-muted-foreground", children: [
1970
+ return /* @__PURE__ */ jsxs28("div", { className: "font-sans text-foreground", children: [
1971
+ summary ? /* @__PURE__ */ jsxs28("p", { className: "mb-3 text-sm text-muted-foreground", children: [
1866
1972
  summary.total,
1867
1973
  " scenarios \xB7 ",
1868
- /* @__PURE__ */ jsxs26("span", { className: "text-pass", children: [
1974
+ /* @__PURE__ */ jsxs28("span", { className: "text-pass", children: [
1869
1975
  summary.passed,
1870
1976
  " passed"
1871
1977
  ] }),
1872
1978
  " \xB7",
1873
1979
  " ",
1874
- /* @__PURE__ */ jsxs26("span", { className: "text-fail", children: [
1980
+ /* @__PURE__ */ jsxs28("span", { className: "text-fail", children: [
1875
1981
  summary.failed,
1876
1982
  " failed"
1877
1983
  ] })
1878
1984
  ] }) : null,
1879
- /* @__PURE__ */ jsx40("div", { className: "mb-2 flex flex-wrap gap-1.5", role: "group", "aria-label": "Filter by status", children: STATUS_FILTERS.map((f) => /* @__PURE__ */ jsx40(
1985
+ /* @__PURE__ */ jsx41("div", { className: "mb-2 flex flex-wrap gap-1.5", role: "group", "aria-label": "Filter by status", children: STATUS_FILTERS.map((f) => /* @__PURE__ */ jsx41(
1880
1986
  "button",
1881
1987
  {
1882
1988
  type: "button",
@@ -1891,26 +1997,26 @@ function ScenarioExplorer({ scenarios, summary }) {
1891
1997
  },
1892
1998
  f.value
1893
1999
  )) }),
1894
- /* @__PURE__ */ jsxs26(
2000
+ /* @__PURE__ */ jsxs28(
1895
2001
  Command,
1896
2002
  {
1897
2003
  className: "rounded-lg border border-border bg-card",
1898
2004
  filter: (value, search) => value.toLowerCase().includes(search.toLowerCase()) ? 1 : 0,
1899
2005
  children: [
1900
- /* @__PURE__ */ jsx40(CommandInput, { placeholder: "Search scenarios\u2026" }),
1901
- /* @__PURE__ */ jsxs26(CommandList, { className: "max-h-none", children: [
1902
- /* @__PURE__ */ jsx40(CommandEmpty, { children: "No scenarios match your filters." }),
1903
- /* @__PURE__ */ jsx40(CommandGroup, { children: visible.map((s) => /* @__PURE__ */ jsx40(
2006
+ /* @__PURE__ */ jsx41(CommandInput, { placeholder: "Search scenarios\u2026" }),
2007
+ /* @__PURE__ */ jsxs28(CommandList, { className: "max-h-none", children: [
2008
+ /* @__PURE__ */ jsx41(CommandEmpty, { children: "No scenarios match your filters." }),
2009
+ /* @__PURE__ */ jsx41(CommandGroup, { children: visible.map((s) => /* @__PURE__ */ jsx41(
1904
2010
  CommandItem,
1905
2011
  {
1906
2012
  value: `${s.title} ${s.feature} ${s.tags.join(" ")}`,
1907
2013
  asChild: true,
1908
2014
  className: "cursor-pointer",
1909
- children: /* @__PURE__ */ jsxs26("a", { href: s.href, className: "flex items-center gap-3", children: [
1910
- /* @__PURE__ */ jsx40(Badge, { variant: s.status, children: s.status }),
1911
- /* @__PURE__ */ jsx40("span", { className: "min-w-0 flex-1 truncate font-medium", children: s.title }),
1912
- s.tags.slice(0, 3).map((t) => /* @__PURE__ */ jsx40(Badge, { variant: "tag", className: "hidden sm:inline-flex", children: t }, t)),
1913
- /* @__PURE__ */ jsx40("code", { className: "hidden font-mono text-xs text-muted-foreground md:inline", children: s.feature })
2015
+ children: /* @__PURE__ */ jsxs28("a", { href: s.href, className: "flex items-center gap-3", children: [
2016
+ /* @__PURE__ */ jsx41(Badge, { variant: s.status, children: s.status }),
2017
+ /* @__PURE__ */ jsx41("span", { className: "min-w-0 flex-1 truncate font-medium", children: s.title }),
2018
+ s.tags.slice(0, 3).map((t) => /* @__PURE__ */ jsx41(Badge, { variant: "tag", className: "hidden sm:inline-flex", children: t }, t)),
2019
+ /* @__PURE__ */ jsx41("code", { className: "hidden font-mono text-xs text-muted-foreground md:inline", children: s.feature })
1914
2020
  ] })
1915
2021
  },
1916
2022
  s.id
@@ -2057,7 +2163,7 @@ var story_report_v1_default = {
2057
2163
  title: {
2058
2164
  type: "string",
2059
2165
  minLength: 1,
2060
- description: "Display title. Derived from describe block when present, otherwise the file basename."
2166
+ description: "Display title. Comes from story.feature(...) when declared, otherwise the describe block or file basename."
2061
2167
  },
2062
2168
  sourceFile: {
2063
2169
  type: "string",
@@ -2073,15 +2179,25 @@ var story_report_v1_default = {
2073
2179
  $ref: "#/$defs/Scenario"
2074
2180
  },
2075
2181
  description: "Scenarios in this feature, in declaration order."
2182
+ },
2183
+ kind: {
2184
+ type: "string",
2185
+ enum: ["feature", "ability", "business-need"],
2186
+ description: "How the feature was declared. Absent when the title was derived rather than declared."
2187
+ },
2188
+ narrative: {
2189
+ type: "string",
2190
+ description: "Markdown explaining why the feature exists and who it serves."
2191
+ },
2192
+ glossary: {
2193
+ type: "array",
2194
+ items: {
2195
+ $ref: "#/$defs/GlossaryTerm"
2196
+ },
2197
+ description: "Terms this feature defines, linked wherever they appear in scenario text."
2076
2198
  }
2077
2199
  },
2078
- required: [
2079
- "id",
2080
- "title",
2081
- "sourceFile",
2082
- "summary",
2083
- "scenarios"
2084
- ],
2200
+ required: ["id", "title", "sourceFile", "summary", "scenarios"],
2085
2201
  additionalProperties: false
2086
2202
  },
2087
2203
  Scenario: {
@@ -2211,6 +2327,11 @@ var story_report_v1_default = {
2211
2327
  type: "number",
2212
2328
  minimum: 0
2213
2329
  },
2330
+ assertions: {
2331
+ type: "integer",
2332
+ minimum: 0,
2333
+ description: "Assertions observed or declared for this step. Omit when the adapter cannot observe assertions; zero means observed none."
2334
+ },
2214
2335
  errorMessage: {
2215
2336
  type: "string"
2216
2337
  },
@@ -2238,33 +2359,15 @@ var story_report_v1_default = {
2238
2359
  },
2239
2360
  StepKeyword: {
2240
2361
  type: "string",
2241
- enum: [
2242
- "Given",
2243
- "When",
2244
- "Then",
2245
- "And",
2246
- "But"
2247
- ]
2362
+ enum: ["Given", "When", "Then", "And", "But"]
2248
2363
  },
2249
2364
  StepMode: {
2250
2365
  type: "string",
2251
- enum: [
2252
- "normal",
2253
- "skip",
2254
- "only",
2255
- "todo",
2256
- "fails",
2257
- "concurrent"
2258
- ]
2366
+ enum: ["normal", "skip", "only", "todo", "fails", "concurrent"]
2259
2367
  },
2260
2368
  TestStatus: {
2261
2369
  type: "string",
2262
- enum: [
2263
- "passed",
2264
- "failed",
2265
- "skipped",
2266
- "pending"
2267
- ]
2370
+ enum: ["passed", "failed", "skipped", "pending"]
2268
2371
  },
2269
2372
  Ticket: {
2270
2373
  type: "object",
@@ -2277,9 +2380,7 @@ var story_report_v1_default = {
2277
2380
  type: "string"
2278
2381
  }
2279
2382
  },
2280
- required: [
2281
- "id"
2282
- ],
2383
+ required: ["id"],
2283
2384
  additionalProperties: false
2284
2385
  },
2285
2386
  Attachment: {
@@ -2297,18 +2398,10 @@ var story_report_v1_default = {
2297
2398
  },
2298
2399
  contentEncoding: {
2299
2400
  type: "string",
2300
- enum: [
2301
- "BASE64",
2302
- "IDENTITY"
2303
- ]
2401
+ enum: ["BASE64", "IDENTITY"]
2304
2402
  }
2305
2403
  },
2306
- required: [
2307
- "name",
2308
- "mediaType",
2309
- "body",
2310
- "contentEncoding"
2311
- ],
2404
+ required: ["name", "mediaType", "body", "contentEncoding"],
2312
2405
  additionalProperties: false
2313
2406
  },
2314
2407
  CIInfo: {
@@ -2333,9 +2426,7 @@ var story_report_v1_default = {
2333
2426
  type: "string"
2334
2427
  }
2335
2428
  },
2336
- required: [
2337
- "name"
2338
- ],
2429
+ required: ["name"],
2339
2430
  additionalProperties: false
2340
2431
  },
2341
2432
  CoverageSummary: {
@@ -2366,10 +2457,7 @@ var story_report_v1_default = {
2366
2457
  },
2367
2458
  DocPhase: {
2368
2459
  type: "string",
2369
- enum: [
2370
- "static",
2371
- "runtime"
2372
- ]
2460
+ enum: ["static", "runtime"]
2373
2461
  },
2374
2462
  DocEntry: {
2375
2463
  oneOf: [
@@ -2433,11 +2521,7 @@ var story_report_v1_default = {
2433
2521
  }
2434
2522
  }
2435
2523
  },
2436
- required: [
2437
- "kind",
2438
- "text",
2439
- "phase"
2440
- ],
2524
+ required: ["kind", "text", "phase"],
2441
2525
  additionalProperties: false
2442
2526
  },
2443
2527
  DocTag: {
@@ -2462,11 +2546,7 @@ var story_report_v1_default = {
2462
2546
  }
2463
2547
  }
2464
2548
  },
2465
- required: [
2466
- "kind",
2467
- "names",
2468
- "phase"
2469
- ],
2549
+ required: ["kind", "names", "phase"],
2470
2550
  additionalProperties: false
2471
2551
  },
2472
2552
  DocKv: {
@@ -2489,12 +2569,7 @@ var story_report_v1_default = {
2489
2569
  }
2490
2570
  }
2491
2571
  },
2492
- required: [
2493
- "kind",
2494
- "label",
2495
- "value",
2496
- "phase"
2497
- ],
2572
+ required: ["kind", "label", "value", "phase"],
2498
2573
  additionalProperties: false
2499
2574
  },
2500
2575
  DocCode: {
@@ -2522,12 +2597,7 @@ var story_report_v1_default = {
2522
2597
  }
2523
2598
  }
2524
2599
  },
2525
- required: [
2526
- "kind",
2527
- "label",
2528
- "content",
2529
- "phase"
2530
- ],
2600
+ required: ["kind", "label", "content", "phase"],
2531
2601
  additionalProperties: false
2532
2602
  },
2533
2603
  DocTable: {
@@ -2564,13 +2634,7 @@ var story_report_v1_default = {
2564
2634
  }
2565
2635
  }
2566
2636
  },
2567
- required: [
2568
- "kind",
2569
- "label",
2570
- "columns",
2571
- "rows",
2572
- "phase"
2573
- ],
2637
+ required: ["kind", "label", "columns", "rows", "phase"],
2574
2638
  additionalProperties: false
2575
2639
  },
2576
2640
  DocLink: {
@@ -2595,12 +2659,7 @@ var story_report_v1_default = {
2595
2659
  }
2596
2660
  }
2597
2661
  },
2598
- required: [
2599
- "kind",
2600
- "label",
2601
- "url",
2602
- "phase"
2603
- ],
2662
+ required: ["kind", "label", "url", "phase"],
2604
2663
  additionalProperties: false
2605
2664
  },
2606
2665
  DocSection: {
@@ -2625,12 +2684,7 @@ var story_report_v1_default = {
2625
2684
  }
2626
2685
  }
2627
2686
  },
2628
- required: [
2629
- "kind",
2630
- "title",
2631
- "markdown",
2632
- "phase"
2633
- ],
2687
+ required: ["kind", "title", "markdown", "phase"],
2634
2688
  additionalProperties: false
2635
2689
  },
2636
2690
  DocMermaid: {
@@ -2655,11 +2709,7 @@ var story_report_v1_default = {
2655
2709
  }
2656
2710
  }
2657
2711
  },
2658
- required: [
2659
- "kind",
2660
- "code",
2661
- "phase"
2662
- ],
2712
+ required: ["kind", "code", "phase"],
2663
2713
  additionalProperties: false
2664
2714
  },
2665
2715
  DocScreenshot: {
@@ -2684,11 +2734,7 @@ var story_report_v1_default = {
2684
2734
  }
2685
2735
  }
2686
2736
  },
2687
- required: [
2688
- "kind",
2689
- "path",
2690
- "phase"
2691
- ],
2737
+ required: ["kind", "path", "phase"],
2692
2738
  additionalProperties: false
2693
2739
  },
2694
2740
  DocVideo: {
@@ -2716,11 +2762,7 @@ var story_report_v1_default = {
2716
2762
  }
2717
2763
  }
2718
2764
  },
2719
- required: [
2720
- "kind",
2721
- "path",
2722
- "phase"
2723
- ],
2765
+ required: ["kind", "path", "phase"],
2724
2766
  additionalProperties: false
2725
2767
  },
2726
2768
  DocHtml: {
@@ -2761,26 +2803,17 @@ var story_report_v1_default = {
2761
2803
  }
2762
2804
  }
2763
2805
  },
2764
- required: [
2765
- "kind",
2766
- "phase"
2767
- ],
2806
+ required: ["kind", "phase"],
2768
2807
  additionalProperties: false,
2769
2808
  oneOf: [
2770
2809
  {
2771
- required: [
2772
- "path"
2773
- ]
2810
+ required: ["path"]
2774
2811
  },
2775
2812
  {
2776
- required: [
2777
- "url"
2778
- ]
2813
+ required: ["url"]
2779
2814
  },
2780
2815
  {
2781
- required: [
2782
- "content"
2783
- ]
2816
+ required: ["content"]
2784
2817
  }
2785
2818
  ]
2786
2819
  },
@@ -2804,11 +2837,7 @@ var story_report_v1_default = {
2804
2837
  }
2805
2838
  }
2806
2839
  },
2807
- required: [
2808
- "kind",
2809
- "value",
2810
- "phase"
2811
- ],
2840
+ required: ["kind", "value", "phase"],
2812
2841
  additionalProperties: false
2813
2842
  },
2814
2843
  DocCustom: {
@@ -2832,21 +2861,12 @@ var story_report_v1_default = {
2832
2861
  }
2833
2862
  }
2834
2863
  },
2835
- required: [
2836
- "kind",
2837
- "type",
2838
- "data",
2839
- "phase"
2840
- ],
2864
+ required: ["kind", "type", "data", "phase"],
2841
2865
  additionalProperties: false
2842
2866
  },
2843
2867
  OtelSpan: {
2844
2868
  type: "object",
2845
- required: [
2846
- "spanId",
2847
- "name",
2848
- "status"
2849
- ],
2869
+ required: ["spanId", "name", "status"],
2850
2870
  properties: {
2851
2871
  spanId: {
2852
2872
  type: "string"
@@ -2870,11 +2890,7 @@ var story_report_v1_default = {
2870
2890
  type: "number"
2871
2891
  },
2872
2892
  status: {
2873
- enum: [
2874
- "ok",
2875
- "error",
2876
- "unset"
2877
- ]
2893
+ enum: ["ok", "error", "unset"]
2878
2894
  },
2879
2895
  statusMessage: {
2880
2896
  type: "string"
@@ -2884,6 +2900,22 @@ var story_report_v1_default = {
2884
2900
  }
2885
2901
  },
2886
2902
  additionalProperties: false
2903
+ },
2904
+ GlossaryTerm: {
2905
+ type: "object",
2906
+ description: "One glossary entry: a term the scenarios use and what it means.",
2907
+ properties: {
2908
+ term: {
2909
+ type: "string",
2910
+ minLength: 1
2911
+ },
2912
+ definition: {
2913
+ type: "string",
2914
+ minLength: 1
2915
+ }
2916
+ },
2917
+ required: ["term", "definition"],
2918
+ additionalProperties: false
2887
2919
  }
2888
2920
  }
2889
2921
  };
@@ -2985,6 +3017,7 @@ export {
2985
3017
  parseStoryReport,
2986
3018
  prUrl,
2987
3019
  reportLastRunMs,
3020
+ scenarioLastRunMs,
2988
3021
  storyReportSchema,
2989
3022
  useBuiltinRenderers,
2990
3023
  useCustomRenderers,