executable-stories-react 0.2.1 → 0.4.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_react7 = require("react");
26
+ var import_react8 = require("react");
27
27
  var import_server = require("react-dom/server");
28
28
 
29
29
  // src/result.ts
@@ -75,11 +75,23 @@ function useReport() {
75
75
  return ctx.report;
76
76
  }
77
77
 
78
- // src/components/ReportScenario.tsx
79
- var import_duration3 = require("executable-stories-core/utils/duration");
80
-
81
- // src/components/ReportSteps.tsx
82
- var import_duration = require("executable-stories-core/utils/duration");
78
+ // ../executable-stories-core/dist/utils/duration.js
79
+ function formatDuration(ms) {
80
+ if (ms < 1e3) {
81
+ return `${Math.round(ms)} ms`;
82
+ }
83
+ if (ms < 6e4) {
84
+ return `${(ms / 1e3).toFixed(2)} s`;
85
+ }
86
+ const minutes = Math.floor(ms / 6e4);
87
+ const seconds = Math.floor(ms % 6e4 / 1e3);
88
+ if (minutes < 60) {
89
+ return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
90
+ }
91
+ const hours = Math.floor(minutes / 60);
92
+ const remainingMinutes = minutes % 60;
93
+ return `${hours}h ${remainingMinutes}m`;
94
+ }
83
95
 
84
96
  // src/components/doc/DocNote.tsx
85
97
  var import_jsx_runtime2 = require("react/jsx-runtime");
@@ -263,7 +275,7 @@ function DocSection({ entry }) {
263
275
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
264
276
  "div",
265
277
  {
266
- className: "es-doc-prose text-muted-foreground",
278
+ className: "es-doc-prose prose prose-sm max-w-none",
267
279
  dangerouslySetInnerHTML: { __html: html }
268
280
  }
269
281
  )
@@ -504,7 +516,7 @@ function ReportStepItem({ step }) {
504
516
  }
505
517
  ),
506
518
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "flex-1 text-foreground", children: highlightStepParams(step.text) }),
507
- step.durationMs > 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "shrink-0 whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: (0, import_duration.formatDuration)(step.durationMs) }) : null
519
+ step.durationMs > 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "shrink-0 whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(step.durationMs) }) : null
508
520
  ] }),
509
521
  step.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
510
522
  "pre",
@@ -562,9 +574,6 @@ function ReportAttachments({ attachments }) {
562
574
  );
563
575
  }
564
576
 
565
- // src/components/ReportTrace.tsx
566
- var import_duration2 = require("executable-stories-core/utils/duration");
567
-
568
577
  // src/lib/trace.ts
569
578
  var VALID = /* @__PURE__ */ new Set(["ok", "error", "unset"]);
570
579
  var safeStatus = (s) => VALID.has(s) ? s : "unset";
@@ -659,7 +668,7 @@ function ReportTrace({ spans }) {
659
668
  " span",
660
669
  rows.length === 1 ? "" : "s",
661
670
  ", ",
662
- (0, import_duration2.formatDuration)(totalMs),
671
+ formatDuration(totalMs),
663
672
  ")"
664
673
  ] }),
665
674
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("ul", { className: "flex flex-col gap-0.5 px-3 pb-3 font-mono text-[0.6875rem]", children: rows.map((row, i) => {
@@ -682,33 +691,128 @@ function ReportTrace({ spans }) {
682
691
  style: { left: `${leftPct}%`, width: `${widthPct}%` }
683
692
  }
684
693
  ) }),
685
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: (0, import_duration2.formatDuration)(row.durationMs) })
694
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: formatDuration(row.durationMs) })
686
695
  ] }, `${row.spanId}-${i}`);
687
696
  }) })
688
697
  ] });
689
698
  }
690
699
 
691
- // src/interactive/collapse-context.ts
700
+ // src/interactive/scenario-history-context.ts
692
701
  var import_react5 = require("react");
