executable-stories-react 0.10.0 → 0.10.1

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.
@@ -334,23 +334,11 @@ function useReport() {
334
334
  return ctx.report;
335
335
  }
336
336
 
337
- // ../executable-stories-core/dist/utils/duration.js
338
- function formatDuration(ms) {
339
- if (ms < 1e3) {
340
- return `${Math.round(ms)} ms`;
341
- }
342
- if (ms < 6e4) {
343
- return `${(ms / 1e3).toFixed(2)} s`;
344
- }
345
- const minutes = Math.floor(ms / 6e4);
346
- const seconds = Math.floor(ms % 6e4 / 1e3);
347
- if (minutes < 60) {
348
- return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
349
- }
350
- const hours = Math.floor(minutes / 60);
351
- const remainingMinutes = minutes % 60;
352
- return `${hours}h ${remainingMinutes}m`;
353
- }
337
+ // src/components/ReportScenario.tsx
338
+ var import_duration3 = require("executable-stories-core/utils/duration");
339
+
340
+ // src/components/ReportSteps.tsx
341
+ var import_duration = require("executable-stories-core/utils/duration");
354
342
 
355
343
  // src/components/doc/DocNote.tsx
356
344
  var import_jsx_runtime3 = require("react/jsx-runtime");
@@ -419,7 +407,7 @@ function DocTable({ entry }) {
419
407
  entry.label ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
420
408
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { tabIndex: 0, className: "overflow-x-auto rounded-md border border-border", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("table", { className: "w-full border-collapse text-sm", children: [
421
409
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tr", { className: "bg-muted/50", children: entry.columns.map((c) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: c }, c)) }) }),
422
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tbody", { children: entry.rows.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tr", { children: row.map((cell2, j) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { className: "border-b border-border px-3 py-2 text-foreground", children: cell2 }, j)) }, i)) })
410
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tbody", { children: entry.rows.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tr", { children: row.map((cell, j) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { className: "border-b border-border px-3 py-2 text-foreground", children: cell }, j)) }, i)) })
423
411
  ] }) })
424
412
  ] });
425
413
  }
