executable-stories-react 0.15.0 → 0.16.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/ssr.cjs CHANGED
@@ -23,7 +23,7 @@ __export(ssr_entry_exports, {
23
23
  renderReportToHtml: () => renderReportToHtml
24
24
  });
25
25
  module.exports = __toCommonJS(ssr_entry_exports);
26
- var import_react10 = require("react");
26
+ var import_react12 = require("react");
27
27
  var import_server = require("react-dom/server");
28
28
 
29
29
  // src/result.ts
@@ -544,12 +544,29 @@ function DocVideo({ entry }) {
544
544
  }
545
545
 
546
546
  // src/components/doc/DocHtml.tsx
547
+ var import_react5 = require("react");
547
548
  var import_jsx_runtime14 = require("react/jsx-runtime");
549
+ var MEASURE_SCRIPT = "<script>(function(){if(parent===window)return;var last=0,queued=false;var measure=function(){var kids=document.body.children,h=0;for(var i=0;i<kids.length;i++)h=Math.max(h,kids[i].getBoundingClientRect().bottom);return Math.ceil(h||document.body.getBoundingClientRect().bottom)};var post=function(){queued=false;var h=measure();if(h===last)return;last=h;parent.postMessage({__esHtmlHeight:h},'*')};var schedule=function(){if(queued)return;queued=true;requestAnimationFrame(post)};var start=function(){new ResizeObserver(schedule).observe(document.body);new MutationObserver(schedule).observe(document.body,{subtree:true,childList:true,attributes:true,characterData:true});addEventListener('load',schedule);post()};if(document.body)start();else addEventListener('DOMContentLoaded',start)})();</script>";
550
+ var MAX_MEASURED_HEIGHT = 5e3;
548
551
  function DocHtml({ entry }) {
549
- const height = typeof entry.height === "number" ? `${entry.height}px` : entry.height ?? "400px";
552
+ const declaredHeight = typeof entry.height === "number" ? `${entry.height}px` : entry.height ?? "400px";
550
553
  const title = entry.title ?? "Embedded HTML";
551
554
  const safeHref = (0, import_url.safeUrl)(entry.url ?? entry.path);
552
- const frameProps = entry.content !== void 0 ? { srcDoc: entry.content } : { src: safeHref ?? "" };
555
+ const frameRef = (0, import_react5.useRef)(null);
556
+ const [measuredHeight, setMeasuredHeight] = (0, import_react5.useState)();
557
+ (0, import_react5.useEffect)(() => {
558
+ if (entry.content === void 0) return;
559
+ const onMessage = (event) => {
560
+ if (event.source !== frameRef.current?.contentWindow) return;
561
+ const reported = event.data?.__esHtmlHeight;
562
+ if (typeof reported !== "number" || !Number.isFinite(reported) || reported <= 0) return;
563
+ setMeasuredHeight(Math.min(reported, MAX_MEASURED_HEIGHT));
564
+ };
565
+ window.addEventListener("message", onMessage);
566
+ return () => window.removeEventListener("message", onMessage);
567
+ }, [entry.content]);
568
+ const height = measuredHeight === void 0 ? declaredHeight : `${measuredHeight}px`;
569
+ const frameProps = entry.content !== void 0 ? { srcDoc: entry.content + MEASURE_SCRIPT } : { src: safeHref ?? "" };
553
570
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("figure", { className: "my-3 overflow-hidden rounded-md border border-border", children: [
554
571
  /* @__PURE__ */ (0, import_jsx_runtime14.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: [
555
572
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { children: entry.title ?? "HTML" }),
@@ -569,6 +586,7 @@ function DocHtml({ entry }) {
569
586
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
570
587
  "iframe",
571
588
  {
589
+ ref: frameRef,
572
590
  ...frameProps,
573
591
  sandbox: "allow-scripts",
574
592
  loading: "lazy",
@@ -1022,11 +1040,11 @@ function ReportTrace({ spans }) {
1022
1040
  }
1023
1041
 
1024
1042
  // src/interactive/scenario-history-context.ts
1025
- var import_react5 = require("react");
1026
- var ScenarioHistoryContext = (0, import_react5.createContext)(null);
1043
+ var import_react6 = require("react");
1044
+ var ScenarioHistoryContext = (0, import_react6.createContext)(null);
1027
1045
  var ScenarioHistoryProvider = ScenarioHistoryContext.Provider;
1028
1046
  function useScenarioHistory() {
1029
- return (0, import_react5.useContext)(ScenarioHistoryContext);
1047
+ return (0, import_react6.useContext)(ScenarioHistoryContext);
1030
1048
  }
1031
1049
 
1032
1050
  // src/lib/run-history.ts
@@ -1117,7 +1135,7 @@ function ScenarioRunHistory({ scenarioId }) {
1117
1135
  }
1118
1136
 
1119
1137
  // src/components/ScenarioStaleness.tsx
1120
- var import_react7 = require("react");
1138
+ var import_react8 = require("react");
1121
1139
 
1122
1140
  // src/lib/provenance.ts
1123
1141
  function formatRelativeAge(thenMs, nowMs) {
@@ -1164,11 +1182,11 @@ function prUrl(ci) {
1164
1182
  }
1165
1183
 
1166
1184
  // src/interactive/scenario-freshness-context.tsx
1167
- var import_react6 = require("react");
1168
- var ScenarioFreshnessContext = (0, import_react6.createContext)(null);
1185
+ var import_react7 = require("react");
1186
+ var ScenarioFreshnessContext = (0, import_react7.createContext)(null);
1169
1187
  var ScenarioFreshnessProvider = ScenarioFreshnessContext.Provider;
1170
1188
  function useScenarioFreshness() {
1171
- return (0, import_react6.useContext)(ScenarioFreshnessContext);
1189
+ return (0, import_react7.useContext)(ScenarioFreshnessContext);
1172
1190
  }
1173
1191
 
1174
1192
  // src/components/ScenarioStaleness.tsx
@@ -1176,8 +1194,8 @@ var import_jsx_runtime24 = require("react/jsx-runtime");
1176
1194
  function ScenarioStaleness({ scenario }) {
1177
1195
  const freshness = useScenarioFreshness();
1178
1196
  const fixedNow = freshness?.nowMs;
1179
- const [now, setNow] = (0, import_react7.useState)(fixedNow);
1180
- (0, import_react7.useEffect)(() => {
1197
+ const [now, setNow] = (0, import_react8.useState)(fixedNow);
1198
+ (0, import_react8.useEffect)(() => {
1181
1199
  if (fixedNow === void 0) setNow(Date.now());
1182
1200
  }, [fixedNow]);
1183
1201
  if (!freshness || now === void 0) return null;
@@ -1191,20 +1209,20 @@ function ScenarioStaleness({ scenario }) {
1191
1209
  }
1192
1210
 
1193
1211
  // src/interactive/collapse-context.ts
1194
- var import_react8 = require("react");
1195
- var CollapseContext = (0, import_react8.createContext)(null);
1212
+ var import_react9 = require("react");
1213
+ var CollapseContext = (0, import_react9.createContext)(null);
1196
1214
  var CollapseProvider = CollapseContext.Provider;
1197
1215
  function useCollapse() {
1198
- return (0, import_react8.useContext)(CollapseContext);
1216
+ return (0, import_react9.useContext)(CollapseContext);
1199
1217
  }
1200
1218
 
1201
1219
  // src/interactive/scenario-actions.tsx
1202
- var import_react9 = require("react");
1220
+ var import_react10 = require("react");
1203
1221
  var import_scenario_markdown = require("executable-stories-core/utils/scenario-markdown");
1204
- var ScenarioActionsContext = (0, import_react9.createContext)(null);
1222
+ var ScenarioActionsContext = (0, import_react10.createContext)(null);
1205
1223
  var ScenarioActionsProvider = ScenarioActionsContext.Provider;
1206
1224
  function useScenarioActions() {
1207
- return (0, import_react9.useContext)(ScenarioActionsContext);
1225
+ return (0, import_react10.useContext)(ScenarioActionsContext);
1208
1226
  }
1209
1227
 
1210
1228
  // src/components/ui/card.tsx
@@ -1765,23 +1783,85 @@ function ReportErrorShell({
1765
1783
  return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ReportSchemaError, { error }) });
1766
1784
  }
1767
1785
 
1768
- // src/components/Report.tsx
1786
+ // src/components/ReportSpanGraph.tsx
1787
+ var import_span_graph = require("executable-stories-core/span-graph");
1788
+ var import_react11 = require("react");
1769
1789
  var import_jsx_runtime38 = require("react/jsx-runtime");
1790
+ var LANE_LABEL = {
1791
+ edge: "Edge",
1792
+ service: "Service",
1793
+ queue: "Queue",
1794
+ datastore: "Data"
1795
+ };
1796
+ var MARK_LABEL = {
1797
+ failing: "Failing",
1798
+ added: "New",
1799
+ changed: "Changed"
1800
+ };
1801
+ var MARK_CLASS = {
1802
+ failing: "text-fail",
1803
+ added: "text-pass",
1804
+ changed: "text-pend"
1805
+ };
1806
+ function MarkBadge({ node }) {
1807
+ const mark = (0, import_span_graph.spanGraphNodeMark)(node);
1808
+ if (!mark) return null;
1809
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: cn("ml-2 text-xs font-medium", MARK_CLASS[mark]), children: MARK_LABEL[mark] });
1810
+ }
1811
+ function ReportSpanGraph() {
1812
+ const report = useReport();
1813
+ const { code, graph, titleById } = (0, import_react11.useMemo)(() => {
1814
+ const scenarios = report.features.flatMap((feature) => feature.scenarios);
1815
+ const derived = (0, import_span_graph.deriveSpanGraph)(scenarios);
1816
+ return {
1817
+ graph: derived,
1818
+ code: (0, import_span_graph.spanGraphToMermaid)(derived),
1819
+ titleById: new Map(scenarios.map((scenario) => [scenario.id, scenario.title]))
1820
+ };
1821
+ }, [report]);
1822
+ if (code === "") return null;
1823
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("section", { className: "es-span-graph mt-6", "aria-labelledby": "es-span-graph-heading", children: [
1824
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("h2", { id: "es-span-graph-heading", className: "text-base font-semibold text-foreground", children: "Architecture, as it ran" }),
1825
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { className: "mt-1 text-sm text-muted-foreground", children: "Derived from this run\u2019s traces. Every component was named by a span while a scenario ran, so nothing here is inferred from the code. Instrumented, exercised paths only: a component no scenario reaches does not appear." }),
1826
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(DocMermaid, { entry: { kind: "mermaid", phase: "static", code } }),
1827
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("table", { className: "mt-2 w-full text-left text-sm", children: [
1828
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("caption", { className: "sr-only", children: "Each component and the scenarios that exercised it" }),
1829
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("tr", { className: "text-xs text-muted-foreground", children: [
1830
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("th", { scope: "col", className: "py-1 pr-4 font-medium", children: "Component" }),
1831
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("th", { scope: "col", className: "py-1 pr-4 font-medium", children: "Lane" }),
1832
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("th", { scope: "col", className: "py-1 font-medium", children: "Covered by" })
1833
+ ] }) }),
1834
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("tbody", { children: graph.nodes.map((node) => /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("tr", { className: "border-t border-border align-top", children: [
1835
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("th", { scope: "row", className: "py-1.5 pr-4 font-mono text-xs font-normal", children: [
1836
+ node.id,
1837
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(MarkBadge, { node })
1838
+ ] }),
1839
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("td", { className: "py-1.5 pr-4 text-xs text-muted-foreground", children: LANE_LABEL[node.kind] }),
1840
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("td", { className: "py-1.5", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("ul", { className: "flex flex-col gap-0.5", children: node.scenarioIds.map((id) => /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("a", { href: `#${id}`, className: "text-xs underline underline-offset-2", children: titleById.get(id) ?? id }) }, id)) }) })
1841
+ ] }, node.id)) })
1842
+ ] })
1843
+ ] });
1844
+ }
1845
+
1846
+ // src/components/Report.tsx
1847
+ var import_jsx_runtime39 = require("react/jsx-runtime");
1770
1848
  function Report(props) {
1771
1849
  const { report, customRenderers, renderers, className, title, dataTheme } = props;
1772
1850
  const result = unwrapReport(report);
1773
1851
  if (!result.ok) {
1774
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ReportErrorShell, { error: result.error, className, title, dataTheme });
1852
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportErrorShell, { error: result.error, className, title, dataTheme });
1775
1853
  }
1776
1854
  const data = result.data;
1777
1855
  const hasContent = data.features.length > 0;
1778
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ReportRoot, { report: data, customRenderers, renderers, children: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: [
1779
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("header", { className: "es-report-header", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ReportTitleBlock, { title }) }),
1780
- hasContent ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ReportFeatureList, {}) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ReportEmpty, {})
1856
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportRoot, { report: data, customRenderers, renderers, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: [
1857
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("header", { className: "es-report-header", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportTitleBlock, { title }) }),
1858
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportSpanGraph, {}),
1859
+ hasContent ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportFeatureList, {}) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportEmpty, {})
1781
1860
  ] }) });