693
- var CollapseContext = (0, import_react5.createContext)(null);
702
+ var ScenarioHistoryContext = (0, import_react5.createContext)(null);
703
+ var ScenarioHistoryProvider = ScenarioHistoryContext.Provider;
704
+ function useScenarioHistory() {
705
+ return (0, import_react5.useContext)(ScenarioHistoryContext);
706
+ }
707
+
708
+ // src/lib/run-history.ts
709
+ function currentStreak(entries) {
710
+ const last = entries[entries.length - 1];
711
+ if (!last) return void 0;
712
+ let count = 0;
713
+ for (let i = entries.length - 1; i >= 0 && entries[i].status === last.status; i--) count++;
714
+ return { status: last.status, count };
715
+ }
716
+ var STREAK_VERB = {
717
+ passed: "Passing",
718
+ failed: "Failing",
719
+ skipped: "Skipped",
720
+ pending: "Pending"
721
+ };
722
+ function describeRunHistory(entries) {
723
+ const passed = entries.filter((e) => e.status === "passed").length;
724
+ const base = `${passed}/${entries.length} runs passed`;
725
+ const streak = currentStreak(entries);
726
+ if (!streak || entries.length < 2) return base;
727
+ return `${base} \xB7 ${STREAK_VERB[streak.status]} for the last ${streak.count === 1 ? "run" : `${streak.count} runs`}`;
728
+ }
729
+ var MIN_FLAKINESS_SAMPLES = 3;
730
+ function flakinessOf(entries) {
731
+ const countable = entries.filter((e) => e.status === "passed" || e.status === "failed");
732
+ if (countable.length < MIN_FLAKINESS_SAMPLES) return "stable";
733
+ let transitions = 0;
734
+ for (let i = 1; i < countable.length; i++) {
735
+ if (countable[i].status !== countable[i - 1].status) transitions++;
736
+ }
737
+ const transitionScore = transitions / (countable.length - 1);
738
+ const failureRate = countable.filter((e) => e.status === "failed").length / countable.length;
739
+ if (transitionScore > 0.5 || transitionScore > 0.3 && failureRate > 0.2) return "flaky";
740
+ if (transitionScore > 0.2 || failureRate > 0.3) return "unstable";
741
+ return "stable";
742
+ }
743
+
744
+ // src/components/ScenarioRunHistory.tsx
745
+ var import_jsx_runtime20 = require("react/jsx-runtime");
746
+ var MAX_DOTS = 10;
747
+ var DOT_COLOR = {
748
+ passed: "bg-pass",
749
+ failed: "bg-fail",
750
+ skipped: "bg-skip",
751
+ pending: "bg-pend"
752
+ };
753
+ function dotTitle(e) {
754
+ const parts = [new Date(e.timestamp).toLocaleString(), e.status];
755
+ if (e.commitSha) parts.push(e.commitSha.slice(0, 8));
756
+ return parts.join(" \xB7 ");
757
+ }
758
+ function ScenarioRunHistory({ scenarioId }) {
759
+ const history = useScenarioHistory();
760
+ const entries = history?.[scenarioId];
761
+ if (!entries || entries.length < 2) return null;
762
+ const recent = entries.slice(-MAX_DOTS);
763
+ const flaky = flakinessOf(recent) === "flaky";
764
+ const summary = flaky ? `${describeRunHistory(recent)} \xB7 Flaky` : describeRunHistory(recent);
765
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
766
+ flaky ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Badge, { variant: "pending", "aria-label": "Flaky: status flips between recent runs", title: summary, children: "Flaky" }) : null,
767
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
768
+ "span",
769
+ {
770
+ role: "img",
771
+ "aria-label": `Run history: ${summary}`,
772
+ title: summary,
773
+ className: "flex shrink-0 items-center gap-[3px]",
774
+ children: recent.map((e, i) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
775
+ "span",
776
+ {
777
+ "aria-hidden": true,
778
+ title: dotTitle(e),
779
+ className: cn(
780
+ "inline-block size-1.5 rounded-full",
781
+ DOT_COLOR[e.status],
782
+ // The newest run (rightmost dot) is the one the card's status
783
+ // badge already describes; ring it so the timeline reads as
784
+ // "…leading up to now".
785
+ i === recent.length - 1 && "ring-1 ring-border ring-offset-1 ring-offset-card"
786
+ )
787
+ },
788
+ `${e.timestamp}-${i}`
789
+ ))
790
+ }
791
+ )
792
+ ] });
793
+ }
794
+
795
+ // src/interactive/collapse-context.ts
796
+ var import_react6 = require("react");
797
+ var CollapseContext = (0, import_react6.createContext)(null);
694
798
  var CollapseProvider = CollapseContext.Provider;
695
799
  function useCollapse() {
696
- return (0, import_react5.useContext)(CollapseContext);
800
+ return (0, import_react6.useContext)(CollapseContext);
697
801
  }
698
802
 
699
803
  // src/interactive/scenario-actions.tsx
700
- var import_react6 = require("react");
701
- var ScenarioActionsContext = (0, import_react6.createContext)(null);
804
+ var import_react7 = require("react");
805
+ var ScenarioActionsContext = (0, import_react7.createContext)(null);
702
806
  var ScenarioActionsProvider = ScenarioActionsContext.Provider;