@@ -442,28 +430,13 @@ function DocLink({ entry }) {
442
430
  // src/components/doc/DocSection.tsx
443
431
  var import_marked = require("marked");
444
432
 
445
- // ../executable-stories-core/dist/utils/url.js
446
- function safeUrl(value) {
447
- if (!value) return void 0;
448
- const trimmed = value.trim();
449
- const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(trimmed);
450
- if (scheme && !/^https?$/i.test(scheme[1] ?? "")) return void 0;
451
- return trimmed;
452
- }
453
- function safeImageUrl(value) {
454
- if (!value) return void 0;
455
- const trimmed = value.trim();
456
- if (/^data:image\//i.test(trimmed)) return trimmed;
457
- return safeUrl(trimmed);
458
- }
459
- function isLocalFsPath(value) {
460
- return /^(?:[/\\]|[A-Za-z]:[/\\])/.test(value);
461
- }
433
+ // src/lib/url.ts
434
+ var import_url = require("executable-stories-core/utils/url");
462
435
 
463
436
  // src/components/doc/DocSection.tsx
464
437
  var import_jsx_runtime9 = require("react/jsx-runtime");
465
438
  function neutralizeUrl(value) {
466
- return safeUrl(value) ?? "#";
439
+ return (0, import_url.safeUrl)(value) ?? "#";
467
440
  }
468
441
  function safeMarkdownHtml(markdown) {
469
442
  const raw = import_marked.marked.parse(markdown, { async: false });
@@ -514,7 +487,7 @@ function DocMermaid({ entry }) {
514
487
  // src/components/doc/DocScreenshot.tsx
515
488
  var import_jsx_runtime11 = require("react/jsx-runtime");
516
489
  function DocScreenshot({ entry }) {
517
- const src = isLocalFsPath(entry.path) ? void 0 : safeImageUrl(entry.path);
490
+ const src = (0, import_url.isLocalFsPath)(entry.path) ? void 0 : (0, import_url.safeImageUrl)(entry.path);
518
491
  if (!src) {
519
492
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("figure", { className: "my-3 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground", children: [
520
493
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "font-medium", children: "Screenshot unavailable" }),
@@ -539,15 +512,15 @@ function DocScreenshot({ entry }) {
539
512
  // src/components/doc/DocVideo.tsx
540
513
  var import_jsx_runtime12 = require("react/jsx-runtime");
541
514
  function DocVideo({ entry }) {
542
- if (isLocalFsPath(entry.path)) {
515
+ if ((0, import_url.isLocalFsPath)(entry.path)) {
543
516
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("figure", { className: "my-3 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground", children: [
544
517
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "font-medium", children: "Video unavailable" }),
545
518
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
546
519
  entry.caption ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("figcaption", { className: "mt-1.5 text-xs", children: entry.caption }) : null
547
520
  ] });
548
521
  }
549
- const src = safeUrl(entry.path);
550
- const poster = safeUrl(entry.poster);
522
+ const src = (0, import_url.safeUrl)(entry.path);
523
+ const poster = (0, import_url.safeUrl)(entry.poster);
551
524
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("figure", { className: "my-3", children: [
552
525
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
553
526
  "video",
@@ -568,7 +541,7 @@ var import_jsx_runtime13 = require("react/jsx-runtime");
568
541
  function DocHtml({ entry }) {
569
542
  const height = typeof entry.height === "number" ? `${entry.height}px` : entry.height ?? "400px";
570
543
  const title = entry.title ?? "Embedded HTML";
571
- const safeHref = safeUrl(entry.url ?? entry.path);
544
+ const safeHref = (0, import_url.safeUrl)(entry.url ?? entry.path);
572
545
  const frameProps = entry.content !== void 0 ? { srcDoc: entry.content } : { src: safeHref ?? "" };
573
546
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("figure", { className: "my-3 overflow-hidden rounded-md border border-border", children: [
574
547
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("figcaption", { className: "flex items-center justify-between border-b border-border bg-muted/40 px-3 py-1.5 text-xs font-medium text-muted-foreground", children: [
@@ -735,7 +708,7 @@ function ReportStepItem({ step }) {
735
708
  }
736
709
  ),
737
710
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "flex-1 text-foreground", children: highlightStepParams(step.text) }),
738
- step.durationMs > 0 ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "shrink-0 whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(step.durationMs) }) : null
711
+ step.durationMs > 0 ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "shrink-0 whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: (0, import_duration.formatDuration)(step.durationMs) }) : null
739
712
  ] }),
740
713
  step.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
741
714
  "pre",
@@ -751,125 +724,10 @@ function ReportStepItem({ step }) {
751
724
  );
752
725
  }
753
726
 
754
- // ../executable-stories-core/dist/state-diff.js
755
- var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
756
- function deepEqual(a, b) {
757
- if (Object.is(a, b)) return true;
758
- if (Array.isArray(a) && Array.isArray(b)) {
759
- return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
760
- }
761
- if (isPlainObject(a) && isPlainObject(b)) {
762
- const ak = Object.keys(a);
763
- const bk = Object.keys(b);
764
- return ak.length === bk.length && ak.every((k) => k in b && deepEqual(a[k], b[k]));
765
- }
766
- return false;
767
- }
768
- var joinKey = (path, key) => path === "" ? key : `${path}.${key}`;
769
- function walk(path, before, after, out) {
770
- if (Object.is(before, after)) return;
771
- if (Array.isArray(before) && Array.isArray(after)) {
772
- const shared = Math.min(before.length, after.length);
773
- for (let i = 0; i < shared; i++) walk(`${path}[${i}]`, before[i], after[i], out);
774
- for (let i = shared; i < after.length; i++) out.push({ path: `${path}[${i}]`, kind: "added", after: after[i] });
775
- for (let i = shared; i < before.length; i++) out.push({ path: `${path}[${i}]`, kind: "removed", before: before[i] });
776
- return;
777
- }
778
- if (isPlainObject(before) && isPlainObject(after)) {
779
- for (const key of Object.keys(after)) {
780
- const p = joinKey(path, key);
781
- if (key in before) walk(p, before[key], after[key], out);
782
- else out.push({ path: p, kind: "added", after: after[key] });
783
- }
784
- for (const key of Object.keys(before)) {
785
- if (!(key in after)) out.push({ path: joinKey(path, key), kind: "removed", before: before[key] });
786
- }
787
- return;
788
- }
789
- if (!deepEqual(before, after)) out.push({ path, kind: "changed", before, after });
790
- }
791
- function diffStateValues(before, after) {
792
- const out = [];
793
- walk("", before, after, out);
794
- return out;
795
- }
796
- function formatStateValue(value, maxLen = 60) {
797
- const s = value === void 0 ? "undefined" : JSON.stringify(value);
798
- return s.length > maxLen ? `${s.slice(0, maxLen - 1)}\u2026` : s;
799
- }
800
- function summarizeStateChanges(changes) {
801
- return changes.map((c) => {
802
- const at = c.path === "" ? "(root)" : c.path;
803
- switch (c.kind) {
804
- case "added":
805
- return `+ ${at}: ${formatStateValue(c.after)}`;
806
- case "removed":
807
- return `\u2212 ${at}`;
808
- case "changed":
809
- return `${at}: ${formatStateValue(c.before)} \u2192 ${formatStateValue(c.after)}`;
810
- }
811
- });
812
- }
813
-
814
- // ../executable-stories-core/dist/storyboard.js
815
- function findScreenshot(entries) {
816
- for (const entry of entries) {
817
- if (entry.kind === "screenshot") return entry;
818
- if (entry.children) {
819
- const nested = findScreenshot(entry.children);
820
- if (nested) return nested;
821
- }
822
- }
823
- return void 0;
824
- }
825
- function findStates(entries, out = []) {
826
- for (const entry of entries) {
827
- if (entry.kind === "state") out.push(entry);
828
- if (entry.children) findStates(entry.children, out);
829
- }
830
- return out;
831
- }
832
- var laneOf = (label) => label ?? "";
833
- function extractStoryboardFrames(scenario) {
834
- const frames = [];
835
- const laneOrder = [];
836
- const previous = /* @__PURE__ */ new Map();
837
- for (const step of scenario.steps) {
838
- const shot = findScreenshot(step.docEntries);
839
- const stateDocs = findStates(step.docEntries);
840
- if (!shot && stateDocs.length === 0) continue;
841
- const byLane = /* @__PURE__ */ new Map();
842
- for (const doc of stateDocs) {
843
- const lane = laneOf(doc.label);
844
- if (!byLane.has(lane) && !laneOrder.includes(lane)) laneOrder.push(lane);
845
- byLane.set(lane, doc);
846
- }
847
- const states = [];
848
- for (const lane of laneOrder) {
849
- const doc = byLane.get(lane);
850
- if (!doc) continue;
851
- const card = {
852
- ...doc.label !== void 0 && { label: doc.label },
853
- value: doc.value
854
- };
855
- if (previous.has(lane)) card.changes = diffStateValues(previous.get(lane), doc.value);
856
- previous.set(lane, doc.value);
857
- states.push(card);
858
- }
859
- frames.push({
860
- stepId: step.id,
861
- stepIndex: step.index,
862
- keyword: step.keyword,
863
- text: step.text,
864
- status: step.status,
865
- ...shot && { screenshot: { path: shot.path, ...shot.alt !== void 0 && { alt: shot.alt } } },
866
- states
867
- });
868
- }
869
- return frames;
870
- }
871
-
872
727
  // src/components/ReportStoryboard.tsx
728
+ var import_storyboard = require("executable-stories-core/storyboard");
729
+ var import_state_diff = require("executable-stories-core/state-diff");
730
+ var import_url6 = require("executable-stories-core/utils/url");
873
731
  var import_jsx_runtime20 = require("react/jsx-runtime");
874
732
  var FRAME_GLYPH = {
875
733
  passed: "\u2713",
@@ -884,7 +742,7 @@ var FRAME_GLYPH_COLOR = {
884
742
  pending: "text-pend"
885
743
  };
886
744
  function ReportStoryboard({ scenario }) {
887
- const frames = extractStoryboardFrames(scenario);
745
+ const frames = (0, import_storyboard.extractStoryboardFrames)(scenario);
888
746
  if (frames.length < 2) return null;
889
747
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("section", { "data-slot": "storyboard", "aria-label": "Storyboard", className: "mb-3", children: [
890
748
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("h4", { className: "mb-1.5 text-xs font-semibold tracking-wide text-muted-foreground uppercase", children: "Storyboard" }),
@@ -918,7 +776,7 @@ function StoryboardFrameItem({ frame }) {
918
776
  }
919
777
  function FrameScreenshot({ frame, label }) {
920
778
  const path = frame.screenshot?.path ?? "";
921
- const src = isLocalFsPath(path) ? void 0 : safeImageUrl(path);
779
+ const src = (0, import_url6.isLocalFsPath)(path) ? void 0 : (0, import_url6.safeImageUrl)(path);
922
780
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
923
781
  "a",
924
782
  {
@@ -964,7 +822,7 @@ function StateCardView({ card }) {
964
822
  "rounded-sm px-1.5 py-0.5 font-mono text-[0.625rem] leading-snug break-all",
965
823
  CHANGE_TONE[change.kind]
966
824
  ),
967
- children: summarizeStateChanges([change])[0]
825
+ children: (0, import_state_diff.summarizeStateChanges)([change])[0]
968
826
  },
969
827
  i
970
828
  )) }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "mt-1.5 text-[0.625rem] text-muted-foreground", children: "No changes" })),
@@ -1024,6 +882,9 @@ function ReportAttachments({ attachments }) {
1024
882
  );
1025
883
  }
1026
884
 
885
+ // src/components/ReportTrace.tsx
886
+ var import_duration2 = require("executable-stories-core/utils/duration");
887
+
1027
888
  // src/lib/trace.ts
1028
889
  var VALID = /* @__PURE__ */ new Set(["ok", "error", "unset"]);
1029
890
  var safeStatus = (s) => VALID.has(s) ? s : "unset";
@@ -1076,15 +937,15 @@ function toTraceRows(spans) {
1076
937
  roots.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
1077
938
  const rows = [];
1078
939
  const seen = /* @__PURE__ */ new Set();
1079
- const walk2 = (node, depth) => {
940
+ const walk = (node, depth) => {
1080
941
  if (seen.has(node.span.spanId)) return;
1081
942
  seen.add(node.span.spanId);
1082
943
  rows.push({ ...node.span, depth });
1083
- for (const child of node.children) walk2(child, depth + 1);
944
+ for (const child of node.children) walk(child, depth + 1);
1084
945
  };
1085
- for (const root of roots) walk2(root, 0);
946
+ for (const root of roots) walk(root, 0);
1086
947
  for (const node of byId.values()) {
1087
- if (!seen.has(node.span.spanId)) walk2(node, 0);
948
+ if (!seen.has(node.span.spanId)) walk(node, 0);
1088
949
  }
1089
950
  return rows;
1090
951
  }
@@ -1118,7 +979,7 @@ function ReportTrace({ spans }) {
1118
979
  " span",
1119
980
  rows.length === 1 ? "" : "s",
1120
981
  ", ",
1121
- formatDuration(totalMs),
982
+ (0, import_duration2.formatDuration)(totalMs),
1122
983
  ")"
1123
984
  ] }),
1124
985
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("ul", { className: "flex flex-col gap-0.5 px-3 pb-3 font-mono text-[0.6875rem]", children: rows.map((row, i) => {
@@ -1141,7 +1002,7 @@ function ReportTrace({ spans }) {
1141
1002
  style: { left: `${leftPct}%`, width: `${widthPct}%` }
1142
1003
  }
1143
1004
  ) }),
1144
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: formatDuration(row.durationMs) })
1005
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: (0, import_duration2.formatDuration)(row.durationMs) })
1145
1006
  ] }, `${row.spanId}-${i}`);
1146
1007
  }) })
