executable-stories-react 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -50,11 +50,23 @@ function useReport() {
50
50
  return ctx.report;
51
51
  }
52
52
 
53
- // src/components/ReportScenario.tsx
54
- import { formatDuration as formatDuration3 } from "executable-stories-core/utils/duration";
55
-
56
- // src/components/ReportSteps.tsx
57
- import { formatDuration } from "executable-stories-core/utils/duration";
53
+ // ../executable-stories-core/dist/utils/duration.js
54
+ function formatDuration(ms) {
55
+ if (ms < 1e3) {
56
+ return `${Math.round(ms)} ms`;
57
+ }
58
+ if (ms < 6e4) {
59
+ return `${(ms / 1e3).toFixed(2)} s`;
60
+ }
61
+ const minutes = Math.floor(ms / 6e4);
62
+ const seconds = Math.floor(ms % 6e4 / 1e3);
63
+ if (minutes < 60) {
64
+ return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
65
+ }
66
+ const hours = Math.floor(minutes / 60);
67
+ const remainingMinutes = minutes % 60;
68
+ return `${hours}h ${remainingMinutes}m`;
69
+ }
58
70
 
59
71
  // src/components/doc/DocNote.tsx
60
72
  import { jsx as jsx2 } from "react/jsx-runtime";
@@ -238,7 +250,7 @@ function DocSection({ entry }) {
238
250
  /* @__PURE__ */ jsx8(
239
251
  "div",
240
252
  {
241
- className: "es-doc-prose text-muted-foreground",
253
+ className: "es-doc-prose prose prose-sm max-w-none",
242
254
  dangerouslySetInnerHTML: { __html: html }
243
255
  }
244
256
  )
@@ -537,9 +549,6 @@ function ReportAttachments({ attachments }) {
537
549
  );
538
550
  }
539
551
 
540
- // src/components/ReportTrace.tsx
541
- import { formatDuration as formatDuration2 } from "executable-stories-core/utils/duration";
542
-
543
552
  // src/lib/trace.ts
544
553
  var VALID = /* @__PURE__ */ new Set(["ok", "error", "unset"]);
545
554
  var safeStatus = (s) => VALID.has(s) ? s : "unset";
@@ -634,7 +643,7 @@ function ReportTrace({ spans }) {
634
643
  " span",
635
644
  rows.length === 1 ? "" : "s",
636
645
  ", ",
637
- formatDuration2(totalMs),
646
+ formatDuration(totalMs),
638
647
  ")"
639
648
  ] }),
640
649
  /* @__PURE__ */ jsx19("ul", { className: "flex flex-col gap-0.5 px-3 pb-3 font-mono text-[0.6875rem]", children: rows.map((row, i) => {
@@ -657,33 +666,128 @@ function ReportTrace({ spans }) {
657
666
  style: { left: `${leftPct}%`, width: `${widthPct}%` }
658
667
  }
659
668
  ) }),
660
- /* @__PURE__ */ jsx19("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: formatDuration2(row.durationMs) })
669
+ /* @__PURE__ */ jsx19("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: formatDuration(row.durationMs) })
661
670
  ] }, `${row.spanId}-${i}`);
662
671
  }) })
663
672
  ] });
664
673
  }
665
674
 
