executable-stories-react 0.2.1 → 0.3.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.
@@ -6,7 +6,7 @@ import {
6
6
  useDeferredValue,
7
7
  useMemo as useMemo3,
8
8
  useRef as useRef3,
9
- useState as useState4
9
+ useState as useState5
10
10
  } from "react";
11
11
 
12
12
  // src/result.ts
@@ -58,11 +58,23 @@ function useReport() {
58
58
  return ctx.report;
59
59
  }
60
60
 
61
- // src/components/ReportScenario.tsx
62
- import { formatDuration as formatDuration3 } from "executable-stories-core/utils/duration";
63
-
64
- // src/components/ReportSteps.tsx
65
- import { formatDuration } from "executable-stories-core/utils/duration";
61
+ // ../executable-stories-core/dist/utils/duration.js
62
+ function formatDuration(ms) {
63
+ if (ms < 1e3) {
64
+ return `${Math.round(ms)} ms`;
65
+ }
66
+ if (ms < 6e4) {
67
+ return `${(ms / 1e3).toFixed(2)} s`;
68
+ }
69
+ const minutes = Math.floor(ms / 6e4);
70
+ const seconds = Math.floor(ms % 6e4 / 1e3);
71
+ if (minutes < 60) {
72
+ return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
73
+ }
74
+ const hours = Math.floor(minutes / 60);
75
+ const remainingMinutes = minutes % 60;
76
+ return `${hours}h ${remainingMinutes}m`;
77
+ }
66
78
 
67
79
  // src/components/doc/DocNote.tsx
68
80
  import { jsx as jsx2 } from "react/jsx-runtime";
@@ -545,9 +557,6 @@ function ReportAttachments({ attachments }) {
545
557
  );
546
558
  }
547
559
 
548
- // src/components/ReportTrace.tsx
549
- import { formatDuration as formatDuration2 } from "executable-stories-core/utils/duration";
550
-
551
560
  // src/lib/trace.ts
552
561
  var VALID = /* @__PURE__ */ new Set(["ok", "error", "unset"]);
553
562
  var safeStatus = (s) => VALID.has(s) ? s : "unset";
@@ -642,7 +651,7 @@ function ReportTrace({ spans }) {
642
651
  " span",
643
652
  rows.length === 1 ? "" : "s",
644
653
  ", ",
645
- formatDuration2(totalMs),
654
+ formatDuration(totalMs),
646
655
  ")"
647
656
  ] }),
648
657
  /* @__PURE__ */ jsx19("ul", { className: "flex flex-col gap-0.5 px-3 pb-3 font-mono text-[0.6875rem]", children: rows.map((row, i) => {
@@ -665,18 +674,131 @@ function ReportTrace({ spans }) {
665
674
  style: { left: `${leftPct}%`, width: `${widthPct}%` }
666
675
  }
667
676
  ) }),
668
- /* @__PURE__ */ jsx19("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: formatDuration2(row.durationMs) })
677
+ /* @__PURE__ */ jsx19("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: formatDuration(row.durationMs) })
669
678
  ] }, `${row.spanId}-${i}`);
670
679
  }) })
671
680
  ] });
672
681
  }
673
682
 
