executable-stories-react 0.10.0 → 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.js CHANGED
@@ -286,23 +286,11 @@ function useReport() {
286
286
  return ctx.report;
287
287
  }
288
288
 
289
- // ../executable-stories-core/dist/utils/duration.js
290
- function formatDuration(ms) {
291
- if (ms < 1e3) {
292
- return `${Math.round(ms)} ms`;
293
- }
294
- if (ms < 6e4) {
295
- return `${(ms / 1e3).toFixed(2)} s`;
296
- }
297
- const minutes = Math.floor(ms / 6e4);
298
- const seconds = Math.floor(ms % 6e4 / 1e3);
299
- if (minutes < 60) {
300
- return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
301
- }
302
- const hours = Math.floor(minutes / 60);
303
- const remainingMinutes = minutes % 60;
304
- return `${hours}h ${remainingMinutes}m`;
305
- }
289
+ // src/components/ReportScenario.tsx
290
+ import { formatDuration as formatDuration3 } from "executable-stories-core/utils/duration";
291
+
292
+ // src/components/ReportSteps.tsx
293
+ import { formatDuration } from "executable-stories-core/utils/duration";
306
294
 
307
295
  // src/components/doc/DocNote.tsx
308
296
  import { jsx as jsx3 } from "react/jsx-runtime";
@@ -391,36 +379,29 @@ function DocLink({ entry }) {
391
379
  );
392
380
  }
393
381
 
394
- // src/components/doc/DocSection.tsx
382
+ // src/lib/markdown.ts
395
383
  import { marked } from "marked";
396
384
 
397
- // ../executable-stories-core/dist/utils/url.js
398
- function safeUrl(value) {
399
- if (!value) return void 0;
400
- const trimmed = value.trim();
401
- const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(trimmed);
402
- if (scheme && !/^https?$/i.test(scheme[1] ?? "")) return void 0;
403
- return trimmed;
385
+ // src/lib/url.ts
386
+ import { safeUrl, safeImageUrl, isLocalFsPath } from "executable-stories-core/utils/url";
387
+
388
+ // src/lib/markdown.ts
389
+ function safeMarkdownHtml(markdown) {
390
+ const raw = marked.parse(markdown, { async: false });
391
+ 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)}'`);
404
392
  }
