executable-stories-react 0.2.0 → 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.
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";
@@ -208,6 +220,15 @@ function safeUrl(value) {
208
220
  if (scheme && !/^https?$/i.test(scheme[1] ?? "")) return void 0;
209
221
  return trimmed;
210
222
  }
223
+ function safeImageUrl(value) {
224
+ if (!value) return void 0;
225
+ const trimmed = value.trim();
226
+ if (/^data:image\//i.test(trimmed)) return trimmed;
227
+ return safeUrl(trimmed);
228
+ }
229
+ function isLocalFsPath(value) {
230
+ return /^(?:[/\\]|[A-Za-z]:[/\\])/.test(value);
231
+ }
211
232
 
212
233
  // src/components/doc/DocSection.tsx
213
234
  import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
@@ -263,11 +284,19 @@ function DocMermaid({ entry }) {
263
284
  // src/components/doc/DocScreenshot.tsx
264
285
  import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
265
286
  function DocScreenshot({ entry }) {
287
+ const src = isLocalFsPath(entry.path) ? void 0 : safeImageUrl(entry.path);
288
+ if (!src) {
289
+ return /* @__PURE__ */ jsxs6("figure", { className: "my-3 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground", children: [
290
+ /* @__PURE__ */ jsx10("div", { className: "font-medium", children: "Screenshot unavailable" }),
291
+ /* @__PURE__ */ jsx10("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
292
+ entry.alt ? /* @__PURE__ */ jsx10("figcaption", { className: "mt-1.5 text-xs", children: entry.alt }) : null
293
+ ] });
294
+ }
266
295
  return /* @__PURE__ */ jsxs6("figure", { className: "my-3", children: [
267
296
  /* @__PURE__ */ jsx10(
268
297
  "img",
269
298
  {
270
- src: entry.path,
299
+ src,
271
300
  alt: entry.alt ?? "",
272
301
  loading: "lazy",
273
302
  className: "max-w-full rounded-md border border-border"
@@ -280,6 +309,13 @@ function DocScreenshot({ entry }) {
280
309
  // src/components/doc/DocVideo.tsx
281
310
  import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
282
311
  function DocVideo({ entry }) {
312
+ if (isLocalFsPath(entry.path)) {
313
+ return /* @__PURE__ */ jsxs7("figure", { className: "my-3 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground", children: [
314
+ /* @__PURE__ */ jsx11("div", { className: "font-medium", children: "Video unavailable" }),
315
+ /* @__PURE__ */ jsx11("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
316
+ entry.caption ? /* @__PURE__ */ jsx11("figcaption", { className: "mt-1.5 text-xs", children: entry.caption }) : null
317
+ ] });
318
+ }
283
319
  const src = safeUrl(entry.path);
284
320
  const poster = safeUrl(entry.poster);
285
321
  return /* @__PURE__ */ jsxs7("figure", { className: "my-3", children: [
@@ -513,9 +549,6 @@ function ReportAttachments({ attachments }) {
513
549
  );
514
550
  }
515
551
 
516
- // src/components/ReportTrace.tsx
517
- import { formatDuration as formatDuration2 } from "executable-stories-core/utils/duration";
518
-
519
552
  // src/lib/trace.ts
520
553
  var VALID = /* @__PURE__ */ new Set(["ok", "error", "unset"]);
521
554
  var safeStatus = (s) => VALID.has(s) ? s : "unset";
@@ -610,7 +643,7 @@ function ReportTrace({ spans }) {
610
643
  " span",
611
644
  rows.length === 1 ? "" : "s",
612
645
  ", ",
613
- formatDuration2(totalMs),
646
+ formatDuration(totalMs),
614
647
  ")"
615
648
  ] }),
616
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) => {
@@ -633,33 +666,128 @@ function ReportTrace({ spans }) {
633
666
  style: { left: `${leftPct}%`, width: `${widthPct}%` }
634
667
  }
635
668
  ) }),
636
- /* @__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) })
637
670
  ] }, `${row.spanId}-${i}`);
638
671
  }) })
639
672
  ] });
640
673
  }
641
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
+
642
770
  // src/interactive/collapse-context.ts
643
- import { createContext as createContext2, useCallback, useContext as useContext3, useState } from "react";
644
- var CollapseContext = createContext2(null);
771
+ import { createContext as createContext3, useCallback, useContext as useContext4, useState } from "react";
772
+ var CollapseContext = createContext3(null);
645
773
  var CollapseProvider = CollapseContext.Provider;
646
774
  function useCollapse() {
647
- return useContext3(CollapseContext);
775
+ return useContext4(CollapseContext);
648
776
  }
649
777
 
650
778
  // src/interactive/scenario-actions.tsx
651
- import { createContext as createContext3, useContext as useContext4 } from "react";
652
- var ScenarioActionsContext = createContext3(null);
779
+ import { createContext as createContext4, useContext as useContext5 } from "react";
780
+ var ScenarioActionsContext = createContext4(null);
653
781
  var ScenarioActionsProvider = ScenarioActionsContext.Provider;
654
782
  function useScenarioActions() {
655
- return useContext4(ScenarioActionsContext);
783
+ return useContext5(ScenarioActionsContext);
656
784
  }
657
785
 
658
786
  // src/components/ui/card.tsx
659
787
  import "react";
660
- import { jsx as jsx20 } from "react/jsx-runtime";
788
+ import { jsx as jsx21 } from "react/jsx-runtime";
661
789
  function Card({ className, ...props }) {
662
- return /* @__PURE__ */ jsx20(
790
+ return /* @__PURE__ */ jsx21(
663
791
  "div",
664
792
  {
665
793
  "data-slot": "card",
@@ -672,7 +800,7 @@ function Card({ className, ...props }) {
672
800
  );
673
801
  }
674
802
  function CardHeader({ className, ...props }) {
675
- return /* @__PURE__ */ jsx20(
803
+ return /* @__PURE__ */ jsx21(
676
804
  "div",
677
805
  {
678
806
  "data-slot": "card-header",
@@ -685,7 +813,7 @@ function CardHeader({ className, ...props }) {
685
813
  );
686
814
  }
687
815
  function CardContent({ className, ...props }) {
688
- return /* @__PURE__ */ jsx20(
816
+ return /* @__PURE__ */ jsx21(
689
817
  "div",
690
818
  {
691
819
  "data-slot": "card-content",
@@ -696,7 +824,7 @@ function CardContent({ className, ...props }) {
696
824
  }
697
825
 
698
826
  // src/components/ReportScenario.tsx
699
- import { jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
827
+ import { jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
700
828
  var STATUS_LABEL = {
701
829
  passed: "Passed",
702
830
  failed: "Failed",
@@ -724,12 +852,12 @@ var STATUS_BADGE = {
724
852
  function ReportScenario({ scenario, hideTitle = false }) {
725
853
  const titleId = `${scenario.id}-title`;
726
854
  const bodyId = `${scenario.id}-body`;
727
- const label = STATUS_LABEL[scenario.status];
855
+ const label = scenario.planned ? "Planned" : STATUS_LABEL[scenario.status];
728
856
  const collapse = useCollapse();
729
857
  const collapsible = collapse !== null && !hideTitle;
730
858
  const collapsed = collapsible ? collapse.isCollapsed(scenario.id) : false;
731
859
  const actions = useScenarioActions();
732
- return /* @__PURE__ */ jsxs13(
860
+ return /* @__PURE__ */ jsxs14(
733
861
  Card,
734
862
  {
735
863
  id: scenario.id,
@@ -737,10 +865,10 @@ function ReportScenario({ scenario, hideTitle = false }) {
737
865
  ...hideTitle ? { "aria-label": `${scenario.title} \u2014 ${label}` } : { "aria-labelledby": titleId },
738
866
  className: "gap-0 py-0",
739
867
  children: [
740
- /* @__PURE__ */ jsxs13(CardHeader, { className: "px-4 py-3", children: [
741
- /* @__PURE__ */ jsxs13("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
742
- hideTitle ? null : /* @__PURE__ */ jsxs13("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
743
- 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(
744
872
  "button",
745
873
  {
746
874
  type: "button",
@@ -749,17 +877,18 @@ function ReportScenario({ scenario, hideTitle = false }) {
749
877
  "aria-controls": bodyId,
750
878
  "aria-label": `Toggle ${scenario.title}`,
751
879
  className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
752
- 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" })
753
881
  }
754
882
  ) : null,
755
- /* @__PURE__ */ jsx21("span", { "aria-hidden": true, className: cn("text-sm leading-none", STATUS_GLYPH_COLOR[scenario.status]), children: STATUS_GLYPH[scenario.status] }),
756
- /* @__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 })
757
885
  ] }),
758
- /* @__PURE__ */ jsxs13("div", { className: "flex shrink-0 items-center gap-2", children: [
759
- scenario.durationMs > 0 ? /* @__PURE__ */ jsx21("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration3(scenario.durationMs) }) : null,
760
- /* @__PURE__ */ jsx21(Badge, { variant: STATUS_BADGE[scenario.status], "aria-label": `Status: ${label}`, children: label }),
761
- actions ? /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-0.5", children: [
762
- /* @__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(
763
892
  "button",
764
893
  {
765
894
  type: "button",
@@ -770,7 +899,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
770
899
  children: "link"
771
900
  }
772
901
  ),
773
- /* @__PURE__ */ jsx21(
902
+ /* @__PURE__ */ jsx22(
774
903
  "button",
775
904
  {
776
905
  type: "button",
@@ -781,7 +910,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
781
910
  children: "md"
782
911
  }
783
912
  ),
784
- /* @__PURE__ */ jsx21(
913
+ /* @__PURE__ */ jsx22(
785
914
  "button",
786
915
  {
787
916
  type: "button",
@@ -795,13 +924,13 @@ function ReportScenario({ scenario, hideTitle = false }) {
795
924
  ] }) : null
796
925
  ] })
797
926
  ] }),
798
- 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: [
799
- scenario.tags.map((t) => /* @__PURE__ */ jsx21("li", { children: /* @__PURE__ */ jsx21(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
800
- (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}`))
801
930
  ] }) : null
802
931
  ] }),
803
- /* @__PURE__ */ jsxs13(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
804
- 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(
805
934
  "pre",
806
935
  {
807
936
  role: "alert",
@@ -809,10 +938,10 @@ function ReportScenario({ scenario, hideTitle = false }) {
809
938
  children: scenario.errorMessage
810
939
  }
811
940
  ) : null,
812
- /* @__PURE__ */ jsx21(ReportSteps, { scenario }),
813
- scenario.docEntries.length > 0 ? /* @__PURE__ */ jsx21("div", { "data-es-docs": true, className: "mt-3", children: /* @__PURE__ */ jsx21(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
814
- /* @__PURE__ */ jsx21(ReportAttachments, { attachments: scenario.attachments }),
815
- /* @__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 })
816
945
  ] })
817
946
  ]
818
947
  }
@@ -820,13 +949,13 @@ function ReportScenario({ scenario, hideTitle = false }) {
820
949
  }
821
950
 
822
951
  // src/components/ReportScenarioList.tsx
823
- import { Fragment as Fragment7, jsx as jsx22 } from "react/jsx-runtime";
952
+ import { Fragment as Fragment8, jsx as jsx23 } from "react/jsx-runtime";
824
953
  function ReportScenarioList({ feature }) {
825
- 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)) });
826
955
  }
827
956
 
828
957
  // src/components/ReportFeature.tsx
829
- import { jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
958
+ import { jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
830
959
  function ReportFeature({ feature }) {
831
960
  const titleId = `${feature.id}-title`;
832
961
  const bodyId = `${feature.id}-body`;
@@ -834,7 +963,7 @@ function ReportFeature({ feature }) {
834
963
  const skipped = s.skipped + s.pending;
835
964
  const collapse = useCollapse();
836
965
  const collapsed = collapse?.isCollapsed(feature.id) ?? false;
837
- return /* @__PURE__ */ jsxs14(
966
+ return /* @__PURE__ */ jsxs15(
838
967
  "section",
839
968
  {
840
969
  id: feature.id,
@@ -842,9 +971,9 @@ function ReportFeature({ feature }) {
842
971
  "aria-labelledby": titleId,
843
972
  className: "overflow-hidden rounded-xl border border-border bg-card shadow-sm",
844
973
  children: [
845
- /* @__PURE__ */ jsxs14("div", { className: "flex items-center justify-between gap-4 border-b border-border bg-muted/40 px-4 py-3", children: [
846
- /* @__PURE__ */ jsxs14("div", { className: "flex min-w-0 items-center gap-2", children: [
847
- 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(
848
977
  "button",
849
978
  {
850
979
  type: "button",
@@ -853,30 +982,30 @@ function ReportFeature({ feature }) {
853
982
  "aria-controls": bodyId,
854
983
  "aria-label": `Toggle ${feature.title}`,
855
984
  className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
856
- 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" })
857
986
  }
858
987
  ) : null,
859
- /* @__PURE__ */ jsxs14("div", { className: "min-w-0", children: [
860
- /* @__PURE__ */ jsx23("h2", { id: titleId, className: "text-sm font-semibold tracking-tight text-foreground", children: feature.title }),
861
- /* @__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 })
862
991
  ] })
863
992
  ] }),
864
- /* @__PURE__ */ jsxs14(
993
+ /* @__PURE__ */ jsxs15(
865
994
  "div",
866
995
  {
867
996
  role: "img",
868
997
  className: "flex shrink-0 items-center gap-2.5 font-mono text-xs font-medium",
869
998
  "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
870
999
  children: [
871
- /* @__PURE__ */ jsxs14("span", { "aria-hidden": true, className: "text-pass", children: [
1000
+ /* @__PURE__ */ jsxs15("span", { "aria-hidden": true, className: "text-pass", children: [
872
1001
  "\u2713",
873
1002
  s.passed
874
1003
  ] }),
875
- /* @__PURE__ */ jsxs14("span", { "aria-hidden": true, className: "text-fail", children: [
1004
+ /* @__PURE__ */ jsxs15("span", { "aria-hidden": true, className: "text-fail", children: [
876
1005
  "\u2717",
877
1006
  s.failed
878
1007
  ] }),
879
- /* @__PURE__ */ jsxs14("span", { "aria-hidden": true, className: "text-skip", children: [
1008
+ /* @__PURE__ */ jsxs15("span", { "aria-hidden": true, className: "text-skip", children: [
880
1009
  "\u25CB",
881
1010
  skipped
882
1011
  ] })
@@ -884,24 +1013,24 @@ function ReportFeature({ feature }) {
884
1013
  }
885
1014
  )
886
1015
  ] }),
887
- /* @__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 }) })
888
1017
  ]
889
1018
  }
890
1019
  );
891
1020
  }
892
1021
 
893
1022
  // src/components/ReportFeatureList.tsx
894
- import { Fragment as Fragment8, jsx as jsx24 } from "react/jsx-runtime";
1023
+ import { Fragment as Fragment9, jsx as jsx25 } from "react/jsx-runtime";
895
1024
  function ReportFeatureList() {
896
1025
  const report = useReport();
897
- 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)) });
898
1027
  }
899
1028
 
900
1029
  // src/components/ui/empty.tsx
901
1030
  import { cva as cva2 } from "class-variance-authority";
902
- import { jsx as jsx25 } from "react/jsx-runtime";
1031
+ import { jsx as jsx26 } from "react/jsx-runtime";
903
1032
  function Empty({ className, ...props }) {
904
- return /* @__PURE__ */ jsx25(
1033
+ return /* @__PURE__ */ jsx26(
905
1034
  "div",
906
1035
  {
907
1036
  "data-slot": "empty",
@@ -914,7 +1043,7 @@ function Empty({ className, ...props }) {
914
1043
  );
915
1044
  }
916
1045
  function EmptyHeader({ className, ...props }) {
917
- return /* @__PURE__ */ jsx25(
1046
+ return /* @__PURE__ */ jsx26(
918
1047
  "div",
919
1048
  {
920
1049
  "data-slot": "empty-header",
@@ -941,7 +1070,7 @@ var emptyMediaVariants = cva2(
941
1070
  }
942
1071
  );
943
1072
  function EmptyTitle({ className, ...props }) {
944
- return /* @__PURE__ */ jsx25(
1073
+ return /* @__PURE__ */ jsx26(
945
1074
  "div",
946
1075
  {
947
1076
  "data-slot": "empty-title",
@@ -952,16 +1081,16 @@ function EmptyTitle({ className, ...props }) {
952
1081
  }
953
1082
 
954
1083
  // src/components/ReportEmpty.tsx
955
- import { jsx as jsx26 } from "react/jsx-runtime";
1084
+ import { jsx as jsx27 } from "react/jsx-runtime";
956
1085
  function ReportEmpty({ message }) {
957
- 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." }) }) });
958
1087
  }
959
1088
 
960
1089
  // src/components/ReportSummary.tsx
961
- 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";
962
1091
  function ReportSummary({ className }) {
963
1092
  const report = useReport();
964
- return /* @__PURE__ */ jsx27(
1093
+ return /* @__PURE__ */ jsx28(
965
1094
  ReportSummaryView,
966
1095
  {
967
1096
  summary: report.summary,
@@ -971,32 +1100,32 @@ function ReportSummary({ className }) {
971
1100
  );
972
1101
  }
973
1102
  function ReportSummaryView({ summary, className, ariaLabel }) {
974
- return /* @__PURE__ */ jsxs15("p", { className: cn("text-sm text-muted-foreground", className), "aria-label": ariaLabel, children: [
975
- /* @__PURE__ */ jsxs15("span", { children: [
976
- /* @__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 }),
977
1106
  " scenario",
978
1107
  summary.total === 1 ? "" : "s"
979
1108
  ] }),
980
1109
  " \xB7 ",
981
- /* @__PURE__ */ jsxs15("span", { "data-status": "passed", className: "text-pass", children: [
1110
+ /* @__PURE__ */ jsxs16("span", { "data-status": "passed", className: "text-pass", children: [
982
1111
  summary.passed,
983
1112
  " passed"
984
1113
  ] }),
985
1114
  " \xB7 ",
986
- /* @__PURE__ */ jsxs15("span", { "data-status": "failed", className: "text-fail", children: [
1115
+ /* @__PURE__ */ jsxs16("span", { "data-status": "failed", className: "text-fail", children: [
987
1116
  summary.failed,
988
1117
  " failed"
989
1118
  ] }),
990
- summary.skipped > 0 ? /* @__PURE__ */ jsxs15(Fragment9, { children: [
1119
+ summary.skipped > 0 ? /* @__PURE__ */ jsxs16(Fragment10, { children: [
991
1120
  " \xB7 ",
992
- /* @__PURE__ */ jsxs15("span", { "data-status": "skipped", className: "text-skip", children: [
1121
+ /* @__PURE__ */ jsxs16("span", { "data-status": "skipped", className: "text-skip", children: [
993
1122
  summary.skipped,
994
1123
  " skipped"
995
1124
  ] })
996
1125
  ] }) : null,
997
- summary.pending > 0 ? /* @__PURE__ */ jsxs15(Fragment9, { children: [
1126
+ summary.pending > 0 ? /* @__PURE__ */ jsxs16(Fragment10, { children: [
998
1127
  " \xB7 ",
999
- /* @__PURE__ */ jsxs15("span", { "data-status": "pending", className: "text-pend", children: [
1128
+ /* @__PURE__ */ jsxs16("span", { "data-status": "pending", className: "text-pend", children: [
1000
1129
  summary.pending,
1001
1130
  " pending"
1002
1131
  ] })
@@ -1004,69 +1133,135 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1004
1133
  ] });
1005
1134
  }
1006
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
+
1007
1185
  // src/components/ReportMeta.tsx
1008
- import { formatDuration as formatDuration4 } from "executable-stories-core/utils/duration";
1009
- import { jsx as jsx28, jsxs as jsxs16 } from "react/jsx-runtime";
1186
+ import { jsx as jsx29, jsxs as jsxs17 } from "react/jsx-runtime";
1010
1187
  function ReportMeta() {
1011
1188
  const report = useReport();
1012
1189
  const items = [];
1190
+ const linkClass = "text-primary underline underline-offset-2";
1013
1191
  if (report.startedAtMs) {
1014
1192
  items.push({ label: "Started", value: new Date(report.startedAtMs).toLocaleString() });
1015
1193
  }
1016
1194
  if (report.durationMs) {
1017
- items.push({ label: "Duration", value: formatDuration4(report.durationMs) });
1195
+ items.push({ label: "Duration", value: formatDuration(report.durationMs) });
1018
1196
  }
1019
1197
  if (report.packageVersion) {
1020
1198
  items.push({ label: "Version", value: report.packageVersion });
1021
1199
  }
1022
- if (report.gitSha) {
1023
- 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
+ });
1024
1211
  }
1025
1212
  if (report.ci) {
1026
1213
  items.push({
1027
1214
  label: "CI",
1028
- value: report.ci.url ? /* @__PURE__ */ jsxs16("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: "text-primary underline underline-offset-2", children: [
1029
- 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),
1030
1217
  report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
1031
- ] }) : `${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
1032
1227
  });
1033
1228
  }
1034
1229
  if (items.length === 0) return null;
1035
- 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: [
1036
- /* @__PURE__ */ jsx28("dt", { className: "font-medium", children: item.label }),
1037
- /* @__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 })
1038
1233
  ] }, item.label)) });
1039
1234
  }
1040
1235
 
1041
1236
  // src/components/ReportSchemaError.tsx
1042
- import { jsx as jsx29, jsxs as jsxs17 } from "react/jsx-runtime";
1237
+ import { jsx as jsx30, jsxs as jsxs18 } from "react/jsx-runtime";
1043
1238
  function ReportSchemaError({ error }) {
1044
- return /* @__PURE__ */ jsxs17("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1045
- /* @__PURE__ */ jsx29("p", { children: /* @__PURE__ */ jsx29("strong", { children: "Report could not be displayed." }) }),
1046
- /* @__PURE__ */ jsx29("p", { children: error.message }),
1047
- 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: [
1048
1243
  "The report bundle is newer than this version of ",
1049
- /* @__PURE__ */ jsx29("code", { children: "executable-stories-react" }),
1244
+ /* @__PURE__ */ jsx30("code", { children: "executable-stories-react" }),
1050
1245
  ". Upgrade the package, or regenerate the report with an older formatters CLI."
1051
1246
  ] }) : null,
1052
- error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs17("details", { children: [
1053
- /* @__PURE__ */ jsxs17("summary", { children: [
1247
+ error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs18("details", { children: [
1248
+ /* @__PURE__ */ jsxs18("summary", { children: [
1054
1249
  error.issues.length,
1055
1250
  " validation issue",
1056
1251
  error.issues.length === 1 ? "" : "s"
1057
1252
  ] }),
1058
- /* @__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") })
1059
1254
  ] }) : null
1060
1255
  ] });
1061
1256
  }
1062
1257
 
1063
1258
  // src/components/ReportShell.tsx
1064
- 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";
1065
1260
  function ReportTitleBlock({ title }) {
1066
- return /* @__PURE__ */ jsxs18(Fragment10, { children: [
1067
- /* @__PURE__ */ jsx30("h1", { children: title ?? "Story Report" }),
1068
- /* @__PURE__ */ jsx30(ReportSummary, {}),
1069
- /* @__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, {})
1070
1265
  ] });
1071
1266
  }
1072
1267
  function ReportErrorShell({
@@ -1075,28 +1270,28 @@ function ReportErrorShell({
1075
1270
  title,
1076
1271
  dataTheme
1077
1272
  }) {
1078
- 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 }) });
1079
1274
  }
1080
1275
 
1081
1276
  // src/components/Report.tsx
1082
- import { jsx as jsx31, jsxs as jsxs19 } from "react/jsx-runtime";
1277
+ import { jsx as jsx32, jsxs as jsxs20 } from "react/jsx-runtime";
1083
1278
  function Report(props) {
1084
1279
  const { report, customRenderers, renderers, className, title, dataTheme } = props;
1085
1280
  const result = unwrapReport(report);
1086
1281
  if (!result.ok) {
1087
- return /* @__PURE__ */ jsx31(ReportErrorShell, { error: result.error, className, title, dataTheme });
1282
+ return /* @__PURE__ */ jsx32(ReportErrorShell, { error: result.error, className, title, dataTheme });
1088
1283
  }
1089
1284
  const data = result.data;
1090
1285
  const hasContent = data.features.length > 0;
1091
- 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: [
1092
- /* @__PURE__ */ jsx31("header", { className: "es-report-header", children: /* @__PURE__ */ jsx31(ReportTitleBlock, { title }) }),
1093
- 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, {})
1094
1289
  ] }) });
1095
1290
  }
1096
1291
 
1097
1292
  // src/components/doc/MermaidView.tsx
1098
1293
  import { useEffect, useRef, useState as useState2 } from "react";
1099
- import { jsx as jsx32, jsxs as jsxs20 } from "react/jsx-runtime";
1294
+ import { jsx as jsx33, jsxs as jsxs21 } from "react/jsx-runtime";
1100
1295
  function prefersDark(el) {
1101
1296
  const themed = el?.closest("[data-theme]");
1102
1297
  if (themed?.dataset.theme === "dark") return true;
@@ -1133,9 +1328,9 @@ function MermaidView({ entry, load }) {
1133
1328
  };
1134
1329
  }, [entry.code, load]);
1135
1330
  if (svg) {
1136
- return /* @__PURE__ */ jsxs20("figure", { ref, className: "my-3", children: [
1137
- entry.title ? /* @__PURE__ */ jsx32("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
1138
- /* @__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(
1139
1334
  "div",
1140
1335
  {
1141
1336
  role: "img",
@@ -1146,18 +1341,18 @@ function MermaidView({ entry, load }) {
1146
1341
  )
1147
1342
  ] });
1148
1343
  }
1149
- return /* @__PURE__ */ jsx32(MermaidSource, { entry, ref });
1344
+ return /* @__PURE__ */ jsx33(MermaidSource, { entry, ref });
1150
1345
  }
1151
1346
 
1152
1347
  // src/components/doc/MermaidDiagram.tsx
1153
- import { jsx as jsx33 } from "react/jsx-runtime";
1348
+ import { jsx as jsx34 } from "react/jsx-runtime";
1154
1349
  var mermaidLoader;
1155
1350
  function loadMermaid() {
1156
1351
  mermaidLoader ??= import("mermaid").then((m) => m.default);
1157
1352
  return mermaidLoader;
1158
1353
  }
1159
1354
  function MermaidDiagram({ entry, load = loadMermaid }) {
1160
- return /* @__PURE__ */ jsx33(MermaidView, { entry, load });
1355
+ return /* @__PURE__ */ jsx34(MermaidView, { entry, load });
1161
1356
  }
1162
1357
 
1163
1358
  // src/components/ScenarioExplorer.tsx
@@ -1167,12 +1362,12 @@ import { useState as useState3 } from "react";
1167
1362
  import "react";
1168
1363
  import { Command as CommandPrimitive } from "cmdk";
1169
1364
  import { SearchIcon } from "lucide-react";
1170
- import { jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
1365
+ import { jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
1171
1366
  function Command({
1172
1367
  className,
1173
1368
  ...props
1174
1369
  }) {
1175
- return /* @__PURE__ */ jsx34(
1370
+ return /* @__PURE__ */ jsx35(
1176
1371
  CommandPrimitive,
1177
1372
  {
1178
1373
  "data-slot": "command",
@@ -1188,14 +1383,14 @@ function CommandInput({
1188
1383
  className,
1189
1384
  ...props
1190
1385
  }) {
1191
- return /* @__PURE__ */ jsxs21(
1386
+ return /* @__PURE__ */ jsxs22(
1192
1387
  "div",
1193
1388
  {
1194
1389
  "data-slot": "command-input-wrapper",
1195
1390
  className: "flex h-9 items-center gap-2 border-b px-3",
1196
1391
  children: [
1197
- /* @__PURE__ */ jsx34(SearchIcon, { className: "size-4 shrink-0 opacity-50" }),
1198
- /* @__PURE__ */ jsx34(
1392
+ /* @__PURE__ */ jsx35(SearchIcon, { className: "size-4 shrink-0 opacity-50" }),
1393
+ /* @__PURE__ */ jsx35(
1199
1394
  CommandPrimitive.Input,
1200
1395
  {
1201
1396
  "data-slot": "command-input",
@@ -1214,7 +1409,7 @@ function CommandList({
1214
1409
  className,
1215
1410
  ...props
1216
1411
  }) {
1217
- return /* @__PURE__ */ jsx34(
1412
+ return /* @__PURE__ */ jsx35(
1218
1413
  CommandPrimitive.List,
1219
1414
  {
1220
1415
  "data-slot": "command-list",
@@ -1229,7 +1424,7 @@ function CommandList({
1229
1424
  function CommandEmpty({
1230
1425
  ...props
1231
1426
  }) {
1232
- return /* @__PURE__ */ jsx34(
1427
+ return /* @__PURE__ */ jsx35(
1233
1428
  CommandPrimitive.Empty,
1234
1429
  {
1235
1430
  "data-slot": "command-empty",
@@ -1242,7 +1437,7 @@ function CommandGroup({
1242
1437
  className,
1243
1438
  ...props
1244
1439
  }) {
1245
- return /* @__PURE__ */ jsx34(
1440
+ return /* @__PURE__ */ jsx35(
1246
1441
  CommandPrimitive.Group,
1247
1442
  {
1248
1443
  "data-slot": "command-group",
@@ -1258,7 +1453,7 @@ function CommandItem({
1258
1453
  className,
1259
1454
  ...props
1260
1455
  }) {
1261
- return /* @__PURE__ */ jsx34(
1456
+ return /* @__PURE__ */ jsx35(
1262
1457
  CommandPrimitive.Item,
1263
1458
  {
1264
1459
  "data-slot": "command-item",
@@ -1272,7 +1467,7 @@ function CommandItem({
1272
1467
  }
1273
1468
 
1274
1469
  // src/components/ScenarioExplorer.tsx
1275
- import { jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
1470
+ import { jsx as jsx36, jsxs as jsxs23 } from "react/jsx-runtime";
1276
1471
  var STATUS_FILTERS = [
1277
1472
  { value: "all", label: "All" },
1278
1473
  { value: "passed", label: "Passed" },
@@ -1283,22 +1478,22 @@ var STATUS_FILTERS = [
1283
1478
  function ScenarioExplorer({ scenarios, summary }) {
1284
1479
  const [status, setStatus] = useState3("all");
1285
1480
  const visible = status === "all" ? scenarios : scenarios.filter((s) => s.status === status);
1286
- return /* @__PURE__ */ jsxs22("div", { className: "font-sans text-foreground", children: [
1287
- 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: [
1288
1483
  summary.total,
1289
1484
  " scenarios \xB7 ",
1290
- /* @__PURE__ */ jsxs22("span", { className: "text-pass", children: [
1485
+ /* @__PURE__ */ jsxs23("span", { className: "text-pass", children: [
1291
1486
  summary.passed,
1292
1487
  " passed"
1293
1488
  ] }),
1294
1489
  " \xB7",
1295
1490
  " ",
1296
- /* @__PURE__ */ jsxs22("span", { className: "text-fail", children: [
1491
+ /* @__PURE__ */ jsxs23("span", { className: "text-fail", children: [
1297
1492
  summary.failed,
1298
1493
  " failed"
1299
1494
  ] })
1300
1495
  ] }) : null,
1301
- /* @__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(
1302
1497
  "button",
1303
1498
  {
1304
1499
  type: "button",
@@ -1313,26 +1508,26 @@ function ScenarioExplorer({ scenarios, summary }) {
1313
1508
  },
1314
1509
  f.value
1315
1510
  )) }),
1316
- /* @__PURE__ */ jsxs22(
1511
+ /* @__PURE__ */ jsxs23(
1317
1512
  Command,
1318
1513
  {
1319
1514
  className: "rounded-lg border border-border bg-card",
1320
1515
  filter: (value, search) => value.toLowerCase().includes(search.toLowerCase()) ? 1 : 0,
1321
1516
  children: [
1322
- /* @__PURE__ */ jsx35(CommandInput, { placeholder: "Search scenarios\u2026" }),
1323
- /* @__PURE__ */ jsxs22(CommandList, { className: "max-h-none", children: [
1324
- /* @__PURE__ */ jsx35(CommandEmpty, { children: "No scenarios match your filters." }),
1325
- /* @__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(
1326
1521
  CommandItem,
1327
1522
  {
1328
1523
  value: `${s.title} ${s.feature} ${s.tags.join(" ")}`,
1329
1524
  asChild: true,
1330
1525
  className: "cursor-pointer",
1331
- children: /* @__PURE__ */ jsxs22("a", { href: s.href, className: "flex items-center gap-3", children: [
1332
- /* @__PURE__ */ jsx35(Badge, { variant: s.status, children: s.status }),
1333
- /* @__PURE__ */ jsx35("span", { className: "min-w-0 flex-1 truncate font-medium", children: s.title }),
1334
- s.tags.slice(0, 3).map((t) => /* @__PURE__ */ jsx35(Badge, { variant: "tag", className: "hidden sm:inline-flex", children: t }, t)),
1335
- /* @__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 })
1336
1531
  ] })
1337
1532
  },
1338
1533
  s.id
@@ -1522,6 +1717,10 @@ var story_report_v1_default = {
1522
1717
  status: {
1523
1718
  $ref: "#/$defs/TestStatus"
1524
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
+ },
1525
1724
  durationMs: {
1526
1725
  type: "number",
1527
1726
  minimum: 0
@@ -2353,9 +2552,18 @@ export {
2353
2552
  ReportTrace,
2354
2553
  STORY_REPORT_SCHEMA_MAJOR,
2355
2554
  ScenarioExplorer,
2555
+ ScenarioRunHistory,
2556
+ ciDisplayName,
2557
+ commitUrl,
2558
+ currentStreak,
2559
+ describeRunHistory,
2356
2560
  err,
2561
+ formatRelativeAge,
2562
+ isReportStale,
2357
2563
  ok,
2358
2564
  parseStoryReport,
2565
+ prUrl,
2566
+ reportLastRunMs,
2359
2567
  storyReportSchema,
2360
2568
  useBuiltinRenderers,
2361
2569
  useCustomRenderers,