1782
1861
  }
1783
1862
 
1784
1863
  // src/ssr-entry.ts
1864
+ var import_report_favicon = require("executable-stories-core/utils/report-favicon");
1785
1865
  var ROOT_ID = "es-report-root";
1786
1866
  var DATA_ID = "es-report-data";
1787
1867
  var HISTORY_ID = "es-report-history";
@@ -1840,7 +1920,7 @@ function renderReportToHtml(report, options = {}) {
1840
1920
  shareCommand = ""
1841
1921
  } = options;
1842
1922
  const markup = (0, import_server.renderToStaticMarkup)(
1843
- (0, import_react10.createElement)(Report, { report, title })
1923
+ (0, import_react12.createElement)(Report, { report, title })
1844
1924
  );
1845
1925
  const cdn = cdnAssets(syntaxHighlighting, mermaid);
1846
1926
  const parsed = unwrapReport(report);
@@ -1859,6 +1939,7 @@ function renderReportToHtml(report, options = {}) {
1859
1939
  <meta charset="UTF-8">
1860
1940
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
1861
1941
  <meta name="color-scheme" content="light dark">
1942
+ ${import_report_favicon.REPORT_FAVICON_LINK}
1862
1943
  <title>${escapeHtml(title)}</title>
1863
1944
  ${cdn.head}
1864
1945
  ${headExtra}