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.
@@ -22,6 +22,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
22
22
  var interactive_exports = {};
23
23
  __export(interactive_exports, {
24
24
  ReportFailureBanner: () => ReportFailureBanner,
25
+ ReportFreshness: () => ReportFreshness,
25
26
  ReportInteractive: () => ReportInteractive,
26
27
  ReportInteractiveIsland: () => ReportInteractiveIsland,
27
28
  ReportSearch: () => ReportSearch,
@@ -36,7 +37,7 @@ __export(interactive_exports, {
36
37
  module.exports = __toCommonJS(interactive_exports);
37
38
 
38
39
  // src/interactive/ReportInteractive.tsx
39
- var import_react14 = require("react");
40
+ var import_react16 = require("react");
40
41
 
41
42
  // src/result.ts
42
43
  var ok = (data) => ({ ok: true, data });
@@ -87,11 +88,23 @@ function useReport() {
87
88
  return ctx.report;
88
89
  }
89
90
 
90
- // src/components/ReportScenario.tsx
91
- var import_duration3 = require("executable-stories-core/utils/duration");
92
-
93
- // src/components/ReportSteps.tsx
94
- var import_duration = require("executable-stories-core/utils/duration");
91
+ // ../executable-stories-core/dist/utils/duration.js
92
+ function formatDuration(ms) {
93
+ if (ms < 1e3) {
94
+ return `${Math.round(ms)} ms`;
95
+ }
96
+ if (ms < 6e4) {
97
+ return `${(ms / 1e3).toFixed(2)} s`;
98
+ }
99
+ const minutes = Math.floor(ms / 6e4);
100
+ const seconds = Math.floor(ms % 6e4 / 1e3);
101
+ if (minutes < 60) {
102
+ return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
103
+ }
104
+ const hours = Math.floor(minutes / 60);
105
+ const remainingMinutes = minutes % 60;
106
+ return `${hours}h ${remainingMinutes}m`;
107
+ }
95
108
 
96
109
  // src/components/doc/DocNote.tsx
97
110
  var import_jsx_runtime2 = require("react/jsx-runtime");
@@ -275,7 +288,7 @@ function DocSection({ entry }) {
275
288
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
276
289
  "div",
277
290
  {
278
- className: "es-doc-prose text-muted-foreground",
291
+ className: "es-doc-prose prose prose-sm max-w-none",
279
292
  dangerouslySetInnerHTML: { __html: html }
280
293
  }
281
294
  )
@@ -516,7 +529,7 @@ function ReportStepItem({ step }) {
516
529
  }
517
530
  ),
518
531
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "flex-1 text-foreground", children: highlightStepParams(step.text) }),
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: (0, import_duration.formatDuration)(step.durationMs) }) : null
532
+ 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
520
533
  ] }),
521
534
  step.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
522
535
  "pre",
@@ -574,9 +587,6 @@ function ReportAttachments({ attachments }) {
574
587
  );
575
588
  }
576
589
 
577
- // src/components/ReportTrace.tsx
578
- var import_duration2 = require("executable-stories-core/utils/duration");
579
-
580
590
  // src/lib/trace.ts
581
591
  var VALID = /* @__PURE__ */ new Set(["ok", "error", "unset"]);
582
592
  var safeStatus = (s) => VALID.has(s) ? s : "unset";
@@ -671,7 +681,7 @@ function ReportTrace({ spans }) {
671
681
  " span",
672
682
  rows.length === 1 ? "" : "s",
673
683
  ", ",
674
- (0, import_duration2.formatDuration)(totalMs),
684
+ formatDuration(totalMs),
675
685
  ")"
676
686
  ] }),
677
687
  /* @__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) => {
@@ -694,18 +704,131 @@ function ReportTrace({ spans }) {
694
704
  style: { left: `${leftPct}%`, width: `${widthPct}%` }
695
705
  }
696
706
  ) }),
697
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: (0, import_duration2.formatDuration)(row.durationMs) })
707
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: formatDuration(row.durationMs) })
698
708
  ] }, `${row.spanId}-${i}`);
699
709
  }) })
700
710
  ] });
701
711
  }
702
712
 
703
- // src/interactive/collapse-context.ts
713
+ // src/interactive/scenario-history-context.ts
704
714
  var import_react5 = require("react");
705
- var CollapseContext = (0, import_react5.createContext)(null);
715
+ var ScenarioHistoryContext = (0, import_react5.createContext)(null);
716
+ var ScenarioHistoryProvider = ScenarioHistoryContext.Provider;
717
+ function useScenarioHistory() {
718
+ return (0, import_react5.useContext)(ScenarioHistoryContext);
719
+ }
720
+
721
+ // src/lib/run-history.ts
722
+ function currentStreak(entries) {
723
+ const last = entries[entries.length - 1];
724
+ if (!last) return void 0;
725
+ let count = 0;
726
+ for (let i = entries.length - 1; i >= 0 && entries[i].status === last.status; i--) count++;
727
+ return { status: last.status, count };
728
+ }
729
+ var STREAK_VERB = {
730
+ passed: "Passing",
731
+ failed: "Failing",
732
+ skipped: "Skipped",
733
+ pending: "Pending"
734
+ };
735
+ function describeRunHistory(entries) {
736
+ const passed = entries.filter((e) => e.status === "passed").length;
737
+ const base = `${passed}/${entries.length} runs passed`;
738
+ const streak = currentStreak(entries);
739
+ if (!streak || entries.length < 2) return base;
740
+ return `${base} \xB7 ${STREAK_VERB[streak.status]} for the last ${streak.count === 1 ? "run" : `${streak.count} runs`}`;
741
+ }
742
+ var MIN_FLAKINESS_SAMPLES = 3;
743
+ function flakinessOf(entries) {
744
+ const countable = entries.filter((e) => e.status === "passed" || e.status === "failed");
745
+ if (countable.length < MIN_FLAKINESS_SAMPLES) return "stable";
746
+ let transitions = 0;
747
+ for (let i = 1; i < countable.length; i++) {
748
+ if (countable[i].status !== countable[i - 1].status) transitions++;
749
+ }
750
+ const transitionScore = transitions / (countable.length - 1);
751
+ const failureRate = countable.filter((e) => e.status === "failed").length / countable.length;
752
+ if (transitionScore > 0.5 || transitionScore > 0.3 && failureRate > 0.2) return "flaky";
753
+ if (transitionScore > 0.2 || failureRate > 0.3) return "unstable";
754
+ return "stable";
755
+ }
756
+ function diffSinceLastRun(history2, scenarios) {
757
+ let hasPreviousRun = false;
758
+ const delta = { newlyFailing: [], newlyPassing: [], added: [] };
759
+ for (const scenario of scenarios) {
760
+ const entries = history2[scenario.id];
761
+ if (!entries || entries.length === 0) continue;
762
+ const last = entries[entries.length - 1];
763
+ const prev = entries[entries.length - 2];
764
+ if (!prev) {
765
+ delta.added.push(scenario);
766
+ continue;
767
+ }
768
+ hasPreviousRun = true;
769
+ if (last.status === "failed" && prev.status !== "failed") delta.newlyFailing.push(scenario);
770
+ else if (last.status === "passed" && prev.status === "failed") delta.newlyPassing.push(scenario);
771
+ }
772
+ return hasPreviousRun ? delta : void 0;
773
+ }
774
+
775
+ // src/components/ScenarioRunHistory.tsx
776
+ var import_jsx_runtime20 = require("react/jsx-runtime");
777
+ var MAX_DOTS = 10;
778
+ var DOT_COLOR = {
779
+ passed: "bg-pass",
780
+ failed: "bg-fail",
781
+ skipped: "bg-skip",
782
+ pending: "bg-pend"
783
+ };
784
+ function dotTitle(e) {
785
+ const parts = [new Date(e.timestamp).toLocaleString(), e.status];
786
+ if (e.commitSha) parts.push(e.commitSha.slice(0, 8));
787
+ return parts.join(" \xB7 ");
788
+ }
789
+ function ScenarioRunHistory({ scenarioId }) {
790
+ const history2 = useScenarioHistory();
791
+ const entries = history2?.[scenarioId];
792
+ if (!entries || entries.length < 2) return null;
793
+ const recent = entries.slice(-MAX_DOTS);
794
+ const flaky = flakinessOf(recent) === "flaky";
795
+ const summary = flaky ? `${describeRunHistory(recent)} \xB7 Flaky` : describeRunHistory(recent);
796
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
797
+ flaky ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Badge, { variant: "pending", "aria-label": "Flaky: status flips between recent runs", title: summary, children: "Flaky" }) : null,
798
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
799
+ "span",
800
+ {
801
+ role: "img",
802
+ "aria-label": `Run history: ${summary}`,
803
+ title: summary,
804
+ className: "flex shrink-0 items-center gap-[3px]",
805
+ children: recent.map((e, i) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
806
+ "span",
807
+ {
808
+ "aria-hidden": true,
809
+ title: dotTitle(e),
810
+ className: cn(
811
+ "inline-block size-1.5 rounded-full",
812
+ DOT_COLOR[e.status],
813
+ // The newest run (rightmost dot) is the one the card's status
814
+ // badge already describes; ring it so the timeline reads as
815
+ // "…leading up to now".
816
+ i === recent.length - 1 && "ring-1 ring-border ring-offset-1 ring-offset-card"
817
+ )
818
+ },
819
+ `${e.timestamp}-${i}`
820
+ ))
821
+ }
822
+ )
823
+ ] });
824
+ }
825
+
826
+ // src/interactive/collapse-context.ts
827
+ var import_react6 = require("react");
828
+ var CollapseContext = (0, import_react6.createContext)(null);
706
829
  var CollapseProvider = CollapseContext.Provider;
707
830
  function useCollapse() {
708
- return (0, import_react5.useContext)(CollapseContext);
831
+ return (0, import_react6.useContext)(CollapseContext);
709
832
  }
710
833
  var STORAGE_KEY = "es-collapsed-ids";
711
834
  function readPersisted() {
@@ -725,9 +848,9 @@ function persist(ids) {
725
848
  }
726
849
  }
727
850
  function useCollapseState() {
728
- const [collapsed, setCollapsed] = (0, import_react5.useState)(readPersisted);
729
- const isCollapsed = (0, import_react5.useCallback)((id) => collapsed.has(id), [collapsed]);
730
- const toggle = (0, import_react5.useCallback)((id) => {
851
+ const [collapsed, setCollapsed] = (0, import_react6.useState)(readPersisted);
852
+ const isCollapsed = (0, import_react6.useCallback)((id) => collapsed.has(id), [collapsed]);
853
+ const toggle = (0, import_react6.useCallback)((id) => {
731
854
  setCollapsed((prev) => {
732
855
  const next = new Set(prev);
733
856
  if (next.has(id)) next.delete(id);
@@ -736,14 +859,14 @@ function useCollapseState() {
736
859
  return next;
737
860
  });
738
861
  }, []);
739
- const collapseAll = (0, import_react5.useCallback)((ids) => {
862
+ const collapseAll = (0, import_react6.useCallback)((ids) => {
740
863
  setCollapsed(() => {
741
864
  const next = new Set(ids);
742
865
  persist(next);
743
866
  return next;
744
867
  });
745
868
  }, []);
746
- const expandAll = (0, import_react5.useCallback)(() => {
869
+ const expandAll = (0, import_react6.useCallback)(() => {
747
870
  setCollapsed(() => {
748
871
  const next = /* @__PURE__ */ new Set();
