executable-stories-react 0.10.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -470,21 +470,29 @@ function DocLink({ entry }) {
470
470
  );
471
471
  }
472
472
 
473
- // src/components/doc/DocSection.tsx
473
+ // src/lib/markdown.ts
474
474
  var import_marked = require("marked");
475
475
 
476
476
  // src/lib/url.ts
477
477
  var import_url = require("executable-stories-core/utils/url");
478
478
 
479
- // src/components/doc/DocSection.tsx
480
- var import_jsx_runtime9 = require("react/jsx-runtime");
481
- function neutralizeUrl(value) {
482
- return (0, import_url.safeUrl)(value) ?? "#";
483
- }
479
+ // src/lib/markdown.ts
484
480
  function safeMarkdownHtml(markdown) {
485
481
  const raw = import_marked.marked.parse(markdown, { async: false });
486
482
  return raw.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<\/?(?:iframe|object|embed|form|base)\b[^>]*>/gi, "").replace(/\son[a-z]+\s*=\s*"[^"]*"/gi, "").replace(/\son[a-z]+\s*=\s*'[^']*'/gi, "").replace(/\son[a-z]+\s*=\s*[^\s>]+/gi, "").replace(/(href|src)\s*=\s*"([^"]*)"/gi, (_m, attr, val) => `${attr}="${neutralizeUrl(val)}"`).replace(/(href|src)\s*=\s*'([^']*)'/gi, (_m, attr, val) => `${attr}='${neutralizeUrl(val)}'`);
487
483
  }
484
+ function dedent(text) {
485
+ const lines = text.replace(/\t/g, " ").split("\n");
486
+ const indents = lines.filter((line) => line.trim().length > 0).map((line) => line.length - line.trimStart().length);
487
+ const shortest = indents.length > 0 ? Math.min(...indents) : 0;
488
+ return lines.map((line) => line.slice(shortest)).join("\n").trim();
489
+ }
490
+ function neutralizeUrl(value) {
491
+ return (0, import_url.safeUrl)(value) ?? "#";
492
+ }
493
+
494
+ // src/components/doc/DocSection.tsx
495
+ var import_jsx_runtime9 = require("react/jsx-runtime");
488
496
  function DocSection({ entry }) {
489
497
  const renderers = useBuiltinRenderers();
490
498
  if (renderers.section) {
@@ -1361,8 +1369,35 @@ function ReportScenarioList({ feature }) {
1361
1369
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_jsx_runtime27.Fragment, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ReportScenario, { scenario }, scenario.id)) });
1362
1370
  }
1363
1371
 
1364
- // src/components/ReportFeature.tsx
1372
+ // src/components/ReportFeatureIntro.tsx
1365
1373
  var import_jsx_runtime28 = require("react/jsx-runtime");