703
807
  function useScenarioActions() {
704
- return (0, import_react6.useContext)(ScenarioActionsContext);
808
+ return (0, import_react7.useContext)(ScenarioActionsContext);
705
809
  }
706
810
 
707
811
  // src/components/ui/card.tsx
708
812
  var React2 = require("react");
709
- var import_jsx_runtime20 = require("react/jsx-runtime");
813
+ var import_jsx_runtime21 = require("react/jsx-runtime");
710
814
  function Card({ className, ...props }) {
711
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
815
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
712
816
  "div",
713
817
  {
714
818
  "data-slot": "card",
@@ -721,7 +825,7 @@ function Card({ className, ...props }) {
721
825
  );
722
826
  }
723
827
  function CardHeader({ className, ...props }) {
724
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
828
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
725
829
  "div",
726
830
  {
727
831
  "data-slot": "card-header",
@@ -734,7 +838,7 @@ function CardHeader({ className, ...props }) {
734
838
  );
735
839
  }
736
840
  function CardContent({ className, ...props }) {
737
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
841
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
738
842
  "div",
739
843
  {
740
844
  "data-slot": "card-content",
@@ -745,7 +849,7 @@ function CardContent({ className, ...props }) {
745
849
  }
746
850
 
747
851
  // src/components/ReportScenario.tsx
748
- var import_jsx_runtime21 = require("react/jsx-runtime");
852
+ var import_jsx_runtime22 = require("react/jsx-runtime");
749
853
  var STATUS_LABEL = {
750
854
  passed: "Passed",
751
855
  failed: "Failed",
@@ -773,12 +877,12 @@ var STATUS_BADGE = {
773
877
  function ReportScenario({ scenario, hideTitle = false }) {
774
878
  const titleId = `${scenario.id}-title`;
775
879
  const bodyId = `${scenario.id}-body`;
776
- const label = STATUS_LABEL[scenario.status];
880
+ const label = scenario.planned ? "Planned" : STATUS_LABEL[scenario.status];
777
881
  const collapse = useCollapse();
778
882
  const collapsible = collapse !== null && !hideTitle;
779
883
  const collapsed = collapsible ? collapse.isCollapsed(scenario.id) : false;
780
884
  const actions = useScenarioActions();
781
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
885
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
782
886
  Card,
783
887
  {
784
888
  id: scenario.id,
@@ -786,10 +890,10 @@ function ReportScenario({ scenario, hideTitle = false }) {
786
890
  ...hideTitle ? { "aria-label": `${scenario.title} \u2014 ${label}` } : { "aria-labelledby": titleId },
787
891
  className: "gap-0 py-0",
788
892
  children: [
789
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(CardHeader, { className: "px-4 py-3", children: [
790
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
791
- hideTitle ? null : /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
792
- collapsible ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
893
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(CardHeader, { className: "px-4 py-3", children: [
894
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
895
+ hideTitle ? null : /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
896
+ collapsible ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
793
897
  "button",
794
898
  {
795
899
  type: "button",
@@ -798,17 +902,18 @@ function ReportScenario({ scenario, hideTitle = false }) {
798
902
  "aria-controls": bodyId,
799
903
  "aria-label": `Toggle ${scenario.title}`,
800
904
  className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
801
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
905
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
802
906
  }
803
907
  ) : null,
804
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "aria-hidden": true, className: cn("text-sm leading-none", STATUS_GLYPH_COLOR[scenario.status]), children: STATUS_GLYPH[scenario.status] }),
805
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: scenario.title })
908
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { "aria-hidden": true, className: cn("text-sm leading-none", STATUS_GLYPH_COLOR[scenario.status]), children: STATUS_GLYPH[scenario.status] }),
909
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: scenario.title })
806
910
  ] }),
807
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex shrink-0 items-center gap-2", children: [
808
- scenario.durationMs > 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: (0, import_duration3.formatDuration)(scenario.durationMs) }) : null,
809
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Badge, { variant: STATUS_BADGE[scenario.status], "aria-label": `Status: ${label}`, children: label }),
810
- actions ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-0.5", children: [
811
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
911
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex shrink-0 items-center gap-2", children: [
912
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ScenarioRunHistory, { scenarioId: scenario.id }),
913
+ scenario.durationMs > 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(scenario.durationMs) }) : null,
914
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Badge, { variant: STATUS_BADGE[scenario.status], "aria-label": `Status: ${label}`, children: label }),
915
+ actions ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-0.5", children: [
916
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
812
917
  "button",
813
918
  {
814
919
  type: "button",
@@ -819,7 +924,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
819
924
  children: "link"
820
925
  }