405
- function safeImageUrl(value) {
406
- if (!value) return void 0;
407
- const trimmed = value.trim();
408
- if (/^data:image\//i.test(trimmed)) return trimmed;
409
- return safeUrl(trimmed);
393
+ function dedent(text) {
394
+ const lines = text.replace(/\t/g, " ").split("\n");
395
+ const indents = lines.filter((line) => line.trim().length > 0).map((line) => line.length - line.trimStart().length);
396
+ const shortest = indents.length > 0 ? Math.min(...indents) : 0;
397
+ return lines.map((line) => line.slice(shortest)).join("\n").trim();
410
398
  }
411
- function isLocalFsPath(value) {
412
- return /^(?:[/\\]|[A-Za-z]:[/\\])/.test(value);
399
+ function neutralizeUrl(value) {
400
+ return safeUrl(value) ?? "#";
413
401
  }
414
402
 
415
403
  // src/components/doc/DocSection.tsx
416
404
  import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
417
- function neutralizeUrl(value) {
418
- return safeUrl(value) ?? "#";
419
- }
420
- function safeMarkdownHtml(markdown) {
421
- const raw = marked.parse(markdown, { async: false });
422
- 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)}'`);
423
- }
424
405
  function DocSection({ entry }) {
425
406
  const renderers = useBuiltinRenderers();
426
407
  if (renderers.section) {
@@ -703,125 +684,10 @@ function ReportStepItem({ step }) {
703
684
  );
704
685
  }
705
686
 
706
- // ../executable-stories-core/dist/state-diff.js
707
- var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
708
- function deepEqual(a, b) {
709
- if (Object.is(a, b)) return true;
710
- if (Array.isArray(a) && Array.isArray(b)) {
711
- return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
712
- }
713
- if (isPlainObject(a) && isPlainObject(b)) {
714
- const ak = Object.keys(a);
715
- const bk = Object.keys(b);
716
- return ak.length === bk.length && ak.every((k) => k in b && deepEqual(a[k], b[k]));
717
- }
718
- return false;
719
- }
720
- var joinKey = (path, key) => path === "" ? key : `${path}.${key}`;
721
- function walk(path, before, after, out) {
722
- if (Object.is(before, after)) return;
723
- if (Array.isArray(before) && Array.isArray(after)) {
724
- const shared = Math.min(before.length, after.length);
725
- for (let i = 0; i < shared; i++) walk(`${path}[${i}]`, before[i], after[i], out);
726
- for (let i = shared; i < after.length; i++) out.push({ path: `${path}[${i}]`, kind: "added", after: after[i] });
727
- for (let i = shared; i < before.length; i++) out.push({ path: `${path}[${i}]`, kind: "removed", before: before[i] });
728
- return;
729
- }
730
- if (isPlainObject(before) && isPlainObject(after)) {
731
- for (const key of Object.keys(after)) {
732
- const p = joinKey(path, key);
733
- if (key in before) walk(p, before[key], after[key], out);
734
- else out.push({ path: p, kind: "added", after: after[key] });
735
- }
736
- for (const key of Object.keys(before)) {
737
- if (!(key in after)) out.push({ path: joinKey(path, key), kind: "removed", before: before[key] });
738
- }
739
- return;
740
- }
741
- if (!deepEqual(before, after)) out.push({ path, kind: "changed", before, after });
742
- }
743
- function diffStateValues(before, after) {
744
- const out = [];
745
- walk("", before, after, out);
746
- return out;
747
- }
748
- function formatStateValue(value, maxLen = 60) {
749
- const s = value === void 0 ? "undefined" : JSON.stringify(value);
750
- return s.length > maxLen ? `${s.slice(0, maxLen - 1)}\u2026` : s;
751
- }
752
- function summarizeStateChanges(changes) {
753
- return changes.map((c) => {
754
- const at = c.path === "" ? "(root)" : c.path;
755
- switch (c.kind) {
756
- case "added":
757
- return `+ ${at}: ${formatStateValue(c.after)}`;
758
- case "removed":
759
- return `\u2212 ${at}`;
760
- case "changed":
761
- return `${at}: ${formatStateValue(c.before)} \u2192 ${formatStateValue(c.after)}`;
762
- }
763
- });
764
- }
765
-
766
- // ../executable-stories-core/dist/storyboard.js
767
- function findScreenshot(entries) {
768
- for (const entry of entries) {
769
- if (entry.kind === "screenshot") return entry;
770
- if (entry.children) {
771
- const nested = findScreenshot(entry.children);
772
- if (nested) return nested;
773
- }
774
- }
775
- return void 0;
776
- }
777
- function findStates(entries, out = []) {
778
- for (const entry of entries) {
779
- if (entry.kind === "state") out.push(entry);
780
- if (entry.children) findStates(entry.children, out);
781
- }
782
- return out;
783
- }
784
- var laneOf = (label) => label ?? "";
785
- function extractStoryboardFrames(scenario) {
786
- const frames = [];
787
- const laneOrder = [];
788
- const previous = /* @__PURE__ */ new Map();
789
- for (const step of scenario.steps) {
790
- const shot = findScreenshot(step.docEntries);
791
- const stateDocs = findStates(step.docEntries);
792
- if (!shot && stateDocs.length === 0) continue;
793
- const byLane = /* @__PURE__ */ new Map();
794
- for (const doc of stateDocs) {
795
- const lane = laneOf(doc.label);
796
- if (!byLane.has(lane) && !laneOrder.includes(lane)) laneOrder.push(lane);
797
- byLane.set(lane, doc);
798
- }
799
- const states = [];
800
- for (const lane of laneOrder) {
801
- const doc = byLane.get(lane);
802
- if (!doc) continue;
803
- const card = {
804
- ...doc.label !== void 0 && { label: doc.label },
805
- value: doc.value
806
- };
807
- if (previous.has(lane)) card.changes = diffStateValues(previous.get(lane), doc.value);
808
- previous.set(lane, doc.value);
809
- states.push(card);
810
- }
811
- frames.push({
812
- stepId: step.id,
813
- stepIndex: step.index,
814
- keyword: step.keyword,
815
- text: step.text,
816
- status: step.status,
817
- ...shot && { screenshot: { path: shot.path, ...shot.alt !== void 0 && { alt: shot.alt } } },
818
- states
819
- });
820
- }
821
- return frames;
822
- }
823
-
824
687
  // src/components/ReportStoryboard.tsx
688
+ import { extractStoryboardFrames } from "executable-stories-core/storyboard";
689
+ import { summarizeStateChanges } from "executable-stories-core/state-diff";
690
+ import { isLocalFsPath as isLocalFsPath2, safeImageUrl as safeImageUrl2 } from "executable-stories-core/utils/url";
825
691
  import { jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
826
692
  var FRAME_GLYPH = {
827
693
  passed: "\u2713",
@@ -870,7 +736,7 @@ function StoryboardFrameItem({ frame }) {
870
736
  }
871
737
  function FrameScreenshot({ frame, label }) {
872
738
  const path = frame.screenshot?.path ?? "";
873
- const src = isLocalFsPath(path) ? void 0 : safeImageUrl(path);
739
+ const src = isLocalFsPath2(path) ? void 0 : safeImageUrl2(path);
874
740
  return /* @__PURE__ */ jsx20(
875
741
  "a",
876
742
  {
@@ -976,6 +842,9 @@ function ReportAttachments({ attachments }) {
976
842
  );
977
843
  }
978
844
 
845
+ // src/components/ReportTrace.tsx
846
+ import { formatDuration as formatDuration2 } from "executable-stories-core/utils/duration";
847
+
979
848
  // src/lib/trace.ts
980
849
  var VALID = /* @__PURE__ */ new Set(["ok", "error", "unset"]);
981
850
  var safeStatus = (s) => VALID.has(s) ? s : "unset";
@@ -1028,15 +897,15 @@ function toTraceRows(spans) {
1028
897
  roots.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
1029
898
  const rows = [];
1030
899
  const seen = /* @__PURE__ */ new Set();
1031
- const walk2 = (node, depth) => {
900
+ const walk = (node, depth) => {
1032
901
  if (seen.has(node.span.spanId)) return;
1033
902
  seen.add(node.span.spanId);
1034
903
  rows.push({ ...node.span, depth });
1035
- for (const child of node.children) walk2(child, depth + 1);
904
+ for (const child of node.children) walk(child, depth + 1);
1036
905
  };
1037
- for (const root of roots) walk2(root, 0);
906
+ for (const root of roots) walk(root, 0);
1038
907
  for (const node of byId.values()) {
1039
- if (!seen.has(node.span.spanId)) walk2(node, 0);
908
+ if (!seen.has(node.span.spanId)) walk(node, 0);
1040
909
  }
1041
910
  return rows;
1042
911
  }
@@ -1070,7 +939,7 @@ function ReportTrace({ spans }) {
1070
939
  " span",
1071
940
  rows.length === 1 ? "" : "s",
1072
941
  ", ",
1073
- formatDuration(totalMs),
942
+ formatDuration2(totalMs),
1074
943
  ")"
1075
944
  ] }),
1076
945
  /* @__PURE__ */ jsx22("ul", { className: "flex flex-col gap-0.5 px-3 pb-3 font-mono text-[0.6875rem]", children: rows.map((row, i) => {
@@ -1093,7 +962,7 @@ function ReportTrace({ spans }) {
1093
962
  style: { left: `${leftPct}%`, width: `${widthPct}%` }
1094
963
  }
1095
964
  ) }),
1096
- /* @__PURE__ */ jsx22("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: formatDuration(row.durationMs) })
965
+ /* @__PURE__ */ jsx22("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: formatDuration2(row.durationMs) })
1097
966
  ] }, `${row.spanId}-${i}`);
1098
967
  }) })
1099
968
  ] });
@@ -1204,6 +1073,7 @@ function useCollapse() {
1204
1073
 
1205
1074
  // src/interactive/scenario-actions.tsx
1206
1075
  import { createContext as createContext4, useContext as useContext5 } from "react";
1076
+ import { scenarioToMarkdown as coreScenarioToMarkdown } from "executable-stories-core/utils/scenario-markdown";
1207
1077
  var ScenarioActionsContext = createContext4(null);
1208
1078
  var ScenarioActionsProvider = ScenarioActionsContext.Provider;
1209
1079
  function useScenarioActions() {
@@ -1349,7 +1219,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
1349
1219
  ] }),