1147
1008
  ] });
@@ -1317,130 +1178,14 @@ function useCollapseState(computeDefault) {
1317
1178
 
1318
1179
  // src/interactive/scenario-actions.tsx
1319
1180
  var import_react7 = require("react");
1320
-
1321
- // ../executable-stories-core/dist/utils/scenario-markdown.js
1322
- function cell(value) {
1323
- return value.replaceAll("|", "\\|").replaceAll("\n", " ");
1324
- }
1325
- function stringify(value) {
1326
- if (typeof value === "string") return value;
1327
- try {
1328
- return JSON.stringify(value, null, 2) ?? String(value);
1329
- } catch {
1330
- return String(value);
1331
- }
1332
- }
1333
- function docEntryToMarkdown(entry, stateLanes) {
1334
- const lines = [];
1335
- switch (entry.kind) {
1336
- case "note":
1337
- lines.push(entry.text);
1338
- break;
1339
- case "tag":
1340
- lines.push(entry.names.map((n) => `\`@${n}\``).join(" "));
1341
- break;
1342
- case "kv":
1343
- lines.push(`**${entry.label}:** ${stringify(entry.value)}`);
1344
- break;
1345
- case "code":
1346
- lines.push(`**${entry.label}**`, "", "```" + (entry.lang ?? ""), entry.content, "```");
1347
- break;
1348
- case "table": {
1349
- lines.push(`**${entry.label}**`, "");
1350
- lines.push(`| ${entry.columns.map(cell).join(" | ")} |`);
1351
- lines.push(`| ${entry.columns.map(() => "---").join(" | ")} |`);
1352
- for (const row of entry.rows) lines.push(`| ${row.map(cell).join(" | ")} |`);
1353
- break;
1354
- }
1355
- case "link":
1356
- lines.push(`[${entry.label}](${entry.url})`);
1357
- break;
1358
- case "section":
1359
- lines.push(`### ${entry.title}`, "", entry.markdown);
1360
- break;
1361
- case "mermaid":
1362
- if (entry.title) lines.push(`**${entry.title}**`, "");
1363
- lines.push("```mermaid", entry.code, "```");
1364
- break;
1365
- case "screenshot":
1366
- lines.push(`![${entry.alt ?? "screenshot"}](${entry.path})`);
1367
- break;
1368
- case "video":
1369
- lines.push(`Video: ${entry.path}${entry.caption ? ` \u2014 ${entry.caption}` : ""}`);
1370
- break;
1371
- case "html": {
1372
- const htmlTitle = entry.title ?? "Embedded HTML";
1373
- if (entry.content) {
1374
- lines.push(`**${htmlTitle}**`, "", "```html", entry.content, "```");
1375
- } else if (entry.url) {
1376
- lines.push(`[${htmlTitle}](${entry.url})`);
1377
- } else {
1378
- lines.push(`Embedded HTML: ${entry.path ?? "(inline)"}`);
1379
- }
1380
- break;
1381
- }
1382
- case "custom":
1383
- lines.push(`**${entry.type}**`, "", "```json", stringify(entry.data), "```");
1384
- break;
1385
- case "state": {
1386
- const lane = entry.label ?? "";
1387
- lines.push(`**${entry.label ?? "State"}**`);
1388
- if (stateLanes?.has(lane)) {
1389
- const summary = summarizeStateChanges(diffStateValues(stateLanes.get(lane), entry.value));
1390
- if (summary.length > 0) lines.push("", ...summary.map((l) => `- ${l}`));
1391
- }
1392
- stateLanes?.set(lane, entry.value);
1393
- lines.push("", "<details>", "<summary>snapshot</summary>", "", "```json", stringify(entry.value ?? null), "```", "", "</details>");
1394
- break;
1395
- }
1396
- }
1397
- for (const child of entry.children ?? []) {
1398
- lines.push("", ...docEntryToMarkdown(child, stateLanes));
1399
- }
1400
- return lines;
1401
- }
1402
- function stepLines(step, ordinal, full, stateLanes) {
1403
- const status = step.status === "passed" ? "" : ` \u2014 **${step.status}**`;
1404
- const lines = [`${ordinal}. **${step.keyword}** ${step.text}${status}`];
1405
- if (!full) return lines;
1406
- if (step.errorMessage) {
1407
- lines.push(...step.errorMessage.split("\n").map((l) => ` > ${l}`));
1408
- }
1409
- for (const entry of step.docEntries) {
1410
- lines.push("", ...docEntryToMarkdown(entry, stateLanes).map((l) => l === "" ? l : ` ${l}`));
1411
- }
1412
- return lines;
1413
- }
1414
- function scenarioToMarkdown(scenario, options = {}) {
1415
- const { variant = "full", meta = [] } = options;
1416
- const full = variant === "full";
1417
- const lines = full ? [`# ${scenario.title}`, ""] : [`## ${scenario.title} _(${scenario.status})_`, ""];
1418
- if (full && meta.length > 0) lines.push(...meta);
1419
- const stateLanes = /* @__PURE__ */ new Map();
1420
- if (scenario.steps.length > 0) {
1421
- if (full) lines.push("", "## Steps", "");
1422
- scenario.steps.forEach((step, i) => lines.push(...stepLines(step, i + 1, full, stateLanes)));
1423
- }
1424
- if (scenario.errorMessage) {
1425
- if (full) lines.push("", "## Failure", "");
1426
- else lines.push("");
1427
- lines.push("```", scenario.errorMessage.trim(), "```");
1428
- }
1429
- if (full && scenario.docEntries.length > 0) {
1430
- lines.push("", "## Docs", "");
1431
- for (const entry of scenario.docEntries) lines.push(...docEntryToMarkdown(entry, stateLanes), "");
1432
- }
1433
- return lines.join("\n").replaceAll(/\n{3,}/g, "\n\n").trimEnd() + (full ? "\n" : "");
1434
- }
1435
-
1436
- // src/interactive/scenario-actions.tsx
1181
+ var import_scenario_markdown = require("executable-stories-core/utils/scenario-markdown");
1437
1182
  var ScenarioActionsContext = (0, import_react7.createContext)(null);
1438
1183
  var ScenarioActionsProvider = ScenarioActionsContext.Provider;
1439
1184
  function useScenarioActions() {
1440
1185
  return (0, import_react7.useContext)(ScenarioActionsContext);
1441
1186
  }
1442
- function scenarioToMarkdown2(scenario) {
1443
- return scenarioToMarkdown(scenario, { variant: "compact" });
1187
+ function scenarioToMarkdown(scenario) {
1188
+ return (0, import_scenario_markdown.scenarioToMarkdown)(scenario, { variant: "compact" });
1444
1189
  }
1445
1190
  function scenarioToPrompt(scenario) {
1446
1191
  const lines = [
@@ -1605,7 +1350,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
1605
1350
  ] }),
1606
1351
  /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex shrink-0 items-center gap-2", children: [
1607
1352
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ScenarioRunHistory, { scenarioId: scenario.id }),
1608
- scenario.durationMs > 0 ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(scenario.durationMs) }) : null,
1353
+ scenario.durationMs > 0 ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: (0, import_duration3.formatDuration)(scenario.durationMs) }) : null,
1609
1354
  meta.showPill ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Badge, { variant: meta.badgeVariant, "aria-label": `Status: ${label}`, children: label }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "sr-only", children: `Status: ${label}` }),