1374
+ function ReportFeatureIntro({ feature }) {
1375
+ const hasNarrative = Boolean(feature.narrative?.trim());
1376
+ const hasGlossary = Boolean(feature.glossary?.length);
1377
+ if (!hasNarrative && !hasGlossary) return null;
1378
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex flex-col gap-3 border-l-2 border-border pl-4 text-sm", children: [
1379
+ hasNarrative ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1380
+ "div",
1381
+ {
1382
+ className: "es-doc-prose prose prose-sm max-w-none text-muted-foreground",
1383
+ dangerouslySetInnerHTML: { __html: safeMarkdownHtml(dedent(feature.narrative)) }
1384
+ }
1385
+ ) : null,
1386
+ hasGlossary ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("dl", { className: "grid gap-1.5", "aria-label": "Glossary", children: feature.glossary.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex flex-wrap gap-x-2 text-xs", children: [
1387
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("dt", { className: "font-medium text-foreground", children: entry.term }),
1388
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("dd", { className: "flex-1 text-muted-foreground", children: entry.definition })
1389
+ ] }, entry.term)) }) : null
1390
+ ] });
1391
+ }
1392
+ function featureKindLabel(kind) {
1393
+ if (kind === "ability") return "Ability";
1394
+ if (kind === "business-need") return "Business need";
1395
+ if (kind === "feature") return "Feature";
1396
+ return null;
1397
+ }
1398
+
1399
+ // src/components/ReportFeature.tsx
1400
+ var import_jsx_runtime29 = require("react/jsx-runtime");
1366
1401
  function ReportFeature({ feature }) {
1367
1402
  const titleId = `${feature.id}-title`;
1368
1403
  const bodyId = `${feature.id}-body`;
@@ -1370,10 +1405,11 @@ function ReportFeature({ feature }) {
1370
1405
  const skipped = s.skipped + s.pending;
1371
1406
  const collapse = useCollapse();
1372
1407
  const collapsed = collapse?.isCollapsed(feature.id) ?? false;
1408
+ const kindLabel = featureKindLabel(feature.kind);
1373
1409
  return (
1374
1410
  // Suite = a section heading + a divider, NOT another bordered card. Drops a
1375
1411
  // layer of nesting so the scenario cards below read as the primary unit.
1376
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1412
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
1377
1413
  "section",
1378
1414
  {
1379
1415
  id: feature.id,
@@ -1381,9 +1417,9 @@ function ReportFeature({ feature }) {
1381
1417
  "aria-labelledby": titleId,
1382
1418
  className: "flex flex-col gap-3",
1383
1419
  children: [
1384
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1385
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "min-w-0", children: [
1386
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("h2", { id: titleId, className: "text-base font-semibold tracking-tight text-foreground", children: collapse ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1420
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1421
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "min-w-0", children: [
1422
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("h2", { id: titleId, className: "text-base font-semibold tracking-tight text-foreground", children: collapse ? /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
1387
1423
  "button",
1388
1424
  {
1389
1425
  type: "button",
@@ -1392,31 +1428,34 @@ function ReportFeature({ feature }) {
1392
1428
  "aria-controls": bodyId,
1393
1429
  className: "flex w-full cursor-pointer items-center gap-2 text-left hover:text-foreground/80",
1394
1430
  children: [
1395
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { "aria-hidden": true, className: cn("shrink-0 text-xs text-muted-foreground transition-transform", !collapsed && "rotate-90"), children: "\u25B8" }),
1396
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "truncate", children: feature.title })
1431
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { "aria-hidden": true, className: cn("shrink-0 text-xs text-muted-foreground transition-transform", !collapsed && "rotate-90"), children: "\u25B8" }),
1432
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "truncate", children: feature.title })
1397
1433
  ]
1398
1434
  }
1399
1435
  ) : feature.title }),
1400
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("p", { className: cn("mt-0.5 truncate font-mono text-xs text-muted-foreground", collapse && "pl-5"), children: feature.sourceFile })
1436
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("p", { className: cn("mt-0.5 flex items-center gap-2 text-xs text-muted-foreground", collapse && "pl-5"), children: [
1437
+ kindLabel ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "shrink-0 rounded-sm bg-muted px-1.5 py-0.5 font-medium text-foreground", children: kindLabel }) : null,
1438
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "truncate font-mono", children: feature.sourceFile })
1439
+ ] })
1401
1440
  ] }),
1402
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1441
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
1403
1442
  "p",
1404
1443
  {
1405
1444
  className: "flex shrink-0 items-center gap-1.5 text-xs text-muted-foreground",
1406
1445
  "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
1407
1446
  children: [
1408
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { children: [
1447
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("span", { children: [
1409
1448
  s.passed,
1410
1449
  " passed"
1411
1450
  ] }),
1412
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1413
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
1451
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1452
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
1414
1453
  s.failed,
1415
1454
  " failed"
1416
1455
  ] }),
1417
- skipped > 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
1418
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1419
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { children: [
1456
+ skipped > 0 ? /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_jsx_runtime29.Fragment, { children: [
1457
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1458
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("span", { children: [
1420
1459
  skipped,
1421
1460
  " skipped"
1422
1461
  ] })
@@ -1425,7 +1464,10 @@ function ReportFeature({ feature }) {
1425
1464
  }
1426
1465
  )
1427
1466
  ] }),
1428
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-3", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ReportScenarioList, { feature }) })
1467
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-3", children: [
1468
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(ReportFeatureIntro, { feature }),
1469
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(ReportScenarioList, { feature })
1470
+ ] })
1429
1471
  ]
1430
1472
  }
1431
1473
  )
@@ -1433,17 +1475,17 @@ function ReportFeature({ feature }) {
1433
1475
  }
1434
1476
 
1435
1477
  // src/components/ReportFeatureList.tsx