749
872
  persist(next);
@@ -754,11 +877,11 @@ function useCollapseState() {
754
877
  }
755
878
 
756
879
  // src/interactive/scenario-actions.tsx
757
- var import_react6 = require("react");
758
- var ScenarioActionsContext = (0, import_react6.createContext)(null);
880
+ var import_react7 = require("react");
881
+ var ScenarioActionsContext = (0, import_react7.createContext)(null);
759
882
  var ScenarioActionsProvider = ScenarioActionsContext.Provider;
760
883
  function useScenarioActions() {
761
- return (0, import_react6.useContext)(ScenarioActionsContext);
884
+ return (0, import_react7.useContext)(ScenarioActionsContext);
762
885
  }
763
886
  function scenarioToMarkdown(scenario) {
764
887
  const lines = [`## ${scenario.title} _(${scenario.status})_`, ""];
@@ -796,9 +919,9 @@ function scenarioPermalink(scenario) {
796
919
 
797
920
  // src/components/ui/card.tsx
798
921
  var React2 = require("react");
799
- var import_jsx_runtime20 = require("react/jsx-runtime");
922
+ var import_jsx_runtime21 = require("react/jsx-runtime");
800
923
  function Card({ className, ...props }) {
801
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
924
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
802
925
  "div",
803
926
  {
804
927
  "data-slot": "card",
@@ -811,7 +934,7 @@ function Card({ className, ...props }) {
811
934
  );
812
935
  }
813
936
  function CardHeader({ className, ...props }) {
814
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
937
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
815
938
  "div",
816
939
  {
817
940
  "data-slot": "card-header",
@@ -824,7 +947,7 @@ function CardHeader({ className, ...props }) {
824
947
  );
825
948
  }
826
949
  function CardContent({ className, ...props }) {
827
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
950
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
828
951
  "div",
829
952
  {
830
953
  "data-slot": "card-content",
@@ -835,7 +958,7 @@ function CardContent({ className, ...props }) {
835
958
  }
836
959
 
837
960
  // src/components/ReportScenario.tsx
838
- var import_jsx_runtime21 = require("react/jsx-runtime");
961
+ var import_jsx_runtime22 = require("react/jsx-runtime");
839
962
  var STATUS_LABEL = {
840
963
  passed: "Passed",
841
964
  failed: "Failed",
@@ -863,12 +986,12 @@ var STATUS_BADGE = {
863
986
  function ReportScenario({ scenario, hideTitle = false }) {
864
987
  const titleId = `${scenario.id}-title`;
865
988
  const bodyId = `${scenario.id}-body`;
866
- const label = STATUS_LABEL[scenario.status];
989
+ const label = scenario.planned ? "Planned" : STATUS_LABEL[scenario.status];
867
990
  const collapse = useCollapse();
868
991
  const collapsible = collapse !== null && !hideTitle;
869
992
  const collapsed = collapsible ? collapse.isCollapsed(scenario.id) : false;
870
993
  const actions = useScenarioActions();
871
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
994
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
872
995
  Card,
873
996
  {
874
997
  id: scenario.id,
@@ -876,10 +999,10 @@ function ReportScenario({ scenario, hideTitle = false }) {
876
999
  ...hideTitle ? { "aria-label": `${scenario.title} \u2014 ${label}` } : { "aria-labelledby": titleId },
877
1000
  className: "gap-0 py-0",
878
1001
  children: [
879
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(CardHeader, { className: "px-4 py-3", children: [
880
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
881
- hideTitle ? null : /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
882
- collapsible ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1002
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(CardHeader, { className: "px-4 py-3", children: [
1003
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
1004
+ hideTitle ? null : /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1005
+ collapsible ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
883
1006
  "button",
884
1007
  {
885
1008
  type: "button",
@@ -888,17 +1011,18 @@ function ReportScenario({ scenario, hideTitle = false }) {
888
1011
  "aria-controls": bodyId,
889
1012
  "aria-label": `Toggle ${scenario.title}`,
890
1013
  className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
891
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1014
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
892
1015
  }
893
1016
  ) : null,
894
- /* @__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] }),
895
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: scenario.title })
1017
+ /* @__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] }),
1018
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: scenario.title })
896
1019
  ] }),
897
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex shrink-0 items-center gap-2", children: [
898
- 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,
899
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Badge, { variant: STATUS_BADGE[scenario.status], "aria-label": `Status: ${label}`, children: label }),
900
- actions ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-0.5", children: [
901
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1020
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex shrink-0 items-center gap-2", children: [
1021
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ScenarioRunHistory, { scenarioId: scenario.id }),
1022
+ 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,
1023
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Badge, { variant: STATUS_BADGE[scenario.status], "aria-label": `Status: ${label}`, children: label }),
1024
+ actions ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-0.5", children: [
1025
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
902
1026
  "button",
903
1027
  {
904
1028
  type: "button",
@@ -909,7 +1033,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
909
1033
  children: "link"
910
1034
  }
911
1035
  ),
912
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1036
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
913
1037
  "button",
914
1038
  {
915
1039
  type: "button",
@@ -920,7 +1044,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
920
1044
  children: "md"
921
1045
  }
922
1046
  ),
923
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1047
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
924
1048
  "button",
925
1049
  {
926
1050
  type: "button",
@@ -934,13 +1058,13 @@ function ReportScenario({ scenario, hideTitle = false }) {
934
1058
  ] }) : null
935
1059
  ] })
936
1060
  ] }),
937
- 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: [
938
- 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}`)),
939
- (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}`))
1061
+ 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: [
1062
+ 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}`)),
1063
+ (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}`))
940
1064
  ] }) : null
941
1065
  ] }),
942
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
943
- scenario.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1066
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
1067
+ scenario.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
944
1068
  "pre",
945
1069
  {
946
1070
  role: "alert",
@@ -948,10 +1072,10 @@ function ReportScenario({ scenario, hideTitle = false }) {
948
1072
  children: scenario.errorMessage
949
1073
  }
950
1074
  ) : null,
951
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ReportSteps, { scenario }),
952
- 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,
953
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ReportAttachments, { attachments: scenario.attachments }),
954
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ReportTrace, { spans: scenario.otelSpans })
1075
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ReportSteps, { scenario }),
1076
+ 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,
1077
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ReportAttachments, { attachments: scenario.attachments }),
1078
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ReportTrace, { spans: scenario.otelSpans })
955
1079
  ] })
956
1080
  ]
957
1081
  }
@@ -959,13 +1083,13 @@ function ReportScenario({ scenario, hideTitle = false }) {
959
1083
  }
960
1084
 
961
1085
  // src/components/ReportScenarioList.tsx
962
- var import_jsx_runtime22 = require("react/jsx-runtime");
1086
+ var import_jsx_runtime23 = require("react/jsx-runtime");
963
1087
  function ReportScenarioList({ feature }) {
964
- 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)) });
1088
+ 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)) });
965
1089
  }
966
1090
 
967
1091
  // src/components/ReportFeature.tsx
968
- var import_jsx_runtime23 = require("react/jsx-runtime");
1092
+ var import_jsx_runtime24 = require("react/jsx-runtime");
969
1093
  function ReportFeature({ feature }) {
970
1094
  const titleId = `${feature.id}-title`;
971
1095
  const bodyId = `${feature.id}-body`;
@@ -973,7 +1097,7 @@ function ReportFeature({ feature }) {
973
1097
  const skipped = s.skipped + s.pending;
974
1098
  const collapse = useCollapse();
975
1099
  const collapsed = collapse?.isCollapsed(feature.id) ?? false;
976
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
1100
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
977
1101
  "section",
978
1102
  {
979
1103
  id: feature.id,
@@ -981,9 +1105,9 @@ function ReportFeature({ feature }) {
981
1105
  "aria-labelledby": titleId,
982
1106
  className: "overflow-hidden rounded-xl border border-border bg-card shadow-sm",
983
1107
  children: [
984
- /* @__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: [
985
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex min-w-0 items-center gap-2", children: [
986
- collapse ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1108
+ /* @__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: [
1109
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex min-w-0 items-center gap-2", children: [
1110
+ collapse ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
987
1111
  "button",
988
1112
  {
989
1113
  type: "button",
@@ -992,30 +1116,30 @@ function ReportFeature({ feature }) {
992
1116
  "aria-controls": bodyId,
993
1117
  "aria-label": `Toggle ${feature.title}`,
994
1118
  className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
995
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1119
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
996
1120
  }
997
1121
  ) : null,
998
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "min-w-0", children: [
999
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("h2", { id: titleId, className: "text-sm font-semibold tracking-tight text-foreground", children: feature.title }),
1000
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "mt-0.5 truncate font-mono text-xs text-muted-foreground", children: feature.sourceFile })
1122
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "min-w-0", children: [
1123
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h2", { id: titleId, className: "text-sm font-semibold tracking-tight text-foreground", children: feature.title }),
1124
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "mt-0.5 truncate font-mono text-xs text-muted-foreground", children: feature.sourceFile })
1001
1125
  ] })
1002
1126
  ] }),
1003
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
1127
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
1004
1128
  "div",
1005
1129
  {
1006
1130
  role: "img",
1007
1131
  className: "flex shrink-0 items-center gap-2.5 font-mono text-xs font-medium",
1008
1132
  "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
1009
1133
  children: [
1010
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { "aria-hidden": true, className: "text-pass", children: [
1134
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { "aria-hidden": true, className: "text-pass", children: [
1011
1135
  "\u2713",
1012
1136
  s.passed
1013
1137
  ] }),
1014
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { "aria-hidden": true, className: "text-fail", children: [
1138
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { "aria-hidden": true, className: "text-fail", children: [
1015
1139
  "\u2717",
1016
1140
  s.failed
1017
1141
  ] }),
1018
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { "aria-hidden": true, className: "text-skip", children: [
1142
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { "aria-hidden": true, className: "text-skip", children: [
1019
1143
  "\u25CB",
1020
1144
  skipped
1021
1145
  ] })
@@ -1023,24 +1147,24 @@ function ReportFeature({ feature }) {
1023
1147
  }
1024
1148
  )
1025
1149
  ] }),
1026
- /* @__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 }) })
1150
+ /* @__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 }) })
1027
1151
  ]
1028
1152
  }
1029
1153
  );
1030
1154
  }
1031
1155
 
1032
1156
  // src/components/ReportFeatureList.tsx
1033
- var import_jsx_runtime24 = require("react/jsx-runtime");
1157
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1034
1158
  function ReportFeatureList() {
1035
1159
  const report = useReport();
1036
- 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)) });
1160
+ 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)) });
1037
1161
  }
1038
1162
 
1039
1163
  // src/components/ui/empty.tsx
1040
1164
  var import_class_variance_authority2 = require("class-variance-authority");
1041
- var import_jsx_runtime25 = require("react/jsx-runtime");
1165
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1042
1166
  function Empty({ className, ...props }) {
1043
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1167
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1044
1168
  "div",
1045
1169
  {
1046
1170
  "data-slot": "empty",
@@ -1053,7 +1177,7 @@ function Empty({ className, ...props }) {
1053
1177
  );
1054
1178
  }
1055
1179
  function EmptyHeader({ className, ...props }) {
1056
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1180
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1057
1181
  "div",
1058
1182
  {
1059
1183
  "data-slot": "empty-header",
@@ -1080,7 +1204,7 @@ var emptyMediaVariants = (0, import_class_variance_authority2.cva)(
1080
1204
  }
1081
1205
  );
1082
1206
  function EmptyTitle({ className, ...props }) {
1083
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1207
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1084
1208
  "div",
1085
1209
  {
1086
1210
  "data-slot": "empty-title",
@@ -1091,16 +1215,16 @@ function EmptyTitle({ className, ...props }) {
1091
1215
  }
1092
1216
 
1093
1217
  // src/components/ReportEmpty.tsx
1094
- var import_jsx_runtime26 = require("react/jsx-runtime");
1218
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1095
1219
  function ReportEmpty({ message }) {
1096
- 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." }) }) });
1220
+ 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." }) }) });
1097
1221
  }
1098
1222
 
1099
1223
  // src/components/ReportSummary.tsx
1100
- var import_jsx_runtime27 = require("react/jsx-runtime");
1224
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1101
1225
  function ReportSummary({ className }) {
1102
1226
  const report = useReport();
1103
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1227
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1104
1228
  ReportSummaryView,
1105
1229
  {
1106
1230
  summary: report.summary,
@@ -1110,32 +1234,32 @@ function ReportSummary({ className }) {
1110
1234
  );
1111
1235
  }
1112
1236
  function ReportSummaryView({ summary, className, ariaLabel }) {
1113
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("p", { className: cn("text-sm text-muted-foreground", className), "aria-label": ariaLabel, children: [
1114
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { children: [
1115
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("strong", { className: "font-semibold text-foreground", children: summary.total }),
1237
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("p", { className: cn("text-sm text-muted-foreground", className), "aria-label": ariaLabel, children: [
1238
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { children: [
1239
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("strong", { className: "font-semibold text-foreground", children: summary.total }),
1116
1240
  " scenario",
1117
1241
  summary.total === 1 ? "" : "s"
1118
1242
  ] }),
1119
1243
  " \xB7 ",
1120
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { "data-status": "passed", className: "text-pass", children: [
1244
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { "data-status": "passed", className: "text-pass", children: [
1121
1245
  summary.passed,
1122
1246
  " passed"
1123
1247
  ] }),
1124
1248
  " \xB7 ",
1125
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { "data-status": "failed", className: "text-fail", children: [
1249
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { "data-status": "failed", className: "text-fail", children: [
1126
1250
  summary.failed,
1127
1251
  " failed"
1128
1252
  ] }),
1129
- summary.skipped > 0 ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
1253
+ summary.skipped > 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
1130
1254
  " \xB7 ",
1131
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { "data-status": "skipped", className: "text-skip", children: [
1255
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { "data-status": "skipped", className: "text-skip", children: [
1132
1256
  summary.skipped,
1133
1257
  " skipped"
1134
1258
  ] })
1135
1259
  ] }) : null,
1136
- summary.pending > 0 ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
1260
+ summary.pending > 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
1137
1261
  " \xB7 ",
1138
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { "data-status": "pending", className: "text-pend", children: [
1262
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { "data-status": "pending", className: "text-pend", children: [
1139
1263
  summary.pending,
1140
1264
  " pending"
1141
1265
  ] })
@@ -1143,69 +1267,135 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1143
1267
  ] });
1144
1268
  }
1145
1269
 
1270
+ // src/lib/provenance.ts
1271
+ function reportLastRunMs(report) {
1272
+ if (report.finishedAtMs && report.finishedAtMs > 0) return report.finishedAtMs;
1273
+ if (report.startedAtMs && report.startedAtMs > 0) return report.startedAtMs;
1274
+ return void 0;
1275
+ }
1276
+ function formatRelativeAge(thenMs, nowMs) {
1277
+ const deltaMs = Math.max(0, nowMs - thenMs);
1278
+ const minutes = Math.floor(deltaMs / 6e4);
1279
+ if (minutes < 1) return "just now";
1280
+ if (minutes < 60) return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
1281
+ const hours = Math.floor(minutes / 60);
1282
+ if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
1283
+ const days = Math.floor(hours / 24);
1284
+ return `${days} day${days === 1 ? "" : "s"} ago`;
1285
+ }
1286
+ function isReportStale(lastRunMs, staleAfterDays, nowMs) {
1287
+ if (lastRunMs === void 0 || staleAfterDays <= 0) return false;
1288
+ return nowMs - lastRunMs >= staleAfterDays * 864e5;
1289
+ }
1290
+ var CI_DISPLAY_NAMES = {
1291
+ github: "GitHub Actions",
1292
+ gitlab: "GitLab CI",
1293
+ circleci: "CircleCI",
1294
+ jenkins: "Jenkins",
1295
+ azure: "Azure DevOps",
1296
+ buildkite: "Buildkite",
1297
+ travis: "Travis CI",
1298
+ ci: "CI"
1299
+ };
1300
+ function ciDisplayName(name) {
1301
+ return CI_DISPLAY_NAMES[name] ?? name;
1302
+ }
1303
+ function commitUrl(ci, sha) {
1304
+ const commitSha = ci?.commitSha ?? sha;
1305
+ if (!ci?.url || !commitSha) return void 0;
1306
+ const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1307
+ if (github) return `${github[1]}/commit/${commitSha}`;
1308
+ const gitlab = ci.url.match(/^(https?:\/\/.+)\/-\/pipelines\//);
1309
+ if (gitlab) return `${gitlab[1]}/-/commit/${commitSha}`;
1310
+ return void 0;
1311
+ }
1312
+ function prUrl(ci) {
1313
+ if (!ci?.url || !ci.prNumber) return void 0;
1314
+ const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1315
+ if (github) return `${github[1]}/pull/${ci.prNumber}`;
1316
+ return void 0;
1317
+ }
1318
+
1146
1319
  // src/components/ReportMeta.tsx
1147
- var import_duration4 = require("executable-stories-core/utils/duration");
1148
- var import_jsx_runtime28 = require("react/jsx-runtime");
1320
+ var import_jsx_runtime29 = require("react/jsx-runtime");
1149
1321
  function ReportMeta() {
1150
1322
  const report = useReport();
1151
1323
  const items = [];
1324
+ const linkClass = "text-primary underline underline-offset-2";
1152
1325
  if (report.startedAtMs) {
1153
1326
  items.push({ label: "Started", value: new Date(report.startedAtMs).toLocaleString() });
1154
1327
  }
1155
1328
  if (report.durationMs) {
1156
- items.push({ label: "Duration", value: (0, import_duration4.formatDuration)(report.durationMs) });
1329
+ items.push({ label: "Duration", value: formatDuration(report.durationMs) });
1157
1330
  }
1158
1331
  if (report.packageVersion) {
1159
1332
  items.push({ label: "Version", value: report.packageVersion });
1160
1333
  }
1161
- if (report.gitSha) {
1162
- items.push({ label: "Commit", value: report.gitSha.slice(0, 8) });
1334
+ if (report.ci?.branch) {
1335
+ items.push({ label: "Branch", value: report.ci.branch });
1336
+ }
1337
+ const sha = report.ci?.commitSha ?? report.gitSha;
1338
+ if (sha) {
1339
+ const url = commitUrl(report.ci, report.gitSha);
1340
+ const short = sha.slice(0, 8);
1341
+ items.push({
1342
+ label: "Commit",
1343
+ value: url ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1344
+ });
1163
1345
  }
1164
1346
  if (report.ci) {
1165
1347
  items.push({
1166
1348
  label: "CI",
1167
- 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: [
1168
- report.ci.name,
1349
+ value: report.ci.url ? /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1350
+ ciDisplayName(report.ci.name),
1169
1351
  report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
1170
- ] }) : `${report.ci.name}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
1352
+ ] }) : `${ciDisplayName(report.ci.name)}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
1353
+ });
1354
+ }
1355
+ if (report.ci?.prNumber) {
1356
+ const url = prUrl(report.ci);
1357
+ const label = `#${report.ci.prNumber}`;
1358
+ items.push({
1359
+ label: "PR",
1360
+ value: url ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1171
1361
  });
1172
1362
  }
1173
1363
  if (items.length === 0) return null;
1174
- 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: [
1175
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("dt", { className: "font-medium", children: item.label }),
1176
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("dd", { className: "font-mono text-foreground", children: item.value })
1364
+ 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: [
1365
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("dt", { className: "font-medium", children: item.label }),
1366
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("dd", { className: "font-mono text-foreground", children: item.value })
1177
1367
  ] }, item.label)) });
1178
1368
  }
1179
1369
 
1180
1370
  // src/components/ReportSchemaError.tsx
1181
- var import_jsx_runtime29 = require("react/jsx-runtime");
1371
+ var import_jsx_runtime30 = require("react/jsx-runtime");
1182
1372
  function ReportSchemaError({ error }) {
1183
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1184
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("strong", { children: "Report could not be displayed." }) }),
1185
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { children: error.message }),
1186
- error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("p", { children: [
1373
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1374
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("strong", { children: "Report could not be displayed." }) }),
1375
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { children: error.message }),
1376
+ error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("p", { children: [
1187
1377
  "The report bundle is newer than this version of ",
1188
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("code", { children: "executable-stories-react" }),
1378
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("code", { children: "executable-stories-react" }),
1189
1379
  ". Upgrade the package, or regenerate the report with an older formatters CLI."
1190
1380
  ] }) : null,
1191
- error.issues && error.issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("details", { children: [
1192
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("summary", { children: [
1381
+ error.issues && error.issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("details", { children: [
1382
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("summary", { children: [
1193
1383
  error.issues.length,
1194
1384
  " validation issue",
1195
1385
  error.issues.length === 1 ? "" : "s"
1196
1386
  ] }),
1197
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1387
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1198
1388
  ] }) : null
1199
1389
  ] });
1200
1390
  }
1201
1391
 
1202
1392
  // src/components/ReportShell.tsx
1203
- var import_jsx_runtime30 = require("react/jsx-runtime");
1393
+ var import_jsx_runtime31 = require("react/jsx-runtime");
1204
1394
  function ReportTitleBlock({ title }) {
1205
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
1206
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("h1", { children: title ?? "Story Report" }),
1207
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ReportSummary, {}),
1208
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ReportMeta, {})
1395
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_jsx_runtime31.Fragment, { children: [
1396
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("h1", { children: title ?? "Story Report" }),
1397
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ReportSummary, {}),
1398
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ReportMeta, {})
1209
1399
  ] });
1210
1400
  }
1211
1401
  function ReportErrorShell({
@@ -1214,13 +1404,13 @@ function ReportErrorShell({
1214
1404
  title,
1215
1405
  dataTheme
1216
1406
  }) {
1217
- 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 }) });
1407
+ 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 }) });
1218
1408
  }
1219
1409
 
1220
1410
  // src/interactive/ReportSearch.tsx
1221
- var import_react7 = require("react");
1222
- var import_jsx_runtime31 = require("react/jsx-runtime");
1223
- var ReportSearch = (0, import_react7.forwardRef)(
1411
+ var import_react8 = require("react");
1412
+ var import_jsx_runtime32 = require("react/jsx-runtime");
1413
+ var ReportSearch = (0, import_react8.forwardRef)(
1224
1414
  function ReportSearch2(props, ref) {
1225
1415
  const {
1226
1416
  value,
@@ -1230,7 +1420,7 @@ var ReportSearch = (0, import_react7.forwardRef)(
1230
1420
  placeholder = "Search scenarios, tags, or step text\u2026",
1231
1421
  className
1232
1422
  } = props;
1233
- const inputId = (0, import_react7.useId)();
1423
+ const inputId = (0, import_react8.useId)();
1234
1424
  const showCounts = typeof matchedCount === "number" && typeof totalCount === "number";
1235
1425
  function handleChange(e) {
1236
1426
  onChange(e.target.value);
@@ -1241,9 +1431,9 @@ var ReportSearch = (0, import_react7.forwardRef)(
1241
1431
  e.preventDefault();
1242
1432
  }
1243
1433
  }
1244
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: ["es-search", className].filter(Boolean).join(" "), children: [
1245
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("label", { htmlFor: inputId, className: "es-search-label", children: "Search" }),
1246
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1434
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: ["es-search", className].filter(Boolean).join(" "), children: [
1435
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("label", { htmlFor: inputId, className: "es-search-label", children: "Search" }),
1436
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1247
1437
  "input",
1248
1438
  {
1249
1439
  ref,
@@ -1258,13 +1448,13 @@ var ReportSearch = (0, import_react7.forwardRef)(
1258
1448
  "aria-keyshortcuts": "/"
1259
1449
  }
1260
1450
  ),
1261
- showCounts ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "es-search-counts", "aria-live": "polite", children: value ? `${matchedCount} of ${totalCount}` : `${totalCount} total` }) : null
1451
+ showCounts ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "es-search-counts", "aria-live": "polite", children: value ? `${matchedCount} of ${totalCount}` : `${totalCount} total` }) : null
1262
1452
  ] });
1263
1453
  }
1264
1454
  );
1265
1455
 
1266
1456
  // src/interactive/ReportFailureBanner.tsx
1267
- var import_react8 = require("react");
1457
+ var import_react9 = require("react");
1268
1458
 
1269
1459
  // src/lib/scroll.ts
1270
1460
  function prefersReducedMotion() {
@@ -1287,15 +1477,15 @@ function scrollToScenarioId(scenarioId, options = {}) {
1287
1477
  }
1288
1478
 
1289
1479
  // src/interactive/ReportFailureBanner.tsx
1290
- var import_jsx_runtime32 = require("react/jsx-runtime");
1480
+ var import_jsx_runtime33 = require("react/jsx-runtime");
1291
1481
  function ReportFailureBanner({ failures }) {
1292
1482
  const first = failures[0];
1293
- const jumpToFirst = (0, import_react8.useCallback)(() => {
1483
+ const jumpToFirst = (0, import_react9.useCallback)(() => {
1294
1484
  if (!first) return;
1295
1485
  scrollToScenarioId(first.scenarioId);
1296
1486
  }, [first]);
1297
1487
  if (failures.length === 0) return null;
1298
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
1488
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
1299
1489
  "div",
1300
1490
  {
1301
1491
  className: "es-failure-banner",
@@ -1303,13 +1493,13 @@ function ReportFailureBanner({ failures }) {
1303
1493
  "aria-live": "polite",
1304
1494
  "aria-label": "Failure summary",
1305
1495
  children: [
1306
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: "es-failure-banner-text", children: [
1307
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("strong", { children: failures.length }),
1496
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("span", { className: "es-failure-banner-text", children: [
1497
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("strong", { children: failures.length }),
1308
1498
  " ",
1309
1499
  "failure",
1310
1500
  failures.length === 1 ? "" : "s"
1311
1501
  ] }),
1312
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1502
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
1313
1503
  "button",
1314
1504
  {
1315
1505
  type: "button",
@@ -1324,9 +1514,101 @@ function ReportFailureBanner({ failures }) {
1324
1514
  );
1325
1515
  }
1326
1516
 
1517
+ // src/interactive/ReportFreshness.tsx
1518
+ var import_react10 = require("react");
1519
+ var import_jsx_runtime34 = require("react/jsx-runtime");
1520
+ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
1521
+ const [now, setNow] = (0, import_react10.useState)(nowMs);
1522
+ (0, import_react10.useEffect)(() => {
1523
+ if (nowMs === void 0) setNow(Date.now());
1524
+ }, [nowMs]);
1525
+ if (lastRunMs === void 0 || now === void 0) return null;
1526
+ const age = formatRelativeAge(lastRunMs, now);
1527
+ const runLink = ciUrl ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
1528
+ "a",
1529
+ {
1530
+ href: ciUrl,
1531
+ target: "_blank",
1532
+ rel: "noreferrer noopener",
1533
+ className: "underline underline-offset-2",
1534
+ children: "View CI run"
1535
+ }
1536
+ ) : null;
1537
+ if (isReportStale(lastRunMs, staleAfterDays, now)) {
1538
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "es-stale-banner", role: "status", "aria-label": "Report freshness", children: [
1539
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { children: [
1540
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("strong", { children: [
1541
+ "Last verified ",
1542
+ age,
1543
+ "."
1544
+ ] }),
1545
+ " This report may no longer match the current behavior \u2014 look for a newer run."
1546
+ ] }),
1547
+ runLink
1548
+ ] });
1549
+ }
1550
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("p", { className: "es-freshness", "aria-label": "Report freshness", children: [
1551
+ "Verified ",
1552
+ age,
1553
+ runLink ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
1554
+ " \xB7 ",
1555
+ runLink
1556
+ ] }) : null
1557
+ ] });
1558
+ }
1559
+
1560
+ // src/interactive/ReportLastRunDelta.tsx
1561
+ var import_jsx_runtime35 = require("react/jsx-runtime");
1562
+ var MAX_LINKS = 5;
1563
+ function ScenarioLinks({ scenarios, tone }) {
1564
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
1565
+ scenarios.slice(0, MAX_LINKS).map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { children: [
1566
+ i > 0 ? ", " : "",
1567
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("a", { href: `#${s.id}`, className: cn("underline underline-offset-2 hover:no-underline", tone), children: s.title })
1568
+ ] }, s.id)),
1569
+ scenarios.length > MAX_LINKS ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { children: [
1570
+ " and ",
1571
+ scenarios.length - MAX_LINKS,
1572
+ " more"
1573
+ ] }) : null
1574
+ ] });
1575
+ }
1576
+ function ReportLastRunDelta({
1577
+ history: history2,
1578
+ report
1579
+ }) {
1580
+ if (!history2) return null;
1581
+ const scenarios = report.features.flatMap((f) => f.scenarios.map((s) => ({ id: s.id, title: s.title })));
1582
+ const delta = diffSinceLastRun(history2, scenarios);
1583
+ if (!delta) return null;
1584
+ const { newlyFailing, newlyPassing, added } = delta;
1585
+ if (newlyFailing.length === 0 && newlyPassing.length === 0 && added.length === 0) {
1586
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: "Since last run: no behavior changes." });
1587
+ }
1588
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: [
1589
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "font-medium text-foreground", children: "Since last run:" }),
1590
+ newlyFailing.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "ml-2 text-fail", children: [
1591
+ newlyFailing.length,
1592
+ " newly failing (",
1593
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ScenarioLinks, { scenarios: newlyFailing, tone: "text-fail" }),
1594
+ ")"
1595
+ ] }) : null,
1596
+ newlyPassing.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "ml-2 text-pass", children: [
1597
+ newlyPassing.length,
1598
+ " fixed (",
1599
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ScenarioLinks, { scenarios: newlyPassing, tone: "text-pass" }),
1600
+ ")"
1601
+ ] }) : null,
1602
+ added.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "ml-2", children: [
1603
+ added.length,
1604
+ " new"
1605
+ ] }) : null
1606
+ ] });
1607
+ }
1608
+
1327
1609
  // src/interactive/ReportShortcutsHelp.tsx
1328
- var import_react9 = require("react");
1329
- var import_jsx_runtime33 = require("react/jsx-runtime");
1610
+ var import_react11 = require("react");
1611
+ var import_jsx_runtime36 = require("react/jsx-runtime");
1330
1612
  var SHORTCUTS = [
1331
1613
  { keys: "/", description: "Focus search" },
1332
1614
  { keys: "f", description: "Jump to next failure" },
@@ -1335,14 +1617,14 @@ var SHORTCUTS = [
1335
1617
  { keys: "?", description: "Toggle this help" }
1336
1618
  ];
1337
1619
  function ReportShortcutsHelp({ open, onClose }) {
1338
- const dialogRef = (0, import_react9.useRef)(null);
1339
- (0, import_react9.useEffect)(() => {
1620
+ const dialogRef = (0, import_react11.useRef)(null);
1621
+ (0, import_react11.useEffect)(() => {
1340
1622
  const dialog = dialogRef.current;
1341
1623
  if (!dialog) return;
1342
1624
  if (open && !dialog.open) dialog.showModal();
1343
1625
  else if (!open && dialog.open) dialog.close();
1344
1626
  }, [open]);
1345
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
1627
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
1346
1628
  "dialog",
1347
1629
  {
1348
1630
  ref: dialogRef,
@@ -1350,19 +1632,19 @@ function ReportShortcutsHelp({ open, onClose }) {
1350
1632
  "aria-label": "Keyboard shortcuts",
1351
1633
  onClose,
1352
1634
  children: [
1353
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("h2", { children: "Keyboard shortcuts" }),
1354
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("dl", { children: SHORTCUTS.map((s) => /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { children: [
1355
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("dt", { children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("kbd", { children: s.keys }) }),
1356
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("dd", { children: s.description })
1635
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("h2", { children: "Keyboard shortcuts" }),
1636
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("dl", { children: SHORTCUTS.map((s) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { children: [
1637
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("dt", { children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("kbd", { children: s.keys }) }),
1638
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("dd", { children: s.description })
1357
1639
  ] }, s.keys)) }),
1358
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("form", { method: "dialog", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("button", { type: "submit", className: "es-shortcuts-close", children: "Close" }) })
1640
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("form", { method: "dialog", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("button", { type: "submit", className: "es-shortcuts-close", children: "Close" }) })
1359
1641
  ]
1360
1642
  }
1361
1643
  );
1362
1644
  }
1363
1645
 
1364
1646
  // src/interactive/use-keyboard-shortcuts.ts
1365
- var import_react10 = require("react");
1647
+ var import_react12 = require("react");
1366
1648
  function isEditableTarget(target) {
1367
1649
  if (!(target instanceof HTMLElement)) return false;
1368
1650
  const tag = target.tagName;
@@ -1371,9 +1653,9 @@ function isEditableTarget(target) {
1371
1653
  return false;
1372
1654
  }
1373
1655
  function useKeyboardShortcuts(handlers) {
1374
- const ref = (0, import_react10.useRef)(handlers);
1656
+ const ref = (0, import_react12.useRef)(handlers);
1375
1657
  ref.current = handlers;
1376
- (0, import_react10.useEffect)(() => {
1658
+ (0, import_react12.useEffect)(() => {
1377
1659
  function onKeyDown(e) {
1378
1660
  const h = ref.current;
1379
1661
  if (e.key === "Escape") {
@@ -1417,9 +1699,9 @@ function useKeyboardShortcuts(handlers) {
1417
1699
  }
1418
1700
 
1419
1701
  // src/interactive/use-deep-link-scroll.ts
1420
- var import_react11 = require("react");
1702
+ var import_react13 = require("react");
1421
1703
  function useDeepLinkScroll() {
1422
- (0, import_react11.useEffect)(() => {
1704
+ (0, import_react13.useEffect)(() => {
1423
1705
  function scrollToHash() {
1424
1706
  const hash = window.location.hash.replace(/^#/, "");
1425
1707
  if (!hash) return;
@@ -1528,7 +1810,7 @@ function listFailures(report) {
1528
1810
  }
1529
1811
 
1530
1812
  // src/interactive/ReportFilters.tsx
1531
- var import_jsx_runtime34 = require("react/jsx-runtime");
1813
+ var import_jsx_runtime37 = require("react/jsx-runtime");
1532
1814
  function ReportFilters({
1533
1815
  statuses,
1534
1816
  status,
@@ -1538,10 +1820,10 @@ function ReportFilters({
1538
1820
  onToggleTag
1539
1821
  }) {
1540
1822
  if (statuses.length === 0 && tags.length === 0) return null;
1541
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex flex-col gap-2", children: [
1542
- statuses.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { role: "group", "aria-label": "Filter by status", className: "flex flex-wrap gap-1", children: statuses.map((opt) => {
1823
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "flex flex-col gap-2", children: [
1824
+ statuses.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { role: "group", "aria-label": "Filter by status", className: "flex flex-wrap gap-1", children: statuses.map((opt) => {
1543
1825
  const active = status === opt.key;
1544
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
1826
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
1545
1827
  "button",
1546
1828
  {
1547
1829
  type: "button",
@@ -1553,22 +1835,22 @@ function ReportFilters({
1553
1835
  ),
1554
1836
  children: [
1555
1837
  opt.label,
1556
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "ml-1 font-mono opacity-70", children: opt.count })
1838
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "ml-1 font-mono opacity-70", children: opt.count })
1557
1839
  ]
1558
1840
  },
1559
1841
  opt.key
1560
1842
  );
1561
1843
  }) }) : null,
1562
- tags.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("ul", { "aria-label": "Filter by tag", className: "flex flex-wrap gap-1.5", children: tags.map((tag) => {
1844
+ tags.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("ul", { "aria-label": "Filter by tag", className: "flex flex-wrap gap-1.5", children: tags.map((tag) => {
1563
1845
  const active = activeTags.includes(tag);
1564
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
1846
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
1565
1847
  "button",
1566
1848
  {
1567
1849
  type: "button",
1568
1850
  "aria-pressed": active,
1569
1851
  onClick: () => onToggleTag(tag),
1570
1852
  className: "cursor-pointer",
1571
- children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(Badge, { variant: active ? "default" : "tag", children: tag })
1853
+ children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Badge, { variant: active ? "default" : "tag", children: tag })
1572
1854
  }
1573
1855
  ) }, tag);
1574
1856
  }) }) : null
@@ -1576,8 +1858,8 @@ function ReportFilters({
1576
1858
  }
1577
1859
 
1578
1860
  // src/interactive/ReportToc.tsx
1579
- var import_react12 = require("react");
1580
- var import_jsx_runtime35 = require("react/jsx-runtime");
1861
+ var import_react14 = require("react");
1862
+ var import_jsx_runtime38 = require("react/jsx-runtime");
1581
1863
  var DOT = {
1582
1864
  passed: "text-pass",
1583
1865
  failed: "text-fail",
@@ -1586,12 +1868,12 @@ var DOT = {
1586
1868
  };
1587
1869
  function ReportToc() {
1588
1870
  const report = useReport();
1589
- const [activeId, setActiveId] = (0, import_react12.useState)(null);
1590
- const scenarioIds = (0, import_react12.useMemo)(
1871
+ const [activeId, setActiveId] = (0, import_react14.useState)(null);
1872
+ const scenarioIds = (0, import_react14.useMemo)(
1591
1873
  () => report.features.flatMap((f) => f.scenarios.map((s) => s.id)),
1592
1874
  [report]
1593
1875
  );
1594
- (0, import_react12.useEffect)(() => {
1876
+ (0, import_react14.useEffect)(() => {
1595
1877
  if (typeof IntersectionObserver === "undefined") return;
1596
1878
  const els = scenarioIds.map((id) => document.getElementById(id)).filter((el) => el !== null);
1597
1879
  if (els.length === 0) return;
@@ -1606,16 +1888,16 @@ function ReportToc() {
1606
1888
  return () => observer.disconnect();
1607
1889
  }, [scenarioIds]);
1608
1890
  if (report.features.length === 0) return null;
1609
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
1891
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
1610
1892
  "nav",
1611
1893
  {
1612
1894
  "aria-label": "Table of contents",
1613
1895
  className: "sticky top-4 hidden h-fit w-56 shrink-0 self-start text-xs lg:block",
1614
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("ul", { className: "flex flex-col gap-3", children: report.features.map((feature) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("li", { children: [
1615
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "mb-1 truncate font-semibold text-foreground", children: feature.title }),
1616
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("ul", { className: "flex flex-col", children: feature.scenarios.map((scenario) => {
1896
+ children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("ul", { className: "flex flex-col gap-3", children: report.features.map((feature) => /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("li", { children: [
1897
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { className: "mb-1 truncate font-semibold text-foreground", children: feature.title }),
1898
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("ul", { className: "flex flex-col", children: feature.scenarios.map((scenario) => {
1617
1899
  const active = activeId === scenario.id;
1618
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
1900
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
1619
1901
  "a",
1620
1902
  {
1621
1903
  href: `#${scenario.id}`,
@@ -1629,8 +1911,8 @@ function ReportToc() {
1629
1911
  active ? "border-primary font-medium text-foreground" : "border-border text-muted-foreground"
1630
1912
  ),
1631
1913
  children: [
1632
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { "aria-hidden": true, className: cn("shrink-0", DOT[scenario.status]), children: "\u2022" }),
1633
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "truncate", children: scenario.title })
1914
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { "aria-hidden": true, className: cn("shrink-0", DOT[scenario.status]), children: "\u2022" }),
1915
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "truncate", children: scenario.title })
1634
1916
  ]
1635
1917
  }
1636
1918
  ) }, scenario.id);
@@ -1641,7 +1923,7 @@ function ReportToc() {
1641
1923
  }
1642
1924
 
1643
1925
  // src/interactive/use-theme.ts
1644
- var import_react13 = require("react");
1926
+ var import_react15 = require("react");
1645
1927
  var KEY = "es-theme";
1646
1928
  function applyTheme(theme) {
1647
1929
  if (typeof document !== "undefined") {
@@ -1662,13 +1944,13 @@ function initialTheme() {
1662
1944
  return "light";
1663
1945
  }
1664
1946
  function useTheme() {
1665
- const [theme, setTheme] = (0, import_react13.useState)("light");
1666
- (0, import_react13.useEffect)(() => {
1947
+ const [theme, setTheme] = (0, import_react15.useState)("light");
1948
+ (0, import_react15.useEffect)(() => {
1667
1949
  const t = initialTheme();
1668
1950
  setTheme(t);
1669
1951
  applyTheme(t);
1670
1952
  }, []);
1671
- const toggle = (0, import_react13.useCallback)(() => {
1953
+ const toggle = (0, import_react15.useCallback)(() => {
1672
1954
  setTheme((prev) => {
1673
1955
  const next = prev === "dark" ? "light" : "dark";
1674
1956
  applyTheme(next);
@@ -1683,14 +1965,14 @@ function useTheme() {
1683
1965
  }
1684
1966
 
1685
1967
  // src/interactive/ReportInteractive.tsx
1686
- var import_jsx_runtime36 = require("react/jsx-runtime");
1968
+ var import_jsx_runtime39 = require("react/jsx-runtime");
1687
1969
  function ReportInteractive(props) {
1688
1970
  const { report, className, title, dataTheme } = props;
1689
1971
  const result = unwrapReport(report);
1690
1972
  if (!result.ok) {
1691
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportErrorShell, { error: result.error, className, title, dataTheme });
1973
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportErrorShell, { error: result.error, className, title, dataTheme });
1692
1974
  }
1693
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportInteractiveView, { ...props, report: result.data });
1975
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportInteractiveView, { ...props, report: result.data });
1694
1976
  }
1695
1977
  function ReportInteractiveView({
1696
1978
  report,
@@ -1699,23 +1981,25 @@ function ReportInteractiveView({
1699
1981
  className,
1700
1982
  title,
1701
1983
  dataTheme,
1702
- hideHeader = false
1984
+ hideHeader = false,
1985
+ staleAfterDays = 7,
1986
+ scenarioHistory
1703
1987
  }) {
1704
- const [query, setQuery] = (0, import_react14.useState)("");
1705
- const [statusFilter, setStatusFilter] = (0, import_react14.useState)("all");
1706
- const [activeTags, setActiveTags] = (0, import_react14.useState)([]);
1707
- const [detail, setDetail] = (0, import_react14.useState)("full");
1708
- const [helpOpen, setHelpOpen] = (0, import_react14.useState)(false);
1988
+ const [query, setQuery] = (0, import_react16.useState)("");
1989
+ const [statusFilter, setStatusFilter] = (0, import_react16.useState)("all");
1990
+ const [activeTags, setActiveTags] = (0, import_react16.useState)([]);
1991
+ const [detail, setDetail] = (0, import_react16.useState)("full");
1992
+ const [helpOpen, setHelpOpen] = (0, import_react16.useState)(false);
1709
1993
  const { theme, toggle: toggleTheme } = useTheme();
1710
- const searchRef = (0, import_react14.useRef)(null);
1711
- const deferredQuery = (0, import_react14.useDeferredValue)(query);
1712
- const failures = (0, import_react14.useMemo)(() => listFailures(report), [report]);
1713
- const filtered = (0, import_react14.useMemo)(
1994
+ const searchRef = (0, import_react16.useRef)(null);
1995
+ const deferredQuery = (0, import_react16.useDeferredValue)(query);
1996
+ const failures = (0, import_react16.useMemo)(() => listFailures(report), [report]);
1997
+ const filtered = (0, import_react16.useMemo)(
1714
1998
  () => filterReport(report, { query: deferredQuery, status: statusFilter, tags: activeTags }),
1715
1999
  [report, deferredQuery, statusFilter, activeTags]
1716
2000
  );
1717
2001
  const isFiltering = query !== deferredQuery;
1718
- const statusOptions = (0, import_react14.useMemo)(() => {
2002
+ const statusOptions = (0, import_react16.useMemo)(() => {
1719
2003
  const s = report.summary;
1720
2004
  const opts = [
1721
2005
  { key: "all", label: "All", count: s.total }
@@ -1726,19 +2010,19 @@ function ReportInteractiveView({
1726
2010
  if (s.pending) opts.push({ key: "pending", label: "Pending", count: s.pending });
1727
2011
  return opts;
1728
2012
  }, [report]);
1729
- const tagOptions = (0, import_react14.useMemo)(() => allTags(report), [report]);
1730
- const toggleTag = (0, import_react14.useCallback)(
2013
+ const tagOptions = (0, import_react16.useMemo)(() => allTags(report), [report]);
2014
+ const toggleTag = (0, import_react16.useCallback)(
1731
2015
  (tag) => setActiveTags((prev) => prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag]),
1732
2016
  []
1733
2017
  );
1734
- const [toast, setToast] = (0, import_react14.useState)(null);
1735
- const toastTimer = (0, import_react14.useRef)(null);
1736
- const showToast = (0, import_react14.useCallback)((msg) => {
2018
+ const [toast, setToast] = (0, import_react16.useState)(null);
2019
+ const toastTimer = (0, import_react16.useRef)(null);
2020
+ const showToast = (0, import_react16.useCallback)((msg) => {
1737
2021
  setToast(msg);
1738
2022
  if (toastTimer.current) clearTimeout(toastTimer.current);
1739
2023
  toastTimer.current = setTimeout(() => setToast(null), 1800);
1740
2024
  }, []);
1741
- const copy = (0, import_react14.useCallback)(
2025
+ const copy = (0, import_react16.useCallback)(
1742
2026
  (text, msg) => {
1743
2027
  if (typeof navigator !== "undefined" && navigator.clipboard) {
1744
2028
  navigator.clipboard.writeText(text).then(() => showToast(msg), () => showToast("Copy failed"));
@@ -1746,7 +2030,7 @@ function ReportInteractiveView({
1746
2030
  },
1747
2031
  [showToast]
1748
2032
  );
1749
- const scenarioActions = (0, import_react14.useMemo)(
2033
+ const scenarioActions = (0, import_react16.useMemo)(
1750
2034
  () => ({
1751
2035
  copyLink: (s) => copy(scenarioPermalink(s), "Link copied"),
1752
2036
  copyMarkdown: (s) => copy(scenarioToMarkdown(s), "Markdown copied"),
@@ -1754,17 +2038,17 @@ function ReportInteractiveView({
1754
2038
  }),
1755
2039
  [copy]
1756
2040
  );
1757
- const failureIndexRef = (0, import_react14.useRef)(0);
2041
+ const failureIndexRef = (0, import_react16.useRef)(0);
1758
2042
  const collapse = useCollapseState();
1759
- const allCollapsibleIds = (0, import_react14.useMemo)(
2043
+ const allCollapsibleIds = (0, import_react16.useMemo)(
1760
2044
  () => report.features.flatMap((f) => [f.id, ...f.scenarios.map((s) => s.id)]),
1761
2045
  [report]
1762
2046
  );
1763
- const collapseAll = (0, import_react14.useCallback)(() => collapse.collapseAll(allCollapsibleIds), [collapse, allCollapsibleIds]);
1764
- const focusSearch = (0, import_react14.useCallback)(() => {
2047
+ const collapseAll = (0, import_react16.useCallback)(() => collapse.collapseAll(allCollapsibleIds), [collapse, allCollapsibleIds]);
2048
+ const focusSearch = (0, import_react16.useCallback)(() => {
1765
2049
  searchRef.current?.focus();
1766
2050
  }, []);
1767
- const stepFailure = (0, import_react14.useCallback)(
2051
+ const stepFailure = (0, import_react16.useCallback)(
1768
2052
  (direction) => {
1769
2053
  if (failures.length === 0) return;
1770
2054
  failureIndexRef.current = (failureIndexRef.current + direction + failures.length) % failures.length;
@@ -1773,10 +2057,10 @@ function ReportInteractiveView({
1773
2057
  },
1774
2058
  [failures]
1775
2059
  );
1776
- const toggleHelp = (0, import_react14.useCallback)(() => {
2060
+ const toggleHelp = (0, import_react16.useCallback)(() => {
1777
2061
  setHelpOpen((v) => !v);
1778
2062
  }, []);
1779
- const escape = (0, import_react14.useCallback)(() => {
2063
+ const escape = (0, import_react16.useCallback)(() => {
1780
2064
  if (helpOpen) setHelpOpen(false);
1781
2065
  else if (query !== "") setQuery("");
1782
2066
  }, [helpOpen, query]);
@@ -1793,13 +2077,13 @@ function ReportInteractiveView({
1793
2077
  const hasContent = filtered.features.length > 0;
1794
2078
  const totalScenarios = report.summary.total;
1795
2079
  const matchedScenarios = filtered.summary.total;
1796
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(CollapseProvider, { value: collapse.api, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ScenarioActionsProvider, { value: scenarioActions, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2080
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(CollapseProvider, { value: collapse.api, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ScenarioActionsProvider, { value: scenarioActions, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ScenarioHistoryProvider, { value: scenarioHistory ?? null, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1797
2081
  ReportRoot,
1798
2082
  {
1799
2083
  report: filtered,
1800
2084
  customRenderers,
1801
2085
  renderers,
1802
- children: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
2086
+ children: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
1803
2087
  "main",
1804
2088
  {
1805
2089
  className: cn("es-report", "es-report-interactive", className),
@@ -1808,10 +2092,19 @@ function ReportInteractiveView({
1808
2092
  "data-theme": dataTheme,
1809
2093
  "data-detail-level": detail,
1810
2094
  children: [
1811
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("header", { className: "es-report-header", children: [
1812
- hideHeader ? null : /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportTitleBlock, { title }),
1813
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
1814
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2095
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("header", { className: "es-report-header", children: [
2096
+ hideHeader ? null : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportTitleBlock, { title }),
2097
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2098
+ ReportFreshness,
2099
+ {
2100
+ lastRunMs: reportLastRunMs(report),
2101
+ ciUrl: report.ci?.url,
2102
+ staleAfterDays
2103
+ }
2104
+ ),
2105
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportLastRunDelta, { history: scenarioHistory, report }),
2106
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
2107
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1815
2108
  ReportSearch,
1816
2109
  {
1817
2110
  ref: searchRef,
@@ -1821,8 +2114,8 @@ function ReportInteractiveView({
1821
2114
  totalCount: totalScenarios
1822
2115
  }
1823
2116
  ),
1824
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex shrink-0 gap-1", children: [
1825
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2117
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex shrink-0 gap-1", children: [
2118
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1826
2119
  "button",
1827
2120
  {
1828
2121
  type: "button",
@@ -1832,7 +2125,7 @@ function ReportInteractiveView({
1832
2125
  children: "Expand all"
1833
2126
  }
1834
2127
  ),
1835
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2128
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1836
2129
  "button",
1837
2130
  {
1838
2131
  type: "button",
@@ -1842,7 +2135,7 @@ function ReportInteractiveView({
1842
2135
  children: "Collapse all"
1843
2136
  }
1844
2137
  ),
1845
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2138
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1846
2139
  "button",
1847
2140
  {
1848
2141
  type: "button",
@@ -1852,7 +2145,7 @@ function ReportInteractiveView({
1852
2145
  children: detail === "full" ? "Hide docs" : "Show docs"
1853
2146
  }
1854
2147
  ),
1855
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2148
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1856
2149
  "button",
1857
2150
  {
1858
2151
  type: "button",
@@ -1864,7 +2157,7 @@ function ReportInteractiveView({
1864
2157
  )
1865
2158
  ] })
1866
2159
  ] }),
1867
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2160
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1868
2161
  ReportFilters,
1869
2162
  {
1870
2163
  statuses: statusOptions,
@@ -1876,12 +2169,12 @@ function ReportInteractiveView({
1876
2169
  }
1877
2170
  )
1878
2171
  ] }),
1879
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportFailureBanner, { failures }),
1880
- hasContent ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex gap-6", children: [
1881
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportToc, {}),
1882
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "flex min-w-0 flex-1 flex-col gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportFeatureList, {}) })
1883
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportEmpty, { message: query ? "No scenarios match the search." : void 0 }),
1884
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2172
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportFailureBanner, { failures }),
2173
+ hasContent ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex gap-6", children: [
2174
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportToc, {}),
2175
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "flex min-w-0 flex-1 flex-col gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportFeatureList, {}) })
2176
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportEmpty, { message: query ? "No scenarios match the search." : void 0 }),
2177
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1885
2178
  "button",
1886
2179
  {
1887
2180
  type: "button",
@@ -1892,8 +2185,8 @@ function ReportInteractiveView({
1892
2185
  children: "?"
1893
2186
  }
1894
2187
  ),
1895
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportShortcutsHelp, { open: helpOpen, onClose: () => setHelpOpen(false) }),
1896
- toast ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2188
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportShortcutsHelp, { open: helpOpen, onClose: () => setHelpOpen(false) }),
2189
+ toast ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1897
2190
  "div",
1898
2191
  {
1899
2192
  role: "status",
@@ -1906,18 +2199,18 @@ function ReportInteractiveView({
1906
2199
  }
1907
2200
  )
1908
2201
  }
1909
- ) }) });
2202
+ ) }) }) });
1910
2203
  }
1911
2204
 
1912
2205
  // src/interactive/ReportInteractiveIsland.tsx
1913
- var import_react17 = require("react");
2206
+ var import_react19 = require("react");
1914
2207
 
1915
2208
  // src/interactive/island-renderers.tsx
1916
- var import_react16 = require("react");
2209
+ var import_react18 = require("react");
1917
2210
 
1918
2211
  // src/components/doc/MermaidView.tsx
1919
- var import_react15 = require("react");
1920
- var import_jsx_runtime37 = require("react/jsx-runtime");
2212
+ var import_react17 = require("react");
2213
+ var import_jsx_runtime40 = require("react/jsx-runtime");
1921
2214
  function prefersDark(el) {
1922
2215
  const themed = el?.closest("[data-theme]");
1923
2216
  if (themed?.dataset.theme === "dark") return true;
@@ -1925,9 +2218,9 @@ function prefersDark(el) {
1925
2218
  return typeof window !== "undefined" && !!window.matchMedia?.("(prefers-color-scheme: dark)").matches;
1926
2219
  }
1927
2220
  function MermaidView({ entry, load }) {
1928
- const ref = (0, import_react15.useRef)(null);
1929
- const [svg, setSvg] = (0, import_react15.useState)(null);
1930
- (0, import_react15.useEffect)(() => {
2221
+ const ref = (0, import_react17.useRef)(null);
2222
+ const [svg, setSvg] = (0, import_react17.useState)(null);
2223
+ (0, import_react17.useEffect)(() => {
1931
2224
  let cancelled = false;
1932
2225
  const draw = async () => {
1933
2226
  try {
@@ -1954,9 +2247,9 @@ function MermaidView({ entry, load }) {
1954
2247
  };
1955
2248
  }, [entry.code, load]);
1956
2249
  if (svg) {
1957
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("figure", { ref, className: "my-3", children: [
1958
- entry.title ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
1959
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
2250
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("figure", { ref, className: "my-3", children: [
2251
+ entry.title ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
2252
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
1960
2253
  "div",
1961
2254
  {
1962
2255
  role: "img",
@@ -1967,11 +2260,11 @@ function MermaidView({ entry, load }) {
1967
2260
  )
1968
2261
  ] });
1969
2262
  }
1970
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(MermaidSource, { entry, ref });
2263
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(MermaidSource, { entry, ref });
1971
2264
  }
1972
2265
 
1973
2266
  // src/interactive/island-renderers.tsx
1974
- var import_jsx_runtime38 = require("react/jsx-runtime");
2267
+ var import_jsx_runtime41 = require("react/jsx-runtime");
1975
2268
  var HLJS_ESM = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/es/highlight.min.js";
1976
2269
  var MERMAID_ESM = "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
1977
2270
  function cachedCdnModule(url) {
@@ -1988,8 +2281,8 @@ function cachedCdnModule(url) {
1988
2281
  var loadMermaidFromCdn = cachedCdnModule(MERMAID_ESM);
1989
2282
  var loadHljs = cachedCdnModule(HLJS_ESM);
1990
2283
  function CdnHighlightedCode({ entry }) {
1991
- const [html, setHtml] = (0, import_react16.useState)(null);
1992
- (0, import_react16.useEffect)(() => {
2284
+ const [html, setHtml] = (0, import_react18.useState)(null);
2285
+ (0, import_react18.useEffect)(() => {
1993
2286
  let cancelled = false;
1994
2287
  void (async () => {
1995
2288
  try {
@@ -2005,27 +2298,27 @@ function CdnHighlightedCode({ entry }) {
2005
2298
  cancelled = true;
2006
2299
  };
2007
2300
  }, [entry.content, entry.lang]);
2008
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(CodeFigure, { label: entry.label, children: html !== null ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2301
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(CodeFigure, { label: entry.label, children: html !== null ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
2009
2302
  "code",
2010
2303
  {
2011
2304
  className: cn("hljs font-mono text-xs", entry.lang && `language-${entry.lang}`),
2012
2305
  dangerouslySetInnerHTML: { __html: html }
2013
2306
  }
2014
- ) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
2307
+ ) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
2015
2308
  }
2016
2309
  function buildIslandRenderers(opts) {
2017
2310
  const renderers = {};
2018
2311
  if (opts.mermaid) {
2019
- renderers.mermaid = (entry) => (0, import_react16.createElement)(MermaidView, { entry, load: loadMermaidFromCdn });
2312
+ renderers.mermaid = (entry) => (0, import_react18.createElement)(MermaidView, { entry, load: loadMermaidFromCdn });
2020
2313
  }
2021
2314
  if (opts.syntaxHighlighting) {
2022
- renderers.code = (entry) => (0, import_react16.createElement)(CdnHighlightedCode, { entry });
2315
+ renderers.code = (entry) => (0, import_react18.createElement)(CdnHighlightedCode, { entry });
2023
2316
  }
2024
2317
  return renderers;
2025
2318
  }
2026
2319
 
2027
2320
  // src/interactive/ReportInteractiveIsland.tsx
2028
- var import_jsx_runtime39 = require("react/jsx-runtime");
2321
+ var import_jsx_runtime42 = require("react/jsx-runtime");
2029
2322
  function ReportInteractiveIsland({
2030
2323
  report,
2031
2324
  title,
@@ -2034,11 +2327,11 @@ function ReportInteractiveIsland({
2034
2327
  syntaxHighlighting = true,
2035
2328
  mermaid = true
2036
2329
  }) {
2037
- const renderers = (0, import_react17.useMemo)(
2330
+ const renderers = (0, import_react19.useMemo)(
2038
2331
  () => buildIslandRenderers({ syntaxHighlighting, mermaid }),
2039
2332
  [syntaxHighlighting, mermaid]
2040
2333
  );
2041
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2334
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2042
2335
  ReportInteractive,
2043
2336
  {
2044
2337
  report,
@@ -2052,6 +2345,7 @@ function ReportInteractiveIsland({
2052
2345
  // Annotate the CommonJS export names for ESM import in node:
2053
2346
  0 && (module.exports = {
2054
2347
  ReportFailureBanner,
2348
+ ReportFreshness,
2055
2349
  ReportInteractive,
2056
2350
  ReportInteractiveIsland,
2057
2351
  ReportSearch,