1610
1355
  actions ? /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenu, { children: [
1611
1356
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
@@ -1865,6 +1610,9 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1865
1610
  );
1866
1611
  }
1867
1612
 
1613
+ // src/components/ReportMeta.tsx
1614
+ var import_duration4 = require("executable-stories-core/utils/duration");
1615
+
1868
1616
  // src/lib/provenance.ts
1869
1617
  function reportLastRunMs(report) {
1870
1618
  if (report.finishedAtMs && report.finishedAtMs > 0) return report.finishedAtMs;
@@ -1924,7 +1672,7 @@ function ReportMeta() {
1924
1672
  items.push({ label: "Started", value: new Date(report.startedAtMs).toLocaleString() });
1925
1673
  }
1926
1674
  if (report.durationMs) {
1927
- items.push({ label: "Duration", value: formatDuration(report.durationMs) });
1675
+ items.push({ label: "Duration", value: (0, import_duration4.formatDuration)(report.durationMs) });
1928
1676
  }
1929
1677
  if (report.packageVersion) {
1930
1678
  items.push({ label: "Version", value: report.packageVersion });
@@ -3050,7 +2798,7 @@ function ReportInteractiveView({
3050
2798
  const scenarioActions = (0, import_react19.useMemo)(
3051
2799
  () => ({
3052
2800
  copyLink: (s) => copy(scenarioPermalink(s), "Link copied"),
3053
- copyMarkdown: (s) => copy(scenarioToMarkdown2(s), "Markdown copied"),
2801
+ copyMarkdown: (s) => copy(scenarioToMarkdown(s), "Markdown copied"),
3054
2802
  copyPrompt: (s) => copy(scenarioToPrompt(s), "Prompt copied")
3055
2803
  }),
3056
2804
  [copy]