821
926
  ),
822
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
927
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
823
928
  "button",
824
929
  {
825
930
  type: "button",
@@ -830,7 +935,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
830
935
  children: "md"
831
936
  }
832
937
  ),
833
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
938
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
834
939
  "button",
835
940
  {
836
941
  type: "button",
@@ -844,13 +949,13 @@ function ReportScenario({ scenario, hideTitle = false }) {
844
949
  ] }) : null
845
950
  ] })
846
951
  ] }),
847
- scenario.tags.length > 0 || (scenario.tickets?.length ?? 0) > 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("ul", { "aria-label": "Tags and tickets", className: "flex flex-wrap gap-1.5", children: [
848
- scenario.tags.map((t) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
849
- (scenario.tickets ?? []).map((ticket) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("li", { children: ticket.url ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("a", { href: ticket.url, target: "_blank", rel: "noreferrer noopener", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Badge, { variant: "outline", children: ticket.id }) }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Badge, { variant: "outline", children: ticket.id }) }, `ticket-${ticket.id}`))
952
+ scenario.tags.length > 0 || (scenario.tickets?.length ?? 0) > 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("ul", { "aria-label": "Tags and tickets", className: "flex flex-wrap gap-1.5", children: [
953
+ scenario.tags.map((t) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
954
+ (scenario.tickets ?? []).map((ticket) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("li", { children: ticket.url ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("a", { href: ticket.url, target: "_blank", rel: "noreferrer noopener", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Badge, { variant: "outline", children: ticket.id }) }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Badge, { variant: "outline", children: ticket.id }) }, `ticket-${ticket.id}`))
850
955
  ] }) : null
851
956
  ] }),
852
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
853
- scenario.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
957
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
958
+ scenario.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
854
959
  "pre",
855
960
  {
856
961
  role: "alert",
@@ -858,10 +963,10 @@ function ReportScenario({ scenario, hideTitle = false }) {
858
963
  children: scenario.errorMessage
859
964
  }
860
965
  ) : null,
861
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ReportSteps, { scenario }),
862
- scenario.docEntries.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { "data-es-docs": true, className: "mt-3", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
863
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ReportAttachments, { attachments: scenario.attachments }),
864
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ReportTrace, { spans: scenario.otelSpans })
966
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ReportSteps, { scenario }),
967
+ scenario.docEntries.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { "data-es-docs": true, className: "mt-3", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
968
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ReportAttachments, { attachments: scenario.attachments }),
969
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ReportTrace, { spans: scenario.otelSpans })
865
970
  ] })
866
971
  ]
867
972
  }
@@ -869,13 +974,13 @@ function ReportScenario({ scenario, hideTitle = false }) {
869
974
  }
870
975
 
871
976
  // src/components/ReportScenarioList.tsx
872
- var import_jsx_runtime22 = require("react/jsx-runtime");
977
+ var import_jsx_runtime23 = require("react/jsx-runtime");
873
978
  function ReportScenarioList({ feature }) {
874
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_jsx_runtime22.Fragment, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ReportScenario, { scenario }, scenario.id)) });
979
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_jsx_runtime23.Fragment, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ReportScenario, { scenario }, scenario.id)) });
875
980
  }
876
981
 
877
982
  // src/components/ReportFeature.tsx
878
- var import_jsx_runtime23 = require("react/jsx-runtime");
983
+ var import_jsx_runtime24 = require("react/jsx-runtime");
879
984
  function ReportFeature({ feature }) {
880
985
  const titleId = `${feature.id}-title`;
881
986
  const bodyId = `${feature.id}-body`;
@@ -883,7 +988,7 @@ function ReportFeature({ feature }) {
883
988
  const skipped = s.skipped + s.pending;
884
989
  const collapse = useCollapse();
885
990
  const collapsed = collapse?.isCollapsed(feature.id) ?? false;
886
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
991
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
887
992
  "section",
888
993
  {
889
994
  id: feature.id,
@@ -891,9 +996,9 @@ function ReportFeature({ feature }) {
891
996
  "aria-labelledby": titleId,
892
997
  className: "overflow-hidden rounded-xl border border-border bg-card shadow-sm",
893
998
  children: [
894
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex items-center justify-between gap-4 border-b border-border bg-muted/40 px-4 py-3", children: [
895
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex min-w-0 items-center gap-2", children: [
896
- collapse ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
999
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex items-center justify-between gap-4 border-b border-border bg-muted/40 px-4 py-3", children: [
1000
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex min-w-0 items-center gap-2", children: [
1001
+ collapse ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
897
1002
  "button",
898
1003
  {
899
1004
  type: "button",
@@ -902,30 +1007,30 @@ function ReportFeature({ feature }) {
902
1007
  "aria-controls": bodyId,
903
1008
  "aria-label": `Toggle ${feature.title}`,
904
1009
  className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
905
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1010
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
906
1011
  }
907
1012
  ) : null,
908
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "min-w-0", children: [
909
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("h2", { id: titleId, className: "text-sm font-semibold tracking-tight text-foreground", children: feature.title }),
910
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "mt-0.5 truncate font-mono text-xs text-muted-foreground", children: feature.sourceFile })
1013
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "min-w-0", children: [
1014
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h2", { id: titleId, className: "text-sm font-semibold tracking-tight text-foreground", children: feature.title }),
1015
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "mt-0.5 truncate font-mono text-xs text-muted-foreground", children: feature.sourceFile })
911
1016
  ] })