675
+ // src/interactive/scenario-history-context.ts
676
+ import { createContext as createContext2, useContext as useContext3 } from "react";
677
+ var ScenarioHistoryContext = createContext2(null);
678
+ var ScenarioHistoryProvider = ScenarioHistoryContext.Provider;
679
+ function useScenarioHistory() {
680
+ return useContext3(ScenarioHistoryContext);
681
+ }
682
+
683
+ // src/lib/run-history.ts
684
+ function currentStreak(entries) {
685
+ const last = entries[entries.length - 1];
686
+ if (!last) return void 0;
687
+ let count = 0;
688
+ for (let i = entries.length - 1; i >= 0 && entries[i].status === last.status; i--) count++;
689
+ return { status: last.status, count };
690
+ }
691
+ var STREAK_VERB = {
692
+ passed: "Passing",
693
+ failed: "Failing",
694
+ skipped: "Skipped",
695
+ pending: "Pending"
696
+ };
697
+ function describeRunHistory(entries) {
698
+ const passed = entries.filter((e) => e.status === "passed").length;
699
+ const base = `${passed}/${entries.length} runs passed`;
700
+ const streak = currentStreak(entries);
701
+ if (!streak || entries.length < 2) return base;
702
+ return `${base} \xB7 ${STREAK_VERB[streak.status]} for the last ${streak.count === 1 ? "run" : `${streak.count} runs`}`;
703
+ }
704
+ var MIN_FLAKINESS_SAMPLES = 3;
705
+ function flakinessOf(entries) {
706
+ const countable = entries.filter((e) => e.status === "passed" || e.status === "failed");
707
+ if (countable.length < MIN_FLAKINESS_SAMPLES) return "stable";
708
+ let transitions = 0;
709
+ for (let i = 1; i < countable.length; i++) {
710
+ if (countable[i].status !== countable[i - 1].status) transitions++;
711
+ }
712
+ const transitionScore = transitions / (countable.length - 1);
713
+ const failureRate = countable.filter((e) => e.status === "failed").length / countable.length;
714
+ if (transitionScore > 0.5 || transitionScore > 0.3 && failureRate > 0.2) return "flaky";
715
+ if (transitionScore > 0.2 || failureRate > 0.3) return "unstable";
716
+ return "stable";
717
+ }
718
+
719
+ // src/components/ScenarioRunHistory.tsx
720
+ import { Fragment as Fragment7, jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
721
+ var MAX_DOTS = 10;
722
+ var DOT_COLOR = {
723
+ passed: "bg-pass",
724
+ failed: "bg-fail",
725
+ skipped: "bg-skip",
726
+ pending: "bg-pend"
727
+ };
728
+ function dotTitle(e) {
729
+ const parts = [new Date(e.timestamp).toLocaleString(), e.status];
730
+ if (e.commitSha) parts.push(e.commitSha.slice(0, 8));
731
+ return parts.join(" \xB7 ");
732
+ }
733
+ function ScenarioRunHistory({ scenarioId }) {
734
+ const history = useScenarioHistory();
735
+ const entries = history?.[scenarioId];
736
+ if (!entries || entries.length < 2) return null;
737
+ const recent = entries.slice(-MAX_DOTS);
738
+ const flaky = flakinessOf(recent) === "flaky";
739
+ const summary = flaky ? `${describeRunHistory(recent)} \xB7 Flaky` : describeRunHistory(recent);
740
+ return /* @__PURE__ */ jsxs13(Fragment7, { children: [
741
+ flaky ? /* @__PURE__ */ jsx20(Badge, { variant: "pending", "aria-label": "Flaky: status flips between recent runs", title: summary, children: "Flaky" }) : null,
742
+ /* @__PURE__ */ jsx20(
743
+ "span",
744
+ {
745
+ role: "img",
746
+ "aria-label": `Run history: ${summary}`,
747
+ title: summary,
748
+ className: "flex shrink-0 items-center gap-[3px]",
749
+ children: recent.map((e, i) => /* @__PURE__ */ jsx20(
750
+ "span",
751
+ {
752
+ "aria-hidden": true,
753
+ title: dotTitle(e),
754
+ className: cn(
755
+ "inline-block size-1.5 rounded-full",
756
+ DOT_COLOR[e.status],
757
+ // The newest run (rightmost dot) is the one the card's status
758
+ // badge already describes; ring it so the timeline reads as
759
+ // "…leading up to now".
760
+ i === recent.length - 1 && "ring-1 ring-border ring-offset-1 ring-offset-card"
761
+ )
762
+ },
763
+ `${e.timestamp}-${i}`
764
+ ))
765
+ }
766
+ )
767
+ ] });
768
+ }
769
+
666
770
  // src/interactive/collapse-context.ts
667
- import { createContext as createContext2, useCallback, useContext as useContext3, useState } from "react";
668
- var CollapseContext = createContext2(null);
771
+ import { createContext as createContext3, useCallback, useContext as useContext4, useState } from "react";
772
+ var CollapseContext = createContext3(null);
669
773
  var CollapseProvider = CollapseContext.Provider;
670
774
  function useCollapse() {
671
- return useContext3(CollapseContext);
775
+ return useContext4(CollapseContext);
672
776
  }
673
777
 
674
778
  // src/interactive/scenario-actions.tsx
675
- import { createContext as createContext3, useContext as useContext4 } from "react";
676
- var ScenarioActionsContext = createContext3(null);
779
+ import { createContext as createContext4, useContext as useContext5 } from "react";
780
+ var ScenarioActionsContext = createContext4(null);
677
781
  var ScenarioActionsProvider = ScenarioActionsContext.Provider;
678
782
  function useScenarioActions() {
679
- return useContext4(ScenarioActionsContext);
783
+ return useContext5(ScenarioActionsContext);
680
784
  }
681
785
 
682
786
  // src/components/ui/card.tsx
683
787
  import "react";
684
- import { jsx as jsx20 } from "react/jsx-runtime";
788
+ import { jsx as jsx21 } from "react/jsx-runtime";
685
789
  function Card({ className, ...props }) {
686
- return /* @__PURE__ */ jsx20(
790
+ return /* @__PURE__ */ jsx21(
687
791
  "div",
688
792
  {
689
793
  "data-slot": "card",
@@ -696,7 +800,7 @@ function Card({ className, ...props }) {
696
800
  );
697
801
  }
698
802
  function CardHeader({ className, ...props }) {
699
- return /* @__PURE__ */ jsx20(
803
+ return /* @__PURE__ */ jsx21(
700
804
  "div",
701
805
  {
702
806
  "data-slot": "card-header",
@@ -709,7 +813,7 @@ function CardHeader({ className, ...props }) {
709
813
  );
710
814
  }
711
815
  function CardContent({ className, ...props }) {
712
- return /* @__PURE__ */ jsx20(
816
+ return /* @__PURE__ */ jsx21(
713
817
  "div",
714
818
  {
715
819
  "data-slot": "card-content",
@@ -720,7 +824,7 @@ function CardContent({ className, ...props }) {
720
824
  }
721
825
 
722
826
  // src/components/ReportScenario.tsx
723
- import { jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
827
+ import { jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
724
828
  var STATUS_LABEL = {
725
829
  passed: "Passed",
726
830
  failed: "Failed",
@@ -748,12 +852,12 @@ var STATUS_BADGE = {
748
852
  function ReportScenario({ scenario, hideTitle = false }) {
749
853
  const titleId = `${scenario.id}-title`;
750
854
  const bodyId = `${scenario.id}-body`;
751
- const label = STATUS_LABEL[scenario.status];
855
+ const label = scenario.planned ? "Planned" : STATUS_LABEL[scenario.status];
752
856
  const collapse = useCollapse();
753
857
  const collapsible = collapse !== null && !hideTitle;
754
858
  const collapsed = collapsible ? collapse.isCollapsed(scenario.id) : false;
755
859
  const actions = useScenarioActions();
756
- return /* @__PURE__ */ jsxs13(
860
+ return /* @__PURE__ */ jsxs14(
757
861
  Card,
758
862
  {
759
863
  id: scenario.id,
@@ -761,10 +865,10 @@ function ReportScenario({ scenario, hideTitle = false }) {
761
865
  ...hideTitle ? { "aria-label": `${scenario.title} \u2014 ${label}` } : { "aria-labelledby": titleId },
762
866
  className: "gap-0 py-0",
763
867
  children: [
764
- /* @__PURE__ */ jsxs13(CardHeader, { className: "px-4 py-3", children: [
765
- /* @__PURE__ */ jsxs13("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
766
- hideTitle ? null : /* @__PURE__ */ jsxs13("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
767
- collapsible ? /* @__PURE__ */ jsx21(
868
+ /* @__PURE__ */ jsxs14(CardHeader, { className: "px-4 py-3", children: [
869
+ /* @__PURE__ */ jsxs14("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
870
+ hideTitle ? null : /* @__PURE__ */ jsxs14("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
871
+ collapsible ? /* @__PURE__ */ jsx22(
768
872
  "button",
769
873
  {
770
874
  type: "button",
@@ -773,17 +877,18 @@ function ReportScenario({ scenario, hideTitle = false }) {
773
877
  "aria-controls": bodyId,
774
878
  "aria-label": `Toggle ${scenario.title}`,
775
879
  className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
776
- children: /* @__PURE__ */ jsx21("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
880
+ children: /* @__PURE__ */ jsx22("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
777
881
  }
778
882
  ) : null,
779
- /* @__PURE__ */ jsx21("span", { "aria-hidden": true, className: cn("text-sm leading-none", STATUS_GLYPH_COLOR[scenario.status]), children: STATUS_GLYPH[scenario.status] }),
780
- /* @__PURE__ */ jsx21("span", { children: scenario.title })
883
+ /* @__PURE__ */ jsx22("span", { "aria-hidden": true, className: cn("text-sm leading-none", STATUS_GLYPH_COLOR[scenario.status]), children: STATUS_GLYPH[scenario.status] }),
884
+ /* @__PURE__ */ jsx22("span", { children: scenario.title })
781
885
  ] }),
782
- /* @__PURE__ */ jsxs13("div", { className: "flex shrink-0 items-center gap-2", children: [
783
- scenario.durationMs > 0 ? /* @__PURE__ */ jsx21("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration3(scenario.durationMs) }) : null,
784
- /* @__PURE__ */ jsx21(Badge, { variant: STATUS_BADGE[scenario.status], "aria-label": `Status: ${label}`, children: label }),
785
- actions ? /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-0.5", children: [
786
- /* @__PURE__ */ jsx21(
886
+ /* @__PURE__ */ jsxs14("div", { className: "flex shrink-0 items-center gap-2", children: [
887
+ /* @__PURE__ */ jsx22(ScenarioRunHistory, { scenarioId: scenario.id }),
888
+ scenario.durationMs > 0 ? /* @__PURE__ */ jsx22("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(scenario.durationMs) }) : null,
889
+ /* @__PURE__ */ jsx22(Badge, { variant: STATUS_BADGE[scenario.status], "aria-label": `Status: ${label}`, children: label }),
890
+ actions ? /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-0.5", children: [
891
+ /* @__PURE__ */ jsx22(
787
892
  "button",
788
893
  {
789
894
  type: "button",
@@ -794,7 +899,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
794
899
  children: "link"
795
900
  }
796
901
  ),
797
- /* @__PURE__ */ jsx21(
902
+ /* @__PURE__ */ jsx22(
798
903
  "button",
799
904
  {
800
905
  type: "button",
@@ -805,7 +910,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
805
910
  children: "md"
806
911
  }
807
912
  ),
808
- /* @__PURE__ */ jsx21(
913
+ /* @__PURE__ */ jsx22(
809
914
  "button",
810
915
  {
811
916
  type: "button",
@@ -819,13 +924,13 @@ function ReportScenario({ scenario, hideTitle = false }) {
819
924
  ] }) : null
820
925
  ] })
821
926
  ] }),
822
- 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: [
823
- scenario.tags.map((t) => /* @__PURE__ */ jsx21("li", { children: /* @__PURE__ */ jsx21(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
824
- (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}`))
927
+ 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: [
928
+ scenario.tags.map((t) => /* @__PURE__ */ jsx22("li", { children: /* @__PURE__ */ jsx22(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
929
+ (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}`))
825
930
  ] }) : null
826
931
  ] }),
827
- /* @__PURE__ */ jsxs13(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
828
- scenario.errorMessage ? /* @__PURE__ */ jsx21(
932
+ /* @__PURE__ */ jsxs14(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
933
+ scenario.errorMessage ? /* @__PURE__ */ jsx22(
829
934
  "pre",
830
935
  {
831
936
  role: "alert",
@@ -833,10 +938,10 @@ function ReportScenario({ scenario, hideTitle = false }) {
833
938
  children: scenario.errorMessage
834
939
  }
835
940
  ) : null,
836
- /* @__PURE__ */ jsx21(ReportSteps, { scenario }),
837
- scenario.docEntries.length > 0 ? /* @__PURE__ */ jsx21("div", { "data-es-docs": true, className: "mt-3", children: /* @__PURE__ */ jsx21(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
838
- /* @__PURE__ */ jsx21(ReportAttachments, { attachments: scenario.attachments }),
839
- /* @__PURE__ */ jsx21(ReportTrace, { spans: scenario.otelSpans })
941
+ /* @__PURE__ */ jsx22(ReportSteps, { scenario }),
942
+ scenario.docEntries.length > 0 ? /* @__PURE__ */ jsx22("div", { "data-es-docs": true, className: "mt-3", children: /* @__PURE__ */ jsx22(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
943
+ /* @__PURE__ */ jsx22(ReportAttachments, { attachments: scenario.attachments }),
944
+ /* @__PURE__ */ jsx22(ReportTrace, { spans: scenario.otelSpans })
840
945
  ] })
841
946
  ]
842
947
  }
@@ -844,13 +949,13 @@ function ReportScenario({ scenario, hideTitle = false }) {
844
949
  }
845
950
 
846
951
  // src/components/ReportScenarioList.tsx
847
- import { Fragment as Fragment7, jsx as jsx22 } from "react/jsx-runtime";
952
+ import { Fragment as Fragment8, jsx as jsx23 } from "react/jsx-runtime";
848
953
  function ReportScenarioList({ feature }) {
849
- return /* @__PURE__ */ jsx22(Fragment7, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ jsx22(ReportScenario, { scenario }, scenario.id)) });
954
+ return /* @__PURE__ */ jsx23(Fragment8, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ jsx23(ReportScenario, { scenario }, scenario.id)) });
850
955
  }
851
956
 
852
957
  // src/components/ReportFeature.tsx
853
- import { jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
958
+ import { jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
854
959
  function ReportFeature({ feature }) {
855
960
  const titleId = `${feature.id}-title`;
856
961
  const bodyId = `${feature.id}-body`;
@@ -858,7 +963,7 @@ function ReportFeature({ feature }) {
858
963
  const skipped = s.skipped + s.pending;
859
964
  const collapse = useCollapse();
860
965
  const collapsed = collapse?.isCollapsed(feature.id) ?? false;
861
- return /* @__PURE__ */ jsxs14(
966
+ return /* @__PURE__ */ jsxs15(
862
967
  "section",
863
968
  {
864
969
  id: feature.id,
@@ -866,9 +971,9 @@ function ReportFeature({ feature }) {
866
971
  "aria-labelledby": titleId,
867
972
  className: "overflow-hidden rounded-xl border border-border bg-card shadow-sm",
868
973
  children: [
869
- /* @__PURE__ */ jsxs14("div", { className: "flex items-center justify-between gap-4 border-b border-border bg-muted/40 px-4 py-3", children: [
870
- /* @__PURE__ */ jsxs14("div", { className: "flex min-w-0 items-center gap-2", children: [
871
- collapse ? /* @__PURE__ */ jsx23(
974
+ /* @__PURE__ */ jsxs15("div", { className: "flex items-center justify-between gap-4 border-b border-border bg-muted/40 px-4 py-3", children: [
975
+ /* @__PURE__ */ jsxs15("div", { className: "flex min-w-0 items-center gap-2", children: [
976
+ collapse ? /* @__PURE__ */ jsx24(
872
977
  "button",
873
978
  {
874
979
  type: "button",
@@ -877,30 +982,30 @@ function ReportFeature({ feature }) {
877
982
  "aria-controls": bodyId,
878
983
  "aria-label": `Toggle ${feature.title}`,
879
984
  className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
880
- children: /* @__PURE__ */ jsx23("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
985
+ children: /* @__PURE__ */ jsx24("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
881
986
  }
882
987
  ) : null,
883
- /* @__PURE__ */ jsxs14("div", { className: "min-w-0", children: [
884
- /* @__PURE__ */ jsx23("h2", { id: titleId, className: "text-sm font-semibold tracking-tight text-foreground", children: feature.title }),
885
- /* @__PURE__ */ jsx23("p", { className: "mt-0.5 truncate font-mono text-xs text-muted-foreground", children: feature.sourceFile })
988
+ /* @__PURE__ */ jsxs15("div", { className: "min-w-0", children: [
989
+ /* @__PURE__ */ jsx24("h2", { id: titleId, className: "text-sm font-semibold tracking-tight text-foreground", children: feature.title }),
990
+ /* @__PURE__ */ jsx24("p", { className: "mt-0.5 truncate font-mono text-xs text-muted-foreground", children: feature.sourceFile })
886
991
  ] })
887
992
  ] }),
888
- /* @__PURE__ */ jsxs14(
993
+ /* @__PURE__ */ jsxs15(
889
994
  "div",
890
995
  {
891
996
  role: "img",
892
997
  className: "flex shrink-0 items-center gap-2.5 font-mono text-xs font-medium",
893
998
  "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
894
999
  children: [
895
- /* @__PURE__ */ jsxs14("span", { "aria-hidden": true, className: "text-pass", children: [
1000
+ /* @__PURE__ */ jsxs15("span", { "aria-hidden": true, className: "text-pass", children: [
896
1001
  "\u2713",
897
1002
  s.passed
898
1003
  ] }),
899
- /* @__PURE__ */ jsxs14("span", { "aria-hidden": true, className: "text-fail", children: [
1004
+ /* @__PURE__ */ jsxs15("span", { "aria-hidden": true, className: "text-fail", children: [
900
1005
  "\u2717",
901
1006
  s.failed
902
1007
  ] }),
903
- /* @__PURE__ */ jsxs14("span", { "aria-hidden": true, className: "text-skip", children: [
1008
+ /* @__PURE__ */ jsxs15("span", { "aria-hidden": true, className: "text-skip", children: [
904
1009
  "\u25CB",
905
1010
  skipped
906
1011
  ] })
@@ -908,24 +1013,24 @@ function ReportFeature({ feature }) {
908
1013
  }
909
1014
  )
910
1015
  ] }),
911
- /* @__PURE__ */ jsx23("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-2 p-3", children: /* @__PURE__ */ jsx23(ReportScenarioList, { feature }) })
1016
+ /* @__PURE__ */ jsx24("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-2 p-3", children: /* @__PURE__ */ jsx24(ReportScenarioList, { feature }) })
912
1017
  ]
913
1018
  }
914
1019
  );
915
1020
  }
916
1021
 
917
1022
  // src/components/ReportFeatureList.tsx
918
- import { Fragment as Fragment8, jsx as jsx24 } from "react/jsx-runtime";
1023
+ import { Fragment as Fragment9, jsx as jsx25 } from "react/jsx-runtime";
919
1024
  function ReportFeatureList() {
920
1025
  const report = useReport();
921
- return /* @__PURE__ */ jsx24(Fragment8, { children: report.features.map((feature) => /* @__PURE__ */ jsx24(ReportFeature, { feature }, feature.id)) });
1026
+ return /* @__PURE__ */ jsx25(Fragment9, { children: report.features.map((feature) => /* @__PURE__ */ jsx25(ReportFeature, { feature }, feature.id)) });
922
1027
  }
923
1028
 
924
1029
  // src/components/ui/empty.tsx
925
1030
  import { cva as cva2 } from "class-variance-authority";
926
- import { jsx as jsx25 } from "react/jsx-runtime";
1031
+ import { jsx as jsx26 } from "react/jsx-runtime";
927
1032
  function Empty({ className, ...props }) {
928
- return /* @__PURE__ */ jsx25(
1033
+ return /* @__PURE__ */ jsx26(
929
1034
  "div",
930
1035
  {
931
1036
  "data-slot": "empty",
@@ -938,7 +1043,7 @@ function Empty({ className, ...props }) {
938
1043
  );
939
1044
  }
940
1045
  function EmptyHeader({ className, ...props }) {
941
- return /* @__PURE__ */ jsx25(
1046
+ return /* @__PURE__ */ jsx26(
942
1047
  "div",
943
1048
  {
944
1049
  "data-slot": "empty-header",
@@ -965,7 +1070,7 @@ var emptyMediaVariants = cva2(
965
1070
  }
966
1071
  );
967
1072
  function EmptyTitle({ className, ...props }) {
968
- return /* @__PURE__ */ jsx25(
1073
+ return /* @__PURE__ */ jsx26(
969
1074
  "div",
970
1075
  {
971
1076
  "data-slot": "empty-title",
@@ -976,16 +1081,16 @@ function EmptyTitle({ className, ...props }) {
976
1081
  }
977
1082
 
978
1083
  // src/components/ReportEmpty.tsx
979
- import { jsx as jsx26 } from "react/jsx-runtime";
1084
+ import { jsx as jsx27 } from "react/jsx-runtime";
980
1085
  function ReportEmpty({ message }) {
981
- return /* @__PURE__ */ jsx26(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ jsx26(EmptyHeader, { children: /* @__PURE__ */ jsx26(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1086
+ return /* @__PURE__ */ jsx27(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ jsx27(EmptyHeader, { children: /* @__PURE__ */ jsx27(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
982
1087
  }
983
1088
 
984
1089
  // src/components/ReportSummary.tsx
985
- import { Fragment as Fragment9, jsx as jsx27, jsxs as jsxs15 } from "react/jsx-runtime";
1090
+ import { Fragment as Fragment10, jsx as jsx28, jsxs as jsxs16 } from "react/jsx-runtime";
986
1091
  function ReportSummary({ className }) {
987
1092
  const report = useReport();
988
- return /* @__PURE__ */ jsx27(
1093
+ return /* @__PURE__ */ jsx28(
989
1094
  ReportSummaryView,
990
1095
  {
991
1096
  summary: report.summary,
@@ -995,32 +1100,32 @@ function ReportSummary({ className }) {
995
1100
  );
996
1101
  }
997
1102
  function ReportSummaryView({ summary, className, ariaLabel }) {
998
- return /* @__PURE__ */ jsxs15("p", { className: cn("text-sm text-muted-foreground", className), "aria-label": ariaLabel, children: [
999
- /* @__PURE__ */ jsxs15("span", { children: [
1000
- /* @__PURE__ */ jsx27("strong", { className: "font-semibold text-foreground", children: summary.total }),
1103
+ return /* @__PURE__ */ jsxs16("p", { className: cn("text-sm text-muted-foreground", className), "aria-label": ariaLabel, children: [
1104
+ /* @__PURE__ */ jsxs16("span", { children: [
1105
+ /* @__PURE__ */ jsx28("strong", { className: "font-semibold text-foreground", children: summary.total }),
1001
1106
  " scenario",
1002
1107
  summary.total === 1 ? "" : "s"
1003
1108
  ] }),
1004
1109
  " \xB7 ",
1005
- /* @__PURE__ */ jsxs15("span", { "data-status": "passed", className: "text-pass", children: [
1110
+ /* @__PURE__ */ jsxs16("span", { "data-status": "passed", className: "text-pass", children: [
1006
1111
  summary.passed,
1007
1112
  " passed"
1008
1113
  ] }),
1009
1114
  " \xB7 ",
1010
- /* @__PURE__ */ jsxs15("span", { "data-status": "failed", className: "text-fail", children: [
1115
+ /* @__PURE__ */ jsxs16("span", { "data-status": "failed", className: "text-fail", children: [
1011
1116
  summary.failed,
1012
1117
  " failed"
1013
1118
  ] }),
1014
- summary.skipped > 0 ? /* @__PURE__ */ jsxs15(Fragment9, { children: [
1119
+ summary.skipped > 0 ? /* @__PURE__ */ jsxs16(Fragment10, { children: [
1015
1120
  " \xB7 ",
1016
- /* @__PURE__ */ jsxs15("span", { "data-status": "skipped", className: "text-skip", children: [
1121
+ /* @__PURE__ */ jsxs16("span", { "data-status": "skipped", className: "text-skip", children: [
1017
1122
  summary.skipped,
1018
1123
  " skipped"
1019
1124
  ] })
1020
1125
  ] }) : null,
1021
- summary.pending > 0 ? /* @__PURE__ */ jsxs15(Fragment9, { children: [
1126
+ summary.pending > 0 ? /* @__PURE__ */ jsxs16(Fragment10, { children: [
1022
1127
  " \xB7 ",
1023
- /* @__PURE__ */ jsxs15("span", { "data-status": "pending", className: "text-pend", children: [
1128
+ /* @__PURE__ */ jsxs16("span", { "data-status": "pending", className: "text-pend", children: [
1024
1129
  summary.pending,
1025
1130
  " pending"
1026
1131
  ] })
@@ -1028,69 +1133,135 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1028
1133
  ] });
1029
1134
  }
1030
1135
 
1136
+ // src/lib/provenance.ts
1137
+ function reportLastRunMs(report) {
1138
+ if (report.finishedAtMs && report.finishedAtMs > 0) return report.finishedAtMs;
1139
+ if (report.startedAtMs && report.startedAtMs > 0) return report.startedAtMs;
1140
+ return void 0;
1141
+ }
1142
+ function formatRelativeAge(thenMs, nowMs) {
1143
+ const deltaMs = Math.max(0, nowMs - thenMs);
1144
+ const minutes = Math.floor(deltaMs / 6e4);
1145
+ if (minutes < 1) return "just now";
1146
+ if (minutes < 60) return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
1147
+ const hours = Math.floor(minutes / 60);
1148
+ if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
1149
+ const days = Math.floor(hours / 24);
1150
+ return `${days} day${days === 1 ? "" : "s"} ago`;
1151
+ }
1152
+ function isReportStale(lastRunMs, staleAfterDays, nowMs) {
1153
+ if (lastRunMs === void 0 || staleAfterDays <= 0) return false;
1154
+ return nowMs - lastRunMs >= staleAfterDays * 864e5;
1155
+ }
1156
+ var CI_DISPLAY_NAMES = {
1157
+ github: "GitHub Actions",
1158
+ gitlab: "GitLab CI",
1159
+ circleci: "CircleCI",
1160
+ jenkins: "Jenkins",
1161
+ azure: "Azure DevOps",
1162
+ buildkite: "Buildkite",
1163
+ travis: "Travis CI",
1164
+ ci: "CI"
1165
+ };
1166
+ function ciDisplayName(name) {
1167
+ return CI_DISPLAY_NAMES[name] ?? name;
1168
+ }
1169
+ function commitUrl(ci, sha) {
1170
+ const commitSha = ci?.commitSha ?? sha;
1171
+ if (!ci?.url || !commitSha) return void 0;
1172
+ const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1173
+ if (github) return `${github[1]}/commit/${commitSha}`;
1174
+ const gitlab = ci.url.match(/^(https?:\/\/.+)\/-\/pipelines\//);
1175
+ if (gitlab) return `${gitlab[1]}/-/commit/${commitSha}`;
1176
+ return void 0;
1177
+ }
1178
+ function prUrl(ci) {
1179
+ if (!ci?.url || !ci.prNumber) return void 0;
1180
+ const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
1181
+ if (github) return `${github[1]}/pull/${ci.prNumber}`;
1182
+ return void 0;
1183
+ }
1184
+
1031
1185
  // src/components/ReportMeta.tsx
1032
- import { formatDuration as formatDuration4 } from "executable-stories-core/utils/duration";
1033
- import { jsx as jsx28, jsxs as jsxs16 } from "react/jsx-runtime";
1186
+ import { jsx as jsx29, jsxs as jsxs17 } from "react/jsx-runtime";
1034
1187
  function ReportMeta() {
1035
1188
  const report = useReport();
1036
1189
  const items = [];
1190
+ const linkClass = "text-primary underline underline-offset-2";
1037
1191
  if (report.startedAtMs) {
1038
1192
  items.push({ label: "Started", value: new Date(report.startedAtMs).toLocaleString() });
1039
1193
  }
1040
1194
  if (report.durationMs) {
1041
- items.push({ label: "Duration", value: formatDuration4(report.durationMs) });
1195
+ items.push({ label: "Duration", value: formatDuration(report.durationMs) });
1042
1196
  }
1043
1197
  if (report.packageVersion) {
1044
1198
  items.push({ label: "Version", value: report.packageVersion });
1045
1199
  }
1046
- if (report.gitSha) {
1047
- items.push({ label: "Commit", value: report.gitSha.slice(0, 8) });
1200
+ if (report.ci?.branch) {
1201
+ items.push({ label: "Branch", value: report.ci.branch });
1202
+ }
1203
+ const sha = report.ci?.commitSha ?? report.gitSha;
1204
+ if (sha) {
1205
+ const url = commitUrl(report.ci, report.gitSha);
1206
+ const short = sha.slice(0, 8);
1207
+ items.push({
1208
+ label: "Commit",
1209
+ value: url ? /* @__PURE__ */ jsx29("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1210
+ });
1048
1211
  }
1049
1212
  if (report.ci) {
1050
1213
  items.push({
1051
1214
  label: "CI",
1052
- value: report.ci.url ? /* @__PURE__ */ jsxs16("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: "text-primary underline underline-offset-2", children: [
1053
- report.ci.name,
1215
+ value: report.ci.url ? /* @__PURE__ */ jsxs17("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1216
+ ciDisplayName(report.ci.name),
1054
1217
  report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
1055
- ] }) : `${report.ci.name}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
1218
+ ] }) : `${ciDisplayName(report.ci.name)}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
1219
+ });
1220
+ }
1221
+ if (report.ci?.prNumber) {
1222
+ const url = prUrl(report.ci);
1223
+ const label = `#${report.ci.prNumber}`;
1224
+ items.push({
1225
+ label: "PR",
1226
+ value: url ? /* @__PURE__ */ jsx29("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1056
1227
  });
1057
1228
  }
1058
1229
  if (items.length === 0) return null;
1059
- 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: [
1060
- /* @__PURE__ */ jsx28("dt", { className: "font-medium", children: item.label }),
1061
- /* @__PURE__ */ jsx28("dd", { className: "font-mono text-foreground", children: item.value })
1230
+ 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: [
1231
+ /* @__PURE__ */ jsx29("dt", { className: "font-medium", children: item.label }),
1232
+ /* @__PURE__ */ jsx29("dd", { className: "font-mono text-foreground", children: item.value })
1062
1233
  ] }, item.label)) });
1063
1234
  }
1064
1235
 
1065
1236
  // src/components/ReportSchemaError.tsx
1066
- import { jsx as jsx29, jsxs as jsxs17 } from "react/jsx-runtime";
1237
+ import { jsx as jsx30, jsxs as jsxs18 } from "react/jsx-runtime";
1067
1238
  function ReportSchemaError({ error }) {
1068
- return /* @__PURE__ */ jsxs17("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1069
- /* @__PURE__ */ jsx29("p", { children: /* @__PURE__ */ jsx29("strong", { children: "Report could not be displayed." }) }),
1070
- /* @__PURE__ */ jsx29("p", { children: error.message }),
1071
- error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs17("p", { children: [
1239
+ return /* @__PURE__ */ jsxs18("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1240
+ /* @__PURE__ */ jsx30("p", { children: /* @__PURE__ */ jsx30("strong", { children: "Report could not be displayed." }) }),
1241
+ /* @__PURE__ */ jsx30("p", { children: error.message }),
1242
+ error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs18("p", { children: [
1072
1243
  "The report bundle is newer than this version of ",
1073
- /* @__PURE__ */ jsx29("code", { children: "executable-stories-react" }),
1244
+ /* @__PURE__ */ jsx30("code", { children: "executable-stories-react" }),
1074
1245
  ". Upgrade the package, or regenerate the report with an older formatters CLI."
1075
1246
  ] }) : null,
1076
- error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs17("details", { children: [
1077
- /* @__PURE__ */ jsxs17("summary", { children: [
1247
+ error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs18("details", { children: [
1248
+ /* @__PURE__ */ jsxs18("summary", { children: [
1078
1249
  error.issues.length,
1079
1250
  " validation issue",
1080
1251
  error.issues.length === 1 ? "" : "s"
1081
1252
  ] }),
1082
- /* @__PURE__ */ jsx29("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1253
+ /* @__PURE__ */ jsx30("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1083
1254
  ] }) : null
1084
1255
  ] });
1085
1256
  }
1086
1257
 
1087
1258
  // src/components/ReportShell.tsx
1088
- import { Fragment as Fragment10, jsx as jsx30, jsxs as jsxs18 } from "react/jsx-runtime";
1259
+ import { Fragment as Fragment11, jsx as jsx31, jsxs as jsxs19 } from "react/jsx-runtime";
1089
1260
  function ReportTitleBlock({ title }) {
1090
- return /* @__PURE__ */ jsxs18(Fragment10, { children: [
1091
- /* @__PURE__ */ jsx30("h1", { children: title ?? "Story Report" }),
1092
- /* @__PURE__ */ jsx30(ReportSummary, {}),
1093
- /* @__PURE__ */ jsx30(ReportMeta, {})
1261
+ return /* @__PURE__ */ jsxs19(Fragment11, { children: [
1262
+ /* @__PURE__ */ jsx31("h1", { children: title ?? "Story Report" }),
1263
+ /* @__PURE__ */ jsx31(ReportSummary, {}),
1264
+ /* @__PURE__ */ jsx31(ReportMeta, {})
1094
1265
  ] });
1095
1266
  }
1096
1267
  function ReportErrorShell({
@@ -1099,28 +1270,28 @@ function ReportErrorShell({
1099
1270
  title,
1100
1271
  dataTheme
1101
1272
  }) {
1102
- return /* @__PURE__ */ jsx30("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ jsx30(ReportSchemaError, { error }) });
1273
+ return /* @__PURE__ */ jsx31("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ jsx31(ReportSchemaError, { error }) });
1103
1274
  }
1104
1275
 
1105
1276
  // src/components/Report.tsx
1106
- import { jsx as jsx31, jsxs as jsxs19 } from "react/jsx-runtime";
1277
+ import { jsx as jsx32, jsxs as jsxs20 } from "react/jsx-runtime";
1107
1278
  function Report(props) {
1108
1279
  const { report, customRenderers, renderers, className, title, dataTheme } = props;
1109
1280
  const result = unwrapReport(report);
1110
1281
  if (!result.ok) {
1111
- return /* @__PURE__ */ jsx31(ReportErrorShell, { error: result.error, className, title, dataTheme });
1282
+ return /* @__PURE__ */ jsx32(ReportErrorShell, { error: result.error, className, title, dataTheme });
1112
1283
  }
1113
1284
  const data = result.data;
1114
1285
  const hasContent = data.features.length > 0;
1115
- return /* @__PURE__ */ jsx31(ReportRoot, { report: data, customRenderers, renderers, children: /* @__PURE__ */ jsxs19("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: [
1116
- /* @__PURE__ */ jsx31("header", { className: "es-report-header", children: /* @__PURE__ */ jsx31(ReportTitleBlock, { title }) }),
1117
- hasContent ? /* @__PURE__ */ jsx31(ReportFeatureList, {}) : /* @__PURE__ */ jsx31(ReportEmpty, {})
1286
+ return /* @__PURE__ */ jsx32(ReportRoot, { report: data, customRenderers, renderers, children: /* @__PURE__ */ jsxs20("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: [
1287
+ /* @__PURE__ */ jsx32("header", { className: "es-report-header", children: /* @__PURE__ */ jsx32(ReportTitleBlock, { title }) }),
1288
+ hasContent ? /* @__PURE__ */ jsx32(ReportFeatureList, {}) : /* @__PURE__ */ jsx32(ReportEmpty, {})
1118
1289
  ] }) });
1119
1290
  }
1120
1291
 
1121
1292
  // src/components/doc/MermaidView.tsx
1122
1293
  import { useEffect, useRef, useState as useState2 } from "react";
1123
- import { jsx as jsx32, jsxs as jsxs20 } from "react/jsx-runtime";
1294
+ import { jsx as jsx33, jsxs as jsxs21 } from "react/jsx-runtime";
1124
1295
  function prefersDark(el) {
1125
1296
  const themed = el?.closest("[data-theme]");
1126
1297
  if (themed?.dataset.theme === "dark") return true;
@@ -1157,9 +1328,9 @@ function MermaidView({ entry, load }) {
1157
1328
  };
1158
1329
  }, [entry.code, load]);
1159
1330
  if (svg) {
1160
- return /* @__PURE__ */ jsxs20("figure", { ref, className: "my-3", children: [
1161
- entry.title ? /* @__PURE__ */ jsx32("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
1162
- /* @__PURE__ */ jsx32(
1331
+ return /* @__PURE__ */ jsxs21("figure", { ref, className: "my-3", children: [
1332
+ entry.title ? /* @__PURE__ */ jsx33("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
1333
+ /* @__PURE__ */ jsx33(
1163
1334
  "div",
1164
1335
  {
1165
1336
  role: "img",
@@ -1170,18 +1341,18 @@ function MermaidView({ entry, load }) {
1170
1341
  )
1171
1342
  ] });
1172
1343
  }
1173
- return /* @__PURE__ */ jsx32(MermaidSource, { entry, ref });
1344
+ return /* @__PURE__ */ jsx33(MermaidSource, { entry, ref });
1174
1345
  }
1175
1346
 
1176
1347
  // src/components/doc/MermaidDiagram.tsx
1177
- import { jsx as jsx33 } from "react/jsx-runtime";
1348
+ import { jsx as jsx34 } from "react/jsx-runtime";
1178
1349
  var mermaidLoader;
1179
1350
  function loadMermaid() {
1180
1351
  mermaidLoader ??= import("mermaid").then((m) => m.default);
1181
1352
  return mermaidLoader;
1182
1353
  }
1183
1354
  function MermaidDiagram({ entry, load = loadMermaid }) {
1184
- return /* @__PURE__ */ jsx33(MermaidView, { entry, load });
1355
+ return /* @__PURE__ */ jsx34(MermaidView, { entry, load });
1185
1356
  }
1186
1357
 
1187
1358
  // src/components/ScenarioExplorer.tsx
@@ -1191,12 +1362,12 @@ import { useState as useState3 } from "react";
1191
1362
  import "react";
1192
1363
  import { Command as CommandPrimitive } from "cmdk";
1193
1364
  import { SearchIcon } from "lucide-react";
1194
- import { jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
1365
+ import { jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
1195
1366
  function Command({
1196
1367
  className,
1197
1368
  ...props
1198
1369
  }) {
1199
- return /* @__PURE__ */ jsx34(
1370
+ return /* @__PURE__ */ jsx35(
1200
1371
  CommandPrimitive,
1201
1372
  {
1202
1373
  "data-slot": "command",
@@ -1212,14 +1383,14 @@ function CommandInput({
1212
1383
  className,
1213
1384
  ...props
1214
1385
  }) {
1215
- return /* @__PURE__ */ jsxs21(
1386
+ return /* @__PURE__ */ jsxs22(
1216
1387
  "div",
1217
1388
  {
1218
1389
  "data-slot": "command-input-wrapper",
1219
1390
  className: "flex h-9 items-center gap-2 border-b px-3",
1220
1391
  children: [
1221
- /* @__PURE__ */ jsx34(SearchIcon, { className: "size-4 shrink-0 opacity-50" }),
1222
- /* @__PURE__ */ jsx34(
1392
+ /* @__PURE__ */ jsx35(SearchIcon, { className: "size-4 shrink-0 opacity-50" }),
1393
+ /* @__PURE__ */ jsx35(
1223
1394
  CommandPrimitive.Input,
1224
1395
  {
1225
1396
  "data-slot": "command-input",
@@ -1238,7 +1409,7 @@ function CommandList({
1238
1409
  className,
1239
1410
  ...props
1240
1411
  }) {
1241
- return /* @__PURE__ */ jsx34(
1412
+ return /* @__PURE__ */ jsx35(
1242
1413
  CommandPrimitive.List,
1243
1414
  {
1244
1415
  "data-slot": "command-list",
@@ -1253,7 +1424,7 @@ function CommandList({
1253
1424
  function CommandEmpty({
1254
1425
  ...props
1255
1426
  }) {
1256
- return /* @__PURE__ */ jsx34(
1427
+ return /* @__PURE__ */ jsx35(
1257
1428
  CommandPrimitive.Empty,
1258
1429
  {
1259
1430
  "data-slot": "command-empty",
@@ -1266,7 +1437,7 @@ function CommandGroup({
1266
1437
  className,
1267
1438
  ...props
1268
1439
  }) {
1269
- return /* @__PURE__ */ jsx34(
1440
+ return /* @__PURE__ */ jsx35(
1270
1441
  CommandPrimitive.Group,
1271
1442
  {
1272
1443
  "data-slot": "command-group",
@@ -1282,7 +1453,7 @@ function CommandItem({
1282
1453
  className,
1283
1454
  ...props
1284
1455
  }) {
1285
- return /* @__PURE__ */ jsx34(
1456
+ return /* @__PURE__ */ jsx35(
1286
1457
  CommandPrimitive.Item,
1287
1458
  {
1288
1459
  "data-slot": "command-item",
@@ -1296,7 +1467,7 @@ function CommandItem({
1296
1467
  }
1297
1468
 
1298
1469
  // src/components/ScenarioExplorer.tsx
1299
- import { jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
1470
+ import { jsx as jsx36, jsxs as jsxs23 } from "react/jsx-runtime";
1300
1471
  var STATUS_FILTERS = [
1301
1472
  { value: "all", label: "All" },
1302
1473
  { value: "passed", label: "Passed" },
@@ -1307,22 +1478,22 @@ var STATUS_FILTERS = [
1307
1478
  function ScenarioExplorer({ scenarios, summary }) {
1308
1479
  const [status, setStatus] = useState3("all");
1309
1480
  const visible = status === "all" ? scenarios : scenarios.filter((s) => s.status === status);
1310
- return /* @__PURE__ */ jsxs22("div", { className: "font-sans text-foreground", children: [
1311
- summary ? /* @__PURE__ */ jsxs22("p", { className: "mb-3 text-sm text-muted-foreground", children: [
1481
+ return /* @__PURE__ */ jsxs23("div", { className: "font-sans text-foreground", children: [
1482
+ summary ? /* @__PURE__ */ jsxs23("p", { className: "mb-3 text-sm text-muted-foreground", children: [
1312
1483
  summary.total,
1313
1484
  " scenarios \xB7 ",
1314
- /* @__PURE__ */ jsxs22("span", { className: "text-pass", children: [
1485
+ /* @__PURE__ */ jsxs23("span", { className: "text-pass", children: [
1315
1486
  summary.passed,
1316
1487
  " passed"
1317
1488
  ] }),
1318
1489
  " \xB7",
1319
1490
  " ",
1320
- /* @__PURE__ */ jsxs22("span", { className: "text-fail", children: [
1491
+ /* @__PURE__ */ jsxs23("span", { className: "text-fail", children: [
1321
1492
  summary.failed,
1322
1493
  " failed"
1323
1494
  ] })
1324
1495
  ] }) : null,
1325
- /* @__PURE__ */ jsx35("div", { className: "mb-2 flex flex-wrap gap-1.5", role: "group", "aria-label": "Filter by status", children: STATUS_FILTERS.map((f) => /* @__PURE__ */ jsx35(
1496
+ /* @__PURE__ */ jsx36("div", { className: "mb-2 flex flex-wrap gap-1.5", role: "group", "aria-label": "Filter by status", children: STATUS_FILTERS.map((f) => /* @__PURE__ */ jsx36(
1326
1497
  "button",
1327
1498
  {
1328
1499
  type: "button",
@@ -1337,26 +1508,26 @@ function ScenarioExplorer({ scenarios, summary }) {
1337
1508
  },
1338
1509
  f.value
1339
1510
  )) }),
1340
- /* @__PURE__ */ jsxs22(
1511
+ /* @__PURE__ */ jsxs23(
1341
1512
  Command,
1342
1513
  {
1343
1514
  className: "rounded-lg border border-border bg-card",
1344
1515
  filter: (value, search) => value.toLowerCase().includes(search.toLowerCase()) ? 1 : 0,
1345
1516
  children: [
1346
- /* @__PURE__ */ jsx35(CommandInput, { placeholder: "Search scenarios\u2026" }),
1347
- /* @__PURE__ */ jsxs22(CommandList, { className: "max-h-none", children: [
1348
- /* @__PURE__ */ jsx35(CommandEmpty, { children: "No scenarios match your filters." }),
1349
- /* @__PURE__ */ jsx35(CommandGroup, { children: visible.map((s) => /* @__PURE__ */ jsx35(
1517
+ /* @__PURE__ */ jsx36(CommandInput, { placeholder: "Search scenarios\u2026" }),
1518
+ /* @__PURE__ */ jsxs23(CommandList, { className: "max-h-none", children: [
1519
+ /* @__PURE__ */ jsx36(CommandEmpty, { children: "No scenarios match your filters." }),
1520
+ /* @__PURE__ */ jsx36(CommandGroup, { children: visible.map((s) => /* @__PURE__ */ jsx36(
1350
1521
  CommandItem,
1351
1522
  {
1352
1523
  value: `${s.title} ${s.feature} ${s.tags.join(" ")}`,
1353
1524
  asChild: true,
1354
1525
  className: "cursor-pointer",
1355
- children: /* @__PURE__ */ jsxs22("a", { href: s.href, className: "flex items-center gap-3", children: [
1356
- /* @__PURE__ */ jsx35(Badge, { variant: s.status, children: s.status }),
1357
- /* @__PURE__ */ jsx35("span", { className: "min-w-0 flex-1 truncate font-medium", children: s.title }),
1358
- s.tags.slice(0, 3).map((t) => /* @__PURE__ */ jsx35(Badge, { variant: "tag", className: "hidden sm:inline-flex", children: t }, t)),
1359
- /* @__PURE__ */ jsx35("code", { className: "hidden font-mono text-xs text-muted-foreground md:inline", children: s.feature })
1526
+ children: /* @__PURE__ */ jsxs23("a", { href: s.href, className: "flex items-center gap-3", children: [
1527
+ /* @__PURE__ */ jsx36(Badge, { variant: s.status, children: s.status }),
1528
+ /* @__PURE__ */ jsx36("span", { className: "min-w-0 flex-1 truncate font-medium", children: s.title }),
1529
+ s.tags.slice(0, 3).map((t) => /* @__PURE__ */ jsx36(Badge, { variant: "tag", className: "hidden sm:inline-flex", children: t }, t)),
1530
+ /* @__PURE__ */ jsx36("code", { className: "hidden font-mono text-xs text-muted-foreground md:inline", children: s.feature })
1360
1531
  ] })
1361
1532
  },
1362
1533
  s.id
@@ -1546,6 +1717,10 @@ var story_report_v1_default = {
1546
1717
  status: {
1547
1718
  $ref: "#/$defs/TestStatus"
1548
1719
  },
1720
+ planned: {
1721
+ type: "boolean",
1722
+ description: "True for scenarios authored with the framework's todo modifier (e.g. it.todo): specified behavior with no implementation yet. Canonical status stays 'pending'; this flags why."
1723
+ },
1549
1724
  durationMs: {
1550
1725
  type: "number",
1551
1726
  minimum: 0
@@ -2377,9 +2552,18 @@ export {
2377
2552
  ReportTrace,
2378
2553
  STORY_REPORT_SCHEMA_MAJOR,
2379
2554
  ScenarioExplorer,
2555
+ ScenarioRunHistory,
2556
+ ciDisplayName,
2557
+ commitUrl,
2558
+ currentStreak,
2559
+ describeRunHistory,
2380
2560
  err,
2561
+ formatRelativeAge,
2562
+ isReportStale,
2381
2563
  ok,
2382
2564
  parseStoryReport,
2565
+ prUrl,
2566
+ reportLastRunMs,
2383
2567
  storyReportSchema,
2384
2568
  useBuiltinRenderers,
2385
2569
  useCustomRenderers,