1350
1220
  /* @__PURE__ */ jsxs17("div", { className: "flex shrink-0 items-center gap-2", children: [
1351
1221
  /* @__PURE__ */ jsx26(ScenarioRunHistory, { scenarioId: scenario.id }),
1352
- scenario.durationMs > 0 ? /* @__PURE__ */ jsx26("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(scenario.durationMs) }) : null,
1222
+ scenario.durationMs > 0 ? /* @__PURE__ */ jsx26("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration3(scenario.durationMs) }) : null,
1353
1223
  meta.showPill ? /* @__PURE__ */ jsx26(Badge, { variant: meta.badgeVariant, "aria-label": `Status: ${label}`, children: label }) : /* @__PURE__ */ jsx26("span", { className: "sr-only", children: `Status: ${label}` }),
1354
1224
  actions ? /* @__PURE__ */ jsxs17(DropdownMenu, { children: [
1355
1225
  /* @__PURE__ */ jsx26(
@@ -1408,8 +1278,35 @@ function ReportScenarioList({ feature }) {
1408
1278
  return /* @__PURE__ */ jsx27(Fragment8, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ jsx27(ReportScenario, { scenario }, scenario.id)) });
1409
1279
  }
1410
1280
 
1281
+ // src/components/ReportFeatureIntro.tsx
1282
+ import { jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
1283
+ function ReportFeatureIntro({ feature }) {
1284
+ const hasNarrative = Boolean(feature.narrative?.trim());
1285
+ const hasGlossary = Boolean(feature.glossary?.length);
1286
+ if (!hasNarrative && !hasGlossary) return null;
1287
+ return /* @__PURE__ */ jsxs18("div", { className: "flex flex-col gap-3 border-l-2 border-border pl-4 text-sm", children: [
1288
+ hasNarrative ? /* @__PURE__ */ jsx28(
1289
+ "div",
1290
+ {
1291
+ className: "es-doc-prose prose prose-sm max-w-none text-muted-foreground",
1292
+ dangerouslySetInnerHTML: { __html: safeMarkdownHtml(dedent(feature.narrative)) }
1293
+ }
1294
+ ) : null,
1295
+ hasGlossary ? /* @__PURE__ */ jsx28("dl", { className: "grid gap-1.5", "aria-label": "Glossary", children: feature.glossary.map((entry) => /* @__PURE__ */ jsxs18("div", { className: "flex flex-wrap gap-x-2 text-xs", children: [
1296
+ /* @__PURE__ */ jsx28("dt", { className: "font-medium text-foreground", children: entry.term }),
1297
+ /* @__PURE__ */ jsx28("dd", { className: "flex-1 text-muted-foreground", children: entry.definition })
1298
+ ] }, entry.term)) }) : null
1299
+ ] });
1300
+ }
1301
+ function featureKindLabel(kind) {
1302
+ if (kind === "ability") return "Ability";
1303
+ if (kind === "business-need") return "Business need";
1304
+ if (kind === "feature") return "Feature";
1305
+ return null;
1306
+ }
1307
+
1411
1308
  // src/components/ReportFeature.tsx
1412
- import { Fragment as Fragment9, jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
1309
+ import { Fragment as Fragment9, jsx as jsx29, jsxs as jsxs19 } from "react/jsx-runtime";
1413
1310
  function ReportFeature({ feature }) {
1414
1311
  const titleId = `${feature.id}-title`;
1415
1312
  const bodyId = `${feature.id}-body`;
@@ -1417,10 +1314,11 @@ function ReportFeature({ feature }) {
1417
1314
  const skipped = s.skipped + s.pending;
1418
1315
  const collapse = useCollapse();
1419
1316
  const collapsed = collapse?.isCollapsed(feature.id) ?? false;
1317
+ const kindLabel = featureKindLabel(feature.kind);
1420
1318
  return (
1421
1319
  // Suite = a section heading + a divider, NOT another bordered card. Drops a
1422
1320
  // layer of nesting so the scenario cards below read as the primary unit.
1423
- /* @__PURE__ */ jsxs18(
1321
+ /* @__PURE__ */ jsxs19(
1424
1322
  "section",
1425
1323
  {
1426
1324
  id: feature.id,
@@ -1428,9 +1326,9 @@ function ReportFeature({ feature }) {
1428
1326
  "aria-labelledby": titleId,
1429
1327
  className: "flex flex-col gap-3",
1430
1328
  children: [
1431
- /* @__PURE__ */ jsxs18("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1432
- /* @__PURE__ */ jsxs18("div", { className: "min-w-0", children: [
1433
- /* @__PURE__ */ jsx28("h2", { id: titleId, className: "text-base font-semibold tracking-tight text-foreground", children: collapse ? /* @__PURE__ */ jsxs18(
1329
+ /* @__PURE__ */ jsxs19("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1330
+ /* @__PURE__ */ jsxs19("div", { className: "min-w-0", children: [
1331
+ /* @__PURE__ */ jsx29("h2", { id: titleId, className: "text-base font-semibold tracking-tight text-foreground", children: collapse ? /* @__PURE__ */ jsxs19(
1434
1332
  "button",
1435
1333
  {
1436
1334
  type: "button",
@@ -1439,31 +1337,34 @@ function ReportFeature({ feature }) {
1439
1337
  "aria-controls": bodyId,
1440
1338
  className: "flex w-full cursor-pointer items-center gap-2 text-left hover:text-foreground/80",
1441
1339
  children: [
1442
- /* @__PURE__ */ jsx28("span", { "aria-hidden": true, className: cn("shrink-0 text-xs text-muted-foreground transition-transform", !collapsed && "rotate-90"), children: "\u25B8" }),
1443
- /* @__PURE__ */ jsx28("span", { className: "truncate", children: feature.title })
1340
+ /* @__PURE__ */ jsx29("span", { "aria-hidden": true, className: cn("shrink-0 text-xs text-muted-foreground transition-transform", !collapsed && "rotate-90"), children: "\u25B8" }),
1341
+ /* @__PURE__ */ jsx29("span", { className: "truncate", children: feature.title })
1444
1342
  ]
1445
1343
  }
1446
1344
  ) : feature.title }),
1447
- /* @__PURE__ */ jsx28("p", { className: cn("mt-0.5 truncate font-mono text-xs text-muted-foreground", collapse && "pl-5"), children: feature.sourceFile })
1345
+ /* @__PURE__ */ jsxs19("p", { className: cn("mt-0.5 flex items-center gap-2 text-xs text-muted-foreground", collapse && "pl-5"), children: [
1346
+ kindLabel ? /* @__PURE__ */ jsx29("span", { className: "shrink-0 rounded-sm bg-muted px-1.5 py-0.5 font-medium text-foreground", children: kindLabel }) : null,
1347
+ /* @__PURE__ */ jsx29("span", { className: "truncate font-mono", children: feature.sourceFile })
1348
+ ] })
1448
1349
  ] }),
1449
- /* @__PURE__ */ jsxs18(
1350
+ /* @__PURE__ */ jsxs19(
1450
1351
  "p",
1451
1352
  {
1452
1353
  className: "flex shrink-0 items-center gap-1.5 text-xs text-muted-foreground",
1453
1354
  "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
1454
1355
  children: [
1455
- /* @__PURE__ */ jsxs18("span", { children: [
1356
+ /* @__PURE__ */ jsxs19("span", { children: [
1456
1357
  s.passed,
1457
1358
  " passed"
1458
1359
  ] }),
1459
- /* @__PURE__ */ jsx28("span", { "aria-hidden": true, children: "\xB7" }),
1460
- /* @__PURE__ */ jsxs18("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
1360
+ /* @__PURE__ */ jsx29("span", { "aria-hidden": true, children: "\xB7" }),
1361
+ /* @__PURE__ */ jsxs19("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
1461
1362
  s.failed,
1462
1363
  " failed"
1463
1364
  ] }),
1464
- skipped > 0 ? /* @__PURE__ */ jsxs18(Fragment9, { children: [
1465
- /* @__PURE__ */ jsx28("span", { "aria-hidden": true, children: "\xB7" }),
1466
- /* @__PURE__ */ jsxs18("span", { children: [
1365
+ skipped > 0 ? /* @__PURE__ */ jsxs19(Fragment9, { children: [
1366
+ /* @__PURE__ */ jsx29("span", { "aria-hidden": true, children: "\xB7" }),
1367
+ /* @__PURE__ */ jsxs19("span", { children: [
1467
1368
  skipped,
1468
1369
  " skipped"
1469
1370
  ] })
@@ -1472,7 +1373,10 @@ function ReportFeature({ feature }) {
1472
1373
  }
1473
1374
  )
1474
1375
  ] }),
1475
- /* @__PURE__ */ jsx28("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-3", children: /* @__PURE__ */ jsx28(ReportScenarioList, { feature }) })
1376
+ /* @__PURE__ */ jsxs19("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-3", children: [
1377
+ /* @__PURE__ */ jsx29(ReportFeatureIntro, { feature }),
1378
+ /* @__PURE__ */ jsx29(ReportScenarioList, { feature })
1379
+ ] })
1476
1380
  ]
1477
1381
  }
1478
1382
  )
@@ -1480,17 +1384,17 @@ function ReportFeature({ feature }) {
1480
1384
  }
1481
1385
 
1482
1386
  // src/components/ReportFeatureList.tsx
1483
- import { jsx as jsx29 } from "react/jsx-runtime";
1387
+ import { jsx as jsx30 } from "react/jsx-runtime";
1484
1388
  function ReportFeatureList() {
1485
1389
  const report = useReport();
1486
- return /* @__PURE__ */ jsx29("div", { className: "flex flex-col gap-8", children: report.features.map((feature) => /* @__PURE__ */ jsx29(ReportFeature, { feature }, feature.id)) });
1390
+ return /* @__PURE__ */ jsx30("div", { className: "flex flex-col gap-8", children: report.features.map((feature) => /* @__PURE__ */ jsx30(ReportFeature, { feature }, feature.id)) });
1487
1391
  }
1488
1392
 
1489
1393
  // src/components/ui/empty.tsx
1490
1394
  import { cva as cva2 } from "class-variance-authority";
1491
- import { jsx as jsx30 } from "react/jsx-runtime";
1395
+ import { jsx as jsx31 } from "react/jsx-runtime";
1492
1396
  function Empty({ className, ...props }) {
1493
- return /* @__PURE__ */ jsx30(
1397
+ return /* @__PURE__ */ jsx31(
1494
1398
  "div",
1495
1399
  {
1496
1400
  "data-slot": "empty",
@@ -1503,7 +1407,7 @@ function Empty({ className, ...props }) {
1503
1407
  );
1504
1408
  }
1505
1409
  function EmptyHeader({ className, ...props }) {
1506
- return /* @__PURE__ */ jsx30(
1410
+ return /* @__PURE__ */ jsx31(
1507
1411
  "div",
1508
1412
  {
1509
1413
  "data-slot": "empty-header",
@@ -1530,7 +1434,7 @@ var emptyMediaVariants = cva2(
1530
1434
  }
1531
1435
  );
1532
1436
  function EmptyTitle({ className, ...props }) {
1533
- return /* @__PURE__ */ jsx30(
1437
+ return /* @__PURE__ */ jsx31(
1534
1438
  "div",
1535
1439
  {
1536
1440
  "data-slot": "empty-title",
@@ -1541,16 +1445,16 @@ function EmptyTitle({ className, ...props }) {
1541
1445
  }
1542
1446
 
1543
1447
  // src/components/ReportEmpty.tsx
1544
- import { jsx as jsx31 } from "react/jsx-runtime";
1448
+ import { jsx as jsx32 } from "react/jsx-runtime";
1545
1449
  function ReportEmpty({ message }) {
1546
- return /* @__PURE__ */ jsx31(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ jsx31(EmptyHeader, { children: /* @__PURE__ */ jsx31(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1450
+ return /* @__PURE__ */ jsx32(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ jsx32(EmptyHeader, { children: /* @__PURE__ */ jsx32(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1547
1451
  }
1548
1452
 
1549
1453
  // src/components/ReportSummary.tsx
1550
- import { jsx as jsx32, jsxs as jsxs19 } from "react/jsx-runtime";
1454
+ import { jsx as jsx33, jsxs as jsxs20 } from "react/jsx-runtime";
1551
1455
  function ReportSummary({ className }) {
1552
1456
  const report = useReport();
1553
- return /* @__PURE__ */ jsx32(
1457
+ return /* @__PURE__ */ jsx33(
1554
1458
  ReportSummaryView,
1555
1459
  {
1556
1460
  summary: report.summary,
@@ -1576,7 +1480,7 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1576
1480
  if (summary.pending > 0) {
1577
1481
  cards.push({ status: "pending", label: "Pending", value: summary.pending });
1578
1482
  }
1579
- return /* @__PURE__ */ jsx32(
1483
+ return /* @__PURE__ */ jsx33(
1580
1484
  "dl",
1581
1485
  {
1582
1486
  className: cn(
@@ -1589,17 +1493,17 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1589
1493
  "aria-label": ariaLabel,
1590
1494
  children: cards.map((c) => {
1591
1495
  const tone = TONE[c.status];
1592
- return /* @__PURE__ */ jsxs19(
1496
+ return /* @__PURE__ */ jsxs20(
1593
1497
  "div",
1594
1498
  {
1595
1499
  "data-status": c.status,
1596
1500
  className: "rounded-lg border bg-card px-4 py-3",
1597
1501
  children: [
1598
- /* @__PURE__ */ jsxs19("dt", { className: "flex items-center gap-1.5 font-mono text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: [
1599
- /* @__PURE__ */ jsx32("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
1502
+ /* @__PURE__ */ jsxs20("dt", { className: "flex items-center gap-1.5 font-mono text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: [
1503
+ /* @__PURE__ */ jsx33("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
1600
1504
  c.label
1601
1505
  ] }),
1602
- /* @__PURE__ */ jsx32("dd", { className: cn("mt-2 text-3xl font-semibold leading-none tracking-tight tabular-nums", tone.num), children: c.value })
1506
+ /* @__PURE__ */ jsx33("dd", { className: cn("mt-2 text-3xl font-semibold leading-none tracking-tight tabular-nums", tone.num), children: c.value })
1603
1507
  ]
1604
1508
  },
1605
1509
  c.status
@@ -1609,6 +1513,9 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1609
1513
  );
1610
1514
  }
1611
1515
 
1516
+ // src/components/ReportMeta.tsx
1517
+ import { formatDuration as formatDuration4 } from "executable-stories-core/utils/duration";
1518
+
1612
1519
  // src/lib/provenance.ts
1613
1520
  function reportLastRunMs(report) {
1614
1521
  if (report.finishedAtMs && report.finishedAtMs > 0) return report.finishedAtMs;
@@ -1659,7 +1566,7 @@ function prUrl(ci) {
1659
1566
  }
1660
1567
 
1661
1568
  // src/components/ReportMeta.tsx
1662
- import { jsx as jsx33, jsxs as jsxs20 } from "react/jsx-runtime";
1569
+ import { jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
1663
1570
  function ReportMeta() {
1664
1571
  const report = useReport();
1665
1572
  const items = [];
@@ -1668,7 +1575,7 @@ function ReportMeta() {
1668
1575
  items.push({ label: "Started", value: new Date(report.startedAtMs).toLocaleString() });
1669
1576
  }
1670
1577
  if (report.durationMs) {
1671
- items.push({ label: "Duration", value: formatDuration(report.durationMs) });
1578
+ items.push({ label: "Duration", value: formatDuration4(report.durationMs) });
1672
1579
  }
1673
1580
  if (report.packageVersion) {
1674
1581
  items.push({ label: "Version", value: report.packageVersion });
@@ -1682,13 +1589,13 @@ function ReportMeta() {
1682
1589
  const short = sha.slice(0, 8);
1683
1590
  items.push({
1684
1591
  label: "Commit",
1685
- value: url ? /* @__PURE__ */ jsx33("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1592
+ value: url ? /* @__PURE__ */ jsx34("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1686
1593
  });
1687
1594
  }
1688
1595
  if (report.ci) {
1689
1596
  items.push({
1690
1597
  label: "CI",
1691
- value: report.ci.url ? /* @__PURE__ */ jsxs20("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1598
+ value: report.ci.url ? /* @__PURE__ */ jsxs21("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1692
1599
  ciDisplayName(report.ci.name),
1693
1600
  report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
1694
1601
  ] }) : `${ciDisplayName(report.ci.name)}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
@@ -1699,7 +1606,7 @@ function ReportMeta() {
1699
1606
  const label = `#${report.ci.prNumber}`;
1700
1607
  items.push({
1701
1608
  label: "PR",
1702
- value: url ? /* @__PURE__ */ jsx33("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1609
+ value: url ? /* @__PURE__ */ jsx34("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1703
1610
  });
1704
1611
  }
1705
1612
  if (items.length === 0) return null;
@@ -1707,48 +1614,48 @@ function ReportMeta() {
1707
1614
  // Run metadata is useful but secondary to "what failed" — tuck it into a
1708
1615
  // collapsed disclosure so it doesn't push the result down. Native <details>
1709
1616
  // so it works with no JS (the SSR/no-hydration report still toggles).
1710
- /* @__PURE__ */ jsxs20("details", { "data-es-meta-card": true, className: "es-run-details group rounded-lg border border-border bg-card text-xs", children: [
1711
- /* @__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: [
1712
- /* @__PURE__ */ jsx33("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
1617
+ /* @__PURE__ */ jsxs21("details", { "data-es-meta-card": true, className: "es-run-details group rounded-lg border border-border bg-card text-xs", children: [
1618
+ /* @__PURE__ */ jsxs21("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: [
1619
+ /* @__PURE__ */ jsx34("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
1713
1620
  "Run details"
1714
1621
  ] }),
1715
- /* @__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: [
1716
- /* @__PURE__ */ jsx33("dt", { className: "font-medium", children: item.label }),
1717
- /* @__PURE__ */ jsx33("dd", { className: "font-mono text-foreground", children: item.value })
1622
+ /* @__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__ */ jsxs21("div", { className: "flex items-baseline gap-1.5", children: [
1623
+ /* @__PURE__ */ jsx34("dt", { className: "font-medium", children: item.label }),
1624
+ /* @__PURE__ */ jsx34("dd", { className: "font-mono text-foreground", children: item.value })
1718
1625
  ] }, item.label)) })
1719
1626
  ] })
1720
1627
  );
1721
1628
  }
1722
1629
 
1723
1630
  // src/components/ReportSchemaError.tsx
1724
- import { jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
1631
+ import { jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
1725
1632
  function ReportSchemaError({ error }) {
1726
- return /* @__PURE__ */ jsxs21("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1727
- /* @__PURE__ */ jsx34("p", { children: /* @__PURE__ */ jsx34("strong", { children: "Report could not be displayed." }) }),
1728
- /* @__PURE__ */ jsx34("p", { children: error.message }),
1729
- error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs21("p", { children: [
1633
+ return /* @__PURE__ */ jsxs22("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1634
+ /* @__PURE__ */ jsx35("p", { children: /* @__PURE__ */ jsx35("strong", { children: "Report could not be displayed." }) }),
1635
+ /* @__PURE__ */ jsx35("p", { children: error.message }),
1636
+ error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs22("p", { children: [
1730
1637
  "The report bundle is newer than this version of ",
1731
- /* @__PURE__ */ jsx34("code", { children: "executable-stories-react" }),
1638
+ /* @__PURE__ */ jsx35("code", { children: "executable-stories-react" }),
1732
1639
  ". Upgrade the package, or regenerate the report with an older formatters CLI."
1733
1640
  ] }) : null,
1734
- error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs21("details", { children: [
1735
- /* @__PURE__ */ jsxs21("summary", { children: [
1641
+ error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs22("details", { children: [
1642
+ /* @__PURE__ */ jsxs22("summary", { children: [
1736
1643
  error.issues.length,
1737
1644
  " validation issue",
1738
1645
  error.issues.length === 1 ? "" : "s"
1739
1646
  ] }),
1740
- /* @__PURE__ */ jsx34("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1647
+ /* @__PURE__ */ jsx35("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1741
1648
  ] }) : null
1742
1649
  ] });
1743
1650
  }
1744
1651
 
1745
1652
  // src/components/ReportShell.tsx
1746
- import { Fragment as Fragment10, jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
1653
+ import { Fragment as Fragment10, jsx as jsx36, jsxs as jsxs23 } from "react/jsx-runtime";
1747
1654
  function ReportTitleBlock({ title }) {
1748
- return /* @__PURE__ */ jsxs22(Fragment10, { children: [
1749
- /* @__PURE__ */ jsx35("h1", { children: title ?? "Story Report" }),
1750
- /* @__PURE__ */ jsx35(ReportSummary, {}),
1751
- /* @__PURE__ */ jsx35(ReportMeta, {})
1655
+ return /* @__PURE__ */ jsxs23(Fragment10, { children: [
1656
+ /* @__PURE__ */ jsx36("h1", { children: title ?? "Story Report" }),
1657
+ /* @__PURE__ */ jsx36(ReportSummary, {}),
1658
+ /* @__PURE__ */ jsx36(ReportMeta, {})
1752
1659
  ] });
1753
1660
  }
1754
1661
  function ReportErrorShell({
@@ -1757,28 +1664,28 @@ function ReportErrorShell({
1757
1664
  title,
1758
1665
  dataTheme
1759
1666
  }) {
1760
- return /* @__PURE__ */ jsx35("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ jsx35(ReportSchemaError, { error }) });
1667
+ return /* @__PURE__ */ jsx36("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ jsx36(ReportSchemaError, { error }) });
1761
1668
  }
1762
1669
 
1763
1670
  // src/components/Report.tsx
1764
- import { jsx as jsx36, jsxs as jsxs23 } from "react/jsx-runtime";
1671
+ import { jsx as jsx37, jsxs as jsxs24 } from "react/jsx-runtime";
1765
1672
  function Report(props) {
1766
1673
  const { report, customRenderers, renderers, className, title, dataTheme } = props;
1767
1674
  const result = unwrapReport(report);
1768
1675
  if (!result.ok) {
1769
- return /* @__PURE__ */ jsx36(ReportErrorShell, { error: result.error, className, title, dataTheme });
1676
+ return /* @__PURE__ */ jsx37(ReportErrorShell, { error: result.error, className, title, dataTheme });
1770
1677
  }
1771
1678
  const data = result.data;
1772
1679
  const hasContent = data.features.length > 0;
1773
- 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: [
1774
- /* @__PURE__ */ jsx36("header", { className: "es-report-header", children: /* @__PURE__ */ jsx36(ReportTitleBlock, { title }) }),
1775
- hasContent ? /* @__PURE__ */ jsx36(ReportFeatureList, {}) : /* @__PURE__ */ jsx36(ReportEmpty, {})
1680
+ return /* @__PURE__ */ jsx37(ReportRoot, { report: data, customRenderers, renderers, children: /* @__PURE__ */ jsxs24("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: [
1681
+ /* @__PURE__ */ jsx37("header", { className: "es-report-header", children: /* @__PURE__ */ jsx37(ReportTitleBlock, { title }) }),
1682
+ hasContent ? /* @__PURE__ */ jsx37(ReportFeatureList, {}) : /* @__PURE__ */ jsx37(ReportEmpty, {})
1776
1683
  ] }) });
1777
1684
  }
1778
1685
 
1779
1686
  // src/components/doc/MermaidView.tsx
1780
1687
  import { useEffect, useRef, useState as useState2 } from "react";
1781
- import { Fragment as Fragment11, jsx as jsx37, jsxs as jsxs24 } from "react/jsx-runtime";
1688
+ import { Fragment as Fragment11, jsx as jsx38, jsxs as jsxs25 } from "react/jsx-runtime";
1782
1689
  function prefersDark(el) {
1783
1690
  const themed = el?.closest("[data-theme]");
1784
1691
  if (themed?.dataset.theme === "dark") return true;
@@ -1832,9 +1739,9 @@ function MermaidView({ entry, load }) {
1832
1739
  };
1833
1740
  }, [entry.code, load]);
1834
1741
  if (svg) {
1835
- return /* @__PURE__ */ jsxs24("figure", { ref, className: "my-3", children: [
1836
- entry.title ? /* @__PURE__ */ jsx37("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
1837
- /* @__PURE__ */ jsx37(
1742
+ return /* @__PURE__ */ jsxs25("figure", { ref, className: "my-3", children: [
1743
+ entry.title ? /* @__PURE__ */ jsx38("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
1744
+ /* @__PURE__ */ jsx38(
1838
1745
  "div",
1839
1746
  {
1840
1747
  role: "img",
@@ -1845,8 +1752,8 @@ function MermaidView({ entry, load }) {
1845
1752
  )
1846
1753
  ] });
1847
1754
  }
1848
- return /* @__PURE__ */ jsxs24(Fragment11, { children: [
1849
- error ? /* @__PURE__ */ jsxs24(
1755
+ return /* @__PURE__ */ jsxs25(Fragment11, { children: [
1756
+ error ? /* @__PURE__ */ jsxs25(
1850
1757
  "p",
1851
1758
  {
1852
1759
  role: "alert",
@@ -1858,19 +1765,19 @@ function MermaidView({ entry, load }) {
1858
1765
  ]
1859
1766
  }
1860
1767
  ) : null,
1861
- /* @__PURE__ */ jsx37(MermaidSource, { entry, ref })
1768
+ /* @__PURE__ */ jsx38(MermaidSource, { entry, ref })
1862
1769
  ] });
1863
1770
  }
1864
1771
 
1865
1772
  // src/components/doc/MermaidDiagram.tsx
1866
- import { jsx as jsx38 } from "react/jsx-runtime";
1773
+ import { jsx as jsx39 } from "react/jsx-runtime";
1867
1774
  var mermaidLoader;
1868
1775
  function loadMermaid() {
1869
1776
  mermaidLoader ??= import("mermaid").then((m) => m.default);
1870
1777
  return mermaidLoader;
1871
1778
  }
1872
1779
  function MermaidDiagram({ entry, load = loadMermaid }) {
1873
- return /* @__PURE__ */ jsx38(MermaidView, { entry, load });
1780
+ return /* @__PURE__ */ jsx39(MermaidView, { entry, load });
1874
1781
  }
1875
1782
 
1876
1783
  // src/components/ScenarioExplorer.tsx
@@ -1880,12 +1787,12 @@ import { useState as useState3 } from "react";
1880
1787
  import "react";
1881
1788
  import { Command as CommandPrimitive } from "cmdk";
1882
1789
  import { SearchIcon } from "lucide-react";
1883
- import { jsx as jsx39, jsxs as jsxs25 } from "react/jsx-runtime";
1790
+ import { jsx as jsx40, jsxs as jsxs26 } from "react/jsx-runtime";
1884
1791
  function Command({
1885
1792
  className,
1886
1793
  ...props
1887
1794
  }) {
1888
- return /* @__PURE__ */ jsx39(
1795
+ return /* @__PURE__ */ jsx40(
1889
1796
  CommandPrimitive,
1890
1797
  {
1891
1798
  "data-slot": "command",
@@ -1901,14 +1808,14 @@ function CommandInput({
1901
1808
  className,
1902
1809
  ...props
1903
1810
  }) {
1904
- return /* @__PURE__ */ jsxs25(
1811
+ return /* @__PURE__ */ jsxs26(
1905
1812
  "div",
1906
1813
  {
1907
1814
  "data-slot": "command-input-wrapper",
1908
1815
  className: "flex h-9 items-center gap-2 border-b px-3",
1909
1816
  children: [
1910
- /* @__PURE__ */ jsx39(SearchIcon, { className: "size-4 shrink-0 opacity-50" }),
1911
- /* @__PURE__ */ jsx39(
1817
+ /* @__PURE__ */ jsx40(SearchIcon, { className: "size-4 shrink-0 opacity-50" }),
1818
+ /* @__PURE__ */ jsx40(
1912
1819
  CommandPrimitive.Input,
1913
1820
  {
1914
1821
  "data-slot": "command-input",
@@ -1927,7 +1834,7 @@ function CommandList({
1927
1834
  className,
1928
1835
  ...props
1929
1836
  }) {
1930
- return /* @__PURE__ */ jsx39(
1837
+ return /* @__PURE__ */ jsx40(
1931
1838
  CommandPrimitive.List,
1932
1839
  {
1933
1840
  "data-slot": "command-list",
@@ -1942,7 +1849,7 @@ function CommandList({
1942
1849
  function CommandEmpty({
1943
1850
  ...props
1944
1851
  }) {
1945
- return /* @__PURE__ */ jsx39(
1852
+ return /* @__PURE__ */ jsx40(
1946
1853
  CommandPrimitive.Empty,
1947
1854
  {
1948
1855
  "data-slot": "command-empty",
@@ -1955,7 +1862,7 @@ function CommandGroup({
1955
1862
  className,
1956
1863
  ...props
1957
1864
  }) {
1958
- return /* @__PURE__ */ jsx39(
1865
+ return /* @__PURE__ */ jsx40(
1959
1866
  CommandPrimitive.Group,
1960
1867
  {
1961
1868
  "data-slot": "command-group",
@@ -1971,7 +1878,7 @@ function CommandItem({
1971
1878
  className,
1972
1879
  ...props
1973
1880
  }) {
1974
- return /* @__PURE__ */ jsx39(
1881
+ return /* @__PURE__ */ jsx40(
1975
1882
  CommandPrimitive.Item,
1976
1883
  {
1977
1884
  "data-slot": "command-item",
@@ -1985,7 +1892,7 @@ function CommandItem({
1985
1892
  }
1986
1893
 
1987
1894
  // src/components/ScenarioExplorer.tsx
1988
- import { jsx as jsx40, jsxs as jsxs26 } from "react/jsx-runtime";
1895
+ import { jsx as jsx41, jsxs as jsxs27 } from "react/jsx-runtime";
1989
1896
  var STATUS_FILTERS = [
1990
1897
  { value: "all", label: "All" },
1991
1898
  { value: "passed", label: "Passed" },
@@ -1996,22 +1903,22 @@ var STATUS_FILTERS = [
1996
1903
  function ScenarioExplorer({ scenarios, summary }) {
1997
1904
  const [status, setStatus] = useState3("all");
1998
1905
  const visible = status === "all" ? scenarios : scenarios.filter((s) => s.status === status);
1999
- return /* @__PURE__ */ jsxs26("div", { className: "font-sans text-foreground", children: [
2000
- summary ? /* @__PURE__ */ jsxs26("p", { className: "mb-3 text-sm text-muted-foreground", children: [
1906
+ return /* @__PURE__ */ jsxs27("div", { className: "font-sans text-foreground", children: [
1907
+ summary ? /* @__PURE__ */ jsxs27("p", { className: "mb-3 text-sm text-muted-foreground", children: [
2001
1908
  summary.total,
2002
1909
  " scenarios \xB7 ",
2003
- /* @__PURE__ */ jsxs26("span", { className: "text-pass", children: [
1910
+ /* @__PURE__ */ jsxs27("span", { className: "text-pass", children: [
2004
1911
  summary.passed,
2005
1912
  " passed"
2006
1913
  ] }),
2007
1914
  " \xB7",
2008
1915
  " ",
2009
- /* @__PURE__ */ jsxs26("span", { className: "text-fail", children: [
1916
+ /* @__PURE__ */ jsxs27("span", { className: "text-fail", children: [
2010
1917
  summary.failed,
2011
1918
  " failed"
2012
1919
  ] })
2013
1920
  ] }) : null,
2014
- /* @__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(
1921
+ /* @__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(
2015
1922
  "button",
2016
1923
  {
2017
1924
  type: "button",
@@ -2026,26 +1933,26 @@ function ScenarioExplorer({ scenarios, summary }) {
2026
1933
  },
2027
1934
  f.value
2028
1935
  )) }),
2029
- /* @__PURE__ */ jsxs26(
1936
+ /* @__PURE__ */ jsxs27(
2030
1937
  Command,
2031
1938
  {
2032
1939
  className: "rounded-lg border border-border bg-card",
2033
1940
  filter: (value, search) => value.toLowerCase().includes(search.toLowerCase()) ? 1 : 0,
2034
1941
  children: [
2035
- /* @__PURE__ */ jsx40(CommandInput, { placeholder: "Search scenarios\u2026" }),
2036
- /* @__PURE__ */ jsxs26(CommandList, { className: "max-h-none", children: [
2037
- /* @__PURE__ */ jsx40(CommandEmpty, { children: "No scenarios match your filters." }),
2038
- /* @__PURE__ */ jsx40(CommandGroup, { children: visible.map((s) => /* @__PURE__ */ jsx40(
1942
+ /* @__PURE__ */ jsx41(CommandInput, { placeholder: "Search scenarios\u2026" }),
1943
+ /* @__PURE__ */ jsxs27(CommandList, { className: "max-h-none", children: [
1944
+ /* @__PURE__ */ jsx41(CommandEmpty, { children: "No scenarios match your filters." }),
1945
+ /* @__PURE__ */ jsx41(CommandGroup, { children: visible.map((s) => /* @__PURE__ */ jsx41(
2039
1946
  CommandItem,
2040
1947
  {
2041
1948
  value: `${s.title} ${s.feature} ${s.tags.join(" ")}`,
2042
1949
  asChild: true,
2043
1950
  className: "cursor-pointer",
2044
- children: /* @__PURE__ */ jsxs26("a", { href: s.href, className: "flex items-center gap-3", children: [
2045
- /* @__PURE__ */ jsx40(Badge, { variant: s.status, children: s.status }),
2046
- /* @__PURE__ */ jsx40("span", { className: "min-w-0 flex-1 truncate font-medium", children: s.title }),
2047
- s.tags.slice(0, 3).map((t) => /* @__PURE__ */ jsx40(Badge, { variant: "tag", className: "hidden sm:inline-flex", children: t }, t)),
2048
- /* @__PURE__ */ jsx40("code", { className: "hidden font-mono text-xs text-muted-foreground md:inline", children: s.feature })
1951
+ children: /* @__PURE__ */ jsxs27("a", { href: s.href, className: "flex items-center gap-3", children: [
1952
+ /* @__PURE__ */ jsx41(Badge, { variant: s.status, children: s.status }),
1953
+ /* @__PURE__ */ jsx41("span", { className: "min-w-0 flex-1 truncate font-medium", children: s.title }),
1954
+ s.tags.slice(0, 3).map((t) => /* @__PURE__ */ jsx41(Badge, { variant: "tag", className: "hidden sm:inline-flex", children: t }, t)),
1955
+ /* @__PURE__ */ jsx41("code", { className: "hidden font-mono text-xs text-muted-foreground md:inline", children: s.feature })
2049
1956
  ] })
2050
1957
  },
2051
1958
  s.id
@@ -2192,7 +2099,7 @@ var story_report_v1_default = {
2192
2099
  title: {
2193
2100
  type: "string",
2194
2101
  minLength: 1,
2195
- description: "Display title. Derived from describe block when present, otherwise the file basename."
2102
+ description: "Display title. Comes from story.feature(...) when declared, otherwise the describe block or file basename."
2196
2103
  },
2197
2104
  sourceFile: {
2198
2105
  type: "string",
@@ -2208,6 +2115,26 @@ var story_report_v1_default = {
2208
2115
  $ref: "#/$defs/Scenario"
2209
2116
  },
2210
2117
  description: "Scenarios in this feature, in declaration order."
2118
+ },
2119
+ kind: {
2120
+ type: "string",
2121
+ enum: [
2122
+ "feature",
2123
+ "ability",
2124
+ "business-need"
2125
+ ],
2126
+ description: "How the feature was declared. Absent when the title was derived rather than declared."
2127
+ },
2128
+ narrative: {
2129
+ type: "string",
2130
+ description: "Markdown explaining why the feature exists and who it serves."
2131
+ },
2132
+ glossary: {
2133
+ type: "array",
2134
+ items: {
2135
+ $ref: "#/$defs/GlossaryTerm"
2136
+ },
2137
+ description: "Terms this feature defines, linked wherever they appear in scenario text."
2211
2138
  }
2212
2139
  },
2213
2140
  required: [
@@ -3019,6 +2946,25 @@ var story_report_v1_default = {
3019
2946
  }
3020
2947
  },
3021
2948
  additionalProperties: false
2949
+ },
2950
+ GlossaryTerm: {
2951
+ type: "object",
2952
+ description: "One glossary entry: a term the scenarios use and what it means.",
2953
+ properties: {
2954
+ term: {
2955
+ type: "string",
2956
+ minLength: 1
2957
+ },
2958
+ definition: {
2959
+ type: "string",
2960
+ minLength: 1
2961
+ }
2962
+ },
2963
+ required: [
2964
+ "term",
2965
+ "definition"
2966
+ ],
2967
+ additionalProperties: false
3022
2968
  }
3023
2969
  }
3024
2970
  };