912
1017
  ] }),
913
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
1018
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
914
1019
  "div",
915
1020
  {
916
1021
  role: "img",
917
1022
  className: "flex shrink-0 items-center gap-2.5 font-mono text-xs font-medium",
918
1023
  "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
919
1024
  children: [
920
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { "aria-hidden": true, className: "text-pass", children: [
1025
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { "aria-hidden": true, className: "text-pass", children: [
921
1026
  "\u2713",
922
1027
  s.passed
923
1028
  ] }),
924
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { "aria-hidden": true, className: "text-fail", children: [
1029
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { "aria-hidden": true, className: "text-fail", children: [
925
1030
  "\u2717",
926
1031
  s.failed
927
1032
  ] }),
928
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { "aria-hidden": true, className: "text-skip", children: [
1033
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { "aria-hidden": true, className: "text-skip", children: [
929
1034
  "\u25CB",
930
1035
  skipped
931
1036
  ] })
@@ -933,24 +1038,24 @@ function ReportFeature({ feature }) {
933
1038
  }
934
1039
  )
935
1040
  ] }),
936
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-2 p-3", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ReportScenarioList, { feature }) })
1041
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-2 p-3", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ReportScenarioList, { feature }) })
937
1042
  ]
938
1043
  }
939
1044
  );
940
1045
  }
941
1046
 
942
1047
  // src/components/ReportFeatureList.tsx
943
- var import_jsx_runtime24 = require("react/jsx-runtime");
1048
+ var import_jsx_runtime25 = require("react/jsx-runtime");
944
1049
  function ReportFeatureList() {
945
1050
  const report = useReport();
946
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_jsx_runtime24.Fragment, { children: report.features.map((feature) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ReportFeature, { feature }, feature.id)) });
1051
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_jsx_runtime25.Fragment, { children: report.features.map((feature) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ReportFeature, { feature }, feature.id)) });
947
1052
  }
948
1053
 
949
1054
  // src/components/ui/empty.tsx
950
1055
  var import_class_variance_authority2 = require("class-variance-authority");