1436
- var import_jsx_runtime29 = require("react/jsx-runtime");
1478
+ var import_jsx_runtime30 = require("react/jsx-runtime");
1437
1479
  function ReportFeatureList() {
1438
1480
  const report = useReport();
1439
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "flex flex-col gap-8", children: report.features.map((feature) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(ReportFeature, { feature }, feature.id)) });
1481
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "flex flex-col gap-8", children: report.features.map((feature) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ReportFeature, { feature }, feature.id)) });
1440
1482
  }
1441
1483
 
1442
1484
  // src/components/ui/empty.tsx
1443
1485
  var import_class_variance_authority2 = require("class-variance-authority");
1444
- var import_jsx_runtime30 = require("react/jsx-runtime");
1486
+ var import_jsx_runtime31 = require("react/jsx-runtime");
1445
1487
  function Empty({ className, ...props }) {
1446
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1488
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1447
1489
  "div",
1448
1490
  {
1449
1491
  "data-slot": "empty",
@@ -1456,7 +1498,7 @@ function Empty({ className, ...props }) {
1456
1498
  );
1457
1499
  }
1458
1500
  function EmptyHeader({ className, ...props }) {
1459
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1501
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1460
1502
  "div",
1461
1503
  {
1462
1504
  "data-slot": "empty-header",
@@ -1483,7 +1525,7 @@ var emptyMediaVariants = (0, import_class_variance_authority2.cva)(
1483
1525
  }
1484
1526
  );
1485
1527
  function EmptyTitle({ className, ...props }) {
1486
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1528
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1487
1529
  "div",
1488
1530
  {
1489
1531
  "data-slot": "empty-title",
@@ -1494,16 +1536,16 @@ function EmptyTitle({ className, ...props }) {
1494
1536
  }
1495
1537
 
1496
1538
  // src/components/ReportEmpty.tsx
1497
- var import_jsx_runtime31 = require("react/jsx-runtime");
1539
+ var import_jsx_runtime32 = require("react/jsx-runtime");
1498
1540
  function ReportEmpty({ message }) {
1499
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(EmptyHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1541
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(EmptyHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1500
1542
  }
1501
1543
 
1502
1544
  // src/components/ReportSummary.tsx
1503
- var import_jsx_runtime32 = require("react/jsx-runtime");
1545
+ var import_jsx_runtime33 = require("react/jsx-runtime");
1504
1546
  function ReportSummary({ className }) {
1505
1547
  const report = useReport();
1506
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1548
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
1507
1549
  ReportSummaryView,
1508
1550
  {
1509
1551
  summary: report.summary,
@@ -1529,7 +1571,7 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1529
1571
  if (summary.pending > 0) {
1530
1572
  cards.push({ status: "pending", label: "Pending", value: summary.pending });
1531
1573
  }
1532
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1574
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
1533
1575
  "dl",
1534
1576
  {
1535
1577
  className: cn(
@@ -1542,17 +1584,17 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1542
1584
  "aria-label": ariaLabel,
1543
1585
  children: cards.map((c) => {
1544
1586
  const tone = TONE[c.status];
1545
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
1587
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
1546
1588
  "div",
1547
1589
  {
1548
1590
  "data-status": c.status,
1549
1591
  className: "rounded-lg border bg-card px-4 py-3",
1550
1592
  children: [
1551
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("dt", { className: "flex items-center gap-1.5 font-mono text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: [
1552
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
1593
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("dt", { className: "flex items-center gap-1.5 font-mono text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: [
1594
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
1553
1595
  c.label
1554
1596
  ] }),
1555
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("dd", { className: cn("mt-2 text-3xl font-semibold leading-none tracking-tight tabular-nums", tone.num), children: c.value })
1597
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("dd", { className: cn("mt-2 text-3xl font-semibold leading-none tracking-tight tabular-nums", tone.num), children: c.value })
1556
1598
  ]
1557
1599
  },
1558
1600
  c.status
@@ -1615,7 +1657,7 @@ function prUrl(ci) {
1615
1657
  }
1616
1658
 
1617
1659
  // src/components/ReportMeta.tsx
1618
- var import_jsx_runtime33 = require("react/jsx-runtime");
1660
+ var import_jsx_runtime34 = require("react/jsx-runtime");
1619
1661
  function ReportMeta() {
1620
1662
  const report = useReport();
1621
1663
  const items = [];
@@ -1638,13 +1680,13 @@ function ReportMeta() {
1638
1680
  const short = sha.slice(0, 8);
1639
1681
  items.push({
1640
1682
  label: "Commit",
1641
- value: url ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1683
+ value: url ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1642
1684
  });
1643
1685
  }
1644
1686
  if (report.ci) {
1645
1687
  items.push({
1646
1688
  label: "CI",
1647
- value: report.ci.url ? /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1689
+ value: report.ci.url ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1648
1690
  ciDisplayName(report.ci.name),
1649
1691
  report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
1650
1692
  ] }) : `${ciDisplayName(report.ci.name)}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
@@ -1655,7 +1697,7 @@ function ReportMeta() {
1655
1697
  const label = `#${report.ci.prNumber}`;
1656
1698
  items.push({
1657
1699
  label: "PR",
1658
- value: url ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1700
+ value: url ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1659
1701
  });
1660
1702
  }
1661
1703
  if (items.length === 0) return null;
@@ -1663,48 +1705,48 @@ function ReportMeta() {
1663
1705
  // Run metadata is useful but secondary to "what failed" — tuck it into a
1664
1706
  // collapsed disclosure so it doesn't push the result down. Native <details>
1665
1707
  // so it works with no JS (the SSR/no-hydration report still toggles).
1666
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("details", { "data-es-meta-card": true, className: "es-run-details group rounded-lg border border-border bg-card text-xs", children: [
1667
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("summary", { className: "flex cursor-pointer list-none select-none items-center gap-1.5 px-4 py-2 font-medium text-muted-foreground [&::-webkit-details-marker]:hidden", children: [
1668
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
1708
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("details", { "data-es-meta-card": true, className: "es-run-details group rounded-lg border border-border bg-card text-xs", children: [
1709
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("summary", { className: "flex cursor-pointer list-none select-none items-center gap-1.5 px-4 py-2 font-medium text-muted-foreground [&::-webkit-details-marker]:hidden", children: [
1710
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
1669
1711
  "Run details"
1670
1712
  ] }),
1671
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("dl", { className: "flex flex-wrap gap-x-5 gap-y-2 border-t border-border px-4 py-3 text-muted-foreground", "aria-label": "Run metadata", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "flex items-baseline gap-1.5", children: [
1672
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("dt", { className: "font-medium", children: item.label }),
1673
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("dd", { className: "font-mono text-foreground", children: item.value })
1713
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("dl", { className: "flex flex-wrap gap-x-5 gap-y-2 border-t border-border px-4 py-3 text-muted-foreground", "aria-label": "Run metadata", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex items-baseline gap-1.5", children: [
1714
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("dt", { className: "font-medium", children: item.label }),
1715
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("dd", { className: "font-mono text-foreground", children: item.value })
1674
1716
  ] }, item.label)) })
1675
1717
  ] })
1676
1718
  );
1677
1719
  }
1678
1720
 
1679
1721
  // src/components/ReportSchemaError.tsx
1680
- var import_jsx_runtime34 = require("react/jsx-runtime");
1722
+ var import_jsx_runtime35 = require("react/jsx-runtime");
1681
1723
  function ReportSchemaError({ error }) {
1682
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1683
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("strong", { children: "Report could not be displayed." }) }),
1684
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { children: error.message }),
1685
- error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("p", { children: [
1724
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1725
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("strong", { children: "Report could not be displayed." }) }),
1726
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { children: error.message }),
1727
+ error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("p", { children: [
1686
1728
  "The report bundle is newer than this version of ",
1687
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("code", { children: "executable-stories-react" }),
1729
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("code", { children: "executable-stories-react" }),
1688
1730
  ". Upgrade the package, or regenerate the report with an older formatters CLI."
1689
1731
  ] }) : null,
1690
- error.issues && error.issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("details", { children: [
1691
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("summary", { children: [
1732
+ error.issues && error.issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("details", { children: [
1733
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("summary", { children: [
1692
1734
  error.issues.length,
1693
1735
  " validation issue",
1694
1736
  error.issues.length === 1 ? "" : "s"
1695
1737
  ] }),
1696
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1738
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1697
1739
  ] }) : null
1698
1740
  ] });
1699
1741
  }
1700
1742
 
1701
1743
  // src/components/ReportShell.tsx
1702
- var import_jsx_runtime35 = require("react/jsx-runtime");
1744
+ var import_jsx_runtime36 = require("react/jsx-runtime");
1703
1745
  function ReportTitleBlock({ title }) {
1704
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
1705
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("h1", { children: title ?? "Story Report" }),
1706
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ReportSummary, {}),
1707
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ReportMeta, {})
1746
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
1747
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("h1", { children: title ?? "Story Report" }),
1748
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportSummary, {}),
1749
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportMeta, {})
1708
1750
  ] });
1709
1751
  }
1710
1752
  function ReportErrorShell({
@@ -1713,28 +1755,28 @@ function ReportErrorShell({
1713
1755
  title,
1714
1756
  dataTheme
1715
1757
  }) {
1716
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ReportSchemaError, { error }) });
1758
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportSchemaError, { error }) });
1717
1759
  }
1718
1760
 
1719
1761
  // src/components/Report.tsx
1720
- var import_jsx_runtime36 = require("react/jsx-runtime");
1762
+ var import_jsx_runtime37 = require("react/jsx-runtime");
1721
1763
  function Report(props) {
1722
1764
  const { report, customRenderers, renderers, className, title, dataTheme } = props;
1723
1765
  const result = unwrapReport(report);
1724
1766
  if (!result.ok) {
1725
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportErrorShell, { error: result.error, className, title, dataTheme });
1767
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ReportErrorShell, { error: result.error, className, title, dataTheme });
1726
1768
  }
1727
1769
  const data = result.data;
1728
1770
  const hasContent = data.features.length > 0;
1729
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportRoot, { report: data, customRenderers, renderers, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: [
1730
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("header", { className: "es-report-header", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportTitleBlock, { title }) }),
1731
- hasContent ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportFeatureList, {}) : /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportEmpty, {})
1771
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ReportRoot, { report: data, customRenderers, renderers, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: [
1772
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("header", { className: "es-report-header", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ReportTitleBlock, { title }) }),
1773
+ hasContent ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ReportFeatureList, {}) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ReportEmpty, {})
1732
1774
  ] }) });
