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.
@@ -295,23 +295,11 @@ function useReport() {
295
295
  return ctx.report;
296
296
  }
297
297
 
298
- // ../executable-stories-core/dist/utils/duration.js
299
- function formatDuration(ms) {
300
- if (ms < 1e3) {
301
- return `${Math.round(ms)} ms`;
302
- }
303
- if (ms < 6e4) {
304
- return `${(ms / 1e3).toFixed(2)} s`;
305
- }
306
- const minutes = Math.floor(ms / 6e4);
307
- const seconds = Math.floor(ms % 6e4 / 1e3);
308
- if (minutes < 60) {
309
- return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
310
- }
311
- const hours = Math.floor(minutes / 60);
312
- const remainingMinutes = minutes % 60;
313
- return `${hours}h ${remainingMinutes}m`;
314
- }
298
+ // src/components/ReportScenario.tsx
299
+ import { formatDuration as formatDuration3 } from "executable-stories-core/utils/duration";
300
+
301
+ // src/components/ReportSteps.tsx
302
+ import { formatDuration } from "executable-stories-core/utils/duration";
315
303
 
316
304
  // src/components/doc/DocNote.tsx
317
305
  import { jsx as jsx3 } from "react/jsx-runtime";
@@ -380,7 +368,7 @@ function DocTable({ entry }) {
380
368
  entry.label ? /* @__PURE__ */ jsx7("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
381
369
  /* @__PURE__ */ jsx7("div", { tabIndex: 0, className: "overflow-x-auto rounded-md border border-border", children: /* @__PURE__ */ jsxs4("table", { className: "w-full border-collapse text-sm", children: [
382
370
  /* @__PURE__ */ jsx7("thead", { children: /* @__PURE__ */ jsx7("tr", { className: "bg-muted/50", children: entry.columns.map((c) => /* @__PURE__ */ jsx7("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: c }, c)) }) }),
383
- /* @__PURE__ */ jsx7("tbody", { children: entry.rows.map((row, i) => /* @__PURE__ */ jsx7("tr", { children: row.map((cell2, j) => /* @__PURE__ */ jsx7("td", { className: "border-b border-border px-3 py-2 text-foreground", children: cell2 }, j)) }, i)) })
371
+ /* @__PURE__ */ jsx7("tbody", { children: entry.rows.map((row, i) => /* @__PURE__ */ jsx7("tr", { children: row.map((cell, j) => /* @__PURE__ */ jsx7("td", { className: "border-b border-border px-3 py-2 text-foreground", children: cell }, j)) }, i)) })
384
372
  ] }) })
385
373
  ] });
386
374
  }
@@ -403,23 +391,8 @@ function DocLink({ entry }) {
403
391
  // src/components/doc/DocSection.tsx
404
392
  import { marked } from "marked";
405
393
 
406
- // ../executable-stories-core/dist/utils/url.js
407
- function safeUrl(value) {
408
- if (!value) return void 0;
409
- const trimmed = value.trim();
410
- const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(trimmed);
411
- if (scheme && !/^https?$/i.test(scheme[1] ?? "")) return void 0;
412
- return trimmed;
413
- }
414
- function safeImageUrl(value) {
415
- if (!value) return void 0;
416
- const trimmed = value.trim();
417
- if (/^data:image\//i.test(trimmed)) return trimmed;
418
- return safeUrl(trimmed);
419
- }
420
- function isLocalFsPath(value) {
421
- return /^(?:[/\\]|[A-Za-z]:[/\\])/.test(value);
422
- }
394
+ // src/lib/url.ts
395
+ import { safeUrl, safeImageUrl, isLocalFsPath } from "executable-stories-core/utils/url";
423
396
 
424
397
  // src/components/doc/DocSection.tsx
425
398
  import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
@@ -712,125 +685,10 @@ function ReportStepItem({ step }) {
712
685
  );
713
686
  }
714
687
 
715
- // ../executable-stories-core/dist/state-diff.js
716
- var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
717
- function deepEqual(a, b) {
718
- if (Object.is(a, b)) return true;
719
- if (Array.isArray(a) && Array.isArray(b)) {
720
- return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
721
- }
722
- if (isPlainObject(a) && isPlainObject(b)) {
723
- const ak = Object.keys(a);
724
- const bk = Object.keys(b);
725
- return ak.length === bk.length && ak.every((k) => k in b && deepEqual(a[k], b[k]));
726
- }
727
- return false;
728
- }
729
- var joinKey = (path, key) => path === "" ? key : `${path}.${key}`;
730
- function walk(path, before, after, out) {
731
- if (Object.is(before, after)) return;
732
- if (Array.isArray(before) && Array.isArray(after)) {
733
- const shared = Math.min(before.length, after.length);
734
- for (let i = 0; i < shared; i++) walk(`${path}[${i}]`, before[i], after[i], out);
735
- for (let i = shared; i < after.length; i++) out.push({ path: `${path}[${i}]`, kind: "added", after: after[i] });
736
- for (let i = shared; i < before.length; i++) out.push({ path: `${path}[${i}]`, kind: "removed", before: before[i] });
737
- return;
738
- }
739
- if (isPlainObject(before) && isPlainObject(after)) {
740
- for (const key of Object.keys(after)) {
741
- const p = joinKey(path, key);
742
- if (key in before) walk(p, before[key], after[key], out);
743
- else out.push({ path: p, kind: "added", after: after[key] });
744
- }
745
- for (const key of Object.keys(before)) {
746
- if (!(key in after)) out.push({ path: joinKey(path, key), kind: "removed", before: before[key] });
747
- }
748
- return;
749
- }
750
- if (!deepEqual(before, after)) out.push({ path, kind: "changed", before, after });
751
- }
752
- function diffStateValues(before, after) {
753
- const out = [];
754
- walk("", before, after, out);
755
- return out;
756
- }
757
- function formatStateValue(value, maxLen = 60) {
758
- const s = value === void 0 ? "undefined" : JSON.stringify(value);
759
- return s.length > maxLen ? `${s.slice(0, maxLen - 1)}\u2026` : s;
760
- }
761
- function summarizeStateChanges(changes) {
762
- return changes.map((c) => {
763
- const at = c.path === "" ? "(root)" : c.path;
764
- switch (c.kind) {
765
- case "added":
766
- return `+ ${at}: ${formatStateValue(c.after)}`;
767
- case "removed":
768
- return `\u2212 ${at}`;
769
- case "changed":
770
- return `${at}: ${formatStateValue(c.before)} \u2192 ${formatStateValue(c.after)}`;
771
- }
772
- });
773
- }
774
-
775
- // ../executable-stories-core/dist/storyboard.js
776
- function findScreenshot(entries) {
777
- for (const entry of entries) {
778
- if (entry.kind === "screenshot") return entry;
779
- if (entry.children) {
780
- const nested = findScreenshot(entry.children);
781
- if (nested) return nested;
782
- }
783
- }
784
- return void 0;
785
- }
786
- function findStates(entries, out = []) {
787
- for (const entry of entries) {
788
- if (entry.kind === "state") out.push(entry);
789
- if (entry.children) findStates(entry.children, out);
790
- }
791
- return out;
792
- }
793
- var laneOf = (label) => label ?? "";
794
- function extractStoryboardFrames(scenario) {
795
- const frames = [];
796
- const laneOrder = [];
797
- const previous = /* @__PURE__ */ new Map();
798
- for (const step of scenario.steps) {
799
- const shot = findScreenshot(step.docEntries);
800
- const stateDocs = findStates(step.docEntries);
801
- if (!shot && stateDocs.length === 0) continue;
802
- const byLane = /* @__PURE__ */ new Map();
803
- for (const doc of stateDocs) {
804
- const lane = laneOf(doc.label);
805
- if (!byLane.has(lane) && !laneOrder.includes(lane)) laneOrder.push(lane);
806
- byLane.set(lane, doc);
807
- }
808
- const states = [];
809
- for (const lane of laneOrder) {
810
- const doc = byLane.get(lane);
811
- if (!doc) continue;
812
- const card = {
813
- ...doc.label !== void 0 && { label: doc.label },
814
- value: doc.value
815
- };
816
- if (previous.has(lane)) card.changes = diffStateValues(previous.get(lane), doc.value);
817
- previous.set(lane, doc.value);
818
- states.push(card);
819
- }
820
- frames.push({
821
- stepId: step.id,
822
- stepIndex: step.index,
823
- keyword: step.keyword,
824
- text: step.text,
825
- status: step.status,
826
- ...shot && { screenshot: { path: shot.path, ...shot.alt !== void 0 && { alt: shot.alt } } },
827
- states
828
- });
829
- }
830
- return frames;
831
- }
832
-
833
688
  // src/components/ReportStoryboard.tsx
689
+ import { extractStoryboardFrames } from "executable-stories-core/storyboard";
690
+ import { summarizeStateChanges } from "executable-stories-core/state-diff";
691
+ import { isLocalFsPath as isLocalFsPath2, safeImageUrl as safeImageUrl2 } from "executable-stories-core/utils/url";
834
692
  import { jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
835
693
  var FRAME_GLYPH = {
836
694
  passed: "\u2713",
@@ -879,7 +737,7 @@ function StoryboardFrameItem({ frame }) {
879
737
  }
880
738
  function FrameScreenshot({ frame, label }) {
881
739
  const path = frame.screenshot?.path ?? "";
882
- const src = isLocalFsPath(path) ? void 0 : safeImageUrl(path);
740
+ const src = isLocalFsPath2(path) ? void 0 : safeImageUrl2(path);
883
741
  return /* @__PURE__ */ jsx20(
884
742
  "a",
885
743
  {
@@ -985,6 +843,9 @@ function ReportAttachments({ attachments }) {
985
843
  );
986
844
  }
987
845
 
846
+ // src/components/ReportTrace.tsx
847
+ import { formatDuration as formatDuration2 } from "executable-stories-core/utils/duration";
848
+
988
849
  // src/lib/trace.ts
989
850
  var VALID = /* @__PURE__ */ new Set(["ok", "error", "unset"]);
990
851
  var safeStatus = (s) => VALID.has(s) ? s : "unset";
@@ -1037,15 +898,15 @@ function toTraceRows(spans) {
1037
898
  roots.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
1038
899
  const rows = [];
1039
900
  const seen = /* @__PURE__ */ new Set();
1040
- const walk2 = (node, depth) => {
901
+ const walk = (node, depth) => {
1041
902
  if (seen.has(node.span.spanId)) return;
1042
903
  seen.add(node.span.spanId);
1043
904
  rows.push({ ...node.span, depth });
1044
- for (const child of node.children) walk2(child, depth + 1);
905
+ for (const child of node.children) walk(child, depth + 1);
1045
906
  };
1046
- for (const root of roots) walk2(root, 0);
907
+ for (const root of roots) walk(root, 0);
1047
908
  for (const node of byId.values()) {
1048
- if (!seen.has(node.span.spanId)) walk2(node, 0);
909
+ if (!seen.has(node.span.spanId)) walk(node, 0);
1049
910
  }
1050
911
  return rows;
1051
912
  }
@@ -1079,7 +940,7 @@ function ReportTrace({ spans }) {
1079
940
  " span",
1080
941
  rows.length === 1 ? "" : "s",
1081
942
  ", ",
1082
- formatDuration(totalMs),
943
+ formatDuration2(totalMs),
1083
944
  ")"
1084
945
  ] }),
1085
946
  /* @__PURE__ */ jsx22("ul", { className: "flex flex-col gap-0.5 px-3 pb-3 font-mono text-[0.6875rem]", children: rows.map((row, i) => {
@@ -1102,7 +963,7 @@ function ReportTrace({ spans }) {
1102
963
  style: { left: `${leftPct}%`, width: `${widthPct}%` }
1103
964
  }
1104
965
  ) }),
1105
- /* @__PURE__ */ jsx22("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: formatDuration(row.durationMs) })
966
+ /* @__PURE__ */ jsx22("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: formatDuration2(row.durationMs) })
1106
967
  ] }, `${row.spanId}-${i}`);
1107
968
  }) })
1108
969
  ] });
@@ -1278,130 +1139,14 @@ function useCollapseState(computeDefault) {
1278
1139
 
1279
1140
  // src/interactive/scenario-actions.tsx
1280
1141
  import { createContext as createContext4, useContext as useContext5 } from "react";
1281
-
1282
- // ../executable-stories-core/dist/utils/scenario-markdown.js
1283
- function cell(value) {
1284
- return value.replaceAll("|", "\\|").replaceAll("\n", " ");
1285
- }
1286
- function stringify(value) {
1287
- if (typeof value === "string") return value;
1288
- try {
1289
- return JSON.stringify(value, null, 2) ?? String(value);
1290
- } catch {
1291
- return String(value);
1292
- }
1293
- }
1294
- function docEntryToMarkdown(entry, stateLanes) {
1295
- const lines = [];
1296
- switch (entry.kind) {
1297
- case "note":
1298
- lines.push(entry.text);
1299
- break;
1300
- case "tag":
1301
- lines.push(entry.names.map((n) => `\`@${n}\``).join(" "));
1302
- break;
1303
- case "kv":
1304
- lines.push(`**${entry.label}:** ${stringify(entry.value)}`);
1305
- break;
1306
- case "code":
1307
- lines.push(`**${entry.label}**`, "", "```" + (entry.lang ?? ""), entry.content, "```");
1308
- break;
1309
- case "table": {
1310
- lines.push(`**${entry.label}**`, "");
1311
- lines.push(`| ${entry.columns.map(cell).join(" | ")} |`);
1312
- lines.push(`| ${entry.columns.map(() => "---").join(" | ")} |`);
1313
- for (const row of entry.rows) lines.push(`| ${row.map(cell).join(" | ")} |`);
1314
- break;
1315
- }
1316
- case "link":
1317
- lines.push(`[${entry.label}](${entry.url})`);
1318
- break;
1319
- case "section":
1320
- lines.push(`### ${entry.title}`, "", entry.markdown);
1321
- break;
1322
- case "mermaid":
1323
- if (entry.title) lines.push(`**${entry.title}**`, "");
1324
- lines.push("```mermaid", entry.code, "```");
1325
- break;
1326
- case "screenshot":
1327
- lines.push(`![${entry.alt ?? "screenshot"}](${entry.path})`);
1328
- break;
1329
- case "video":
1330
- lines.push(`Video: ${entry.path}${entry.caption ? ` \u2014 ${entry.caption}` : ""}`);
1331
- break;
1332
- case "html": {
1333
- const htmlTitle = entry.title ?? "Embedded HTML";
1334
- if (entry.content) {
1335
- lines.push(`**${htmlTitle}**`, "", "```html", entry.content, "```");
1336
- } else if (entry.url) {
1337
- lines.push(`[${htmlTitle}](${entry.url})`);
1338
- } else {
1339
- lines.push(`Embedded HTML: ${entry.path ?? "(inline)"}`);
1340
- }
1341
- break;
1342
- }
1343
- case "custom":
1344
- lines.push(`**${entry.type}**`, "", "```json", stringify(entry.data), "```");
1345
- break;
1346
- case "state": {
1347
- const lane = entry.label ?? "";
1348
- lines.push(`**${entry.label ?? "State"}**`);
1349
- if (stateLanes?.has(lane)) {
1350
- const summary = summarizeStateChanges(diffStateValues(stateLanes.get(lane), entry.value));
1351
- if (summary.length > 0) lines.push("", ...summary.map((l) => `- ${l}`));
1352
- }
1353
- stateLanes?.set(lane, entry.value);
1354
- lines.push("", "<details>", "<summary>snapshot</summary>", "", "```json", stringify(entry.value ?? null), "```", "", "</details>");
1355
- break;
1356
- }
1357
- }
1358
- for (const child of entry.children ?? []) {
1359
- lines.push("", ...docEntryToMarkdown(child, stateLanes));
1360
- }
1361
- return lines;
1362
- }
1363
- function stepLines(step, ordinal, full, stateLanes) {
1364
- const status = step.status === "passed" ? "" : ` \u2014 **${step.status}**`;
1365
- const lines = [`${ordinal}. **${step.keyword}** ${step.text}${status}`];
1366
- if (!full) return lines;
1367
- if (step.errorMessage) {
1368
- lines.push(...step.errorMessage.split("\n").map((l) => ` > ${l}`));
1369
- }
1370
- for (const entry of step.docEntries) {
1371
- lines.push("", ...docEntryToMarkdown(entry, stateLanes).map((l) => l === "" ? l : ` ${l}`));
1372
- }
1373
- return lines;
1374
- }
1375
- function scenarioToMarkdown(scenario, options = {}) {
1376
- const { variant = "full", meta = [] } = options;
1377
- const full = variant === "full";
1378
- const lines = full ? [`# ${scenario.title}`, ""] : [`## ${scenario.title} _(${scenario.status})_`, ""];
1379
- if (full && meta.length > 0) lines.push(...meta);
1380
- const stateLanes = /* @__PURE__ */ new Map();
1381
- if (scenario.steps.length > 0) {
1382
- if (full) lines.push("", "## Steps", "");
1383
- scenario.steps.forEach((step, i) => lines.push(...stepLines(step, i + 1, full, stateLanes)));
1384
- }
1385
- if (scenario.errorMessage) {
1386
- if (full) lines.push("", "## Failure", "");
1387
- else lines.push("");
1388
- lines.push("```", scenario.errorMessage.trim(), "```");
1389
- }
1390
- if (full && scenario.docEntries.length > 0) {
1391
- lines.push("", "## Docs", "");
1392
- for (const entry of scenario.docEntries) lines.push(...docEntryToMarkdown(entry, stateLanes), "");
1393
- }
1394
- return lines.join("\n").replaceAll(/\n{3,}/g, "\n\n").trimEnd() + (full ? "\n" : "");
1395
- }
1396
-
1397
- // src/interactive/scenario-actions.tsx
1142
+ import { scenarioToMarkdown as coreScenarioToMarkdown } from "executable-stories-core/utils/scenario-markdown";
1398
1143
  var ScenarioActionsContext = createContext4(null);
1399
1144
  var ScenarioActionsProvider = ScenarioActionsContext.Provider;
1400
1145
  function useScenarioActions() {
1401
1146
  return useContext5(ScenarioActionsContext);
1402
1147
  }
1403
- function scenarioToMarkdown2(scenario) {
1404
- return scenarioToMarkdown(scenario, { variant: "compact" });
1148
+ function scenarioToMarkdown(scenario) {
1149
+ return coreScenarioToMarkdown(scenario, { variant: "compact" });
1405
1150
  }
1406
1151
  function scenarioToPrompt(scenario) {
1407
1152
  const lines = [
@@ -1566,7 +1311,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
1566
1311
  ] }),
1567
1312
  /* @__PURE__ */ jsxs17("div", { className: "flex shrink-0 items-center gap-2", children: [
1568
1313
  /* @__PURE__ */ jsx26(ScenarioRunHistory, { scenarioId: scenario.id }),
1569
- scenario.durationMs > 0 ? /* @__PURE__ */ jsx26("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(scenario.durationMs) }) : null,
1314
+ scenario.durationMs > 0 ? /* @__PURE__ */ jsx26("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration3(scenario.durationMs) }) : null,
1570
1315
  meta.showPill ? /* @__PURE__ */ jsx26(Badge, { variant: meta.badgeVariant, "aria-label": `Status: ${label}`, children: label }) : /* @__PURE__ */ jsx26("span", { className: "sr-only", children: `Status: ${label}` }),
1571
1316
  actions ? /* @__PURE__ */ jsxs17(DropdownMenu, { children: [
1572
1317
  /* @__PURE__ */ jsx26(
@@ -1826,6 +1571,9 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1826
1571
  );
1827
1572
  }
1828
1573
 
1574
+ // src/components/ReportMeta.tsx
1575
+ import { formatDuration as formatDuration4 } from "executable-stories-core/utils/duration";
1576
+
1829
1577
  // src/lib/provenance.ts
1830
1578
  function reportLastRunMs(report) {
1831
1579
  if (report.finishedAtMs && report.finishedAtMs > 0) return report.finishedAtMs;
@@ -1885,7 +1633,7 @@ function ReportMeta() {
1885
1633
  items.push({ label: "Started", value: new Date(report.startedAtMs).toLocaleString() });
1886
1634
  }
1887
1635
  if (report.durationMs) {
1888
- items.push({ label: "Duration", value: formatDuration(report.durationMs) });
1636
+ items.push({ label: "Duration", value: formatDuration4(report.durationMs) });
1889
1637
  }
1890
1638
  if (report.packageVersion) {
1891
1639
  items.push({ label: "Version", value: report.packageVersion });
@@ -3014,7 +2762,7 @@ function ReportInteractiveView({
3014
2762
  const scenarioActions = useMemo3(
3015
2763
  () => ({
3016
2764
  copyLink: (s) => copy(scenarioPermalink(s), "Link copied"),
3017
- copyMarkdown: (s) => copy(scenarioToMarkdown2(s), "Markdown copied"),
2765
+ copyMarkdown: (s) => copy(scenarioToMarkdown(s), "Markdown copied"),
3018
2766
  copyPrompt: (s) => copy(scenarioToPrompt(s), "Prompt copied")
3019
2767
  }),
3020
2768
  [copy]