951
- var import_jsx_runtime25 = require("react/jsx-runtime");
1056
+ var import_jsx_runtime26 = require("react/jsx-runtime");
952
1057
  function Empty({ className, ...props }) {
953
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1058
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
954
1059
  "div",
955
1060
  {
956
1061
  "data-slot": "empty",
@@ -963,7 +1068,7 @@ function Empty({ className, ...props }) {
963
1068
  );
964
1069
  }
965
1070
  function EmptyHeader({ className, ...props }) {
966
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1071
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
967
1072
  "div",
968
1073
  {
969
1074
  "data-slot": "empty-header",
@@ -990,7 +1095,7 @@ var emptyMediaVariants = (0, import_class_variance_authority2.cva)(
990
1095
  }
991
1096
  );
992
1097
  function EmptyTitle({ className, ...props }) {
993
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1098
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
994
1099
  "div",
995
1100
  {
996
1101
  "data-slot": "empty-title",
@@ -1001,16 +1106,16 @@ function EmptyTitle({ className, ...props }) {
1001
1106
  }
1002
1107
 
1003
1108
  // src/components/ReportEmpty.tsx
1004
- var import_jsx_runtime26 = require("react/jsx-runtime");
1109
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1005
1110
  function ReportEmpty({ message }) {
1006
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(EmptyHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1111
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(EmptyHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1007
1112
  }
1008
1113
 
1009
1114
  // src/components/ReportSummary.tsx
1010
- var import_jsx_runtime27 = require("react/jsx-runtime");
1115
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1011
1116
  function ReportSummary({ className }) {
1012
1117
  const report = useReport();
1013
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1118
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1014
1119
  ReportSummaryView,
1015
1120
  {
1016
1121
  summary: report.summary,
@@ -1020,32 +1125,32 @@ function ReportSummary({ className }) {
1020
1125
  );
1021
1126
  }
1022
1127
  function ReportSummaryView({ summary, className, ariaLabel }) {
1023
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("p", { className: cn("text-sm text-muted-foreground", className), "aria-label": ariaLabel, children: [
1024
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { children: [
1025
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("strong", { className: "font-semibold text-foreground", children: summary.total }),
1128
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("p", { className: cn("text-sm text-muted-foreground", className), "aria-label": ariaLabel, children: [
1129
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { children: [
1130
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("strong", { className: "font-semibold text-foreground", children: summary.total }),
1026
1131
  " scenario",
1027
1132
  summary.total === 1 ? "" : "s"
1028
1133
  ] }),
1029
1134
  " \xB7 ",
1030
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { "data-status": "passed", className: "text-pass", children: [
1135
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { "data-status": "passed", className: "text-pass", children: [
1031
1136
  summary.passed,
1032
1137
  " passed"
1033
1138
  ] }),
1034
1139
  " \xB7 ",
1035
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { "data-status": "failed", className: "text-fail", children: [
1140
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { "data-status": "failed", className: "text-fail", children: [
1036
1141
  summary.failed,
1037
1142
  " failed"
1038
1143
  ] }),
1039
- summary.skipped > 0 ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
1144
+ summary.skipped > 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
1040
1145
  " \xB7 ",
1041
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { "data-status": "skipped", className: "text-skip", children: [
1146
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { "data-status": "skipped", className: "text-skip", children: [
1042
1147
  summary.skipped,
1043
1148
  " skipped"
1044
1149
  ] })
1045
1150
  ] }) : null,
1046
- summary.pending > 0 ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
1151
+ summary.pending > 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
1047
1152
  " \xB7 ",
1048
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { "data-status": "pending", className: "text-pend", children: [
1153
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { "data-status": "pending", className: "text-pend", children: [
1049
1154
  summary.pending,
1050
1155
  " pending"
1051
1156
  ] })
@@ -1053,69 +1158,116 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1053
1158
  ] });
1054
1159
  }
1055
1160
 
1161
+ // src/lib/provenance.ts
1162
+ var CI_DISPLAY_NAMES = {
1163
+ github: "GitHub Actions",
1164
+ gitlab: "GitLab CI",
1165
+ circleci: "CircleCI",
1166
+ jenkins: "Jenkins",
1167
+ azure: "Azure DevOps",
1168
+ buildkite: "Buildkite",
1169
+ travis: "Travis CI",
1170
+ ci: "CI"
1171
+ };
1172
+ function ciDisplayName(name) {
1173
+ return CI_DISPLAY_NAMES[name] ?? name;
1174
+ }
1175
+ function commitUrl(ci, sha) {
1176
+ const commitSha = ci?.commitSha ?? sha;
1177
+ if (!ci?.url || !commitSha) return void 0;
1178
+ const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1179
+ if (github) return `${github[1]}/commit/${commitSha}`;
1180
+ const gitlab = ci.url.match(/^(https?:\/\/.+)\/-\/pipelines\//);
1181
+ if (gitlab) return `${gitlab[1]}/-/commit/${commitSha}`;
1182
+ return void 0;
1183
+ }
1184
+ function prUrl(ci) {
1185
+ if (!ci?.url || !ci.prNumber) return void 0;
1186
+ const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1187
+ if (github) return `${github[1]}/pull/${ci.prNumber}`;
1188
+ return void 0;
1189
+ }
1190
+
1056
1191
  // src/components/ReportMeta.tsx
1057
- var import_duration4 = require("executable-stories-core/utils/duration");
1058
- var import_jsx_runtime28 = require("react/jsx-runtime");
1192
+ var import_jsx_runtime29 = require("react/jsx-runtime");
1059
1193
  function ReportMeta() {
1060
1194
  const report = useReport();
1061
1195
  const items = [];
1196
+ const linkClass = "text-primary underline underline-offset-2";
1062
1197
  if (report.startedAtMs) {
1063
1198
  items.push({ label: "Started", value: new Date(report.startedAtMs).toLocaleString() });
1064
1199
  }
1065
1200
  if (report.durationMs) {
1066
- items.push({ label: "Duration", value: (0, import_duration4.formatDuration)(report.durationMs) });
1201
+ items.push({ label: "Duration", value: formatDuration(report.durationMs) });
1067
1202
  }
1068
1203
  if (report.packageVersion) {
1069
1204
  items.push({ label: "Version", value: report.packageVersion });
1070
1205
  }
1071
- if (report.gitSha) {
1072
- items.push({ label: "Commit", value: report.gitSha.slice(0, 8) });
1206
+ if (report.ci?.branch) {
1207
+ items.push({ label: "Branch", value: report.ci.branch });
1208
+ }
1209
+ const sha = report.ci?.commitSha ?? report.gitSha;
1210
+ if (sha) {
1211
+ const url = commitUrl(report.ci, report.gitSha);
1212
+ const short = sha.slice(0, 8);
1213
+ items.push({
1214
+ label: "Commit",
1215
+ value: url ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1216
+ });
1073
1217
  }
1074
1218
  if (report.ci) {
1075
1219
  items.push({
1076
1220
  label: "CI",
1077
- value: report.ci.url ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: "text-primary underline underline-offset-2", children: [
1078
- report.ci.name,
1221
+ value: report.ci.url ? /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1222
+ ciDisplayName(report.ci.name),
1079
1223
  report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
1080
- ] }) : `${report.ci.name}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
1224
+ ] }) : `${ciDisplayName(report.ci.name)}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
1225
+ });
1226
+ }
1227
+ if (report.ci?.prNumber) {
1228
+ const url = prUrl(report.ci);
1229
+ const label = `#${report.ci.prNumber}`;
1230
+ items.push({
1231
+ label: "PR",
1232
+ value: url ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1081
1233
  });
1082
1234
  }
1083
1235
  if (items.length === 0) return null;
1084
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("dl", { "aria-label": "Run metadata", className: "flex flex-wrap gap-x-6 gap-y-1 text-xs text-muted-foreground", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex gap-1.5", children: [
1085
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("dt", { className: "font-medium", children: item.label }),
1086
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("dd", { className: "font-mono text-foreground", children: item.value })
1236
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("dl", { "aria-label": "Run metadata", className: "flex flex-wrap gap-x-6 gap-y-1 text-xs text-muted-foreground", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex gap-1.5", children: [
1237
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("dt", { className: "font-medium", children: item.label }),
1238
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("dd", { className: "font-mono text-foreground", children: item.value })
1087
1239
  ] }, item.label)) });
1088
1240
  }
1089
1241
 
1090
1242
  // src/components/ReportSchemaError.tsx
1091
- var import_jsx_runtime29 = require("react/jsx-runtime");
1243
+ var import_jsx_runtime30 = require("react/jsx-runtime");
1092
1244
  function ReportSchemaError({ error }) {
1093
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1094
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("strong", { children: "Report could not be displayed." }) }),
1095
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { children: error.message }),
1096
- error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("p", { children: [
1245
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1246
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("strong", { children: "Report could not be displayed." }) }),
1247
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { children: error.message }),
1248
+ error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("p", { children: [
1097
1249
  "The report bundle is newer than this version of ",
1098
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("code", { children: "executable-stories-react" }),
1250
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("code", { children: "executable-stories-react" }),
1099
1251
  ". Upgrade the package, or regenerate the report with an older formatters CLI."
1100
1252
  ] }) : null,
1101
- error.issues && error.issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("details", { children: [
1102
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("summary", { children: [
1253
+ error.issues && error.issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("details", { children: [
1254
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("summary", { children: [
1103
1255
  error.issues.length,
1104
1256
  " validation issue",
1105
1257
  error.issues.length === 1 ? "" : "s"
1106
1258
  ] }),
1107
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1259
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1108
1260
  ] }) : null
1109
1261
  ] });
1110
1262
  }
1111
1263
 
1112
1264
  // src/components/ReportShell.tsx
1113
- var import_jsx_runtime30 = require("react/jsx-runtime");
1265
+ var import_jsx_runtime31 = require("react/jsx-runtime");
1114
1266
  function ReportTitleBlock({ title }) {
1115
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
1116
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("h1", { children: title ?? "Story Report" }),
1117
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ReportSummary, {}),
1118
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ReportMeta, {})
1267
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_jsx_runtime31.Fragment, { children: [
1268
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("h1", { children: title ?? "Story Report" }),
1269
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ReportSummary, {}),
1270
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ReportMeta, {})
1119
1271
  ] });
1120
1272
  }
1121
1273
  function ReportErrorShell({
@@ -1124,28 +1276,29 @@ function ReportErrorShell({
1124
1276
  title,
1125
1277
  dataTheme
1126
1278
  }) {
1127
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ReportSchemaError, { error }) });
1279
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ReportSchemaError, { error }) });
1128
1280
  }
1129
1281
 
1130
1282
  // src/components/Report.tsx
1131
- var import_jsx_runtime31 = require("react/jsx-runtime");
1283
+ var import_jsx_runtime32 = require("react/jsx-runtime");
1132
1284
  function Report(props) {
1133
1285
  const { report, customRenderers, renderers, className, title, dataTheme } = props;
1134
1286
  const result = unwrapReport(report);
1135
1287
  if (!result.ok) {
1136
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ReportErrorShell, { error: result.error, className, title, dataTheme });
1288
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ReportErrorShell, { error: result.error, className, title, dataTheme });
1137
1289
  }