1733
1775
  }
1734
1776
 
1735
1777
  // src/components/doc/MermaidView.tsx
1736
1778
  var import_react8 = require("react");
1737
- var import_jsx_runtime37 = require("react/jsx-runtime");
1779
+ var import_jsx_runtime38 = require("react/jsx-runtime");
1738
1780
  function prefersDark(el) {
1739
1781
  const themed = el?.closest("[data-theme]");
1740
1782
  if (themed?.dataset.theme === "dark") return true;
@@ -1788,9 +1830,9 @@ function MermaidView({ entry, load }) {
1788
1830
  };
1789
1831
  }, [entry.code, load]);
1790
1832
  if (svg) {
1791
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("figure", { ref, className: "my-3", children: [
1792
- entry.title ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
1793
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
1833
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("figure", { ref, className: "my-3", children: [
1834
+ entry.title ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
1835
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
1794
1836
  "div",
1795
1837
  {
1796
1838
  role: "img",
@@ -1801,8 +1843,8 @@ function MermaidView({ entry, load }) {
1801
1843
  )
1802
1844
  ] });
1803
1845
  }
1804
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
1805
- error ? /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
1846
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
1847
+ error ? /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
1806
1848
  "p",
1807
1849
  {
1808
1850
  role: "alert",
@@ -1814,19 +1856,19 @@ function MermaidView({ entry, load }) {
1814
1856
  ]
1815
1857
  }
1816
1858
  ) : null,
1817
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(MermaidSource, { entry, ref })
1859
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(MermaidSource, { entry, ref })
1818
1860
  ] });