683
+ // src/interactive/scenario-history-context.ts
684
+ import { createContext as createContext2, useContext as useContext3 } from "react";
685
+ var ScenarioHistoryContext = createContext2(null);
686
+ var ScenarioHistoryProvider = ScenarioHistoryContext.Provider;
687
+ function useScenarioHistory() {
688
+ return useContext3(ScenarioHistoryContext);
689
+ }
690
+
691
+ // src/lib/run-history.ts
692
+ function currentStreak(entries) {
693
+ const last = entries[entries.length - 1];
694
+ if (!last) return void 0;
695
+ let count = 0;
696
+ for (let i = entries.length - 1; i >= 0 && entries[i].status === last.status; i--) count++;
697
+ return { status: last.status, count };
698
+ }
699
+ var STREAK_VERB = {
700
+ passed: "Passing",
701
+ failed: "Failing",
702
+ skipped: "Skipped",
703
+ pending: "Pending"
704
+ };
705
+ function describeRunHistory(entries) {
706
+ const passed = entries.filter((e) => e.status === "passed").length;
707
+ const base = `${passed}/${entries.length} runs passed`;
708
+ const streak = currentStreak(entries);
709
+ if (!streak || entries.length < 2) return base;
710
+ return `${base} \xB7 ${STREAK_VERB[streak.status]} for the last ${streak.count === 1 ? "run" : `${streak.count} runs`}`;
711
+ }
712
+ var MIN_FLAKINESS_SAMPLES = 3;
713
+ function flakinessOf(entries) {
714
+ const countable = entries.filter((e) => e.status === "passed" || e.status === "failed");
715
+ if (countable.length < MIN_FLAKINESS_SAMPLES) return "stable";
716
+ let transitions = 0;
717
+ for (let i = 1; i < countable.length; i++) {
718
+ if (countable[i].status !== countable[i - 1].status) transitions++;
719
+ }
720
+ const transitionScore = transitions / (countable.length - 1);
721
+ const failureRate = countable.filter((e) => e.status === "failed").length / countable.length;
722
+ if (transitionScore > 0.5 || transitionScore > 0.3 && failureRate > 0.2) return "flaky";
723
+ if (transitionScore > 0.2 || failureRate > 0.3) return "unstable";
724
+ return "stable";
725
+ }
726
+ function diffSinceLastRun(history2, scenarios) {
727
+ let hasPreviousRun = false;
728
+ const delta = { newlyFailing: [], newlyPassing: [], added: [] };
729
+ for (const scenario of scenarios) {
730
+ const entries = history2[scenario.id];
731
+ if (!entries || entries.length === 0) continue;
732
+ const last = entries[entries.length - 1];
733
+ const prev = entries[entries.length - 2];
734
+ if (!prev) {
735
+ delta.added.push(scenario);
736
+ continue;
737
+ }
738
+ hasPreviousRun = true;
739
+ if (last.status === "failed" && prev.status !== "failed") delta.newlyFailing.push(scenario);
740
+ else if (last.status === "passed" && prev.status === "failed") delta.newlyPassing.push(scenario);
741
+ }
742
+ return hasPreviousRun ? delta : void 0;
743
+ }
744
+
745
+ // src/components/ScenarioRunHistory.tsx
746
+ import { Fragment as Fragment7, jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
747
+ var MAX_DOTS = 10;
748
+ var DOT_COLOR = {
749
+ passed: "bg-pass",
750
+ failed: "bg-fail",
751
+ skipped: "bg-skip",
752
+ pending: "bg-pend"
753
+ };
754
+ function dotTitle(e) {
755
+ const parts = [new Date(e.timestamp).toLocaleString(), e.status];
756
+ if (e.commitSha) parts.push(e.commitSha.slice(0, 8));
757
+ return parts.join(" \xB7 ");
758
+ }
759
+ function ScenarioRunHistory({ scenarioId }) {
760
+ const history2 = useScenarioHistory();
761
+ const entries = history2?.[scenarioId];
762
+ if (!entries || entries.length < 2) return null;
763
+ const recent = entries.slice(-MAX_DOTS);
764
+ const flaky = flakinessOf(recent) === "flaky";
765
+ const summary = flaky ? `${describeRunHistory(recent)} \xB7 Flaky` : describeRunHistory(recent);
766
+ return /* @__PURE__ */ jsxs13(Fragment7, { children: [
767
+ flaky ? /* @__PURE__ */ jsx20(Badge, { variant: "pending", "aria-label": "Flaky: status flips between recent runs", title: summary, children: "Flaky" }) : null,
768
+ /* @__PURE__ */ jsx20(
769
+ "span",
770
+ {
771
+ role: "img",
772
+ "aria-label": `Run history: ${summary}`,
773
+ title: summary,
774
+ className: "flex shrink-0 items-center gap-[3px]",
775
+ children: recent.map((e, i) => /* @__PURE__ */ jsx20(
776
+ "span",
777
+ {
778
+ "aria-hidden": true,
779
+ title: dotTitle(e),
780
+ className: cn(
781
+ "inline-block size-1.5 rounded-full",
782
+ DOT_COLOR[e.status],
783
+ // The newest run (rightmost dot) is the one the card's status
784
+ // badge already describes; ring it so the timeline reads as
785
+ // "…leading up to now".
786
+ i === recent.length - 1 && "ring-1 ring-border ring-offset-1 ring-offset-card"
787
+ )
788
+ },
789
+ `${e.timestamp}-${i}`
790
+ ))
791
+ }
792
+ )
793
+ ] });
794
+ }
795
+
674
796
  // src/interactive/collapse-context.ts
675
- import { createContext as createContext2, useCallback, useContext as useContext3, useState } from "react";
676
- var CollapseContext = createContext2(null);
797
+ import { createContext as createContext3, useCallback, useContext as useContext4, useState } from "react";
798
+ var CollapseContext = createContext3(null);
677
799
  var CollapseProvider = CollapseContext.Provider;
678
800
  function useCollapse() {
679
- return useContext3(CollapseContext);
801
+ return useContext4(CollapseContext);
680
802
  }
681
803
  var STORAGE_KEY = "es-collapsed-ids";
682
804
  function readPersisted() {
@@ -725,11 +847,11 @@ function useCollapseState() {
725
847
  }
726
848
 
727
849
  // src/interactive/scenario-actions.tsx
728
- import { createContext as createContext3, useContext as useContext4 } from "react";
729
- var ScenarioActionsContext = createContext3(null);
850
+ import { createContext as createContext4, useContext as useContext5 } from "react";
851
+ var ScenarioActionsContext = createContext4(null);
730
852
  var ScenarioActionsProvider = ScenarioActionsContext.Provider;
731
853
  function useScenarioActions() {
732
- return useContext4(ScenarioActionsContext);
854
+ return useContext5(ScenarioActionsContext);
733
855
  }
734
856
  function scenarioToMarkdown(scenario) {
735
857
  const lines = [`## ${scenario.title} _(${scenario.status})_`, ""];
@@ -767,9 +889,9 @@ function scenarioPermalink(scenario) {
767
889
 
768
890
  // src/components/ui/card.tsx
769
891
  import "react";
770
- import { jsx as jsx20 } from "react/jsx-runtime";
892
+ import { jsx as jsx21 } from "react/jsx-runtime";
771
893
  function Card({ className, ...props }) {
772
- return /* @__PURE__ */ jsx20(
894
+ return /* @__PURE__ */ jsx21(
773
895
  "div",
774
896
  {
775
897
  "data-slot": "card",
@@ -782,7 +904,7 @@ function Card({ className, ...props }) {
782
904
  );
783
905
  }
784
906
  function CardHeader({ className, ...props }) {
785
- return /* @__PURE__ */ jsx20(
907
+ return /* @__PURE__ */ jsx21(
786
908
  "div",
787
909
  {
788
910
  "data-slot": "card-header",
@@ -795,7 +917,7 @@ function CardHeader({ className, ...props }) {
795
917
  );
796
918
  }
797
919
  function CardContent({ className, ...props }) {
798
- return /* @__PURE__ */ jsx20(
920
+ return /* @__PURE__ */ jsx21(
799
921
  "div",
800
922
  {
801
923
  "data-slot": "card-content",
@@ -806,7 +928,7 @@ function CardContent({ className, ...props }) {
806
928
  }
807
929
 
808
930
  // src/components/ReportScenario.tsx
809
- import { jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
931
+ import { jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
810
932
  var STATUS_LABEL = {
811
933
  passed: "Passed",
812
934
  failed: "Failed",
@@ -834,12 +956,12 @@ var STATUS_BADGE = {
834
956
  function ReportScenario({ scenario, hideTitle = false }) {
835
957
  const titleId = `${scenario.id}-title`;
836
958
  const bodyId = `${scenario.id}-body`;
837
- const label = STATUS_LABEL[scenario.status];
959
+ const label = scenario.planned ? "Planned" : STATUS_LABEL[scenario.status];
838
960
  const collapse = useCollapse();
839
961
  const collapsible = collapse !== null && !hideTitle;
840
962
  const collapsed = collapsible ? collapse.isCollapsed(scenario.id) : false;
841
963
  const actions = useScenarioActions();
842
- return /* @__PURE__ */ jsxs13(
964
+ return /* @__PURE__ */ jsxs14(
843
965
  Card,
844
966
  {
845
967
  id: scenario.id,
@@ -847,10 +969,10 @@ function ReportScenario({ scenario, hideTitle = false }) {
847
969
  ...hideTitle ? { "aria-label": `${scenario.title} \u2014 ${label}` } : { "aria-labelledby": titleId },
848
970
  className: "gap-0 py-0",
849
971
  children: [
850
- /* @__PURE__ */ jsxs13(CardHeader, { className: "px-4 py-3", children: [
851
- /* @__PURE__ */ jsxs13("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
852
- hideTitle ? null : /* @__PURE__ */ jsxs13("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
853
- collapsible ? /* @__PURE__ */ jsx21(
972
+ /* @__PURE__ */ jsxs14(CardHeader, { className: "px-4 py-3", children: [
973
+ /* @__PURE__ */ jsxs14("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
974
+ hideTitle ? null : /* @__PURE__ */ jsxs14("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
975
+ collapsible ? /* @__PURE__ */ jsx22(
854
976
  "button",
855
977
  {
856
978
  type: "button",
@@ -859,17 +981,18 @@ function ReportScenario({ scenario, hideTitle = false }) {
859
981
  "aria-controls": bodyId,
860
982
  "aria-label": `Toggle ${scenario.title}`,
861
983
  className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
862
- children: /* @__PURE__ */ jsx21("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
984
+ children: /* @__PURE__ */ jsx22("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
863
985
  }
864
986
  ) : null,
865
- /* @__PURE__ */ jsx21("span", { "aria-hidden": true, className: cn("text-sm leading-none", STATUS_GLYPH_COLOR[scenario.status]), children: STATUS_GLYPH[scenario.status] }),
866
- /* @__PURE__ */ jsx21("span", { children: scenario.title })
987
+ /* @__PURE__ */ jsx22("span", { "aria-hidden": true, className: cn("text-sm leading-none", STATUS_GLYPH_COLOR[scenario.status]), children: STATUS_GLYPH[scenario.status] }),
988
+ /* @__PURE__ */ jsx22("span", { children: scenario.title })
867
989
  ] }),
868
- /* @__PURE__ */ jsxs13("div", { className: "flex shrink-0 items-center gap-2", children: [
869
- scenario.durationMs > 0 ? /* @__PURE__ */ jsx21("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration3(scenario.durationMs) }) : null,
870
- /* @__PURE__ */ jsx21(Badge, { variant: STATUS_BADGE[scenario.status], "aria-label": `Status: ${label}`, children: label }),
871
- actions ? /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-0.5", children: [
872
- /* @__PURE__ */ jsx21(
990
+ /* @__PURE__ */ jsxs14("div", { className: "flex shrink-0 items-center gap-2", children: [
991
+ /* @__PURE__ */ jsx22(ScenarioRunHistory, { scenarioId: scenario.id }),
992
+ scenario.durationMs > 0 ? /* @__PURE__ */ jsx22("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(scenario.durationMs) }) : null,
993
+ /* @__PURE__ */ jsx22(Badge, { variant: STATUS_BADGE[scenario.status], "aria-label": `Status: ${label}`, children: label }),
994
+ actions ? /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-0.5", children: [
995
+ /* @__PURE__ */ jsx22(
873
996
  "button",
874
997
  {
875
998
  type: "button",
@@ -880,7 +1003,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
880
1003
  children: "link"
881
1004
  }
882
1005
  ),
883
- /* @__PURE__ */ jsx21(
1006
+ /* @__PURE__ */ jsx22(
884
1007
  "button",
885
1008
  {
886
1009
  type: "button",
@@ -891,7 +1014,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
891
1014
  children: "md"
892
1015
  }
893
1016
  ),
894
- /* @__PURE__ */ jsx21(
1017
+ /* @__PURE__ */ jsx22(
895
1018
  "button",
896
1019
  {
897
1020
  type: "button",
@@ -905,13 +1028,13 @@ function ReportScenario({ scenario, hideTitle = false }) {
905
1028
  ] }) : null
906
1029
  ] })
907
1030
  ] }),
908
- scenario.tags.length > 0 || (scenario.tickets?.length ?? 0) > 0 ? /* @__PURE__ */ jsxs13("ul", { "aria-label": "Tags and tickets", className: "flex flex-wrap gap-1.5", children: [
909
- scenario.tags.map((t) => /* @__PURE__ */ jsx21("li", { children: /* @__PURE__ */ jsx21(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
910
- (scenario.tickets ?? []).map((ticket) => /* @__PURE__ */ jsx21("li", { children: ticket.url ? /* @__PURE__ */ jsx21("a", { href: ticket.url, target: "_blank", rel: "noreferrer noopener", children: /* @__PURE__ */ jsx21(Badge, { variant: "outline", children: ticket.id }) }) : /* @__PURE__ */ jsx21(Badge, { variant: "outline", children: ticket.id }) }, `ticket-${ticket.id}`))
1031
+ scenario.tags.length > 0 || (scenario.tickets?.length ?? 0) > 0 ? /* @__PURE__ */ jsxs14("ul", { "aria-label": "Tags and tickets", className: "flex flex-wrap gap-1.5", children: [
1032
+ scenario.tags.map((t) => /* @__PURE__ */ jsx22("li", { children: /* @__PURE__ */ jsx22(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
1033
+ (scenario.tickets ?? []).map((ticket) => /* @__PURE__ */ jsx22("li", { children: ticket.url ? /* @__PURE__ */ jsx22("a", { href: ticket.url, target: "_blank", rel: "noreferrer noopener", children: /* @__PURE__ */ jsx22(Badge, { variant: "outline", children: ticket.id }) }) : /* @__PURE__ */ jsx22(Badge, { variant: "outline", children: ticket.id }) }, `ticket-${ticket.id}`))
911
1034
  ] }) : null
912
1035
  ] }),
913
- /* @__PURE__ */ jsxs13(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
914
- scenario.errorMessage ? /* @__PURE__ */ jsx21(
1036
+ /* @__PURE__ */ jsxs14(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
1037
+ scenario.errorMessage ? /* @__PURE__ */ jsx22(
915
1038
  "pre",
916
1039
  {
917
1040
  role: "alert",
@@ -919,10 +1042,10 @@ function ReportScenario({ scenario, hideTitle = false }) {
919
1042
  children: scenario.errorMessage
920
1043
  }
921
1044
  ) : null,
922
- /* @__PURE__ */ jsx21(ReportSteps, { scenario }),
923
- scenario.docEntries.length > 0 ? /* @__PURE__ */ jsx21("div", { "data-es-docs": true, className: "mt-3", children: /* @__PURE__ */ jsx21(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
924
- /* @__PURE__ */ jsx21(ReportAttachments, { attachments: scenario.attachments }),
925
- /* @__PURE__ */ jsx21(ReportTrace, { spans: scenario.otelSpans })
1045
+ /* @__PURE__ */ jsx22(ReportSteps, { scenario }),
1046
+ scenario.docEntries.length > 0 ? /* @__PURE__ */ jsx22("div", { "data-es-docs": true, className: "mt-3", children: /* @__PURE__ */ jsx22(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
1047
+ /* @__PURE__ */ jsx22(ReportAttachments, { attachments: scenario.attachments }),
1048
+ /* @__PURE__ */ jsx22(ReportTrace, { spans: scenario.otelSpans })
926
1049
  ] })
927
1050
  ]
928
1051
  }
@@ -930,13 +1053,13 @@ function ReportScenario({ scenario, hideTitle = false }) {
930
1053
  }
931
1054
 
932
1055
  // src/components/ReportScenarioList.tsx
933
- import { Fragment as Fragment7, jsx as jsx22 } from "react/jsx-runtime";
1056
+ import { Fragment as Fragment8, jsx as jsx23 } from "react/jsx-runtime";
934
1057
  function ReportScenarioList({ feature }) {
935
- return /* @__PURE__ */ jsx22(Fragment7, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ jsx22(ReportScenario, { scenario }, scenario.id)) });
1058
+ return /* @__PURE__ */ jsx23(Fragment8, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ jsx23(ReportScenario, { scenario }, scenario.id)) });
936
1059
  }
937
1060
 
938
1061
  // src/components/ReportFeature.tsx
939
- import { jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
1062
+ import { jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
940
1063
  function ReportFeature({ feature }) {
941
1064
  const titleId = `${feature.id}-title`;
942
1065
  const bodyId = `${feature.id}-body`;
@@ -944,7 +1067,7 @@ function ReportFeature({ feature }) {
944
1067
  const skipped = s.skipped + s.pending;
945
1068
  const collapse = useCollapse();
946
1069
  const collapsed = collapse?.isCollapsed(feature.id) ?? false;
947
- return /* @__PURE__ */ jsxs14(
1070
+ return /* @__PURE__ */ jsxs15(
948
1071
  "section",
949
1072
  {
950
1073
  id: feature.id,
@@ -952,9 +1075,9 @@ function ReportFeature({ feature }) {
952
1075
  "aria-labelledby": titleId,
953
1076
  className: "overflow-hidden rounded-xl border border-border bg-card shadow-sm",
954
1077
  children: [
955
- /* @__PURE__ */ jsxs14("div", { className: "flex items-center justify-between gap-4 border-b border-border bg-muted/40 px-4 py-3", children: [
956
- /* @__PURE__ */ jsxs14("div", { className: "flex min-w-0 items-center gap-2", children: [
957
- collapse ? /* @__PURE__ */ jsx23(
1078
+ /* @__PURE__ */ jsxs15("div", { className: "flex items-center justify-between gap-4 border-b border-border bg-muted/40 px-4 py-3", children: [
1079
+ /* @__PURE__ */ jsxs15("div", { className: "flex min-w-0 items-center gap-2", children: [
1080
+ collapse ? /* @__PURE__ */ jsx24(
958
1081
  "button",
959
1082
  {
960
1083
  type: "button",
@@ -963,30 +1086,30 @@ function ReportFeature({ feature }) {
963
1086
  "aria-controls": bodyId,
964
1087
  "aria-label": `Toggle ${feature.title}`,
965
1088
  className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
966
- children: /* @__PURE__ */ jsx23("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1089
+ children: /* @__PURE__ */ jsx24("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
967
1090
  }
968
1091
  ) : null,
969
- /* @__PURE__ */ jsxs14("div", { className: "min-w-0", children: [
970
- /* @__PURE__ */ jsx23("h2", { id: titleId, className: "text-sm font-semibold tracking-tight text-foreground", children: feature.title }),
971
- /* @__PURE__ */ jsx23("p", { className: "mt-0.5 truncate font-mono text-xs text-muted-foreground", children: feature.sourceFile })
1092
+ /* @__PURE__ */ jsxs15("div", { className: "min-w-0", children: [
1093
+ /* @__PURE__ */ jsx24("h2", { id: titleId, className: "text-sm font-semibold tracking-tight text-foreground", children: feature.title }),
1094
+ /* @__PURE__ */ jsx24("p", { className: "mt-0.5 truncate font-mono text-xs text-muted-foreground", children: feature.sourceFile })
972
1095
  ] })
973
1096
  ] }),
974
- /* @__PURE__ */ jsxs14(
1097
+ /* @__PURE__ */ jsxs15(
975
1098
  "div",
976
1099
  {
977
1100
  role: "img",
978
1101
  className: "flex shrink-0 items-center gap-2.5 font-mono text-xs font-medium",
979
1102
  "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
980
1103
  children: [
981
- /* @__PURE__ */ jsxs14("span", { "aria-hidden": true, className: "text-pass", children: [
1104
+ /* @__PURE__ */ jsxs15("span", { "aria-hidden": true, className: "text-pass", children: [
982
1105
  "\u2713",
983
1106
  s.passed
984
1107
  ] }),
985
- /* @__PURE__ */ jsxs14("span", { "aria-hidden": true, className: "text-fail", children: [
1108
+ /* @__PURE__ */ jsxs15("span", { "aria-hidden": true, className: "text-fail", children: [
986
1109
  "\u2717",
987
1110
  s.failed
988
1111
  ] }),
989
- /* @__PURE__ */ jsxs14("span", { "aria-hidden": true, className: "text-skip", children: [
1112
+ /* @__PURE__ */ jsxs15("span", { "aria-hidden": true, className: "text-skip", children: [
990
1113
  "\u25CB",
991
1114
  skipped
992
1115
  ] })
@@ -994,24 +1117,24 @@ function ReportFeature({ feature }) {
994
1117
  }
995
1118
  )
996
1119
  ] }),
997
- /* @__PURE__ */ jsx23("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-2 p-3", children: /* @__PURE__ */ jsx23(ReportScenarioList, { feature }) })
1120
+ /* @__PURE__ */ jsx24("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-2 p-3", children: /* @__PURE__ */ jsx24(ReportScenarioList, { feature }) })
998
1121
  ]
999
1122
  }
1000
1123
  );
1001
1124
  }
1002
1125
 
1003
1126
  // src/components/ReportFeatureList.tsx
1004
- import { Fragment as Fragment8, jsx as jsx24 } from "react/jsx-runtime";
1127
+ import { Fragment as Fragment9, jsx as jsx25 } from "react/jsx-runtime";
1005
1128
  function ReportFeatureList() {
1006
1129
  const report = useReport();
1007
- return /* @__PURE__ */ jsx24(Fragment8, { children: report.features.map((feature) => /* @__PURE__ */ jsx24(ReportFeature, { feature }, feature.id)) });
1130
+ return /* @__PURE__ */ jsx25(Fragment9, { children: report.features.map((feature) => /* @__PURE__ */ jsx25(ReportFeature, { feature }, feature.id)) });
1008
1131
  }
1009
1132
 
1010
1133
  // src/components/ui/empty.tsx
1011
1134
  import { cva as cva2 } from "class-variance-authority";
1012
- import { jsx as jsx25 } from "react/jsx-runtime";
1135
+ import { jsx as jsx26 } from "react/jsx-runtime";
1013
1136
  function Empty({ className, ...props }) {
1014
- return /* @__PURE__ */ jsx25(
1137
+ return /* @__PURE__ */ jsx26(
1015
1138
  "div",
1016
1139
  {
1017
1140
  "data-slot": "empty",
@@ -1024,7 +1147,7 @@ function Empty({ className, ...props }) {
1024
1147
  );
1025
1148
  }
1026
1149
  function EmptyHeader({ className, ...props }) {
1027
- return /* @__PURE__ */ jsx25(
1150
+ return /* @__PURE__ */ jsx26(
1028
1151
  "div",
1029
1152
  {
1030
1153
  "data-slot": "empty-header",
@@ -1051,7 +1174,7 @@ var emptyMediaVariants = cva2(
1051
1174
  }
1052
1175
  );
1053
1176
  function EmptyTitle({ className, ...props }) {
1054
- return /* @__PURE__ */ jsx25(
1177
+ return /* @__PURE__ */ jsx26(
1055
1178
  "div",
1056
1179
  {
1057
1180
  "data-slot": "empty-title",
@@ -1062,16 +1185,16 @@ function EmptyTitle({ className, ...props }) {
1062
1185
  }
1063
1186
 
1064
1187
  // src/components/ReportEmpty.tsx
1065
- import { jsx as jsx26 } from "react/jsx-runtime";
1188
+ import { jsx as jsx27 } from "react/jsx-runtime";
1066
1189
  function ReportEmpty({ message }) {
1067
- return /* @__PURE__ */ jsx26(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ jsx26(EmptyHeader, { children: /* @__PURE__ */ jsx26(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1190
+ return /* @__PURE__ */ jsx27(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ jsx27(EmptyHeader, { children: /* @__PURE__ */ jsx27(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1068
1191
  }
1069
1192
 
1070
1193
  // src/components/ReportSummary.tsx
1071
- import { Fragment as Fragment9, jsx as jsx27, jsxs as jsxs15 } from "react/jsx-runtime";
1194
+ import { Fragment as Fragment10, jsx as jsx28, jsxs as jsxs16 } from "react/jsx-runtime";
1072
1195
  function ReportSummary({ className }) {
1073
1196
  const report = useReport();
1074
- return /* @__PURE__ */ jsx27(
1197
+ return /* @__PURE__ */ jsx28(
1075
1198
  ReportSummaryView,
1076
1199
  {
1077
1200
  summary: report.summary,
@@ -1081,32 +1204,32 @@ function ReportSummary({ className }) {
1081
1204
  );
1082
1205
  }
1083
1206
  function ReportSummaryView({ summary, className, ariaLabel }) {
1084
- return /* @__PURE__ */ jsxs15("p", { className: cn("text-sm text-muted-foreground", className), "aria-label": ariaLabel, children: [
1085
- /* @__PURE__ */ jsxs15("span", { children: [
1086
- /* @__PURE__ */ jsx27("strong", { className: "font-semibold text-foreground", children: summary.total }),
1207
+ return /* @__PURE__ */ jsxs16("p", { className: cn("text-sm text-muted-foreground", className), "aria-label": ariaLabel, children: [
1208
+ /* @__PURE__ */ jsxs16("span", { children: [
1209
+ /* @__PURE__ */ jsx28("strong", { className: "font-semibold text-foreground", children: summary.total }),
1087
1210
  " scenario",
1088
1211
  summary.total === 1 ? "" : "s"
1089
1212
  ] }),
1090
1213
  " \xB7 ",
1091
- /* @__PURE__ */ jsxs15("span", { "data-status": "passed", className: "text-pass", children: [
1214
+ /* @__PURE__ */ jsxs16("span", { "data-status": "passed", className: "text-pass", children: [
1092
1215
  summary.passed,
1093
1216
  " passed"
1094
1217
  ] }),
1095
1218
  " \xB7 ",
1096
- /* @__PURE__ */ jsxs15("span", { "data-status": "failed", className: "text-fail", children: [
1219
+ /* @__PURE__ */ jsxs16("span", { "data-status": "failed", className: "text-fail", children: [
1097
1220
  summary.failed,
1098
1221
  " failed"
1099
1222
  ] }),
1100
- summary.skipped > 0 ? /* @__PURE__ */ jsxs15(Fragment9, { children: [
1223
+ summary.skipped > 0 ? /* @__PURE__ */ jsxs16(Fragment10, { children: [
1101
1224
  " \xB7 ",
1102
- /* @__PURE__ */ jsxs15("span", { "data-status": "skipped", className: "text-skip", children: [
1225
+ /* @__PURE__ */ jsxs16("span", { "data-status": "skipped", className: "text-skip", children: [
1103
1226
  summary.skipped,
1104
1227
  " skipped"
1105
1228
  ] })
1106
1229
  ] }) : null,
1107
- summary.pending > 0 ? /* @__PURE__ */ jsxs15(Fragment9, { children: [
1230
+ summary.pending > 0 ? /* @__PURE__ */ jsxs16(Fragment10, { children: [
1108
1231
  " \xB7 ",
1109
- /* @__PURE__ */ jsxs15("span", { "data-status": "pending", className: "text-pend", children: [
1232
+ /* @__PURE__ */ jsxs16("span", { "data-status": "pending", className: "text-pend", children: [
1110
1233
  summary.pending,
1111
1234
  " pending"
1112
1235
  ] })
@@ -1114,69 +1237,135 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1114
1237
  ] });
1115
1238
  }
1116
1239
 
1240
+ // src/lib/provenance.ts
1241
+ function reportLastRunMs(report) {
1242
+ if (report.finishedAtMs && report.finishedAtMs > 0) return report.finishedAtMs;
1243
+ if (report.startedAtMs && report.startedAtMs > 0) return report.startedAtMs;
1244
+ return void 0;
1245
+ }
1246
+ function formatRelativeAge(thenMs, nowMs) {
1247
+ const deltaMs = Math.max(0, nowMs - thenMs);
1248
+ const minutes = Math.floor(deltaMs / 6e4);
1249
+ if (minutes < 1) return "just now";
1250
+ if (minutes < 60) return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
1251
+ const hours = Math.floor(minutes / 60);
1252
+ if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
1253
+ const days = Math.floor(hours / 24);
1254
+ return `${days} day${days === 1 ? "" : "s"} ago`;
1255
+ }
1256
+ function isReportStale(lastRunMs, staleAfterDays, nowMs) {
1257
+ if (lastRunMs === void 0 || staleAfterDays <= 0) return false;
1258
+ return nowMs - lastRunMs >= staleAfterDays * 864e5;
1259
+ }
1260
+ var CI_DISPLAY_NAMES = {
1261
+ github: "GitHub Actions",
1262
+ gitlab: "GitLab CI",
1263
+ circleci: "CircleCI",
1264
+ jenkins: "Jenkins",
1265
+ azure: "Azure DevOps",
1266
+ buildkite: "Buildkite",
1267
+ travis: "Travis CI",
1268
+ ci: "CI"
1269
+ };
1270
+ function ciDisplayName(name) {
1271
+ return CI_DISPLAY_NAMES[name] ?? name;
1272
+ }
1273
+ function commitUrl(ci, sha) {
1274
+ const commitSha = ci?.commitSha ?? sha;
1275
+ if (!ci?.url || !commitSha) return void 0;
1276
+ const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1277
+ if (github) return `${github[1]}/commit/${commitSha}`;
1278
+ const gitlab = ci.url.match(/^(https?:\/\/.+)\/-\/pipelines\//);
1279
+ if (gitlab) return `${gitlab[1]}/-/commit/${commitSha}`;
1280
+ return void 0;
1281
+ }
1282
+ function prUrl(ci) {
1283
+ if (!ci?.url || !ci.prNumber) return void 0;
1284
+ const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1285
+ if (github) return `${github[1]}/pull/${ci.prNumber}`;
1286
+ return void 0;
1287
+ }
1288
+
1117
1289
  // src/components/ReportMeta.tsx
1118
- import { formatDuration as formatDuration4 } from "executable-stories-core/utils/duration";
1119
- import { jsx as jsx28, jsxs as jsxs16 } from "react/jsx-runtime";
1290
+ import { jsx as jsx29, jsxs as jsxs17 } from "react/jsx-runtime";
1120
1291
  function ReportMeta() {
1121
1292
  const report = useReport();
1122
1293
  const items = [];
1294
+ const linkClass = "text-primary underline underline-offset-2";
1123
1295
  if (report.startedAtMs) {
1124
1296
  items.push({ label: "Started", value: new Date(report.startedAtMs).toLocaleString() });
1125
1297
  }
1126
1298
  if (report.durationMs) {
1127
- items.push({ label: "Duration", value: formatDuration4(report.durationMs) });
1299
+ items.push({ label: "Duration", value: formatDuration(report.durationMs) });
1128
1300
  }
1129
1301
  if (report.packageVersion) {
1130
1302
  items.push({ label: "Version", value: report.packageVersion });
1131
1303
  }
1132
- if (report.gitSha) {
1133
- items.push({ label: "Commit", value: report.gitSha.slice(0, 8) });
1304
+ if (report.ci?.branch) {
1305
+ items.push({ label: "Branch", value: report.ci.branch });
1306
+ }
1307
+ const sha = report.ci?.commitSha ?? report.gitSha;
1308
+ if (sha) {
1309
+ const url = commitUrl(report.ci, report.gitSha);
1310
+ const short = sha.slice(0, 8);
1311
+ items.push({
1312
+ label: "Commit",
1313
+ value: url ? /* @__PURE__ */ jsx29("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1314
+ });
1134
1315
  }
1135
1316
  if (report.ci) {
1136
1317
  items.push({
1137
1318
  label: "CI",
1138
- value: report.ci.url ? /* @__PURE__ */ jsxs16("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: "text-primary underline underline-offset-2", children: [
1139
- report.ci.name,
1319
+ value: report.ci.url ? /* @__PURE__ */ jsxs17("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1320
+ ciDisplayName(report.ci.name),
1140
1321
  report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
1141
- ] }) : `${report.ci.name}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
1322
+ ] }) : `${ciDisplayName(report.ci.name)}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
1323
+ });
1324
+ }
1325
+ if (report.ci?.prNumber) {
1326
+ const url = prUrl(report.ci);
1327
+ const label = `#${report.ci.prNumber}`;
1328
+ items.push({
1329
+ label: "PR",
1330
+ value: url ? /* @__PURE__ */ jsx29("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1142
1331
  });
1143
1332
  }
1144
1333
  if (items.length === 0) return null;
1145
- return /* @__PURE__ */ jsx28("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__ */ jsxs16("div", { className: "flex gap-1.5", children: [
1146
- /* @__PURE__ */ jsx28("dt", { className: "font-medium", children: item.label }),
1147
- /* @__PURE__ */ jsx28("dd", { className: "font-mono text-foreground", children: item.value })
1334
+ return /* @__PURE__ */ jsx29("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__ */ jsxs17("div", { className: "flex gap-1.5", children: [
1335
+ /* @__PURE__ */ jsx29("dt", { className: "font-medium", children: item.label }),
1336
+ /* @__PURE__ */ jsx29("dd", { className: "font-mono text-foreground", children: item.value })
1148
1337
  ] }, item.label)) });
1149
1338
  }
1150
1339
 
1151
1340
  // src/components/ReportSchemaError.tsx
1152
- import { jsx as jsx29, jsxs as jsxs17 } from "react/jsx-runtime";
1341
+ import { jsx as jsx30, jsxs as jsxs18 } from "react/jsx-runtime";
1153
1342
  function ReportSchemaError({ error }) {
1154
- return /* @__PURE__ */ jsxs17("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1155
- /* @__PURE__ */ jsx29("p", { children: /* @__PURE__ */ jsx29("strong", { children: "Report could not be displayed." }) }),
1156
- /* @__PURE__ */ jsx29("p", { children: error.message }),
1157
- error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs17("p", { children: [
1343
+ return /* @__PURE__ */ jsxs18("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1344
+ /* @__PURE__ */ jsx30("p", { children: /* @__PURE__ */ jsx30("strong", { children: "Report could not be displayed." }) }),
1345
+ /* @__PURE__ */ jsx30("p", { children: error.message }),
1346
+ error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs18("p", { children: [
1158
1347
  "The report bundle is newer than this version of ",
1159
- /* @__PURE__ */ jsx29("code", { children: "executable-stories-react" }),
1348
+ /* @__PURE__ */ jsx30("code", { children: "executable-stories-react" }),
1160
1349
  ". Upgrade the package, or regenerate the report with an older formatters CLI."
1161
1350
  ] }) : null,
1162
- error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs17("details", { children: [
1163
- /* @__PURE__ */ jsxs17("summary", { children: [
1351
+ error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs18("details", { children: [
1352
+ /* @__PURE__ */ jsxs18("summary", { children: [
1164
1353
  error.issues.length,
1165
1354
  " validation issue",
1166
1355
  error.issues.length === 1 ? "" : "s"
1167
1356
  ] }),
1168
- /* @__PURE__ */ jsx29("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1357
+ /* @__PURE__ */ jsx30("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1169
1358
  ] }) : null
1170
1359
  ] });
1171
1360
  }
1172
1361
 
1173
1362
  // src/components/ReportShell.tsx
1174
- import { Fragment as Fragment10, jsx as jsx30, jsxs as jsxs18 } from "react/jsx-runtime";
1363
+ import { Fragment as Fragment11, jsx as jsx31, jsxs as jsxs19 } from "react/jsx-runtime";
1175
1364
  function ReportTitleBlock({ title }) {
1176
- return /* @__PURE__ */ jsxs18(Fragment10, { children: [
1177
- /* @__PURE__ */ jsx30("h1", { children: title ?? "Story Report" }),
1178
- /* @__PURE__ */ jsx30(ReportSummary, {}),
1179
- /* @__PURE__ */ jsx30(ReportMeta, {})
1365
+ return /* @__PURE__ */ jsxs19(Fragment11, { children: [
1366
+ /* @__PURE__ */ jsx31("h1", { children: title ?? "Story Report" }),
1367
+ /* @__PURE__ */ jsx31(ReportSummary, {}),
1368
+ /* @__PURE__ */ jsx31(ReportMeta, {})
1180
1369
  ] });
1181
1370
  }
1182
1371
  function ReportErrorShell({
@@ -1185,7 +1374,7 @@ function ReportErrorShell({
1185
1374
  title,
1186
1375
  dataTheme
1187
1376
  }) {
1188
- return /* @__PURE__ */ jsx30("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ jsx30(ReportSchemaError, { error }) });
1377
+ return /* @__PURE__ */ jsx31("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ jsx31(ReportSchemaError, { error }) });
1189
1378
  }
1190
1379
 
1191
1380
  // src/interactive/ReportSearch.tsx
@@ -1193,7 +1382,7 @@ import {
1193
1382
  forwardRef,
1194
1383
  useId
1195
1384
  } from "react";
1196
- import { jsx as jsx31, jsxs as jsxs19 } from "react/jsx-runtime";
1385
+ import { jsx as jsx32, jsxs as jsxs20 } from "react/jsx-runtime";
1197
1386
  var ReportSearch = forwardRef(
1198
1387
  function ReportSearch2(props, ref) {
1199
1388
  const {
@@ -1215,9 +1404,9 @@ var ReportSearch = forwardRef(
1215
1404
  e.preventDefault();
1216
1405
  }
1217
1406
  }
1218
- return /* @__PURE__ */ jsxs19("div", { className: ["es-search", className].filter(Boolean).join(" "), children: [
1219
- /* @__PURE__ */ jsx31("label", { htmlFor: inputId, className: "es-search-label", children: "Search" }),
1220
- /* @__PURE__ */ jsx31(
1407
+ return /* @__PURE__ */ jsxs20("div", { className: ["es-search", className].filter(Boolean).join(" "), children: [
1408
+ /* @__PURE__ */ jsx32("label", { htmlFor: inputId, className: "es-search-label", children: "Search" }),
1409
+ /* @__PURE__ */ jsx32(
1221
1410
  "input",
1222
1411
  {
1223
1412
  ref,
@@ -1232,7 +1421,7 @@ var ReportSearch = forwardRef(
1232
1421
  "aria-keyshortcuts": "/"
1233
1422
  }
1234
1423
  ),
1235
- showCounts ? /* @__PURE__ */ jsx31("span", { className: "es-search-counts", "aria-live": "polite", children: value ? `${matchedCount} of ${totalCount}` : `${totalCount} total` }) : null
1424
+ showCounts ? /* @__PURE__ */ jsx32("span", { className: "es-search-counts", "aria-live": "polite", children: value ? `${matchedCount} of ${totalCount}` : `${totalCount} total` }) : null
1236
1425
  ] });
1237
1426
  }
1238
1427
  );
@@ -1261,7 +1450,7 @@ function scrollToScenarioId(scenarioId, options = {}) {
1261
1450
  }
1262
1451
 
1263
1452
  // src/interactive/ReportFailureBanner.tsx
1264
- import { jsx as jsx32, jsxs as jsxs20 } from "react/jsx-runtime";
1453
+ import { jsx as jsx33, jsxs as jsxs21 } from "react/jsx-runtime";
1265
1454
  function ReportFailureBanner({ failures }) {
1266
1455
  const first = failures[0];
1267
1456
  const jumpToFirst = useCallback2(() => {
@@ -1269,7 +1458,7 @@ function ReportFailureBanner({ failures }) {
1269
1458
  scrollToScenarioId(first.scenarioId);
1270
1459
  }, [first]);
1271
1460
  if (failures.length === 0) return null;
1272
- return /* @__PURE__ */ jsxs20(
1461
+ return /* @__PURE__ */ jsxs21(
1273
1462
  "div",
1274
1463
  {
1275
1464
  className: "es-failure-banner",
@@ -1277,13 +1466,13 @@ function ReportFailureBanner({ failures }) {
1277
1466
  "aria-live": "polite",
1278
1467
  "aria-label": "Failure summary",
1279
1468
  children: [
1280
- /* @__PURE__ */ jsxs20("span", { className: "es-failure-banner-text", children: [
1281
- /* @__PURE__ */ jsx32("strong", { children: failures.length }),
1469
+ /* @__PURE__ */ jsxs21("span", { className: "es-failure-banner-text", children: [
1470
+ /* @__PURE__ */ jsx33("strong", { children: failures.length }),
1282
1471
  " ",
1283
1472
  "failure",
1284
1473
  failures.length === 1 ? "" : "s"
1285
1474
  ] }),
1286
- /* @__PURE__ */ jsx32(
1475
+ /* @__PURE__ */ jsx33(
1287
1476
  "button",
1288
1477
  {
1289
1478
  type: "button",
@@ -1298,9 +1487,101 @@ function ReportFailureBanner({ failures }) {
1298
1487
  );
1299
1488
  }
1300
1489
 
1490
+ // src/interactive/ReportFreshness.tsx
1491
+ import { useEffect, useState as useState2 } from "react";
1492
+ import { Fragment as Fragment12, jsx as jsx34, jsxs as jsxs22 } from "react/jsx-runtime";
1493
+ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
1494
+ const [now, setNow] = useState2(nowMs);
1495
+ useEffect(() => {
1496
+ if (nowMs === void 0) setNow(Date.now());
1497
+ }, [nowMs]);
1498
+ if (lastRunMs === void 0 || now === void 0) return null;
1499
+ const age = formatRelativeAge(lastRunMs, now);
1500
+ const runLink = ciUrl ? /* @__PURE__ */ jsx34(
1501
+ "a",
1502
+ {
1503
+ href: ciUrl,
1504
+ target: "_blank",
1505
+ rel: "noreferrer noopener",
1506
+ className: "underline underline-offset-2",
1507
+ children: "View CI run"
1508
+ }
1509
+ ) : null;
1510
+ if (isReportStale(lastRunMs, staleAfterDays, now)) {
1511
+ return /* @__PURE__ */ jsxs22("div", { className: "es-stale-banner", role: "status", "aria-label": "Report freshness", children: [
1512
+ /* @__PURE__ */ jsxs22("span", { children: [
1513
+ /* @__PURE__ */ jsxs22("strong", { children: [
1514
+ "Last verified ",
1515
+ age,
1516
+ "."
1517
+ ] }),
1518
+ " This report may no longer match the current behavior \u2014 look for a newer run."
1519
+ ] }),
1520
+ runLink
1521
+ ] });
1522
+ }
1523
+ return /* @__PURE__ */ jsxs22("p", { className: "es-freshness", "aria-label": "Report freshness", children: [
1524
+ "Verified ",
1525
+ age,
1526
+ runLink ? /* @__PURE__ */ jsxs22(Fragment12, { children: [
1527
+ " \xB7 ",
1528
+ runLink
1529
+ ] }) : null
1530
+ ] });
1531
+ }
1532
+
1533
+ // src/interactive/ReportLastRunDelta.tsx
1534
+ import { Fragment as Fragment13, jsx as jsx35, jsxs as jsxs23 } from "react/jsx-runtime";
1535
+ var MAX_LINKS = 5;
1536
+ function ScenarioLinks({ scenarios, tone }) {
1537
+ return /* @__PURE__ */ jsxs23(Fragment13, { children: [
1538
+ scenarios.slice(0, MAX_LINKS).map((s, i) => /* @__PURE__ */ jsxs23("span", { children: [
1539
+ i > 0 ? ", " : "",
1540
+ /* @__PURE__ */ jsx35("a", { href: `#${s.id}`, className: cn("underline underline-offset-2 hover:no-underline", tone), children: s.title })
1541
+ ] }, s.id)),
1542
+ scenarios.length > MAX_LINKS ? /* @__PURE__ */ jsxs23("span", { children: [
1543
+ " and ",
1544
+ scenarios.length - MAX_LINKS,
1545
+ " more"
1546
+ ] }) : null
1547
+ ] });
1548
+ }
1549
+ function ReportLastRunDelta({
1550
+ history: history2,
1551
+ report
1552
+ }) {
1553
+ if (!history2) return null;
1554
+ const scenarios = report.features.flatMap((f) => f.scenarios.map((s) => ({ id: s.id, title: s.title })));
1555
+ const delta = diffSinceLastRun(history2, scenarios);
1556
+ if (!delta) return null;
1557
+ const { newlyFailing, newlyPassing, added } = delta;
1558
+ if (newlyFailing.length === 0 && newlyPassing.length === 0 && added.length === 0) {
1559
+ return /* @__PURE__ */ jsx35("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: "Since last run: no behavior changes." });
1560
+ }
1561
+ return /* @__PURE__ */ jsxs23("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: [
1562
+ /* @__PURE__ */ jsx35("span", { className: "font-medium text-foreground", children: "Since last run:" }),
1563
+ newlyFailing.length > 0 ? /* @__PURE__ */ jsxs23("span", { className: "ml-2 text-fail", children: [
1564
+ newlyFailing.length,
1565
+ " newly failing (",
1566
+ /* @__PURE__ */ jsx35(ScenarioLinks, { scenarios: newlyFailing, tone: "text-fail" }),
1567
+ ")"
1568
+ ] }) : null,
1569
+ newlyPassing.length > 0 ? /* @__PURE__ */ jsxs23("span", { className: "ml-2 text-pass", children: [
1570
+ newlyPassing.length,
1571
+ " fixed (",
1572
+ /* @__PURE__ */ jsx35(ScenarioLinks, { scenarios: newlyPassing, tone: "text-pass" }),
1573
+ ")"
1574
+ ] }) : null,
1575
+ added.length > 0 ? /* @__PURE__ */ jsxs23("span", { className: "ml-2", children: [
1576
+ added.length,
1577
+ " new"
1578
+ ] }) : null
1579
+ ] });
1580
+ }
1581
+
1301
1582
  // src/interactive/ReportShortcutsHelp.tsx
1302
- import { useEffect, useRef } from "react";
1303
- import { jsx as jsx33, jsxs as jsxs21 } from "react/jsx-runtime";
1583
+ import { useEffect as useEffect2, useRef } from "react";
1584
+ import { jsx as jsx36, jsxs as jsxs24 } from "react/jsx-runtime";
1304
1585
  var SHORTCUTS = [
1305
1586
  { keys: "/", description: "Focus search" },
1306
1587
  { keys: "f", description: "Jump to next failure" },
@@ -1310,13 +1591,13 @@ var SHORTCUTS = [
1310
1591
  ];
1311
1592
  function ReportShortcutsHelp({ open, onClose }) {
1312
1593
  const dialogRef = useRef(null);
1313
- useEffect(() => {
1594
+ useEffect2(() => {
1314
1595
  const dialog = dialogRef.current;
1315
1596
  if (!dialog) return;
1316
1597
  if (open && !dialog.open) dialog.showModal();
1317
1598
  else if (!open && dialog.open) dialog.close();
1318
1599
  }, [open]);
1319
- return /* @__PURE__ */ jsxs21(
1600
+ return /* @__PURE__ */ jsxs24(
1320
1601
  "dialog",
1321
1602
  {
1322
1603
  ref: dialogRef,
@@ -1324,19 +1605,19 @@ function ReportShortcutsHelp({ open, onClose }) {
1324
1605
  "aria-label": "Keyboard shortcuts",
1325
1606
  onClose,
1326
1607
  children: [
1327
- /* @__PURE__ */ jsx33("h2", { children: "Keyboard shortcuts" }),
1328
- /* @__PURE__ */ jsx33("dl", { children: SHORTCUTS.map((s) => /* @__PURE__ */ jsxs21("div", { children: [
1329
- /* @__PURE__ */ jsx33("dt", { children: /* @__PURE__ */ jsx33("kbd", { children: s.keys }) }),
1330
- /* @__PURE__ */ jsx33("dd", { children: s.description })
1608
+ /* @__PURE__ */ jsx36("h2", { children: "Keyboard shortcuts" }),
1609
+ /* @__PURE__ */ jsx36("dl", { children: SHORTCUTS.map((s) => /* @__PURE__ */ jsxs24("div", { children: [
1610
+ /* @__PURE__ */ jsx36("dt", { children: /* @__PURE__ */ jsx36("kbd", { children: s.keys }) }),
1611
+ /* @__PURE__ */ jsx36("dd", { children: s.description })
1331
1612
  ] }, s.keys)) }),
1332
- /* @__PURE__ */ jsx33("form", { method: "dialog", children: /* @__PURE__ */ jsx33("button", { type: "submit", className: "es-shortcuts-close", children: "Close" }) })
1613
+ /* @__PURE__ */ jsx36("form", { method: "dialog", children: /* @__PURE__ */ jsx36("button", { type: "submit", className: "es-shortcuts-close", children: "Close" }) })
1333
1614
  ]
1334
1615
  }
1335
1616
  );
1336
1617
  }
1337
1618
 
1338
1619
  // src/interactive/use-keyboard-shortcuts.ts
1339
- import { useEffect as useEffect2, useRef as useRef2 } from "react";
1620
+ import { useEffect as useEffect3, useRef as useRef2 } from "react";
1340
1621
  function isEditableTarget(target) {
1341
1622
  if (!(target instanceof HTMLElement)) return false;
1342
1623
  const tag = target.tagName;
@@ -1347,7 +1628,7 @@ function isEditableTarget(target) {
1347
1628
  function useKeyboardShortcuts(handlers) {
1348
1629
  const ref = useRef2(handlers);
1349
1630
  ref.current = handlers;
1350
- useEffect2(() => {
1631
+ useEffect3(() => {
1351
1632
  function onKeyDown(e) {
1352
1633
  const h = ref.current;
1353
1634
  if (e.key === "Escape") {
@@ -1391,9 +1672,9 @@ function useKeyboardShortcuts(handlers) {
1391
1672
  }
1392
1673
 
1393
1674
  // src/interactive/use-deep-link-scroll.ts
1394
- import { useEffect as useEffect3 } from "react";
1675
+ import { useEffect as useEffect4 } from "react";
1395
1676
  function useDeepLinkScroll() {
1396
- useEffect3(() => {
1677
+ useEffect4(() => {
1397
1678
  function scrollToHash() {
1398
1679
  const hash = window.location.hash.replace(/^#/, "");
1399
1680
  if (!hash) return;
@@ -1502,7 +1783,7 @@ function listFailures(report) {
1502
1783
  }
1503
1784
 
1504
1785
  // src/interactive/ReportFilters.tsx
1505
- import { jsx as jsx34, jsxs as jsxs22 } from "react/jsx-runtime";
1786
+ import { jsx as jsx37, jsxs as jsxs25 } from "react/jsx-runtime";
1506
1787
  function ReportFilters({
1507
1788
  statuses,
1508
1789
  status,
@@ -1512,10 +1793,10 @@ function ReportFilters({
1512
1793
  onToggleTag
1513
1794
  }) {
1514
1795
  if (statuses.length === 0 && tags.length === 0) return null;
1515
- return /* @__PURE__ */ jsxs22("div", { className: "flex flex-col gap-2", children: [
1516
- statuses.length > 0 ? /* @__PURE__ */ jsx34("div", { role: "group", "aria-label": "Filter by status", className: "flex flex-wrap gap-1", children: statuses.map((opt) => {
1796
+ return /* @__PURE__ */ jsxs25("div", { className: "flex flex-col gap-2", children: [
1797
+ statuses.length > 0 ? /* @__PURE__ */ jsx37("div", { role: "group", "aria-label": "Filter by status", className: "flex flex-wrap gap-1", children: statuses.map((opt) => {
1517
1798
  const active = status === opt.key;
1518
- return /* @__PURE__ */ jsxs22(
1799
+ return /* @__PURE__ */ jsxs25(
1519
1800
  "button",
1520
1801
  {
1521
1802
  type: "button",
@@ -1527,22 +1808,22 @@ function ReportFilters({
1527
1808
  ),
1528
1809
  children: [
1529
1810
  opt.label,
1530
- /* @__PURE__ */ jsx34("span", { className: "ml-1 font-mono opacity-70", children: opt.count })
1811
+ /* @__PURE__ */ jsx37("span", { className: "ml-1 font-mono opacity-70", children: opt.count })
1531
1812
  ]
1532
1813
  },
1533
1814
  opt.key
1534
1815
  );
1535
1816
  }) }) : null,
1536
- tags.length > 0 ? /* @__PURE__ */ jsx34("ul", { "aria-label": "Filter by tag", className: "flex flex-wrap gap-1.5", children: tags.map((tag) => {
1817
+ tags.length > 0 ? /* @__PURE__ */ jsx37("ul", { "aria-label": "Filter by tag", className: "flex flex-wrap gap-1.5", children: tags.map((tag) => {
1537
1818
  const active = activeTags.includes(tag);
1538
- return /* @__PURE__ */ jsx34("li", { children: /* @__PURE__ */ jsx34(
1819
+ return /* @__PURE__ */ jsx37("li", { children: /* @__PURE__ */ jsx37(
1539
1820
  "button",
1540
1821
  {
1541
1822
  type: "button",
1542
1823
  "aria-pressed": active,
1543
1824
  onClick: () => onToggleTag(tag),
1544
1825
  className: "cursor-pointer",
1545
- children: /* @__PURE__ */ jsx34(Badge, { variant: active ? "default" : "tag", children: tag })
1826
+ children: /* @__PURE__ */ jsx37(Badge, { variant: active ? "default" : "tag", children: tag })
1546
1827
  }
1547
1828
  ) }, tag);
1548
1829
  }) }) : null
@@ -1550,8 +1831,8 @@ function ReportFilters({
1550
1831
  }
1551
1832
 
1552
1833
  // src/interactive/ReportToc.tsx
1553
- import { useEffect as useEffect4, useMemo as useMemo2, useState as useState2 } from "react";
1554
- import { jsx as jsx35, jsxs as jsxs23 } from "react/jsx-runtime";
1834
+ import { useEffect as useEffect5, useMemo as useMemo2, useState as useState3 } from "react";
1835
+ import { jsx as jsx38, jsxs as jsxs26 } from "react/jsx-runtime";
1555
1836
  var DOT = {
1556
1837
  passed: "text-pass",
1557
1838
  failed: "text-fail",
@@ -1560,12 +1841,12 @@ var DOT = {
1560
1841
  };
1561
1842
  function ReportToc() {
1562
1843
  const report = useReport();
1563
- const [activeId, setActiveId] = useState2(null);
1844
+ const [activeId, setActiveId] = useState3(null);
1564
1845
  const scenarioIds = useMemo2(
1565
1846
  () => report.features.flatMap((f) => f.scenarios.map((s) => s.id)),
1566
1847
  [report]
1567
1848
  );
1568
- useEffect4(() => {
1849
+ useEffect5(() => {
1569
1850
  if (typeof IntersectionObserver === "undefined") return;
1570
1851
  const els = scenarioIds.map((id) => document.getElementById(id)).filter((el) => el !== null);
1571
1852
  if (els.length === 0) return;
@@ -1580,16 +1861,16 @@ function ReportToc() {
1580
1861
  return () => observer.disconnect();
1581
1862
  }, [scenarioIds]);
1582
1863
  if (report.features.length === 0) return null;
1583
- return /* @__PURE__ */ jsx35(
1864
+ return /* @__PURE__ */ jsx38(
1584
1865
  "nav",
1585
1866
  {
1586
1867
  "aria-label": "Table of contents",
1587
1868
  className: "sticky top-4 hidden h-fit w-56 shrink-0 self-start text-xs lg:block",
1588
- children: /* @__PURE__ */ jsx35("ul", { className: "flex flex-col gap-3", children: report.features.map((feature) => /* @__PURE__ */ jsxs23("li", { children: [
1589
- /* @__PURE__ */ jsx35("p", { className: "mb-1 truncate font-semibold text-foreground", children: feature.title }),
1590
- /* @__PURE__ */ jsx35("ul", { className: "flex flex-col", children: feature.scenarios.map((scenario) => {
1869
+ children: /* @__PURE__ */ jsx38("ul", { className: "flex flex-col gap-3", children: report.features.map((feature) => /* @__PURE__ */ jsxs26("li", { children: [
1870
+ /* @__PURE__ */ jsx38("p", { className: "mb-1 truncate font-semibold text-foreground", children: feature.title }),
1871
+ /* @__PURE__ */ jsx38("ul", { className: "flex flex-col", children: feature.scenarios.map((scenario) => {
1591
1872
  const active = activeId === scenario.id;
1592
- return /* @__PURE__ */ jsx35("li", { children: /* @__PURE__ */ jsxs23(
1873
+ return /* @__PURE__ */ jsx38("li", { children: /* @__PURE__ */ jsxs26(
1593
1874
  "a",
1594
1875
  {
1595
1876
  href: `#${scenario.id}`,
@@ -1603,8 +1884,8 @@ function ReportToc() {
1603
1884
  active ? "border-primary font-medium text-foreground" : "border-border text-muted-foreground"
1604
1885
  ),
1605
1886
  children: [
1606
- /* @__PURE__ */ jsx35("span", { "aria-hidden": true, className: cn("shrink-0", DOT[scenario.status]), children: "\u2022" }),
1607
- /* @__PURE__ */ jsx35("span", { className: "truncate", children: scenario.title })
1887
+ /* @__PURE__ */ jsx38("span", { "aria-hidden": true, className: cn("shrink-0", DOT[scenario.status]), children: "\u2022" }),
1888
+ /* @__PURE__ */ jsx38("span", { className: "truncate", children: scenario.title })
1608
1889
  ]
1609
1890
  }
1610
1891
  ) }, scenario.id);
@@ -1615,7 +1896,7 @@ function ReportToc() {
1615
1896
  }
1616
1897
 
1617
1898
  // src/interactive/use-theme.ts
1618
- import { useCallback as useCallback3, useEffect as useEffect5, useState as useState3 } from "react";
1899
+ import { useCallback as useCallback3, useEffect as useEffect6, useState as useState4 } from "react";
1619
1900
  var KEY = "es-theme";
1620
1901
  function applyTheme(theme) {
1621
1902
  if (typeof document !== "undefined") {
@@ -1636,8 +1917,8 @@ function initialTheme() {
1636
1917
  return "light";
1637
1918
  }
1638
1919
  function useTheme() {
1639
- const [theme, setTheme] = useState3("light");
1640
- useEffect5(() => {
1920
+ const [theme, setTheme] = useState4("light");
1921
+ useEffect6(() => {
1641
1922
  const t = initialTheme();
1642
1923
  setTheme(t);
1643
1924
  applyTheme(t);
@@ -1657,14 +1938,14 @@ function useTheme() {
1657
1938
  }
1658
1939
 
1659
1940
  // src/interactive/ReportInteractive.tsx
1660
- import { jsx as jsx36, jsxs as jsxs24 } from "react/jsx-runtime";
1941
+ import { jsx as jsx39, jsxs as jsxs27 } from "react/jsx-runtime";
1661
1942
  function ReportInteractive(props) {
1662
1943
  const { report, className, title, dataTheme } = props;
1663
1944
  const result = unwrapReport(report);
1664
1945
  if (!result.ok) {
1665
- return /* @__PURE__ */ jsx36(ReportErrorShell, { error: result.error, className, title, dataTheme });
1946
+ return /* @__PURE__ */ jsx39(ReportErrorShell, { error: result.error, className, title, dataTheme });
1666
1947
  }
1667
- return /* @__PURE__ */ jsx36(ReportInteractiveView, { ...props, report: result.data });
1948
+ return /* @__PURE__ */ jsx39(ReportInteractiveView, { ...props, report: result.data });
1668
1949
  }
1669
1950
  function ReportInteractiveView({
1670
1951
  report,
@@ -1673,13 +1954,15 @@ function ReportInteractiveView({
1673
1954
  className,
1674
1955
  title,
1675
1956
  dataTheme,
1676
- hideHeader = false
1957
+ hideHeader = false,
1958
+ staleAfterDays = 7,
1959
+ scenarioHistory
1677
1960
  }) {
1678
- const [query, setQuery] = useState4("");
1679
- const [statusFilter, setStatusFilter] = useState4("all");
1680
- const [activeTags, setActiveTags] = useState4([]);
1681
- const [detail, setDetail] = useState4("full");
1682
- const [helpOpen, setHelpOpen] = useState4(false);
1961
+ const [query, setQuery] = useState5("");
1962
+ const [statusFilter, setStatusFilter] = useState5("all");
1963
+ const [activeTags, setActiveTags] = useState5([]);
1964
+ const [detail, setDetail] = useState5("full");
1965
+ const [helpOpen, setHelpOpen] = useState5(false);
1683
1966
  const { theme, toggle: toggleTheme } = useTheme();
1684
1967
  const searchRef = useRef3(null);
1685
1968
  const deferredQuery = useDeferredValue(query);
@@ -1705,7 +1988,7 @@ function ReportInteractiveView({
1705
1988
  (tag) => setActiveTags((prev) => prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag]),
1706
1989
  []
1707
1990
  );
1708
- const [toast, setToast] = useState4(null);
1991
+ const [toast, setToast] = useState5(null);
1709
1992
  const toastTimer = useRef3(null);
1710
1993
  const showToast = useCallback4((msg) => {
1711
1994
  setToast(msg);
@@ -1767,13 +2050,13 @@ function ReportInteractiveView({
1767
2050
  const hasContent = filtered.features.length > 0;
1768
2051
  const totalScenarios = report.summary.total;
1769
2052
  const matchedScenarios = filtered.summary.total;
1770
- return /* @__PURE__ */ jsx36(CollapseProvider, { value: collapse.api, children: /* @__PURE__ */ jsx36(ScenarioActionsProvider, { value: scenarioActions, children: /* @__PURE__ */ jsx36(
2053
+ return /* @__PURE__ */ jsx39(CollapseProvider, { value: collapse.api, children: /* @__PURE__ */ jsx39(ScenarioActionsProvider, { value: scenarioActions, children: /* @__PURE__ */ jsx39(ScenarioHistoryProvider, { value: scenarioHistory ?? null, children: /* @__PURE__ */ jsx39(
1771
2054
  ReportRoot,
1772
2055
  {
1773
2056
  report: filtered,
1774
2057
  customRenderers,
1775
2058
  renderers,
1776
- children: /* @__PURE__ */ jsxs24(
2059
+ children: /* @__PURE__ */ jsxs27(
1777
2060
  "main",
1778
2061
  {
1779
2062
  className: cn("es-report", "es-report-interactive", className),
@@ -1782,10 +2065,19 @@ function ReportInteractiveView({
1782
2065
  "data-theme": dataTheme,
1783
2066
  "data-detail-level": detail,
1784
2067
  children: [
1785
- /* @__PURE__ */ jsxs24("header", { className: "es-report-header", children: [
1786
- hideHeader ? null : /* @__PURE__ */ jsx36(ReportTitleBlock, { title }),
1787
- /* @__PURE__ */ jsxs24("div", { className: "flex flex-wrap items-center gap-2", children: [
1788
- /* @__PURE__ */ jsx36(
2068
+ /* @__PURE__ */ jsxs27("header", { className: "es-report-header", children: [
2069
+ hideHeader ? null : /* @__PURE__ */ jsx39(ReportTitleBlock, { title }),
2070
+ /* @__PURE__ */ jsx39(
2071
+ ReportFreshness,
2072
+ {
2073
+ lastRunMs: reportLastRunMs(report),
2074
+ ciUrl: report.ci?.url,
2075
+ staleAfterDays
2076
+ }
2077
+ ),
2078
+ /* @__PURE__ */ jsx39(ReportLastRunDelta, { history: scenarioHistory, report }),
2079
+ /* @__PURE__ */ jsxs27("div", { className: "flex flex-wrap items-center gap-2", children: [
2080
+ /* @__PURE__ */ jsx39(
1789
2081
  ReportSearch,
1790
2082
  {
1791
2083
  ref: searchRef,
@@ -1795,8 +2087,8 @@ function ReportInteractiveView({
1795
2087
  totalCount: totalScenarios
1796
2088
  }
1797
2089
  ),
1798
- /* @__PURE__ */ jsxs24("div", { className: "flex shrink-0 gap-1", children: [
1799
- /* @__PURE__ */ jsx36(
2090
+ /* @__PURE__ */ jsxs27("div", { className: "flex shrink-0 gap-1", children: [
2091
+ /* @__PURE__ */ jsx39(
1800
2092
  "button",
1801
2093
  {
1802
2094
  type: "button",
@@ -1806,7 +2098,7 @@ function ReportInteractiveView({
1806
2098
  children: "Expand all"
1807
2099
  }
1808
2100
  ),
1809
- /* @__PURE__ */ jsx36(
2101
+ /* @__PURE__ */ jsx39(
1810
2102
  "button",
1811
2103
  {
1812
2104
  type: "button",
@@ -1816,7 +2108,7 @@ function ReportInteractiveView({
1816
2108
  children: "Collapse all"
1817
2109
  }
1818
2110
  ),
1819
- /* @__PURE__ */ jsx36(
2111
+ /* @__PURE__ */ jsx39(
1820
2112
  "button",
1821
2113
  {
1822
2114
  type: "button",
@@ -1826,7 +2118,7 @@ function ReportInteractiveView({
1826
2118
  children: detail === "full" ? "Hide docs" : "Show docs"
1827
2119
  }
1828
2120
  ),
1829
- /* @__PURE__ */ jsx36(
2121
+ /* @__PURE__ */ jsx39(
1830
2122
  "button",
1831
2123
  {
1832
2124
  type: "button",
@@ -1838,7 +2130,7 @@ function ReportInteractiveView({
1838
2130
  )
1839
2131
  ] })
1840
2132
  ] }),
1841
- /* @__PURE__ */ jsx36(
2133
+ /* @__PURE__ */ jsx39(
1842
2134
  ReportFilters,
1843
2135
  {
1844
2136
  statuses: statusOptions,
@@ -1850,12 +2142,12 @@ function ReportInteractiveView({
1850
2142
  }
1851
2143
  )
1852
2144
  ] }),
1853
- /* @__PURE__ */ jsx36(ReportFailureBanner, { failures }),
1854
- hasContent ? /* @__PURE__ */ jsxs24("div", { className: "flex gap-6", children: [
1855
- /* @__PURE__ */ jsx36(ReportToc, {}),
1856
- /* @__PURE__ */ jsx36("div", { className: "flex min-w-0 flex-1 flex-col gap-4", children: /* @__PURE__ */ jsx36(ReportFeatureList, {}) })
1857
- ] }) : /* @__PURE__ */ jsx36(ReportEmpty, { message: query ? "No scenarios match the search." : void 0 }),
1858
- /* @__PURE__ */ jsx36(
2145
+ /* @__PURE__ */ jsx39(ReportFailureBanner, { failures }),
2146
+ hasContent ? /* @__PURE__ */ jsxs27("div", { className: "flex gap-6", children: [
2147
+ /* @__PURE__ */ jsx39(ReportToc, {}),
2148
+ /* @__PURE__ */ jsx39("div", { className: "flex min-w-0 flex-1 flex-col gap-4", children: /* @__PURE__ */ jsx39(ReportFeatureList, {}) })
2149
+ ] }) : /* @__PURE__ */ jsx39(ReportEmpty, { message: query ? "No scenarios match the search." : void 0 }),
2150
+ /* @__PURE__ */ jsx39(
1859
2151
  "button",
1860
2152
  {
1861
2153
  type: "button",
@@ -1866,8 +2158,8 @@ function ReportInteractiveView({
1866
2158
  children: "?"
1867
2159
  }
1868
2160
  ),
1869
- /* @__PURE__ */ jsx36(ReportShortcutsHelp, { open: helpOpen, onClose: () => setHelpOpen(false) }),
1870
- toast ? /* @__PURE__ */ jsx36(
2161
+ /* @__PURE__ */ jsx39(ReportShortcutsHelp, { open: helpOpen, onClose: () => setHelpOpen(false) }),
2162
+ toast ? /* @__PURE__ */ jsx39(
1871
2163
  "div",
1872
2164
  {
1873
2165
  role: "status",
@@ -1880,18 +2172,18 @@ function ReportInteractiveView({
1880
2172
  }
1881
2173
  )
1882
2174
  }
1883
- ) }) });
2175
+ ) }) }) });
1884
2176
  }
1885
2177
 
1886
2178
  // src/interactive/ReportInteractiveIsland.tsx
1887
2179
  import { useMemo as useMemo4 } from "react";
1888
2180
 
1889
2181
  // src/interactive/island-renderers.tsx
1890
- import { createElement, useEffect as useEffect7, useState as useState6 } from "react";
2182
+ import { createElement, useEffect as useEffect8, useState as useState7 } from "react";
1891
2183
 
1892
2184
  // src/components/doc/MermaidView.tsx
1893
- import { useEffect as useEffect6, useRef as useRef4, useState as useState5 } from "react";
1894
- import { jsx as jsx37, jsxs as jsxs25 } from "react/jsx-runtime";
2185
+ import { useEffect as useEffect7, useRef as useRef4, useState as useState6 } from "react";
2186
+ import { jsx as jsx40, jsxs as jsxs28 } from "react/jsx-runtime";
1895
2187
  function prefersDark(el) {
1896
2188
  const themed = el?.closest("[data-theme]");
1897
2189
  if (themed?.dataset.theme === "dark") return true;
@@ -1900,8 +2192,8 @@ function prefersDark(el) {
1900
2192
  }
1901
2193
  function MermaidView({ entry, load }) {
1902
2194
  const ref = useRef4(null);
1903
- const [svg, setSvg] = useState5(null);
1904
- useEffect6(() => {
2195
+ const [svg, setSvg] = useState6(null);
2196
+ useEffect7(() => {
1905
2197
  let cancelled = false;
1906
2198
  const draw = async () => {
1907
2199
  try {
@@ -1928,9 +2220,9 @@ function MermaidView({ entry, load }) {
1928
2220
  };
1929
2221
  }, [entry.code, load]);
1930
2222
  if (svg) {
1931
- return /* @__PURE__ */ jsxs25("figure", { ref, className: "my-3", children: [
1932
- entry.title ? /* @__PURE__ */ jsx37("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
1933
- /* @__PURE__ */ jsx37(
2223
+ return /* @__PURE__ */ jsxs28("figure", { ref, className: "my-3", children: [
2224
+ entry.title ? /* @__PURE__ */ jsx40("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
2225
+ /* @__PURE__ */ jsx40(
1934
2226
  "div",
1935
2227
  {
1936
2228
  role: "img",
@@ -1941,11 +2233,11 @@ function MermaidView({ entry, load }) {
1941
2233
  )
1942
2234
  ] });
1943
2235
  }
1944
- return /* @__PURE__ */ jsx37(MermaidSource, { entry, ref });
2236
+ return /* @__PURE__ */ jsx40(MermaidSource, { entry, ref });
1945
2237
  }
1946
2238
 
1947
2239
  // src/interactive/island-renderers.tsx
1948
- import { jsx as jsx38 } from "react/jsx-runtime";
2240
+ import { jsx as jsx41 } from "react/jsx-runtime";
1949
2241
  var HLJS_ESM = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/es/highlight.min.js";
1950
2242
  var MERMAID_ESM = "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
1951
2243
  function cachedCdnModule(url) {
@@ -1962,8 +2254,8 @@ function cachedCdnModule(url) {
1962
2254
  var loadMermaidFromCdn = cachedCdnModule(MERMAID_ESM);
1963
2255
  var loadHljs = cachedCdnModule(HLJS_ESM);
1964
2256
  function CdnHighlightedCode({ entry }) {
1965
- const [html, setHtml] = useState6(null);
1966
- useEffect7(() => {
2257
+ const [html, setHtml] = useState7(null);
2258
+ useEffect8(() => {
1967
2259
  let cancelled = false;
1968
2260
  void (async () => {
1969
2261
  try {
@@ -1979,13 +2271,13 @@ function CdnHighlightedCode({ entry }) {
1979
2271
  cancelled = true;
1980
2272
  };
1981
2273
  }, [entry.content, entry.lang]);
1982
- return /* @__PURE__ */ jsx38(CodeFigure, { label: entry.label, children: html !== null ? /* @__PURE__ */ jsx38(
2274
+ return /* @__PURE__ */ jsx41(CodeFigure, { label: entry.label, children: html !== null ? /* @__PURE__ */ jsx41(
1983
2275
  "code",
1984
2276
  {
1985
2277
  className: cn("hljs font-mono text-xs", entry.lang && `language-${entry.lang}`),
1986
2278
  dangerouslySetInnerHTML: { __html: html }
1987
2279
  }
1988
- ) : /* @__PURE__ */ jsx38("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
2280
+ ) : /* @__PURE__ */ jsx41("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
1989
2281
  }
1990
2282
  function buildIslandRenderers(opts) {
1991
2283
  const renderers = {};
@@ -1999,7 +2291,7 @@ function buildIslandRenderers(opts) {
1999
2291
  }
2000
2292
 
2001
2293
  // src/interactive/ReportInteractiveIsland.tsx
2002
- import { jsx as jsx39 } from "react/jsx-runtime";
2294
+ import { jsx as jsx42 } from "react/jsx-runtime";
2003
2295
  function ReportInteractiveIsland({
2004
2296
  report,
2005
2297
  title,
@@ -2012,7 +2304,7 @@ function ReportInteractiveIsland({
2012
2304
  () => buildIslandRenderers({ syntaxHighlighting, mermaid }),
2013
2305
  [syntaxHighlighting, mermaid]
2014
2306
  );
2015
- return /* @__PURE__ */ jsx39(
2307
+ return /* @__PURE__ */ jsx42(
2016
2308
  ReportInteractive,
2017
2309
  {
2018
2310
  report,
@@ -2025,6 +2317,7 @@ function ReportInteractiveIsland({
2025
2317
  }
2026
2318
  export {
2027
2319
  ReportFailureBanner,
2320
+ ReportFreshness,
2028
2321
  ReportInteractive,
2029
2322
  ReportInteractiveIsland,
2030
2323
  ReportSearch,