1138
1290
  const data = result.data;
1139
1291
  const hasContent = data.features.length > 0;
1140
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ReportRoot, { report: data, customRenderers, renderers, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: [
1141
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("header", { className: "es-report-header", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ReportTitleBlock, { title }) }),
1142
- hasContent ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ReportFeatureList, {}) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ReportEmpty, {})
1292
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ReportRoot, { report: data, customRenderers, renderers, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: [
1293
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("header", { className: "es-report-header", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ReportTitleBlock, { title }) }),
1294
+ hasContent ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ReportFeatureList, {}) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ReportEmpty, {})
1143
1295
  ] }) });
1144
1296
  }
1145
1297
 
1146
1298
  // src/ssr-entry.ts
1147
1299
  var ROOT_ID = "es-report-root";
1148
1300
  var DATA_ID = "es-report-data";
1301
+ var HISTORY_ID = "es-report-history";
1149
1302
  function escapeJsonForScript(json) {
1150
1303
  return json.replace(/[<\u2028\u2029]/g, (c) => "\\u" + c.charCodeAt(0).toString(16).padStart(4, "0"));
1151
1304
  }
@@ -1194,19 +1347,22 @@ function renderReportToHtml(report, options = {}) {
1194
1347
  lang = "en",
1195
1348
  syntaxHighlighting = true,
1196
1349
  mermaid = true,
1197
- islandScript = ""
1350
+ islandScript = "",
1351
+ staleAfterDays = 7,
1352
+ scenarioHistory
1198
1353
  } = options;