1819
1861
  }
1820
1862
 
1821
1863
  // src/components/doc/MermaidDiagram.tsx
1822
- var import_jsx_runtime38 = require("react/jsx-runtime");
1864
+ var import_jsx_runtime39 = require("react/jsx-runtime");
1823
1865
  var mermaidLoader;
1824
1866
  function loadMermaid() {
1825
1867
  mermaidLoader ??= import("mermaid").then((m) => m.default);
1826
1868
  return mermaidLoader;
1827
1869
  }
1828
1870
  function MermaidDiagram({ entry, load = loadMermaid }) {
1829
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(MermaidView, { entry, load });
1871
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(MermaidView, { entry, load });
1830
1872
  }
1831
1873
 
1832
1874
  // src/components/ScenarioExplorer.tsx
@@ -1836,12 +1878,12 @@ var import_react9 = require("react");
1836
1878
  var React4 = require("react");
1837
1879
  var import_cmdk = require("cmdk");
1838
1880
  var import_lucide_react3 = require("lucide-react");
1839
- var import_jsx_runtime39 = require("react/jsx-runtime");
1881
+ var import_jsx_runtime40 = require("react/jsx-runtime");
1840
1882
  function Command({
1841
1883
  className,
1842
1884
  ...props
1843
1885
  }) {
1844
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1886
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
1845
1887
  import_cmdk.Command,
1846
1888
  {
1847
1889
  "data-slot": "command",
@@ -1857,14 +1899,14 @@ function CommandInput({
1857
1899
  className,
1858
1900
  ...props
1859
1901
  }) {
1860
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
1902
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
1861
1903
  "div",
1862
1904
  {
1863
1905
  "data-slot": "command-input-wrapper",
1864
1906
  className: "flex h-9 items-center gap-2 border-b px-3",
1865
1907
  children: [
1866
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_lucide_react3.SearchIcon, { className: "size-4 shrink-0 opacity-50" }),
1867
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1908
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react3.SearchIcon, { className: "size-4 shrink-0 opacity-50" }),
1909
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
1868
1910
  import_cmdk.Command.Input,
1869
1911
  {
1870
1912
  "data-slot": "command-input",
@@ -1883,7 +1925,7 @@ function CommandList({
1883
1925
  className,
1884
1926
  ...props
1885
1927
  }) {
1886
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1928
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
1887
1929
  import_cmdk.Command.List,
1888
1930
  {
1889
1931
  "data-slot": "command-list",
@@ -1898,7 +1940,7 @@ function CommandList({
1898
1940
  function CommandEmpty({
1899
1941
  ...props
1900
1942
  }) {
1901
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1943
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
1902
1944
  import_cmdk.Command.Empty,
1903
1945
  {
1904
1946
  "data-slot": "command-empty",
@@ -1911,7 +1953,7 @@ function CommandGroup({
1911
1953
  className,
1912
1954
  ...props
1913
1955
  }) {
1914
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1956
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
1915
1957
  import_cmdk.Command.Group,
1916
1958
  {
1917
1959
  "data-slot": "command-group",
@@ -1927,7 +1969,7 @@ function CommandItem({
1927
1969
  className,
1928
1970
  ...props
1929
1971
  }) {
1930
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1972
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
1931
1973
  import_cmdk.Command.Item,
1932
1974
  {
1933
1975
  "data-slot": "command-item",
@@ -1941,7 +1983,7 @@ function CommandItem({
1941
1983
  }
1942
1984
 
1943
1985
  // src/components/ScenarioExplorer.tsx
1944
- var import_jsx_runtime40 = require("react/jsx-runtime");
1986
+ var import_jsx_runtime41 = require("react/jsx-runtime");
1945
1987
  var STATUS_FILTERS = [
1946
1988
  { value: "all", label: "All" },
1947
1989
  { value: "passed", label: "Passed" },
@@ -1952,22 +1994,22 @@ var STATUS_FILTERS = [
1952
1994
  function ScenarioExplorer({ scenarios, summary }) {
1953
1995
  const [status, setStatus] = (0, import_react9.useState)("all");
1954
1996
  const visible = status === "all" ? scenarios : scenarios.filter((s) => s.status === status);
1955
- return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "font-sans text-foreground", children: [
1956
- summary ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("p", { className: "mb-3 text-sm text-muted-foreground", children: [
1997
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "font-sans text-foreground", children: [
1998
+ summary ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("p", { className: "mb-3 text-sm text-muted-foreground", children: [
1957
1999
  summary.total,
1958
2000
  " scenarios \xB7 ",
1959
- /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "text-pass", children: [
2001
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { className: "text-pass", children: [
1960
2002
  summary.passed,
1961
2003
  " passed"
1962
2004
  ] }),
1963
2005
  " \xB7",
1964
2006
  " ",
1965
- /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "text-fail", children: [
2007
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { className: "text-fail", children: [
1966
2008
  summary.failed,
1967
2009
  " failed"
1968
2010
  ] })
1969
2011
  ] }) : null,
1970
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "mb-2 flex flex-wrap gap-1.5", role: "group", "aria-label": "Filter by status", children: STATUS_FILTERS.map((f) => /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2012
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "mb-2 flex flex-wrap gap-1.5", role: "group", "aria-label": "Filter by status", children: STATUS_FILTERS.map((f) => /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
1971
2013
  "button",
1972
2014
  {
1973
2015
  type: "button",
@@ -1982,26 +2024,26 @@ function ScenarioExplorer({ scenarios, summary }) {
1982
2024
  },
1983
2025
  f.value
1984
2026
  )) }),
1985
- /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
2027
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
1986
2028
  Command,
1987
2029
  {
1988
2030
  className: "rounded-lg border border-border bg-card",
1989
2031
  filter: (value, search) => value.toLowerCase().includes(search.toLowerCase()) ? 1 : 0,
1990
2032
  children: [
1991
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(CommandInput, { placeholder: "Search scenarios\u2026" }),
1992
- /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(CommandList, { className: "max-h-none", children: [
1993
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(CommandEmpty, { children: "No scenarios match your filters." }),
1994
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(CommandGroup, { children: visible.map((s) => /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2033
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(CommandInput, { placeholder: "Search scenarios\u2026" }),
2034
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(CommandList, { className: "max-h-none", children: [
2035
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(CommandEmpty, { children: "No scenarios match your filters." }),
2036
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(CommandGroup, { children: visible.map((s) => /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
1995
2037
  CommandItem,
1996
2038
  {
1997
2039
  value: `${s.title} ${s.feature} ${s.tags.join(" ")}`,
1998
2040
  asChild: true,
1999
2041
  className: "cursor-pointer",
2000
- children: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("a", { href: s.href, className: "flex items-center gap-3", children: [
2001
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Badge, { variant: s.status, children: s.status }),
2002
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "min-w-0 flex-1 truncate font-medium", children: s.title }),
2003
- s.tags.slice(0, 3).map((t) => /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Badge, { variant: "tag", className: "hidden sm:inline-flex", children: t }, t)),
2004
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("code", { className: "hidden font-mono text-xs text-muted-foreground md:inline", children: s.feature })
2042
+ children: /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("a", { href: s.href, className: "flex items-center gap-3", children: [
2043
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(Badge, { variant: s.status, children: s.status }),
2044
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "min-w-0 flex-1 truncate font-medium", children: s.title }),
2045
+ s.tags.slice(0, 3).map((t) => /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(Badge, { variant: "tag", className: "hidden sm:inline-flex", children: t }, t)),
2046
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("code", { className: "hidden font-mono text-xs text-muted-foreground md:inline", children: s.feature })
2005
2047
  ] })
2006
2048
  },
2007
2049
  s.id
@@ -2148,7 +2190,7 @@ var story_report_v1_default = {
2148
2190
  title: {
2149
2191
  type: "string",
2150
2192
  minLength: 1,
2151
- description: "Display title. Derived from describe block when present, otherwise the file basename."
2193
+ description: "Display title. Comes from story.feature(...) when declared, otherwise the describe block or file basename."
2152
2194
  },
2153
2195
  sourceFile: {
2154
2196
  type: "string",
@@ -2164,6 +2206,26 @@ var story_report_v1_default = {
2164
2206
  $ref: "#/$defs/Scenario"
2165
2207
  },
2166
2208
  description: "Scenarios in this feature, in declaration order."
2209
+ },
2210
+ kind: {
2211
+ type: "string",
2212
+ enum: [
2213
+ "feature",
2214
+ "ability",
2215
+ "business-need"
2216
+ ],
2217
+ description: "How the feature was declared. Absent when the title was derived rather than declared."
2218
+ },
2219
+ narrative: {
2220
+ type: "string",
2221
+ description: "Markdown explaining why the feature exists and who it serves."
2222
+ },
2223
+ glossary: {
2224
+ type: "array",
2225
+ items: {
2226
+ $ref: "#/$defs/GlossaryTerm"
2227
+ },
2228
+ description: "Terms this feature defines, linked wherever they appear in scenario text."
2167
2229
  }
2168
2230
  },
2169
2231
  required: [
@@ -2975,6 +3037,25 @@ var story_report_v1_default = {
2975
3037
  }
2976
3038
  },
2977
3039
  additionalProperties: false
3040
+ },
3041
+ GlossaryTerm: {
3042
+ type: "object",
3043
+ description: "One glossary entry: a term the scenarios use and what it means.",
3044
+ properties: {
3045
+ term: {
3046
+ type: "string",
3047
+ minLength: 1
3048
+ },
3049
+ definition: {
3050
+ type: "string",
3051
+ minLength: 1
3052
+ }
3053
+ },
3054
+ required: [
3055
+ "term",
3056
+ "definition"
3057
+ ],
3058
+ additionalProperties: false
2978
3059
  }
2979
3060
  }
2980
3061
  };