1199
1354
  const markup = (0, import_server.renderToStaticMarkup)(
1200
- (0, import_react7.createElement)(Report, { report, title })
1355
+ (0, import_react8.createElement)(Report, { report, title })
1201
1356
  );
1202
1357
  const cdn = cdnAssets(syntaxHighlighting, mermaid);
1203
1358
  const parsed = unwrapReport(report);
1204
1359
  const rawReport = parsed.ok ? parsed.data : void 0;
1205
1360
  const interactive = islandScript.length > 0 && rawReport !== void 0;
1206
1361
  const dataScript = interactive ? `<script type="application/json" id="${DATA_ID}">${escapeJsonForScript(JSON.stringify(rawReport))}</script>` : "";
1362
+ const historyScript = interactive && scenarioHistory && Object.keys(scenarioHistory).length > 0 ? `<script type="application/json" id="${HISTORY_ID}">${escapeJsonForScript(JSON.stringify(scenarioHistory))}</script>` : "";
1207
1363
  const islandTag = interactive ? `<script>${islandScript}</script>` : "";
1208
1364
  const cdnBody = interactive ? "" : cdn.body;
1209
- const islandConfigAttrs = interactive ? ` data-es-syntax="${syntaxHighlighting ? "true" : "false"}" data-es-mermaid="${mermaid ? "true" : "false"}"` : "";
1365
+ const islandConfigAttrs = interactive ? ` data-es-syntax="${syntaxHighlighting ? "true" : "false"}" data-es-mermaid="${mermaid ? "true" : "false"}" data-es-stale-days="${Number.isFinite(staleAfterDays) && staleAfterDays >= 0 ? staleAfterDays : 7}"` : "";
1210
1366
  const rootAttrs = interactive ? ` id="${ROOT_ID}" data-title="${escapeHtml(title)}"${islandConfigAttrs}` : "";
1211
1367
  return `<!DOCTYPE html>
1212
1368
  <html lang="${escapeHtml(lang)}" data-theme="${theme === "dark" ? "dark" : "light"}">
@@ -1222,6 +1378,7 @@ ${headExtra}
1222
1378
  <body>
1223
1379
  <div${rootAttrs} class="es-report-island font-sans text-foreground">${markup}</div>
1224
1380
  ${dataScript}
1381
+ ${historyScript}
1225
1382
  ${bodyExtra}
1226
1383
  ${cdnBody}
1227
1384
  ${islandTag}