executable-stories-react 0.4.1 → 0.6.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.
@@ -4,9 +4,10 @@
4
4
  import {
5
5
  useCallback as useCallback4,
6
6
  useDeferredValue,
7
+ useId as useId2,
7
8
  useMemo as useMemo3,
8
9
  useRef as useRef3,
9
- useState as useState5
10
+ useState as useState7
10
11
  } from "react";
11
12
 
12
13
  // src/result.ts
@@ -106,11 +107,15 @@ var badgeVariants = cva(
106
107
  outline: "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
107
108
  ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
108
109
  link: "text-primary underline-offset-4 [a&]:hover:underline",
109
- passed: "border-pass-border bg-pass-bg text-pass font-mono uppercase",
110
+ // Pass/skip/pending are the quiet states: outline + coloured text, no
111
+ // fill, so a green report reads calm. Only `failed` keeps a tinted
112
+ // fill, making it the single loud badge on the page, so red pops the
113
+ // instant something breaks. Colour-as-accent, matching the KPI cards.
114
+ passed: "border-pass-border text-pass font-mono uppercase",
110
115
  failed: "border-fail-border bg-fail-bg text-fail font-mono uppercase",
111
- skipped: "border-skip-border bg-skip-bg text-skip font-mono uppercase",
112
- pending: "border-pend-border bg-pend-bg text-pend font-mono uppercase",
113
- tag: "border-pass-border bg-pass-bg text-pass font-mono font-medium"
116
+ skipped: "border-skip-border text-skip font-mono uppercase",
117
+ pending: "border-pend-border text-pend font-mono uppercase",
118
+ tag: "border-border text-muted-foreground font-mono font-medium [a&]:hover:bg-muted"
114
119
  }
115
120
  },
116
121
  defaultVariants: {
@@ -197,7 +202,7 @@ function DocTable({ entry }) {
197
202
  entry.label ? /* @__PURE__ */ jsx6("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
198
203
  /* @__PURE__ */ jsx6("div", { tabIndex: 0, className: "overflow-x-auto rounded-md border border-border", children: /* @__PURE__ */ jsxs3("table", { className: "w-full border-collapse text-sm", children: [
199
204
  /* @__PURE__ */ jsx6("thead", { children: /* @__PURE__ */ jsx6("tr", { className: "bg-muted/50", children: entry.columns.map((c) => /* @__PURE__ */ jsx6("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: c }, c)) }) }),
200
- /* @__PURE__ */ jsx6("tbody", { children: entry.rows.map((row, i) => /* @__PURE__ */ jsx6("tr", { children: row.map((cell, j) => /* @__PURE__ */ jsx6("td", { className: "border-b border-border px-3 py-2 text-foreground", children: cell }, j)) }, i)) })
205
+ /* @__PURE__ */ jsx6("tbody", { children: entry.rows.map((row, i) => /* @__PURE__ */ jsx6("tr", { children: row.map((cell2, j) => /* @__PURE__ */ jsx6("td", { className: "border-b border-border px-3 py-2 text-foreground", children: cell2 }, j)) }, i)) })
201
206
  ] }) })
202
207
  ] });
203
208
  }
@@ -663,7 +668,7 @@ function ReportTrace({ spans }) {
663
668
  {
664
669
  className: "min-w-0 flex-1 truncate text-foreground",
665
670
  style: { paddingLeft: `${row.depth * 12}px` },
666
- title: row.statusMessage ? `${row.name} \u2014 ${row.statusMessage}` : row.name,
671
+ title: row.statusMessage ? `${row.name}: ${row.statusMessage}` : row.name,
667
672
  children: row.name
668
673
  }
669
674
  ),
@@ -802,12 +807,12 @@ function useCollapse() {
802
807
  }
803
808
  var STORAGE_KEY = "es-collapsed-ids";
804
809
  function readPersisted() {
805
- if (typeof localStorage === "undefined") return /* @__PURE__ */ new Set();
810
+ if (typeof localStorage === "undefined") return null;
806
811
  try {
807
812
  const raw = localStorage.getItem(STORAGE_KEY);
808
- return raw ? new Set(JSON.parse(raw)) : /* @__PURE__ */ new Set();
813
+ return raw ? new Set(JSON.parse(raw)) : null;
809
814
  } catch {
810
- return /* @__PURE__ */ new Set();
815
+ return null;
811
816
  }
812
817
  }
813
818
  function persist(ids) {
@@ -817,8 +822,10 @@ function persist(ids) {
817
822
  } catch {
818
823
  }
819
824
  }
820
- function useCollapseState() {
821
- const [collapsed, setCollapsed] = useState(readPersisted);
825
+ function useCollapseState(computeDefault) {
826
+ const [collapsed, setCollapsed] = useState(
827
+ () => readPersisted() ?? computeDefault?.() ?? /* @__PURE__ */ new Set()
828
+ );
822
829
  const isCollapsed = useCallback((id) => collapsed.has(id), [collapsed]);
823
830
  const toggle = useCallback((id) => {
824
831
  setCollapsed((prev) => {
@@ -848,20 +855,118 @@ function useCollapseState() {
848
855
 
849
856
  // src/interactive/scenario-actions.tsx
850
857
  import { createContext as createContext4, useContext as useContext5 } from "react";
858
+
859
+ // ../executable-stories-core/dist/utils/scenario-markdown.js
860
+ function cell(value) {
861
+ return value.replaceAll("|", "\\|").replaceAll("\n", " ");
862
+ }
863
+ function stringify(value) {
864
+ if (typeof value === "string") return value;
865
+ try {
866
+ return JSON.stringify(value, null, 2) ?? String(value);
867
+ } catch {
868
+ return String(value);
869
+ }
870
+ }
871
+ function docEntryToMarkdown(entry) {
872
+ const lines = [];
873
+ switch (entry.kind) {
874
+ case "note":
875
+ lines.push(entry.text);
876
+ break;
877
+ case "tag":
878
+ lines.push(entry.names.map((n) => `\`@${n}\``).join(" "));
879
+ break;
880
+ case "kv":
881
+ lines.push(`**${entry.label}:** ${stringify(entry.value)}`);
882
+ break;
883
+ case "code":
884
+ lines.push(`**${entry.label}**`, "", "```" + (entry.lang ?? ""), entry.content, "```");
885
+ break;
886
+ case "table": {
887
+ lines.push(`**${entry.label}**`, "");
888
+ lines.push(`| ${entry.columns.map(cell).join(" | ")} |`);
889
+ lines.push(`| ${entry.columns.map(() => "---").join(" | ")} |`);
890
+ for (const row of entry.rows) lines.push(`| ${row.map(cell).join(" | ")} |`);
891
+ break;
892
+ }
893
+ case "link":
894
+ lines.push(`[${entry.label}](${entry.url})`);
895
+ break;
896
+ case "section":
897
+ lines.push(`### ${entry.title}`, "", entry.markdown);
898
+ break;
899
+ case "mermaid":
900
+ if (entry.title) lines.push(`**${entry.title}**`, "");
901
+ lines.push("```mermaid", entry.code, "```");
902
+ break;
903
+ case "screenshot":
904
+ lines.push(`![${entry.alt ?? "screenshot"}](${entry.path})`);
905
+ break;
906
+ case "video":
907
+ lines.push(`Video: ${entry.path}${entry.caption ? ` \u2014 ${entry.caption}` : ""}`);
908
+ break;
909
+ case "html": {
910
+ const htmlTitle = entry.title ?? "Embedded HTML";
911
+ if (entry.content) {
912
+ lines.push(`**${htmlTitle}**`, "", "```html", entry.content, "```");
913
+ } else if (entry.url) {
914
+ lines.push(`[${htmlTitle}](${entry.url})`);
915
+ } else {
916
+ lines.push(`Embedded HTML: ${entry.path ?? "(inline)"}`);
917
+ }
918
+ break;
919
+ }
920
+ case "custom":
921
+ lines.push(`**${entry.type}**`, "", "```json", stringify(entry.data), "```");
922
+ break;
923
+ }
924
+ for (const child of entry.children ?? []) {
925
+ lines.push("", ...docEntryToMarkdown(child));
926
+ }
927
+ return lines;
928
+ }
929
+ function stepLines(step, ordinal, full) {
930
+ const status = step.status === "passed" ? "" : ` \u2014 **${step.status}**`;
931
+ const lines = [`${ordinal}. **${step.keyword}** ${step.text}${status}`];
932
+ if (!full) return lines;
933
+ if (step.errorMessage) {
934
+ lines.push(...step.errorMessage.split("\n").map((l) => ` > ${l}`));
935
+ }
936
+ for (const entry of step.docEntries) {
937
+ lines.push("", ...docEntryToMarkdown(entry).map((l) => l === "" ? l : ` ${l}`));
938
+ }
939
+ return lines;
940
+ }
941
+ function scenarioToMarkdown(scenario, options = {}) {
942
+ const { variant = "full", meta = [] } = options;
943
+ const full = variant === "full";
944
+ const lines = full ? [`# ${scenario.title}`, ""] : [`## ${scenario.title} _(${scenario.status})_`, ""];
945
+ if (full && meta.length > 0) lines.push(...meta);
946
+ if (scenario.steps.length > 0) {
947
+ if (full) lines.push("", "## Steps", "");
948
+ scenario.steps.forEach((step, i) => lines.push(...stepLines(step, i + 1, full)));
949
+ }
950
+ if (scenario.errorMessage) {
951
+ if (full) lines.push("", "## Failure", "");
952
+ else lines.push("");
953
+ lines.push("```", scenario.errorMessage.trim(), "```");
954
+ }
955
+ if (full && scenario.docEntries.length > 0) {
956
+ lines.push("", "## Docs", "");
957
+ for (const entry of scenario.docEntries) lines.push(...docEntryToMarkdown(entry), "");
958
+ }
959
+ return lines.join("\n").replaceAll(/\n{3,}/g, "\n\n").trimEnd() + (full ? "\n" : "");
960
+ }
961
+
962
+ // src/interactive/scenario-actions.tsx
851
963
  var ScenarioActionsContext = createContext4(null);
852
964
  var ScenarioActionsProvider = ScenarioActionsContext.Provider;
853
965
  function useScenarioActions() {
854
966
  return useContext5(ScenarioActionsContext);
855
967
  }
856
- function scenarioToMarkdown(scenario) {
857
- const lines = [`## ${scenario.title} _(${scenario.status})_`, ""];
858
- for (const step of scenario.steps) {
859
- lines.push(`- **${step.keyword}** ${step.text}`);
860
- }
861
- if (scenario.errorMessage) {
862
- lines.push("", "```", scenario.errorMessage.trim(), "```");
863
- }
864
- return lines.join("\n");
968
+ function scenarioToMarkdown2(scenario) {
969
+ return scenarioToMarkdown(scenario, { variant: "compact" });
865
970
  }
866
971
  function scenarioToPrompt(scenario) {
867
972
  const lines = [
@@ -896,7 +1001,7 @@ function Card({ className, ...props }) {
896
1001
  {
897
1002
  "data-slot": "card",
898
1003
  className: cn(
899
- "flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm",
1004
+ "flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-xs",
900
1005
  className
901
1006
  ),
902
1007
  ...props
@@ -927,52 +1032,89 @@ function CardContent({ className, ...props }) {
927
1032
  );
928
1033
  }
929
1034
 
930
- // src/components/ReportScenario.tsx
1035
+ // src/components/ui/dropdown-menu.tsx
1036
+ import "react";
1037
+ import { Menu as MenuPrimitive } from "@base-ui/react/menu";
1038
+ import { CheckIcon } from "lucide-react";
931
1039
  import { jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
932
- var STATUS_LABEL = {
933
- passed: "Passed",
934
- failed: "Failed",
935
- skipped: "Skipped",
936
- pending: "Pending"
937
- };
938
- var STATUS_GLYPH = {
939
- passed: "\u2713",
940
- failed: "\u2717",
941
- skipped: "\u25CB",
942
- pending: "\u25CB"
943
- };
944
- var STATUS_GLYPH_COLOR = {
945
- passed: "text-pass",
946
- failed: "text-fail",
947
- skipped: "text-skip",
948
- pending: "text-pend"
949
- };
950
- var STATUS_BADGE = {
951
- passed: "passed",
952
- failed: "failed",
953
- skipped: "skipped",
954
- pending: "pending"
1040
+ var itemClasses = "relative flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 [&_svg:not([class*='text-'])]:text-muted-foreground";
1041
+ function DropdownMenu(props) {
1042
+ return /* @__PURE__ */ jsx22(MenuPrimitive.Root, { "data-slot": "dropdown-menu", ...props });
1043
+ }
1044
+ function DropdownMenuTrigger(props) {
1045
+ return /* @__PURE__ */ jsx22(MenuPrimitive.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
1046
+ }
1047
+ function DropdownMenuContent({
1048
+ className,
1049
+ align = "end",
1050
+ sideOffset = 4,
1051
+ ...props
1052
+ }) {
1053
+ return /* @__PURE__ */ jsx22(MenuPrimitive.Portal, { children: /* @__PURE__ */ jsx22(
1054
+ MenuPrimitive.Positioner,
1055
+ {
1056
+ "data-slot": "dropdown-menu-positioner",
1057
+ className: "z-50",
1058
+ align,
1059
+ sideOffset,
1060
+ children: /* @__PURE__ */ jsx22(
1061
+ MenuPrimitive.Popup,
1062
+ {
1063
+ "data-slot": "dropdown-menu-content",
1064
+ className: cn(
1065
+ "z-50 min-w-[11rem] origin-[var(--transform-origin)] overflow-hidden rounded-md border border-border bg-popover p-1 font-sans text-sm text-popover-foreground shadow-md transition-[opacity,transform] data-[ending-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:scale-95 data-[starting-style]:opacity-0",
1066
+ className
1067
+ ),
1068
+ ...props
1069
+ }
1070
+ )
1071
+ }
1072
+ ) });
1073
+ }
1074
+ function DropdownMenuItem({
1075
+ className,
1076
+ ...props
1077
+ }) {
1078
+ return /* @__PURE__ */ jsx22(
1079
+ MenuPrimitive.Item,
1080
+ {
1081
+ "data-slot": "dropdown-menu-item",
1082
+ className: cn(itemClasses, className),
1083
+ ...props
1084
+ }
1085
+ );
1086
+ }
1087
+
1088
+ // src/components/ReportScenario.tsx
1089
+ import { LinkIcon, FileTextIcon, SparklesIcon, MoreHorizontalIcon } from "lucide-react";
1090
+ import { jsx as jsx23, jsxs as jsxs15 } from "react/jsx-runtime";
1091
+ var STATUS_META = {
1092
+ passed: { label: "Passed", glyph: "\u2713", glyphColor: "text-pass", badgeVariant: "passed", showPill: false },
1093
+ failed: { label: "Failed", glyph: "\u2717", glyphColor: "text-fail", badgeVariant: "failed", showPill: true },
1094
+ skipped: { label: "Skipped", glyph: "\u25CB", glyphColor: "text-skip", badgeVariant: "skipped", showPill: true },
1095
+ pending: { label: "Pending", glyph: "\u25CB", glyphColor: "text-pend", badgeVariant: "pending", showPill: true }
955
1096
  };
956
1097
  function ReportScenario({ scenario, hideTitle = false }) {
957
1098
  const titleId = `${scenario.id}-title`;
958
1099
  const bodyId = `${scenario.id}-body`;
959
- const label = scenario.planned ? "Planned" : STATUS_LABEL[scenario.status];
1100
+ const meta = STATUS_META[scenario.status];
1101
+ const label = scenario.planned ? "Planned" : meta.label;
960
1102
  const collapse = useCollapse();
961
1103
  const collapsible = collapse !== null && !hideTitle;
962
1104
  const collapsed = collapsible ? collapse.isCollapsed(scenario.id) : false;
963
1105
  const actions = useScenarioActions();
964
- return /* @__PURE__ */ jsxs14(
1106
+ return /* @__PURE__ */ jsxs15(
965
1107
  Card,
966
1108
  {
967
1109
  id: scenario.id,
968
1110
  "data-status": scenario.status,
969
- ...hideTitle ? { "aria-label": `${scenario.title} \u2014 ${label}` } : { "aria-labelledby": titleId },
1111
+ ...hideTitle ? { "aria-label": `${scenario.title}, ${label}` } : { "aria-labelledby": titleId },
970
1112
  className: "gap-0 py-0",
971
1113
  children: [
972
- /* @__PURE__ */ jsxs14(CardHeader, { className: "px-4 py-3", children: [
973
- /* @__PURE__ */ jsxs14("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
974
- hideTitle ? null : /* @__PURE__ */ jsxs14("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
975
- collapsible ? /* @__PURE__ */ jsx22(
1114
+ /* @__PURE__ */ jsxs15(CardHeader, { className: "px-4 py-3", children: [
1115
+ /* @__PURE__ */ jsxs15("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
1116
+ hideTitle ? null : /* @__PURE__ */ jsxs15("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1117
+ collapsible ? /* @__PURE__ */ jsx23(
976
1118
  "button",
977
1119
  {
978
1120
  type: "button",
@@ -981,60 +1123,49 @@ function ReportScenario({ scenario, hideTitle = false }) {
981
1123
  "aria-controls": bodyId,
982
1124
  "aria-label": `Toggle ${scenario.title}`,
983
1125
  className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
984
- children: /* @__PURE__ */ jsx22("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1126
+ children: /* @__PURE__ */ jsx23("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
985
1127
  }
986
1128
  ) : null,
987
- /* @__PURE__ */ jsx22("span", { "aria-hidden": true, className: cn("text-sm leading-none", STATUS_GLYPH_COLOR[scenario.status]), children: STATUS_GLYPH[scenario.status] }),
988
- /* @__PURE__ */ jsx22("span", { children: scenario.title })
1129
+ /* @__PURE__ */ jsx23("span", { "aria-hidden": true, className: cn("text-sm leading-none", meta.glyphColor), children: meta.glyph }),
1130
+ /* @__PURE__ */ jsx23("span", { children: scenario.title })
989
1131
  ] }),
990
- /* @__PURE__ */ jsxs14("div", { className: "flex shrink-0 items-center gap-2", children: [
991
- /* @__PURE__ */ jsx22(ScenarioRunHistory, { scenarioId: scenario.id }),
992
- scenario.durationMs > 0 ? /* @__PURE__ */ jsx22("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(scenario.durationMs) }) : null,
993
- /* @__PURE__ */ jsx22(Badge, { variant: STATUS_BADGE[scenario.status], "aria-label": `Status: ${label}`, children: label }),
994
- actions ? /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-0.5", children: [
995
- /* @__PURE__ */ jsx22(
996
- "button",
997
- {
998
- type: "button",
999
- onClick: () => actions.copyLink(scenario),
1000
- "aria-label": `Copy link to ${scenario.title}`,
1001
- title: "Copy permalink",
1002
- className: "cursor-pointer rounded px-1 py-0.5 font-mono text-[0.625rem] text-muted-foreground hover:bg-muted hover:text-foreground",
1003
- children: "link"
1004
- }
1005
- ),
1006
- /* @__PURE__ */ jsx22(
1007
- "button",
1132
+ /* @__PURE__ */ jsxs15("div", { className: "flex shrink-0 items-center gap-2", children: [
1133
+ /* @__PURE__ */ jsx23(ScenarioRunHistory, { scenarioId: scenario.id }),
1134
+ scenario.durationMs > 0 ? /* @__PURE__ */ jsx23("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(scenario.durationMs) }) : null,
1135
+ meta.showPill ? /* @__PURE__ */ jsx23(Badge, { variant: meta.badgeVariant, "aria-label": `Status: ${label}`, children: label }) : /* @__PURE__ */ jsx23("span", { className: "sr-only", children: `Status: ${label}` }),
1136
+ actions ? /* @__PURE__ */ jsxs15(DropdownMenu, { children: [
1137
+ /* @__PURE__ */ jsx23(
1138
+ DropdownMenuTrigger,
1008
1139
  {
1009
- type: "button",
1010
- onClick: () => actions.copyMarkdown(scenario),
1011
- "aria-label": `Copy ${scenario.title} as Markdown`,
1012
- title: "Copy as Markdown",
1013
- className: "cursor-pointer rounded px-1 py-0.5 font-mono text-[0.625rem] text-muted-foreground hover:bg-muted hover:text-foreground",
1014
- children: "md"
1140
+ "aria-label": `Actions for ${scenario.title}`,
1141
+ className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
1142
+ children: /* @__PURE__ */ jsx23(MoreHorizontalIcon, { "aria-hidden": true })
1015
1143
  }
1016
1144
  ),
1017
- /* @__PURE__ */ jsx22(
1018
- "button",
1019
- {
1020
- type: "button",
1021
- onClick: () => actions.copyPrompt(scenario),
1022
- "aria-label": `Copy ${scenario.title} as an AI investigation prompt`,
1023
- title: "Copy as AI prompt",
1024
- className: "cursor-pointer rounded px-1 py-0.5 font-mono text-[0.625rem] text-muted-foreground hover:bg-muted hover:text-foreground",
1025
- children: "ai"
1026
- }
1027
- )
1145
+ /* @__PURE__ */ jsxs15(DropdownMenuContent, { align: "end", children: [
1146
+ /* @__PURE__ */ jsxs15(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
1147
+ /* @__PURE__ */ jsx23(LinkIcon, {}),
1148
+ " Copy link"
1149
+ ] }),
1150
+ /* @__PURE__ */ jsxs15(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
1151
+ /* @__PURE__ */ jsx23(FileTextIcon, {}),
1152
+ " Copy as Markdown"
1153
+ ] }),
1154
+ /* @__PURE__ */ jsxs15(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
1155
+ /* @__PURE__ */ jsx23(SparklesIcon, {}),
1156
+ " Explain with AI"
1157
+ ] })
1158
+ ] })
1028
1159
  ] }) : null
1029
1160
  ] })
1030
1161
  ] }),
1031
- scenario.tags.length > 0 || (scenario.tickets?.length ?? 0) > 0 ? /* @__PURE__ */ jsxs14("ul", { "aria-label": "Tags and tickets", className: "flex flex-wrap gap-1.5", children: [
1032
- scenario.tags.map((t) => /* @__PURE__ */ jsx22("li", { children: /* @__PURE__ */ jsx22(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
1033
- (scenario.tickets ?? []).map((ticket) => /* @__PURE__ */ jsx22("li", { children: ticket.url ? /* @__PURE__ */ jsx22("a", { href: ticket.url, target: "_blank", rel: "noreferrer noopener", children: /* @__PURE__ */ jsx22(Badge, { variant: "outline", children: ticket.id }) }) : /* @__PURE__ */ jsx22(Badge, { variant: "outline", children: ticket.id }) }, `ticket-${ticket.id}`))
1162
+ scenario.tags.length > 0 || (scenario.tickets?.length ?? 0) > 0 ? /* @__PURE__ */ jsxs15("ul", { "aria-label": "Tags and tickets", className: "flex flex-wrap gap-1.5", children: [
1163
+ scenario.tags.map((t) => /* @__PURE__ */ jsx23("li", { children: /* @__PURE__ */ jsx23(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
1164
+ (scenario.tickets ?? []).map((ticket) => /* @__PURE__ */ jsx23("li", { children: ticket.url ? /* @__PURE__ */ jsx23("a", { href: ticket.url, target: "_blank", rel: "noreferrer noopener", children: /* @__PURE__ */ jsx23(Badge, { variant: "outline", children: ticket.id }) }) : /* @__PURE__ */ jsx23(Badge, { variant: "outline", children: ticket.id }) }, `ticket-${ticket.id}`))
1034
1165
  ] }) : null
1035
1166
  ] }),
1036
- /* @__PURE__ */ jsxs14(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
1037
- scenario.errorMessage ? /* @__PURE__ */ jsx22(
1167
+ /* @__PURE__ */ jsxs15(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
1168
+ scenario.errorMessage ? /* @__PURE__ */ jsx23(
1038
1169
  "pre",
1039
1170
  {
1040
1171
  role: "alert",
@@ -1042,10 +1173,10 @@ function ReportScenario({ scenario, hideTitle = false }) {
1042
1173
  children: scenario.errorMessage
1043
1174
  }
1044
1175
  ) : null,
1045
- /* @__PURE__ */ jsx22(ReportSteps, { scenario }),
1046
- scenario.docEntries.length > 0 ? /* @__PURE__ */ jsx22("div", { "data-es-docs": true, className: "mt-3", children: /* @__PURE__ */ jsx22(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
1047
- /* @__PURE__ */ jsx22(ReportAttachments, { attachments: scenario.attachments }),
1048
- /* @__PURE__ */ jsx22(ReportTrace, { spans: scenario.otelSpans })
1176
+ /* @__PURE__ */ jsx23(ReportSteps, { scenario }),
1177
+ scenario.docEntries.length > 0 ? /* @__PURE__ */ jsx23("div", { "data-es-docs": true, className: "mt-3", children: /* @__PURE__ */ jsx23(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
1178
+ /* @__PURE__ */ jsx23(ReportAttachments, { attachments: scenario.attachments }),
1179
+ /* @__PURE__ */ jsx23(ReportTrace, { spans: scenario.otelSpans })
1049
1180
  ] })
1050
1181
  ]
1051
1182
  }
@@ -1053,13 +1184,13 @@ function ReportScenario({ scenario, hideTitle = false }) {
1053
1184
  }
1054
1185
 
1055
1186
  // src/components/ReportScenarioList.tsx
1056
- import { Fragment as Fragment8, jsx as jsx23 } from "react/jsx-runtime";
1187
+ import { Fragment as Fragment8, jsx as jsx24 } from "react/jsx-runtime";
1057
1188
  function ReportScenarioList({ feature }) {
1058
- return /* @__PURE__ */ jsx23(Fragment8, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ jsx23(ReportScenario, { scenario }, scenario.id)) });
1189
+ return /* @__PURE__ */ jsx24(Fragment8, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ jsx24(ReportScenario, { scenario }, scenario.id)) });
1059
1190
  }
1060
1191
 
1061
1192
  // src/components/ReportFeature.tsx
1062
- import { jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
1193
+ import { Fragment as Fragment9, jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
1063
1194
  function ReportFeature({ feature }) {
1064
1195
  const titleId = `${feature.id}-title`;
1065
1196
  const bodyId = `${feature.id}-body`;
@@ -1067,74 +1198,80 @@ function ReportFeature({ feature }) {
1067
1198
  const skipped = s.skipped + s.pending;
1068
1199
  const collapse = useCollapse();
1069
1200
  const collapsed = collapse?.isCollapsed(feature.id) ?? false;
1070
- return /* @__PURE__ */ jsxs15(
1071
- "section",
1072
- {
1073
- id: feature.id,
1074
- "data-slot": "feature",
1075
- "aria-labelledby": titleId,
1076
- className: "overflow-hidden rounded-xl border border-border bg-card shadow-sm",
1077
- children: [
1078
- /* @__PURE__ */ jsxs15("div", { className: "flex items-center justify-between gap-4 border-b border-border bg-muted/40 px-4 py-3", children: [
1079
- /* @__PURE__ */ jsxs15("div", { className: "flex min-w-0 items-center gap-2", children: [
1080
- collapse ? /* @__PURE__ */ jsx24(
1081
- "button",
1201
+ return (
1202
+ // Suite = a section heading + a divider, NOT another bordered card. Drops a
1203
+ // layer of nesting so the scenario cards below read as the primary unit.
1204
+ /* @__PURE__ */ jsxs16(
1205
+ "section",
1206
+ {
1207
+ id: feature.id,
1208
+ "data-slot": "feature",
1209
+ "aria-labelledby": titleId,
1210
+ className: "flex flex-col gap-3",
1211
+ children: [
1212
+ /* @__PURE__ */ jsxs16("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1213
+ /* @__PURE__ */ jsxs16("div", { className: "min-w-0", children: [
1214
+ /* @__PURE__ */ jsx25("h2", { id: titleId, className: "text-base font-semibold tracking-tight text-foreground", children: collapse ? /* @__PURE__ */ jsxs16(
1215
+ "button",
1216
+ {
1217
+ type: "button",
1218
+ onClick: () => collapse.toggle(feature.id),
1219
+ "aria-expanded": !collapsed,
1220
+ "aria-controls": bodyId,
1221
+ className: "flex w-full cursor-pointer items-center gap-2 text-left hover:text-foreground/80",
1222
+ children: [
1223
+ /* @__PURE__ */ jsx25("span", { "aria-hidden": true, className: cn("shrink-0 text-xs text-muted-foreground transition-transform", !collapsed && "rotate-90"), children: "\u25B8" }),
1224
+ /* @__PURE__ */ jsx25("span", { className: "truncate", children: feature.title })
1225
+ ]
1226
+ }
1227
+ ) : feature.title }),
1228
+ /* @__PURE__ */ jsx25("p", { className: cn("mt-0.5 truncate font-mono text-xs text-muted-foreground", collapse && "pl-5"), children: feature.sourceFile })
1229
+ ] }),
1230
+ /* @__PURE__ */ jsxs16(
1231
+ "p",
1082
1232
  {
1083
- type: "button",
1084
- onClick: () => collapse.toggle(feature.id),
1085
- "aria-expanded": !collapsed,
1086
- "aria-controls": bodyId,
1087
- "aria-label": `Toggle ${feature.title}`,
1088
- className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
1089
- children: /* @__PURE__ */ jsx24("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1233
+ className: "flex shrink-0 items-center gap-1.5 text-xs text-muted-foreground",
1234
+ "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
1235
+ children: [
1236
+ /* @__PURE__ */ jsxs16("span", { children: [
1237
+ s.passed,
1238
+ " passed"
1239
+ ] }),
1240
+ /* @__PURE__ */ jsx25("span", { "aria-hidden": true, children: "\xB7" }),
1241
+ /* @__PURE__ */ jsxs16("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
1242
+ s.failed,
1243
+ " failed"
1244
+ ] }),
1245
+ skipped > 0 ? /* @__PURE__ */ jsxs16(Fragment9, { children: [
1246
+ /* @__PURE__ */ jsx25("span", { "aria-hidden": true, children: "\xB7" }),
1247
+ /* @__PURE__ */ jsxs16("span", { children: [
1248
+ skipped,
1249
+ " skipped"
1250
+ ] })
1251
+ ] }) : null
1252
+ ]
1090
1253
  }
1091
- ) : null,
1092
- /* @__PURE__ */ jsxs15("div", { className: "min-w-0", children: [
1093
- /* @__PURE__ */ jsx24("h2", { id: titleId, className: "text-sm font-semibold tracking-tight text-foreground", children: feature.title }),
1094
- /* @__PURE__ */ jsx24("p", { className: "mt-0.5 truncate font-mono text-xs text-muted-foreground", children: feature.sourceFile })
1095
- ] })
1254
+ )
1096
1255
  ] }),
1097
- /* @__PURE__ */ jsxs15(
1098
- "div",
1099
- {
1100
- role: "img",
1101
- className: "flex shrink-0 items-center gap-2.5 font-mono text-xs font-medium",
1102
- "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
1103
- children: [
1104
- /* @__PURE__ */ jsxs15("span", { "aria-hidden": true, className: "text-pass", children: [
1105
- "\u2713",
1106
- s.passed
1107
- ] }),
1108
- /* @__PURE__ */ jsxs15("span", { "aria-hidden": true, className: "text-fail", children: [
1109
- "\u2717",
1110
- s.failed
1111
- ] }),
1112
- /* @__PURE__ */ jsxs15("span", { "aria-hidden": true, className: "text-skip", children: [
1113
- "\u25CB",
1114
- skipped
1115
- ] })
1116
- ]
1117
- }
1118
- )
1119
- ] }),
1120
- /* @__PURE__ */ jsx24("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-2 p-3", children: /* @__PURE__ */ jsx24(ReportScenarioList, { feature }) })
1121
- ]
1122
- }
1256
+ /* @__PURE__ */ jsx25("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-3", children: /* @__PURE__ */ jsx25(ReportScenarioList, { feature }) })
1257
+ ]
1258
+ }
1259
+ )
1123
1260
  );
1124
1261
  }
1125
1262
 
1126
1263
  // src/components/ReportFeatureList.tsx
1127
- import { Fragment as Fragment9, jsx as jsx25 } from "react/jsx-runtime";
1264
+ import { jsx as jsx26 } from "react/jsx-runtime";
1128
1265
  function ReportFeatureList() {
1129
1266
  const report = useReport();
1130
- return /* @__PURE__ */ jsx25(Fragment9, { children: report.features.map((feature) => /* @__PURE__ */ jsx25(ReportFeature, { feature }, feature.id)) });
1267
+ return /* @__PURE__ */ jsx26("div", { className: "flex flex-col gap-8", children: report.features.map((feature) => /* @__PURE__ */ jsx26(ReportFeature, { feature }, feature.id)) });
1131
1268
  }
1132
1269
 
1133
1270
  // src/components/ui/empty.tsx
1134
1271
  import { cva as cva2 } from "class-variance-authority";
1135
- import { jsx as jsx26 } from "react/jsx-runtime";
1272
+ import { jsx as jsx27 } from "react/jsx-runtime";
1136
1273
  function Empty({ className, ...props }) {
1137
- return /* @__PURE__ */ jsx26(
1274
+ return /* @__PURE__ */ jsx27(
1138
1275
  "div",
1139
1276
  {
1140
1277
  "data-slot": "empty",
@@ -1147,7 +1284,7 @@ function Empty({ className, ...props }) {
1147
1284
  );
1148
1285
  }
1149
1286
  function EmptyHeader({ className, ...props }) {
1150
- return /* @__PURE__ */ jsx26(
1287
+ return /* @__PURE__ */ jsx27(
1151
1288
  "div",
1152
1289
  {
1153
1290
  "data-slot": "empty-header",
@@ -1174,7 +1311,7 @@ var emptyMediaVariants = cva2(
1174
1311
  }
1175
1312
  );
1176
1313
  function EmptyTitle({ className, ...props }) {
1177
- return /* @__PURE__ */ jsx26(
1314
+ return /* @__PURE__ */ jsx27(
1178
1315
  "div",
1179
1316
  {
1180
1317
  "data-slot": "empty-title",
@@ -1185,16 +1322,16 @@ function EmptyTitle({ className, ...props }) {
1185
1322
  }
1186
1323
 
1187
1324
  // src/components/ReportEmpty.tsx
1188
- import { jsx as jsx27 } from "react/jsx-runtime";
1325
+ import { jsx as jsx28 } from "react/jsx-runtime";
1189
1326
  function ReportEmpty({ message }) {
1190
- return /* @__PURE__ */ jsx27(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ jsx27(EmptyHeader, { children: /* @__PURE__ */ jsx27(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1327
+ return /* @__PURE__ */ jsx28(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ jsx28(EmptyHeader, { children: /* @__PURE__ */ jsx28(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1191
1328
  }
1192
1329
 
1193
1330
  // src/components/ReportSummary.tsx
1194
- import { Fragment as Fragment10, jsx as jsx28, jsxs as jsxs16 } from "react/jsx-runtime";
1331
+ import { jsx as jsx29, jsxs as jsxs17 } from "react/jsx-runtime";
1195
1332
  function ReportSummary({ className }) {
1196
1333
  const report = useReport();
1197
- return /* @__PURE__ */ jsx28(
1334
+ return /* @__PURE__ */ jsx29(
1198
1335
  ReportSummaryView,
1199
1336
  {
1200
1337
  summary: report.summary,
@@ -1203,38 +1340,54 @@ function ReportSummary({ className }) {
1203
1340
  }
1204
1341
  );
1205
1342
  }
1343
+ var TONE = {
1344
+ total: { num: "text-foreground", dot: "bg-muted-foreground/50" },
1345
+ passed: { num: "text-pass", dot: "bg-pass" },
1346
+ failed: { num: "text-fail", dot: "bg-fail" },
1347
+ skipped: { num: "text-skip", dot: "bg-skip" },
1348
+ pending: { num: "text-pend", dot: "bg-pend" }
1349
+ };
1206
1350
  function ReportSummaryView({ summary, className, ariaLabel }) {
1207
- return /* @__PURE__ */ jsxs16("p", { className: cn("text-sm text-muted-foreground", className), "aria-label": ariaLabel, children: [
1208
- /* @__PURE__ */ jsxs16("span", { children: [
1209
- /* @__PURE__ */ jsx28("strong", { className: "font-semibold text-foreground", children: summary.total }),
1210
- " scenario",
1211
- summary.total === 1 ? "" : "s"
1212
- ] }),
1213
- " \xB7 ",
1214
- /* @__PURE__ */ jsxs16("span", { "data-status": "passed", className: "text-pass", children: [
1215
- summary.passed,
1216
- " passed"
1217
- ] }),
1218
- " \xB7 ",
1219
- /* @__PURE__ */ jsxs16("span", { "data-status": "failed", className: "text-fail", children: [
1220
- summary.failed,
1221
- " failed"
1222
- ] }),
1223
- summary.skipped > 0 ? /* @__PURE__ */ jsxs16(Fragment10, { children: [
1224
- " \xB7 ",
1225
- /* @__PURE__ */ jsxs16("span", { "data-status": "skipped", className: "text-skip", children: [
1226
- summary.skipped,
1227
- " skipped"
1228
- ] })
1229
- ] }) : null,
1230
- summary.pending > 0 ? /* @__PURE__ */ jsxs16(Fragment10, { children: [
1231
- " \xB7 ",
1232
- /* @__PURE__ */ jsxs16("span", { "data-status": "pending", className: "text-pend", children: [
1233
- summary.pending,
1234
- " pending"
1235
- ] })
1236
- ] }) : null
1237
- ] });
1351
+ const cards = [
1352
+ { status: "total", label: "Total", value: summary.total },
1353
+ { status: "passed", label: "Passed", value: summary.passed },
1354
+ { status: "failed", label: "Failed", value: summary.failed },
1355
+ { status: "skipped", label: "Skipped", value: summary.skipped }
1356
+ ];
1357
+ if (summary.pending > 0) {
1358
+ cards.push({ status: "pending", label: "Pending", value: summary.pending });
1359
+ }
1360
+ return /* @__PURE__ */ jsx29(
1361
+ "dl",
1362
+ {
1363
+ className: cn(
1364
+ "grid grid-cols-2 gap-3",
1365
+ // Columns follow the card count so the row is always full — 5 statuses
1366
+ // (pending present) never leave a lone orphan card below a row of four.
1367
+ cards.length === 5 ? "sm:grid-cols-5" : "sm:grid-cols-4",
1368
+ className
1369
+ ),
1370
+ "aria-label": ariaLabel,
1371
+ children: cards.map((c) => {
1372
+ const tone = TONE[c.status];
1373
+ return /* @__PURE__ */ jsxs17(
1374
+ "div",
1375
+ {
1376
+ "data-status": c.status,
1377
+ className: "rounded-lg border bg-card px-4 py-3",
1378
+ children: [
1379
+ /* @__PURE__ */ jsxs17("dt", { className: "flex items-center gap-1.5 font-mono text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: [
1380
+ /* @__PURE__ */ jsx29("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
1381
+ c.label
1382
+ ] }),
1383
+ /* @__PURE__ */ jsx29("dd", { className: cn("mt-2 text-3xl font-semibold leading-none tracking-tight tabular-nums", tone.num), children: c.value })
1384
+ ]
1385
+ },
1386
+ c.status
1387
+ );
1388
+ })
1389
+ }
1390
+ );
1238
1391
  }
1239
1392
 
1240
1393
  // src/lib/provenance.ts
@@ -1287,7 +1440,7 @@ function prUrl(ci) {
1287
1440
  }
1288
1441
 
1289
1442
  // src/components/ReportMeta.tsx
1290
- import { jsx as jsx29, jsxs as jsxs17 } from "react/jsx-runtime";
1443
+ import { jsx as jsx30, jsxs as jsxs18 } from "react/jsx-runtime";
1291
1444
  function ReportMeta() {
1292
1445
  const report = useReport();
1293
1446
  const items = [];
@@ -1310,13 +1463,13 @@ function ReportMeta() {
1310
1463
  const short = sha.slice(0, 8);
1311
1464
  items.push({
1312
1465
  label: "Commit",
1313
- value: url ? /* @__PURE__ */ jsx29("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1466
+ value: url ? /* @__PURE__ */ jsx30("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1314
1467
  });
1315
1468
  }
1316
1469
  if (report.ci) {
1317
1470
  items.push({
1318
1471
  label: "CI",
1319
- value: report.ci.url ? /* @__PURE__ */ jsxs17("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1472
+ value: report.ci.url ? /* @__PURE__ */ jsxs18("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1320
1473
  ciDisplayName(report.ci.name),
1321
1474
  report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
1322
1475
  ] }) : `${ciDisplayName(report.ci.name)}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
@@ -1327,54 +1480,144 @@ function ReportMeta() {
1327
1480
  const label = `#${report.ci.prNumber}`;
1328
1481
  items.push({
1329
1482
  label: "PR",
1330
- value: url ? /* @__PURE__ */ jsx29("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1483
+ value: url ? /* @__PURE__ */ jsx30("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1331
1484
  });
1332
1485
  }
1333
1486
  if (items.length === 0) return null;
1334
- return /* @__PURE__ */ jsx29("dl", { "aria-label": "Run metadata", className: "flex flex-wrap gap-x-6 gap-y-1 text-xs text-muted-foreground", children: items.map((item) => /* @__PURE__ */ jsxs17("div", { className: "flex gap-1.5", children: [
1335
- /* @__PURE__ */ jsx29("dt", { className: "font-medium", children: item.label }),
1336
- /* @__PURE__ */ jsx29("dd", { className: "font-mono text-foreground", children: item.value })
1337
- ] }, item.label)) });
1487
+ return (
1488
+ // Run metadata is useful but secondary to "what failed" — tuck it into a
1489
+ // collapsed disclosure so it doesn't push the result down. Native <details>
1490
+ // so it works with no JS (the SSR/no-hydration report still toggles).
1491
+ /* @__PURE__ */ jsxs18("details", { "data-es-meta-card": true, className: "es-run-details group rounded-lg border border-border bg-card text-xs", children: [
1492
+ /* @__PURE__ */ jsxs18("summary", { className: "flex cursor-pointer list-none select-none items-center gap-1.5 px-4 py-2 font-medium text-muted-foreground [&::-webkit-details-marker]:hidden", children: [
1493
+ /* @__PURE__ */ jsx30("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
1494
+ "Run details"
1495
+ ] }),
1496
+ /* @__PURE__ */ jsx30("dl", { className: "flex flex-wrap gap-x-5 gap-y-2 border-t border-border px-4 py-3 text-muted-foreground", "aria-label": "Run metadata", children: items.map((item) => /* @__PURE__ */ jsxs18("div", { className: "flex items-baseline gap-1.5", children: [
1497
+ /* @__PURE__ */ jsx30("dt", { className: "font-medium", children: item.label }),
1498
+ /* @__PURE__ */ jsx30("dd", { className: "font-mono text-foreground", children: item.value })
1499
+ ] }, item.label)) })
1500
+ ] })
1501
+ );
1338
1502
  }
1339
1503
 
1340
1504
  // src/components/ReportSchemaError.tsx
1341
- import { jsx as jsx30, jsxs as jsxs18 } from "react/jsx-runtime";
1505
+ import { jsx as jsx31, jsxs as jsxs19 } from "react/jsx-runtime";
1342
1506
  function ReportSchemaError({ error }) {
1343
- return /* @__PURE__ */ jsxs18("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1344
- /* @__PURE__ */ jsx30("p", { children: /* @__PURE__ */ jsx30("strong", { children: "Report could not be displayed." }) }),
1345
- /* @__PURE__ */ jsx30("p", { children: error.message }),
1346
- error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs18("p", { children: [
1507
+ return /* @__PURE__ */ jsxs19("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1508
+ /* @__PURE__ */ jsx31("p", { children: /* @__PURE__ */ jsx31("strong", { children: "Report could not be displayed." }) }),
1509
+ /* @__PURE__ */ jsx31("p", { children: error.message }),
1510
+ error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs19("p", { children: [
1347
1511
  "The report bundle is newer than this version of ",
1348
- /* @__PURE__ */ jsx30("code", { children: "executable-stories-react" }),
1512
+ /* @__PURE__ */ jsx31("code", { children: "executable-stories-react" }),
1349
1513
  ". Upgrade the package, or regenerate the report with an older formatters CLI."
1350
1514
  ] }) : null,
1351
- error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs18("details", { children: [
1352
- /* @__PURE__ */ jsxs18("summary", { children: [
1515
+ error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs19("details", { children: [
1516
+ /* @__PURE__ */ jsxs19("summary", { children: [
1353
1517
  error.issues.length,
1354
1518
  " validation issue",
1355
1519
  error.issues.length === 1 ? "" : "s"
1356
1520
  ] }),
1357
- /* @__PURE__ */ jsx30("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1521
+ /* @__PURE__ */ jsx31("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1358
1522
  ] }) : null
1359
1523
  ] });
1360
1524
  }
1361
1525
 
1362
1526
  // src/components/ReportShell.tsx
1363
- import { Fragment as Fragment11, jsx as jsx31, jsxs as jsxs19 } from "react/jsx-runtime";
1364
- function ReportTitleBlock({ title }) {
1365
- return /* @__PURE__ */ jsxs19(Fragment11, { children: [
1366
- /* @__PURE__ */ jsx31("h1", { children: title ?? "Story Report" }),
1367
- /* @__PURE__ */ jsx31(ReportSummary, {}),
1368
- /* @__PURE__ */ jsx31(ReportMeta, {})
1369
- ] });
1370
- }
1527
+ import { Fragment as Fragment10, jsx as jsx32, jsxs as jsxs20 } from "react/jsx-runtime";
1371
1528
  function ReportErrorShell({
1372
1529
  error,
1373
1530
  className,
1374
1531
  title,
1375
1532
  dataTheme
1376
1533
  }) {
1377
- return /* @__PURE__ */ jsx31("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ jsx31(ReportSchemaError, { error }) });
1534
+ return /* @__PURE__ */ jsx32("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ jsx32(ReportSchemaError, { error }) });
1535
+ }
1536
+
1537
+ // src/interactive/ReportInteractive.tsx
1538
+ import { Monitor, Moon, Sun } from "lucide-react";
1539
+
1540
+ // src/components/ui/switch.tsx
1541
+ import "react";
1542
+ import { Switch as SwitchPrimitive } from "@base-ui/react/switch";
1543
+ import { jsx as jsx33 } from "react/jsx-runtime";
1544
+ function Switch({
1545
+ className,
1546
+ ...props
1547
+ }) {
1548
+ return /* @__PURE__ */ jsx33(
1549
+ SwitchPrimitive.Root,
1550
+ {
1551
+ "data-slot": "switch",
1552
+ className: cn(
1553
+ "peer inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs outline-none transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[checked]:bg-primary data-[unchecked]:bg-input",
1554
+ className
1555
+ ),
1556
+ ...props,
1557
+ children: /* @__PURE__ */ jsx33(
1558
+ SwitchPrimitive.Thumb,
1559
+ {
1560
+ "data-slot": "switch-thumb",
1561
+ className: "pointer-events-none block size-4 rounded-full bg-background ring-0 transition-transform data-[checked]:translate-x-[calc(100%-2px)] data-[unchecked]:translate-x-0"
1562
+ }
1563
+ )
1564
+ }
1565
+ );
1566
+ }
1567
+
1568
+ // src/components/ui/toggle-group.tsx
1569
+ import * as React5 from "react";
1570
+ import { ToggleGroup as ToggleGroupPrimitive } from "@base-ui/react/toggle-group";
1571
+ import { Toggle as TogglePrimitive } from "@base-ui/react/toggle";
1572
+ import { cva as cva3 } from "class-variance-authority";
1573
+ import { jsx as jsx34 } from "react/jsx-runtime";
1574
+ var toggleItemVariants = cva3(
1575
+ "inline-flex items-center justify-center gap-1.5 rounded-md font-medium whitespace-nowrap text-muted-foreground transition-[color,background-color,box-shadow] outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 data-[pressed]:bg-background data-[pressed]:text-foreground data-[pressed]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
1576
+ {
1577
+ variants: {
1578
+ size: {
1579
+ default: "h-9 px-3 text-sm",
1580
+ sm: "h-8 px-2.5 text-[0.82rem]"
1581
+ }
1582
+ },
1583
+ defaultVariants: { size: "default" }
1584
+ }
1585
+ );
1586
+ var ToggleGroupContext = React5.createContext({
1587
+ size: "default"
1588
+ });
1589
+ function ToggleGroup({
1590
+ className,
1591
+ size,
1592
+ children,
1593
+ ...props
1594
+ }) {
1595
+ return /* @__PURE__ */ jsx34(
1596
+ ToggleGroupPrimitive,
1597
+ {
1598
+ "data-slot": "toggle-group",
1599
+ className: cn("flex w-fit items-center gap-0.5 rounded-lg bg-muted p-0.5", className),
1600
+ ...props,
1601
+ children: /* @__PURE__ */ jsx34(ToggleGroupContext.Provider, { value: { size }, children })
1602
+ }
1603
+ );
1604
+ }
1605
+ function ToggleGroupItem({
1606
+ className,
1607
+ children,
1608
+ size,
1609
+ ...props
1610
+ }) {
1611
+ const context = React5.useContext(ToggleGroupContext);
1612
+ return /* @__PURE__ */ jsx34(
1613
+ TogglePrimitive,
1614
+ {
1615
+ "data-slot": "toggle-group-item",
1616
+ className: cn(toggleItemVariants({ size: context.size ?? size }), "flex-1 shrink-0", className),
1617
+ ...props,
1618
+ children
1619
+ }
1620
+ );
1378
1621
  }
1379
1622
 
1380
1623
  // src/interactive/ReportSearch.tsx
@@ -1382,7 +1625,30 @@ import {
1382
1625
  forwardRef,
1383
1626
  useId
1384
1627
  } from "react";
1385
- import { jsx as jsx32, jsxs as jsxs20 } from "react/jsx-runtime";
1628
+ import { Search } from "lucide-react";
1629
+
1630
+ // src/components/ui/input.tsx
1631
+ import "react";
1632
+ import { jsx as jsx35 } from "react/jsx-runtime";
1633
+ function Input({ className, type, ...props }) {
1634
+ return /* @__PURE__ */ jsx35(
1635
+ "input",
1636
+ {
1637
+ type,
1638
+ "data-slot": "input",
1639
+ className: cn(
1640
+ "h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30",
1641
+ "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
1642
+ "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
1643
+ className
1644
+ ),
1645
+ ...props
1646
+ }
1647
+ );
1648
+ }
1649
+
1650
+ // src/interactive/ReportSearch.tsx
1651
+ import { jsx as jsx36, jsxs as jsxs21 } from "react/jsx-runtime";
1386
1652
  var ReportSearch = forwardRef(
1387
1653
  function ReportSearch2(props, ref) {
1388
1654
  const {
@@ -1390,11 +1656,11 @@ var ReportSearch = forwardRef(
1390
1656
  onChange,
1391
1657
  matchedCount,
1392
1658
  totalCount,
1393
- placeholder = "Search scenarios, tags, or step text\u2026",
1659
+ placeholder = "Search scenarios or tags\u2026",
1394
1660
  className
1395
1661
  } = props;
1396
1662
  const inputId = useId();
1397
- const showCounts = typeof matchedCount === "number" && typeof totalCount === "number";
1663
+ const showCounts = value.trim().length > 0 && typeof matchedCount === "number" && typeof totalCount === "number";
1398
1664
  function handleChange(e) {
1399
1665
  onChange(e.target.value);
1400
1666
  }
@@ -1404,24 +1670,46 @@ var ReportSearch = forwardRef(
1404
1670
  e.preventDefault();
1405
1671
  }
1406
1672
  }
1407
- return /* @__PURE__ */ jsxs20("div", { className: ["es-search", className].filter(Boolean).join(" "), children: [
1408
- /* @__PURE__ */ jsx32("label", { htmlFor: inputId, className: "es-search-label", children: "Search" }),
1409
- /* @__PURE__ */ jsx32(
1410
- "input",
1673
+ return /* @__PURE__ */ jsxs21("div", { className: cn("es-search flex flex-col gap-1", className), children: [
1674
+ /* @__PURE__ */ jsx36("label", { htmlFor: inputId, className: "sr-only", children: "Search" }),
1675
+ /* @__PURE__ */ jsxs21("div", { className: "relative w-full sm:w-72", children: [
1676
+ /* @__PURE__ */ jsx36(
1677
+ Search,
1678
+ {
1679
+ "aria-hidden": true,
1680
+ className: "pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground"
1681
+ }
1682
+ ),
1683
+ /* @__PURE__ */ jsx36(
1684
+ Input,
1685
+ {
1686
+ ref,
1687
+ id: inputId,
1688
+ type: "search",
1689
+ value,
1690
+ onChange: handleChange,
1691
+ onKeyDown: handleKeyDown,
1692
+ placeholder,
1693
+ autoComplete: "off",
1694
+ spellCheck: false,
1695
+ "aria-keyshortcuts": "/",
1696
+ className: "pl-8"
1697
+ }
1698
+ )
1699
+ ] }),
1700
+ showCounts ? /* @__PURE__ */ jsxs21(
1701
+ "span",
1411
1702
  {
1412
- ref,
1413
- id: inputId,
1414
- type: "search",
1415
- value,
1416
- onChange: handleChange,
1417
- onKeyDown: handleKeyDown,
1418
- placeholder,
1419
- autoComplete: "off",
1420
- spellCheck: false,
1421
- "aria-keyshortcuts": "/"
1703
+ className: "text-xs tabular-nums text-muted-foreground",
1704
+ "aria-live": "polite",
1705
+ children: [
1706
+ matchedCount,
1707
+ " of ",
1708
+ totalCount,
1709
+ " scenarios"
1710
+ ]
1422
1711
  }
1423
- ),
1424
- showCounts ? /* @__PURE__ */ jsx32("span", { className: "es-search-counts", "aria-live": "polite", children: value ? `${matchedCount} of ${totalCount}` : `${totalCount} total` }) : null
1712
+ ) : null
1425
1713
  ] });
1426
1714
  }
1427
1715
  );
@@ -1450,7 +1738,7 @@ function scrollToScenarioId(scenarioId, options = {}) {
1450
1738
  }
1451
1739
 
1452
1740
  // src/interactive/ReportFailureBanner.tsx
1453
- import { jsx as jsx33, jsxs as jsxs21 } from "react/jsx-runtime";
1741
+ import { jsx as jsx37, jsxs as jsxs22 } from "react/jsx-runtime";
1454
1742
  function ReportFailureBanner({ failures }) {
1455
1743
  const first = failures[0];
1456
1744
  const jumpToFirst = useCallback2(() => {
@@ -1458,7 +1746,7 @@ function ReportFailureBanner({ failures }) {
1458
1746
  scrollToScenarioId(first.scenarioId);
1459
1747
  }, [first]);
1460
1748
  if (failures.length === 0) return null;
1461
- return /* @__PURE__ */ jsxs21(
1749
+ return /* @__PURE__ */ jsxs22(
1462
1750
  "div",
1463
1751
  {
1464
1752
  className: "es-failure-banner",
@@ -1466,20 +1754,20 @@ function ReportFailureBanner({ failures }) {
1466
1754
  "aria-live": "polite",
1467
1755
  "aria-label": "Failure summary",
1468
1756
  children: [
1469
- /* @__PURE__ */ jsxs21("span", { className: "es-failure-banner-text", children: [
1470
- /* @__PURE__ */ jsx33("strong", { children: failures.length }),
1757
+ /* @__PURE__ */ jsxs22("span", { className: "es-failure-banner-text", children: [
1758
+ /* @__PURE__ */ jsx37("strong", { children: failures.length }),
1471
1759
  " ",
1472
1760
  "failure",
1473
1761
  failures.length === 1 ? "" : "s"
1474
1762
  ] }),
1475
- /* @__PURE__ */ jsx33(
1763
+ /* @__PURE__ */ jsx37(
1476
1764
  "button",
1477
1765
  {
1478
1766
  type: "button",
1479
1767
  className: "es-failure-banner-jump",
1480
1768
  onClick: jumpToFirst,
1481
- "aria-label": "Jump to first failure",
1482
- children: "Jump to first \u2193"
1769
+ "aria-label": "View first failure",
1770
+ children: "View first failure \u2193"
1483
1771
  }
1484
1772
  )
1485
1773
  ]
@@ -1489,7 +1777,7 @@ function ReportFailureBanner({ failures }) {
1489
1777
 
1490
1778
  // src/interactive/ReportFreshness.tsx
1491
1779
  import { useEffect, useState as useState2 } from "react";
1492
- import { Fragment as Fragment12, jsx as jsx34, jsxs as jsxs22 } from "react/jsx-runtime";
1780
+ import { Fragment as Fragment11, jsx as jsx38, jsxs as jsxs23 } from "react/jsx-runtime";
1493
1781
  function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
1494
1782
  const [now, setNow] = useState2(nowMs);
1495
1783
  useEffect(() => {
@@ -1497,7 +1785,7 @@ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
1497
1785
  }, [nowMs]);
1498
1786
  if (lastRunMs === void 0 || now === void 0) return null;
1499
1787
  const age = formatRelativeAge(lastRunMs, now);
1500
- const runLink = ciUrl ? /* @__PURE__ */ jsx34(
1788
+ const runLink = ciUrl ? /* @__PURE__ */ jsx38(
1501
1789
  "a",
1502
1790
  {
1503
1791
  href: ciUrl,
@@ -1508,22 +1796,22 @@ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
1508
1796
  }
1509
1797
  ) : null;
1510
1798
  if (isReportStale(lastRunMs, staleAfterDays, now)) {
1511
- return /* @__PURE__ */ jsxs22("div", { className: "es-stale-banner", role: "status", "aria-label": "Report freshness", children: [
1512
- /* @__PURE__ */ jsxs22("span", { children: [
1513
- /* @__PURE__ */ jsxs22("strong", { children: [
1799
+ return /* @__PURE__ */ jsxs23("div", { className: "es-stale-banner", role: "status", "aria-label": "Report freshness", children: [
1800
+ /* @__PURE__ */ jsxs23("span", { children: [
1801
+ /* @__PURE__ */ jsxs23("strong", { children: [
1514
1802
  "Last verified ",
1515
1803
  age,
1516
1804
  "."
1517
1805
  ] }),
1518
- " This report may no longer match the current behavior \u2014 look for a newer run."
1806
+ " This report may no longer match the current behavior. Look for a newer run."
1519
1807
  ] }),
1520
1808
  runLink
1521
1809
  ] });
1522
1810
  }
1523
- return /* @__PURE__ */ jsxs22("p", { className: "es-freshness", "aria-label": "Report freshness", children: [
1811
+ return /* @__PURE__ */ jsxs23("p", { className: "es-freshness", "aria-label": "Report freshness", children: [
1524
1812
  "Verified ",
1525
1813
  age,
1526
- runLink ? /* @__PURE__ */ jsxs22(Fragment12, { children: [
1814
+ runLink ? /* @__PURE__ */ jsxs23(Fragment11, { children: [
1527
1815
  " \xB7 ",
1528
1816
  runLink
1529
1817
  ] }) : null
@@ -1531,15 +1819,15 @@ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
1531
1819
  }
1532
1820
 
1533
1821
  // src/interactive/ReportLastRunDelta.tsx
1534
- import { Fragment as Fragment13, jsx as jsx35, jsxs as jsxs23 } from "react/jsx-runtime";
1822
+ import { Fragment as Fragment12, jsx as jsx39, jsxs as jsxs24 } from "react/jsx-runtime";
1535
1823
  var MAX_LINKS = 5;
1536
1824
  function ScenarioLinks({ scenarios, tone }) {
1537
- return /* @__PURE__ */ jsxs23(Fragment13, { children: [
1538
- scenarios.slice(0, MAX_LINKS).map((s, i) => /* @__PURE__ */ jsxs23("span", { children: [
1825
+ return /* @__PURE__ */ jsxs24(Fragment12, { children: [
1826
+ scenarios.slice(0, MAX_LINKS).map((s, i) => /* @__PURE__ */ jsxs24("span", { children: [
1539
1827
  i > 0 ? ", " : "",
1540
- /* @__PURE__ */ jsx35("a", { href: `#${s.id}`, className: cn("underline underline-offset-2 hover:no-underline", tone), children: s.title })
1828
+ /* @__PURE__ */ jsx39("a", { href: `#${s.id}`, className: cn("underline underline-offset-2 hover:no-underline", tone), children: s.title })
1541
1829
  ] }, s.id)),
1542
- scenarios.length > MAX_LINKS ? /* @__PURE__ */ jsxs23("span", { children: [
1830
+ scenarios.length > MAX_LINKS ? /* @__PURE__ */ jsxs24("span", { children: [
1543
1831
  " and ",
1544
1832
  scenarios.length - MAX_LINKS,
1545
1833
  " more"
@@ -1556,23 +1844,23 @@ function ReportLastRunDelta({
1556
1844
  if (!delta) return null;
1557
1845
  const { newlyFailing, newlyPassing, added } = delta;
1558
1846
  if (newlyFailing.length === 0 && newlyPassing.length === 0 && added.length === 0) {
1559
- return /* @__PURE__ */ jsx35("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: "Since last run: no behavior changes." });
1847
+ return /* @__PURE__ */ jsx39("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: "Since last run: no behavior changes." });
1560
1848
  }
1561
- return /* @__PURE__ */ jsxs23("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: [
1562
- /* @__PURE__ */ jsx35("span", { className: "font-medium text-foreground", children: "Since last run:" }),
1563
- newlyFailing.length > 0 ? /* @__PURE__ */ jsxs23("span", { className: "ml-2 text-fail", children: [
1849
+ return /* @__PURE__ */ jsxs24("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: [
1850
+ /* @__PURE__ */ jsx39("span", { className: "font-medium text-foreground", children: "Since last run:" }),
1851
+ newlyFailing.length > 0 ? /* @__PURE__ */ jsxs24("span", { className: "ml-2 text-fail", children: [
1564
1852
  newlyFailing.length,
1565
1853
  " newly failing (",
1566
- /* @__PURE__ */ jsx35(ScenarioLinks, { scenarios: newlyFailing, tone: "text-fail" }),
1854
+ /* @__PURE__ */ jsx39(ScenarioLinks, { scenarios: newlyFailing, tone: "text-fail" }),
1567
1855
  ")"
1568
1856
  ] }) : null,
1569
- newlyPassing.length > 0 ? /* @__PURE__ */ jsxs23("span", { className: "ml-2 text-pass", children: [
1857
+ newlyPassing.length > 0 ? /* @__PURE__ */ jsxs24("span", { className: "ml-2 text-pass", children: [
1570
1858
  newlyPassing.length,
1571
1859
  " fixed (",
1572
- /* @__PURE__ */ jsx35(ScenarioLinks, { scenarios: newlyPassing, tone: "text-pass" }),
1860
+ /* @__PURE__ */ jsx39(ScenarioLinks, { scenarios: newlyPassing, tone: "text-pass" }),
1573
1861
  ")"
1574
1862
  ] }) : null,
1575
- added.length > 0 ? /* @__PURE__ */ jsxs23("span", { className: "ml-2", children: [
1863
+ added.length > 0 ? /* @__PURE__ */ jsxs24("span", { className: "ml-2", children: [
1576
1864
  added.length,
1577
1865
  " new"
1578
1866
  ] }) : null
@@ -1581,14 +1869,78 @@ function ReportLastRunDelta({
1581
1869
 
1582
1870
  // src/interactive/ReportShortcutsHelp.tsx
1583
1871
  import { useEffect as useEffect2, useRef } from "react";
1584
- import { jsx as jsx36, jsxs as jsxs24 } from "react/jsx-runtime";
1872
+
1873
+ // src/components/ui/button.tsx
1874
+ import "react";
1875
+ import { cva as cva4 } from "class-variance-authority";
1876
+ import { useRender as useRender2 } from "@base-ui/react/use-render";
1877
+ var buttonVariants = cva4(
1878
+ "inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,background-color,border-color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
1879
+ {
1880
+ variants: {
1881
+ variant: {
1882
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
1883
+ destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
1884
+ outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
1885
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
1886
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
1887
+ link: "text-primary underline-offset-4 hover:underline"
1888
+ },
1889
+ size: {
1890
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
1891
+ xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
1892
+ sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
1893
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
1894
+ icon: "size-9",
1895
+ "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
1896
+ "icon-sm": "size-8",
1897
+ "icon-lg": "size-10"
1898
+ }
1899
+ },
1900
+ defaultVariants: {
1901
+ variant: "default",
1902
+ size: "default"
1903
+ }
1904
+ }
1905
+ );
1906
+ function Button({
1907
+ className,
1908
+ variant = "default",
1909
+ size = "default",
1910
+ render,
1911
+ ...props
1912
+ }) {
1913
+ return useRender2({
1914
+ defaultTagName: "button",
1915
+ render,
1916
+ props: {
1917
+ "data-slot": "button",
1918
+ "data-variant": variant,
1919
+ "data-size": size,
1920
+ className: cn(buttonVariants({ variant, size, className })),
1921
+ ...props
1922
+ }
1923
+ });
1924
+ }
1925
+
1926
+ // src/interactive/ReportShortcutsHelp.tsx
1927
+ import { jsx as jsx40, jsxs as jsxs25 } from "react/jsx-runtime";
1585
1928
  var SHORTCUTS = [
1586
1929
  { keys: "/", description: "Focus search" },
1587
1930
  { keys: "f", description: "Jump to next failure" },
1588
- { keys: "Shift+F", description: "Jump to previous failure" },
1589
- { keys: "Esc", description: "Clear search / close dialog" },
1590
- { keys: "?", description: "Toggle this help" }
1931
+ { keys: "Shift F", description: "Jump to previous failure" },
1932
+ { keys: "e", description: "Expand all" },
1933
+ { keys: "c", description: "Collapse all" },
1934
+ { keys: "?", description: "Toggle this help" },
1935
+ { keys: "Esc", description: "Clear search / close" }
1591
1936
  ];
1937
+ function Keys({ keys }) {
1938
+ const parts = keys.split(" ");
1939
+ return /* @__PURE__ */ jsx40("span", { className: "inline-flex items-center gap-1", children: parts.map((k, i) => /* @__PURE__ */ jsxs25("span", { className: "inline-flex items-center gap-1", children: [
1940
+ i > 0 ? /* @__PURE__ */ jsx40("span", { className: "text-xs text-muted-foreground", children: "+" }) : null,
1941
+ /* @__PURE__ */ jsx40("kbd", { className: "inline-flex h-6 min-w-6 items-center justify-center rounded border border-border bg-muted px-1.5 font-mono text-xs font-medium text-muted-foreground", children: k })
1942
+ ] }, k)) });
1943
+ }
1592
1944
  function ReportShortcutsHelp({ open, onClose }) {
1593
1945
  const dialogRef = useRef(null);
1594
1946
  useEffect2(() => {
@@ -1597,20 +1949,20 @@ function ReportShortcutsHelp({ open, onClose }) {
1597
1949
  if (open && !dialog.open) dialog.showModal();
1598
1950
  else if (!open && dialog.open) dialog.close();
1599
1951
  }, [open]);
1600
- return /* @__PURE__ */ jsxs24(
1952
+ return /* @__PURE__ */ jsxs25(
1601
1953
  "dialog",
1602
1954
  {
1603
1955
  ref: dialogRef,
1604
- className: "es-shortcuts-help",
1956
+ className: "es-shortcuts-help m-auto w-[min(90vw,28rem)] rounded-lg border border-border bg-card p-6 text-card-foreground shadow-lg [overscroll-behavior:contain] backdrop:bg-black/40",
1605
1957
  "aria-label": "Keyboard shortcuts",
1606
1958
  onClose,
1607
1959
  children: [
1608
- /* @__PURE__ */ jsx36("h2", { children: "Keyboard shortcuts" }),
1609
- /* @__PURE__ */ jsx36("dl", { children: SHORTCUTS.map((s) => /* @__PURE__ */ jsxs24("div", { children: [
1610
- /* @__PURE__ */ jsx36("dt", { children: /* @__PURE__ */ jsx36("kbd", { children: s.keys }) }),
1611
- /* @__PURE__ */ jsx36("dd", { children: s.description })
1960
+ /* @__PURE__ */ jsx40("h2", { className: "text-base font-semibold", children: "Keyboard shortcuts" }),
1961
+ /* @__PURE__ */ jsx40("dl", { className: "mt-4 grid grid-cols-[auto_1fr] items-center gap-x-4 gap-y-2.5", children: SHORTCUTS.map((s) => /* @__PURE__ */ jsxs25("div", { className: "col-span-2 grid grid-cols-subgrid items-center", children: [
1962
+ /* @__PURE__ */ jsx40("dt", { className: "justify-self-start", children: /* @__PURE__ */ jsx40(Keys, { keys: s.keys }) }),
1963
+ /* @__PURE__ */ jsx40("dd", { className: "text-sm text-muted-foreground", children: s.description })
1612
1964
  ] }, s.keys)) }),
1613
- /* @__PURE__ */ jsx36("form", { method: "dialog", children: /* @__PURE__ */ jsx36("button", { type: "submit", className: "es-shortcuts-close", children: "Close" }) })
1965
+ /* @__PURE__ */ jsx40("form", { method: "dialog", className: "mt-6", children: /* @__PURE__ */ jsx40(Button, { type: "submit", variant: "outline", size: "sm", children: "Close" }) })
1614
1966
  ]
1615
1967
  }
1616
1968
  );
@@ -1782,8 +2134,37 @@ function listFailures(report) {
1782
2134
  return out;
1783
2135
  }
1784
2136
 
2137
+ // src/interactive/triage-default.ts
2138
+ function needsAttention(status) {
2139
+ return status === "failed";
2140
+ }
2141
+ function defaultCollapsedIds(report) {
2142
+ const collapsed = /* @__PURE__ */ new Set();
2143
+ if (report.summary.failed === 0) return collapsed;
2144
+ for (const feature of report.features) {
2145
+ const featureHasFailure = feature.scenarios.some((s) => needsAttention(s.status));
2146
+ if (!featureHasFailure) collapsed.add(feature.id);
2147
+ for (const scenario of feature.scenarios) {
2148
+ if (!needsAttention(scenario.status)) collapsed.add(scenario.id);
2149
+ }
2150
+ }
2151
+ return collapsed;
2152
+ }
2153
+ function failuresFirst(report) {
2154
+ if (report.summary.failed === 0) return report;
2155
+ const withFailures = [];
2156
+ const rest = [];
2157
+ for (const feature of report.features) {
2158
+ (feature.scenarios.some((s) => needsAttention(s.status)) ? withFailures : rest).push(feature);
2159
+ }
2160
+ if (withFailures.length === 0) return report;
2161
+ return { ...report, features: [...withFailures, ...rest] };
2162
+ }
2163
+
1785
2164
  // src/interactive/ReportFilters.tsx
1786
- import { jsx as jsx37, jsxs as jsxs25 } from "react/jsx-runtime";
2165
+ import { useState as useState3 } from "react";
2166
+ import { jsx as jsx41, jsxs as jsxs26 } from "react/jsx-runtime";
2167
+ var TAG_LIMIT = 12;
1787
2168
  function ReportFilters({
1788
2169
  statuses,
1789
2170
  status,
@@ -1792,11 +2173,14 @@ function ReportFilters({
1792
2173
  activeTags,
1793
2174
  onToggleTag
1794
2175
  }) {
2176
+ const [showAllTags, setShowAllTags] = useState3(false);
1795
2177
  if (statuses.length === 0 && tags.length === 0) return null;
1796
- return /* @__PURE__ */ jsxs25("div", { className: "flex flex-col gap-2", children: [
1797
- statuses.length > 0 ? /* @__PURE__ */ jsx37("div", { role: "group", "aria-label": "Filter by status", className: "flex flex-wrap gap-1", children: statuses.map((opt) => {
2178
+ const tagsCollapsed = tags.length > TAG_LIMIT && !showAllTags;
2179
+ const visibleTags = tagsCollapsed ? tags.slice(0, TAG_LIMIT) : tags;
2180
+ return /* @__PURE__ */ jsxs26("div", { className: "flex flex-col gap-2", children: [
2181
+ statuses.length > 0 ? /* @__PURE__ */ jsx41("div", { role: "group", "aria-label": "Filter by status", className: "flex flex-wrap gap-1", children: statuses.map((opt) => {
1798
2182
  const active = status === opt.key;
1799
- return /* @__PURE__ */ jsxs25(
2183
+ return /* @__PURE__ */ jsxs26(
1800
2184
  "button",
1801
2185
  {
1802
2186
  type: "button",
@@ -1808,40 +2192,61 @@ function ReportFilters({
1808
2192
  ),
1809
2193
  children: [
1810
2194
  opt.label,
1811
- /* @__PURE__ */ jsx37("span", { className: "ml-1 font-mono opacity-70", children: opt.count })
2195
+ /* @__PURE__ */ jsx41("span", { className: "ml-1 font-mono opacity-70", children: opt.count })
1812
2196
  ]
1813
2197
  },
1814
2198
  opt.key
1815
2199
  );
1816
2200
  }) }) : null,
1817
- tags.length > 0 ? /* @__PURE__ */ jsx37("ul", { "aria-label": "Filter by tag", className: "flex flex-wrap gap-1.5", children: tags.map((tag) => {
1818
- const active = activeTags.includes(tag);
1819
- return /* @__PURE__ */ jsx37("li", { children: /* @__PURE__ */ jsx37(
2201
+ tags.length > 0 ? /* @__PURE__ */ jsxs26("ul", { "aria-label": "Filter by tag", className: "flex flex-wrap items-center gap-1.5", children: [
2202
+ visibleTags.map((tag) => {
2203
+ const active = activeTags.includes(tag);
2204
+ return /* @__PURE__ */ jsx41("li", { children: /* @__PURE__ */ jsx41(
2205
+ "button",
2206
+ {
2207
+ type: "button",
2208
+ "aria-pressed": active,
2209
+ onClick: () => onToggleTag(tag),
2210
+ className: "cursor-pointer",
2211
+ children: /* @__PURE__ */ jsx41(
2212
+ Badge,
2213
+ {
2214
+ variant: "tag",
2215
+ className: cn(
2216
+ active && "border-primary bg-primary text-primary-foreground"
2217
+ ),
2218
+ children: tag
2219
+ }
2220
+ )
2221
+ }
2222
+ ) }, tag);
2223
+ }),
2224
+ tags.length > TAG_LIMIT ? /* @__PURE__ */ jsx41("li", { children: /* @__PURE__ */ jsx41(
1820
2225
  "button",
1821
2226
  {
1822
2227
  type: "button",
1823
- "aria-pressed": active,
1824
- onClick: () => onToggleTag(tag),
1825
- className: "cursor-pointer",
1826
- children: /* @__PURE__ */ jsx37(Badge, { variant: active ? "default" : "tag", children: tag })
2228
+ "aria-expanded": showAllTags,
2229
+ onClick: () => setShowAllTags((v) => !v),
2230
+ className: "cursor-pointer rounded-full px-2 py-0.5 text-xs font-medium text-muted-foreground hover:text-foreground",
2231
+ children: tagsCollapsed ? `+${tags.length - TAG_LIMIT} more` : "Show fewer"
1827
2232
  }
1828
- ) }, tag);
1829
- }) }) : null
2233
+ ) }) : null
2234
+ ] }) : null
1830
2235
  ] });
1831
2236
  }
1832
2237
 
1833
2238
  // src/interactive/ReportToc.tsx
1834
- import { useEffect as useEffect5, useMemo as useMemo2, useState as useState3 } from "react";
1835
- import { jsx as jsx38, jsxs as jsxs26 } from "react/jsx-runtime";
2239
+ import { useEffect as useEffect5, useMemo as useMemo2, useState as useState4 } from "react";
2240
+ import { jsx as jsx42, jsxs as jsxs27 } from "react/jsx-runtime";
1836
2241
  var DOT = {
1837
- passed: "text-pass",
1838
- failed: "text-fail",
1839
- skipped: "text-skip",
1840
- pending: "text-pend"
2242
+ passed: "bg-pass",
2243
+ failed: "bg-fail",
2244
+ skipped: "bg-skip",
2245
+ pending: "bg-pend"
1841
2246
  };
1842
- function ReportToc() {
2247
+ function TocContent({ onNavigate }) {
1843
2248
  const report = useReport();
1844
- const [activeId, setActiveId] = useState3(null);
2249
+ const [activeId, setActiveId] = useState4(null);
1845
2250
  const scenarioIds = useMemo2(
1846
2251
  () => report.features.flatMap((f) => f.scenarios.map((s) => s.id)),
1847
2252
  [report]
@@ -1861,91 +2266,166 @@ function ReportToc() {
1861
2266
  return () => observer.disconnect();
1862
2267
  }, [scenarioIds]);
1863
2268
  if (report.features.length === 0) return null;
1864
- return /* @__PURE__ */ jsx38(
2269
+ return /* @__PURE__ */ jsx42("ul", { className: "flex list-none flex-col gap-4 pl-0", children: report.features.map((feature) => /* @__PURE__ */ jsxs27("li", { children: [
2270
+ /* @__PURE__ */ jsx42("p", { className: "mb-1.5 font-semibold leading-snug text-foreground", children: feature.title }),
2271
+ /* @__PURE__ */ jsx42("ul", { className: "flex list-none flex-col gap-0.5 pl-0", children: feature.scenarios.map((scenario) => {
2272
+ const active = activeId === scenario.id;
2273
+ return /* @__PURE__ */ jsx42("li", { children: /* @__PURE__ */ jsxs27(
2274
+ "a",
2275
+ {
2276
+ href: `#${scenario.id}`,
2277
+ onClick: (e) => {
2278
+ e.preventDefault();
2279
+ scrollToScenarioId(scenario.id, { updateHash: true });
2280
+ onNavigate?.();
2281
+ },
2282
+ "aria-current": active ? "true" : void 0,
2283
+ className: cn(
2284
+ "flex items-start gap-2 border-l-2 py-1 pl-2.5 leading-snug hover:text-foreground",
2285
+ active ? "border-primary font-medium text-foreground" : "border-border text-muted-foreground"
2286
+ ),
2287
+ children: [
2288
+ /* @__PURE__ */ jsx42(
2289
+ "span",
2290
+ {
2291
+ "aria-hidden": true,
2292
+ className: cn("mt-1 size-1.5 shrink-0 rounded-full", DOT[scenario.status])
2293
+ }
2294
+ ),
2295
+ /* @__PURE__ */ jsx42("span", { className: "break-words", children: scenario.title })
2296
+ ]
2297
+ }
2298
+ ) }, scenario.id);
2299
+ }) })
2300
+ ] }, feature.id)) });
2301
+ }
2302
+ function ReportToc() {
2303
+ const report = useReport();
2304
+ if (report.features.length === 0) return null;
2305
+ return /* @__PURE__ */ jsxs27(
1865
2306
  "nav",
1866
2307
  {
1867
2308
  "aria-label": "Table of contents",
1868
- className: "sticky top-4 hidden h-fit w-56 shrink-0 self-start text-xs lg:block",
1869
- children: /* @__PURE__ */ jsx38("ul", { className: "flex flex-col gap-3", children: report.features.map((feature) => /* @__PURE__ */ jsxs26("li", { children: [
1870
- /* @__PURE__ */ jsx38("p", { className: "mb-1 truncate font-semibold text-foreground", children: feature.title }),
1871
- /* @__PURE__ */ jsx38("ul", { className: "flex flex-col", children: feature.scenarios.map((scenario) => {
1872
- const active = activeId === scenario.id;
1873
- return /* @__PURE__ */ jsx38("li", { children: /* @__PURE__ */ jsxs26(
1874
- "a",
1875
- {
1876
- href: `#${scenario.id}`,
1877
- onClick: (e) => {
1878
- e.preventDefault();
1879
- scrollToScenarioId(scenario.id, { updateHash: true });
1880
- },
1881
- "aria-current": active ? "true" : void 0,
1882
- className: cn(
1883
- "flex items-center gap-1.5 truncate border-l-2 py-0.5 pl-2 hover:text-foreground",
1884
- active ? "border-primary font-medium text-foreground" : "border-border text-muted-foreground"
1885
- ),
1886
- children: [
1887
- /* @__PURE__ */ jsx38("span", { "aria-hidden": true, className: cn("shrink-0", DOT[scenario.status]), children: "\u2022" }),
1888
- /* @__PURE__ */ jsx38("span", { className: "truncate", children: scenario.title })
1889
- ]
1890
- }
1891
- ) }, scenario.id);
1892
- }) })
1893
- ] }, feature.id)) })
2309
+ className: "sticky top-4 hidden h-fit w-64 shrink-0 self-start text-xs lg:block",
2310
+ children: [
2311
+ /* @__PURE__ */ jsx42("p", { className: "mb-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Contents" }),
2312
+ /* @__PURE__ */ jsx42(TocContent, {})
2313
+ ]
1894
2314
  }
1895
2315
  );
1896
2316
  }
1897
2317
 
2318
+ // src/interactive/ReportTocDrawer.tsx
2319
+ import { useState as useState5 } from "react";
2320
+ import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
2321
+ import { ListIcon, XIcon } from "lucide-react";
2322
+ import { jsx as jsx43, jsxs as jsxs28 } from "react/jsx-runtime";
2323
+ function ReportTocDrawer() {
2324
+ const report = useReport();
2325
+ const [open, setOpen] = useState5(false);
2326
+ if (report.features.length === 0) return null;
2327
+ return /* @__PURE__ */ jsxs28(DialogPrimitive.Root, { open, onOpenChange: setOpen, children: [
2328
+ /* @__PURE__ */ jsxs28(DialogPrimitive.Trigger, { className: "flex cursor-pointer items-center gap-1 rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground lg:hidden [&_svg]:size-3.5", children: [
2329
+ /* @__PURE__ */ jsx43(ListIcon, { "aria-hidden": true }),
2330
+ " Contents"
2331
+ ] }),
2332
+ /* @__PURE__ */ jsxs28(DialogPrimitive.Portal, { children: [
2333
+ /* @__PURE__ */ jsx43(DialogPrimitive.Backdrop, { className: "fixed inset-0 z-50 bg-black/50 transition-opacity duration-200 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0" }),
2334
+ /* @__PURE__ */ jsxs28(DialogPrimitive.Popup, { className: "fixed inset-y-0 left-0 z-50 flex w-72 max-w-[80vw] flex-col gap-3 overflow-y-auto border-r border-border bg-background p-4 font-sans text-xs shadow-lg outline-none transition-transform duration-200 data-[ending-style]:-translate-x-full data-[starting-style]:-translate-x-full", children: [
2335
+ /* @__PURE__ */ jsxs28("div", { className: "flex items-center justify-between", children: [
2336
+ /* @__PURE__ */ jsx43(DialogPrimitive.Title, { className: "text-xs font-semibold tracking-wide text-muted-foreground uppercase", children: "Contents" }),
2337
+ /* @__PURE__ */ jsx43(
2338
+ DialogPrimitive.Close,
2339
+ {
2340
+ "aria-label": "Close contents",
2341
+ className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
2342
+ children: /* @__PURE__ */ jsx43(XIcon, { "aria-hidden": true })
2343
+ }
2344
+ )
2345
+ ] }),
2346
+ /* @__PURE__ */ jsx43(TocContent, { onNavigate: () => setOpen(false) })
2347
+ ] })
2348
+ ] })
2349
+ ] });
2350
+ }
2351
+
1898
2352
  // src/interactive/use-theme.ts
1899
- import { useCallback as useCallback3, useEffect as useEffect6, useState as useState4 } from "react";
2353
+ import { useCallback as useCallback3, useEffect as useEffect6, useState as useState6 } from "react";
1900
2354
  var KEY = "es-theme";
1901
- function applyTheme(theme) {
2355
+ function systemDark() {
2356
+ return typeof matchMedia !== "undefined" && matchMedia("(prefers-color-scheme: dark)").matches;
2357
+ }
2358
+ function resolve(pref) {
2359
+ return pref === "system" ? systemDark() ? "dark" : "light" : pref;
2360
+ }
2361
+ function applyTheme(pref) {
1902
2362
  if (typeof document !== "undefined") {
1903
- document.documentElement.setAttribute("data-theme", theme);
2363
+ document.documentElement.setAttribute("data-theme", resolve(pref));
1904
2364
  }
1905
2365
  }
1906
- function initialTheme() {
2366
+ function initialPref() {
1907
2367
  if (typeof localStorage !== "undefined") {
1908
2368
  const stored = localStorage.getItem(KEY);
1909
- if (stored === "light" || stored === "dark") return stored;
1910
- }
1911
- if (typeof document !== "undefined" && document.documentElement.getAttribute("data-theme") === "dark") {
1912
- return "dark";
1913
- }
1914
- if (typeof matchMedia !== "undefined" && matchMedia("(prefers-color-scheme: dark)").matches) {
1915
- return "dark";
2369
+ if (stored === "light" || stored === "dark" || stored === "system") return stored;
1916
2370
  }
1917
- return "light";
2371
+ return "system";
1918
2372
  }
1919
2373
  function useTheme() {
1920
- const [theme, setTheme] = useState4("light");
2374
+ const [pref, setPrefState] = useState6("system");
1921
2375
  useEffect6(() => {
1922
- const t = initialTheme();
1923
- setTheme(t);
1924
- applyTheme(t);
2376
+ const p = initialPref();
2377
+ setPrefState(p);
2378
+ applyTheme(p);
1925
2379
  }, []);
1926
- const toggle = useCallback3(() => {
1927
- setTheme((prev) => {
1928
- const next = prev === "dark" ? "light" : "dark";
1929
- applyTheme(next);
1930
- try {
1931
- localStorage.setItem(KEY, next);
1932
- } catch {
1933
- }
1934
- return next;
1935
- });
2380
+ useEffect6(() => {
2381
+ if (typeof matchMedia === "undefined") return;
2382
+ const mq = matchMedia("(prefers-color-scheme: dark)");
2383
+ const onChange = () => {
2384
+ if (pref === "system") applyTheme("system");
2385
+ };
2386
+ mq.addEventListener("change", onChange);
2387
+ return () => mq.removeEventListener("change", onChange);
2388
+ }, [pref]);
2389
+ const setPref = useCallback3((p) => {
2390
+ setPrefState(p);
2391
+ applyTheme(p);
2392
+ try {
2393
+ localStorage.setItem(KEY, p);
2394
+ } catch {
2395
+ }
1936
2396
  }, []);
1937
- return { theme, toggle };
2397
+ return { pref, setPref };
1938
2398
  }
1939
2399
 
1940
2400
  // src/interactive/ReportInteractive.tsx
1941
- import { jsx as jsx39, jsxs as jsxs27 } from "react/jsx-runtime";
2401
+ import { Fragment as Fragment13, jsx as jsx44, jsxs as jsxs29 } from "react/jsx-runtime";
1942
2402
  function ReportInteractive(props) {
1943
2403
  const { report, className, title, dataTheme } = props;
1944
2404
  const result = unwrapReport(report);
1945
2405
  if (!result.ok) {
1946
- return /* @__PURE__ */ jsx39(ReportErrorShell, { error: result.error, className, title, dataTheme });
2406
+ return /* @__PURE__ */ jsx44(ReportErrorShell, { error: result.error, className, title, dataTheme });
1947
2407
  }
1948
- return /* @__PURE__ */ jsx39(ReportInteractiveView, { ...props, report: result.data });
2408
+ return /* @__PURE__ */ jsx44(ReportInteractiveView, { ...props, report: result.data });
2409
+ }
2410
+ var THEME_OPTIONS = [
2411
+ { value: "light", label: "Light", Icon: Sun },
2412
+ { value: "system", label: "System", Icon: Monitor },
2413
+ { value: "dark", label: "Dark", Icon: Moon }
2414
+ ];
2415
+ function ThemeSegment({ pref, onPref }) {
2416
+ return /* @__PURE__ */ jsx44(
2417
+ ToggleGroup,
2418
+ {
2419
+ size: "sm",
2420
+ value: [pref],
2421
+ onValueChange: (v) => {
2422
+ const next = v[0];
2423
+ if (next) onPref(next);
2424
+ },
2425
+ "aria-label": "Color theme",
2426
+ children: THEME_OPTIONS.map(({ value, label, Icon }) => /* @__PURE__ */ jsx44(ToggleGroupItem, { value, "aria-label": label, title: label, className: "px-2", children: /* @__PURE__ */ jsx44(Icon, {}) }, value))
2427
+ }
2428
+ );
1949
2429
  }
1950
2430
  function ReportInteractiveView({
1951
2431
  report,
@@ -1955,20 +2435,25 @@ function ReportInteractiveView({
1955
2435
  title,
1956
2436
  dataTheme,
1957
2437
  hideHeader = false,
2438
+ hideToc = false,
1958
2439
  staleAfterDays = 7,
1959
2440
  scenarioHistory
1960
2441
  }) {
1961
- const [query, setQuery] = useState5("");
1962
- const [statusFilter, setStatusFilter] = useState5("all");
1963
- const [activeTags, setActiveTags] = useState5([]);
1964
- const [detail, setDetail] = useState5("full");
1965
- const [helpOpen, setHelpOpen] = useState5(false);
1966
- const { theme, toggle: toggleTheme } = useTheme();
2442
+ const [query, setQuery] = useState7("");
2443
+ const [statusFilter, setStatusFilter] = useState7("all");
2444
+ const [activeTags, setActiveTags] = useState7([]);
2445
+ const [detail, setDetail] = useState7("full");
2446
+ const [helpOpen, setHelpOpen] = useState7(false);
2447
+ const { pref: themePref, setPref: setThemePref } = useTheme();
1967
2448
  const searchRef = useRef3(null);
2449
+ const expandId = useId2();
2450
+ const docsId = useId2();
1968
2451
  const deferredQuery = useDeferredValue(query);
1969
2452
  const failures = useMemo3(() => listFailures(report), [report]);
1970
2453
  const filtered = useMemo3(
1971
- () => filterReport(report, { query: deferredQuery, status: statusFilter, tags: activeTags }),
2454
+ // Features carrying failures float to the top so the first thing on screen
2455
+ // is the thing that needs attention; an all-green run keeps source order.
2456
+ () => failuresFirst(filterReport(report, { query: deferredQuery, status: statusFilter, tags: activeTags })),
1972
2457
  [report, deferredQuery, statusFilter, activeTags]
1973
2458
  );
1974
2459
  const isFiltering = query !== deferredQuery;
@@ -1988,7 +2473,7 @@ function ReportInteractiveView({
1988
2473
  (tag) => setActiveTags((prev) => prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag]),
1989
2474
  []
1990
2475
  );
1991
- const [toast, setToast] = useState5(null);
2476
+ const [toast, setToast] = useState7(null);
1992
2477
  const toastTimer = useRef3(null);
1993
2478
  const showToast = useCallback4((msg) => {
1994
2479
  setToast(msg);
@@ -2006,18 +2491,27 @@ function ReportInteractiveView({
2006
2491
  const scenarioActions = useMemo3(
2007
2492
  () => ({
2008
2493
  copyLink: (s) => copy(scenarioPermalink(s), "Link copied"),
2009
- copyMarkdown: (s) => copy(scenarioToMarkdown(s), "Markdown copied"),
2494
+ copyMarkdown: (s) => copy(scenarioToMarkdown2(s), "Markdown copied"),
2010
2495
  copyPrompt: (s) => copy(scenarioToPrompt(s), "Prompt copied")
2011
2496
  }),
2012
2497
  [copy]
2013
2498
  );
2014
2499
  const failureIndexRef = useRef3(0);
2015
- const collapse = useCollapseState();
2016
2500
  const allCollapsibleIds = useMemo3(
2017
2501
  () => report.features.flatMap((f) => [f.id, ...f.scenarios.map((s) => s.id)]),
2018
2502
  [report]
2019
2503
  );
2504
+ const collapse = useCollapseState(useCallback4(() => defaultCollapsedIds(report), [report]));
2020
2505
  const collapseAll = useCallback4(() => collapse.collapseAll(allCollapsibleIds), [collapse, allCollapsibleIds]);
2506
+ const [expandedAll, setExpandedAll] = useState7(() => report.summary.failed === 0);
2507
+ const setExpanded = useCallback4(
2508
+ (expanded) => {
2509
+ if (expanded) collapse.expandAll();
2510
+ else collapseAll();
2511
+ setExpandedAll(expanded);
2512
+ },
2513
+ [collapse, collapseAll]
2514
+ );
2021
2515
  const focusSearch = useCallback4(() => {
2022
2516
  searchRef.current?.focus();
2023
2517
  }, []);
@@ -2043,20 +2537,21 @@ function ReportInteractiveView({
2043
2537
  onPrevFailure: () => stepFailure(-1),
2044
2538
  onToggleHelp: toggleHelp,
2045
2539
  onEscape: escape,
2046
- onExpandAll: collapse.expandAll,
2047
- onCollapseAll: collapseAll
2540
+ onExpandAll: () => setExpanded(true),
2541
+ onCollapseAll: () => setExpanded(false)
2048
2542
  });
2049
2543
  useDeepLinkScroll();
2050
2544
  const hasContent = filtered.features.length > 0;
2051
2545
  const totalScenarios = report.summary.total;
2052
2546
  const matchedScenarios = filtered.summary.total;
2053
- return /* @__PURE__ */ jsx39(CollapseProvider, { value: collapse.api, children: /* @__PURE__ */ jsx39(ScenarioActionsProvider, { value: scenarioActions, children: /* @__PURE__ */ jsx39(ScenarioHistoryProvider, { value: scenarioHistory ?? null, children: /* @__PURE__ */ jsx39(
2547
+ const showToc = !hideToc && totalScenarios > 3;
2548
+ return /* @__PURE__ */ jsx44(CollapseProvider, { value: collapse.api, children: /* @__PURE__ */ jsx44(ScenarioActionsProvider, { value: scenarioActions, children: /* @__PURE__ */ jsx44(ScenarioHistoryProvider, { value: scenarioHistory ?? null, children: /* @__PURE__ */ jsx44(
2054
2549
  ReportRoot,
2055
2550
  {
2056
2551
  report: filtered,
2057
2552
  customRenderers,
2058
2553
  renderers,
2059
- children: /* @__PURE__ */ jsxs27(
2554
+ children: /* @__PURE__ */ jsxs29(
2060
2555
  "main",
2061
2556
  {
2062
2557
  className: cn("es-report", "es-report-interactive", className),
@@ -2065,9 +2560,38 @@ function ReportInteractiveView({
2065
2560
  "data-theme": dataTheme,
2066
2561
  "data-detail-level": detail,
2067
2562
  children: [
2068
- /* @__PURE__ */ jsxs27("header", { className: "es-report-header", children: [
2069
- hideHeader ? null : /* @__PURE__ */ jsx39(ReportTitleBlock, { title }),
2070
- /* @__PURE__ */ jsx39(
2563
+ /* @__PURE__ */ jsxs29("header", { className: "es-report-header", children: [
2564
+ /* @__PURE__ */ jsxs29(
2565
+ "div",
2566
+ {
2567
+ "data-es-navbar": true,
2568
+ className: cn(
2569
+ "flex flex-wrap items-center gap-4",
2570
+ hideHeader ? "justify-end" : "justify-between"
2571
+ ),
2572
+ children: [
2573
+ hideHeader ? null : /* @__PURE__ */ jsx44("h1", { className: "min-w-0", children: title ?? "Story Report" }),
2574
+ /* @__PURE__ */ jsxs29("div", { className: "flex items-center gap-2", children: [
2575
+ /* @__PURE__ */ jsx44(
2576
+ ReportSearch,
2577
+ {
2578
+ ref: searchRef,
2579
+ value: query,
2580
+ onChange: setQuery,
2581
+ matchedCount: matchedScenarios,
2582
+ totalCount: totalScenarios
2583
+ }
2584
+ ),
2585
+ /* @__PURE__ */ jsx44(ThemeSegment, { pref: themePref, onPref: setThemePref })
2586
+ ] })
2587
+ ]
2588
+ }
2589
+ ),
2590
+ hideHeader ? null : /* @__PURE__ */ jsxs29(Fragment13, { children: [
2591
+ /* @__PURE__ */ jsx44(ReportSummary, {}),
2592
+ /* @__PURE__ */ jsx44(ReportMeta, {})
2593
+ ] }),
2594
+ /* @__PURE__ */ jsx44(
2071
2595
  ReportFreshness,
2072
2596
  {
2073
2597
  lastRunMs: reportLastRunMs(report),
@@ -2075,62 +2599,8 @@ function ReportInteractiveView({
2075
2599
  staleAfterDays
2076
2600
  }
2077
2601
  ),
2078
- /* @__PURE__ */ jsx39(ReportLastRunDelta, { history: scenarioHistory, report }),
2079
- /* @__PURE__ */ jsxs27("div", { className: "flex flex-wrap items-center gap-2", children: [
2080
- /* @__PURE__ */ jsx39(
2081
- ReportSearch,
2082
- {
2083
- ref: searchRef,
2084
- value: query,
2085
- onChange: setQuery,
2086
- matchedCount: matchedScenarios,
2087
- totalCount: totalScenarios
2088
- }
2089
- ),
2090
- /* @__PURE__ */ jsxs27("div", { className: "flex shrink-0 gap-1", children: [
2091
- /* @__PURE__ */ jsx39(
2092
- "button",
2093
- {
2094
- type: "button",
2095
- onClick: collapse.expandAll,
2096
- "aria-keyshortcuts": "e",
2097
- className: "cursor-pointer rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground",
2098
- children: "Expand all"
2099
- }
2100
- ),
2101
- /* @__PURE__ */ jsx39(
2102
- "button",
2103
- {
2104
- type: "button",
2105
- onClick: collapseAll,
2106
- "aria-keyshortcuts": "c",
2107
- className: "cursor-pointer rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground",
2108
- children: "Collapse all"
2109
- }
2110
- ),
2111
- /* @__PURE__ */ jsx39(
2112
- "button",
2113
- {
2114
- type: "button",
2115
- onClick: () => setDetail((d) => d === "full" ? "minimal" : "full"),
2116
- "aria-pressed": detail === "minimal",
2117
- className: "cursor-pointer rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground",
2118
- children: detail === "full" ? "Hide docs" : "Show docs"
2119
- }
2120
- ),
2121
- /* @__PURE__ */ jsx39(
2122
- "button",
2123
- {
2124
- type: "button",
2125
- onClick: toggleTheme,
2126
- "aria-label": `Switch to ${theme === "dark" ? "light" : "dark"} theme`,
2127
- className: "cursor-pointer rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground",
2128
- children: theme === "dark" ? "\u2600 Light" : "\u263E Dark"
2129
- }
2130
- )
2131
- ] })
2132
- ] }),
2133
- /* @__PURE__ */ jsx39(
2602
+ /* @__PURE__ */ jsx44(ReportLastRunDelta, { history: scenarioHistory, report }),
2603
+ /* @__PURE__ */ jsx44(
2134
2604
  ReportFilters,
2135
2605
  {
2136
2606
  statuses: statusOptions,
@@ -2140,26 +2610,44 @@ function ReportInteractiveView({
2140
2610
  activeTags,
2141
2611
  onToggleTag: toggleTag
2142
2612
  }
2143
- )
2613
+ ),
2614
+ /* @__PURE__ */ jsxs29("div", { className: "flex flex-wrap items-center gap-x-6 gap-y-2 border-t border-border/60 pt-3", children: [
2615
+ showToc ? /* @__PURE__ */ jsx44(ReportTocDrawer, {}) : null,
2616
+ /* @__PURE__ */ jsxs29("div", { className: "flex items-center gap-2", children: [
2617
+ /* @__PURE__ */ jsx44(Switch, { id: expandId, checked: expandedAll, onCheckedChange: setExpanded }),
2618
+ /* @__PURE__ */ jsx44("label", { htmlFor: expandId, className: "cursor-pointer text-sm text-muted-foreground select-none", children: "Expand all" })
2619
+ ] }),
2620
+ /* @__PURE__ */ jsxs29("div", { className: "flex items-center gap-2", children: [
2621
+ /* @__PURE__ */ jsx44(
2622
+ Switch,
2623
+ {
2624
+ id: docsId,
2625
+ checked: detail === "full",
2626
+ onCheckedChange: (checked) => setDetail(checked ? "full" : "minimal")
2627
+ }
2628
+ ),
2629
+ /* @__PURE__ */ jsx44("label", { htmlFor: docsId, className: "cursor-pointer text-sm text-muted-foreground select-none", children: "Show documentation" })
2630
+ ] })
2631
+ ] })
2144
2632
  ] }),
2145
- /* @__PURE__ */ jsx39(ReportFailureBanner, { failures }),
2146
- hasContent ? /* @__PURE__ */ jsxs27("div", { className: "flex gap-6", children: [
2147
- /* @__PURE__ */ jsx39(ReportToc, {}),
2148
- /* @__PURE__ */ jsx39("div", { className: "flex min-w-0 flex-1 flex-col gap-4", children: /* @__PURE__ */ jsx39(ReportFeatureList, {}) })
2149
- ] }) : /* @__PURE__ */ jsx39(ReportEmpty, { message: query ? "No scenarios match the search." : void 0 }),
2150
- /* @__PURE__ */ jsx39(
2633
+ /* @__PURE__ */ jsx44(ReportFailureBanner, { failures }),
2634
+ hasContent ? showToc ? /* @__PURE__ */ jsxs29("div", { className: "flex gap-6", children: [
2635
+ /* @__PURE__ */ jsx44(ReportToc, {}),
2636
+ /* @__PURE__ */ jsx44("div", { className: "flex min-w-0 flex-1 flex-col gap-4", children: /* @__PURE__ */ jsx44(ReportFeatureList, {}) })
2637
+ ] }) : /* @__PURE__ */ jsx44("div", { className: "flex flex-col gap-4", children: /* @__PURE__ */ jsx44(ReportFeatureList, {}) }) : /* @__PURE__ */ jsx44(ReportEmpty, { message: query ? "No scenarios match the search." : void 0 }),
2638
+ /* @__PURE__ */ jsx44(
2151
2639
  "button",
2152
2640
  {
2153
2641
  type: "button",
2154
- className: "es-shortcuts-trigger",
2642
+ className: "es-shortcuts-trigger fixed right-4 bottom-4 z-40 flex size-9 items-center justify-center rounded-full border border-border bg-card text-muted-foreground shadow-sm transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50",
2155
2643
  "aria-label": "Keyboard shortcuts",
2156
- "aria-keyshortcuts": "Shift+?",
2644
+ "aria-keyshortcuts": "?",
2157
2645
  onClick: toggleHelp,
2158
2646
  children: "?"
2159
2647
  }
2160
2648
  ),
2161
- /* @__PURE__ */ jsx39(ReportShortcutsHelp, { open: helpOpen, onClose: () => setHelpOpen(false) }),
2162
- toast ? /* @__PURE__ */ jsx39(
2649
+ /* @__PURE__ */ jsx44(ReportShortcutsHelp, { open: helpOpen, onClose: () => setHelpOpen(false) }),
2650
+ toast ? /* @__PURE__ */ jsx44(
2163
2651
  "div",
2164
2652
  {
2165
2653
  role: "status",
@@ -2179,11 +2667,11 @@ function ReportInteractiveView({
2179
2667
  import { useMemo as useMemo4 } from "react";
2180
2668
 
2181
2669
  // src/interactive/island-renderers.tsx
2182
- import { createElement, useEffect as useEffect8, useState as useState7 } from "react";
2670
+ import { createElement, useEffect as useEffect8, useState as useState9 } from "react";
2183
2671
 
2184
2672
  // src/components/doc/MermaidView.tsx
2185
- import { useEffect as useEffect7, useRef as useRef4, useState as useState6 } from "react";
2186
- import { jsx as jsx40, jsxs as jsxs28 } from "react/jsx-runtime";
2673
+ import { useEffect as useEffect7, useRef as useRef4, useState as useState8 } from "react";
2674
+ import { jsx as jsx45, jsxs as jsxs30 } from "react/jsx-runtime";
2187
2675
  function prefersDark(el) {
2188
2676
  const themed = el?.closest("[data-theme]");
2189
2677
  if (themed?.dataset.theme === "dark") return true;
@@ -2192,7 +2680,7 @@ function prefersDark(el) {
2192
2680
  }
2193
2681
  function MermaidView({ entry, load }) {
2194
2682
  const ref = useRef4(null);
2195
- const [svg, setSvg] = useState6(null);
2683
+ const [svg, setSvg] = useState8(null);
2196
2684
  useEffect7(() => {
2197
2685
  let cancelled = false;
2198
2686
  const draw = async () => {
@@ -2220,9 +2708,9 @@ function MermaidView({ entry, load }) {
2220
2708
  };
2221
2709
  }, [entry.code, load]);
2222
2710
  if (svg) {
2223
- return /* @__PURE__ */ jsxs28("figure", { ref, className: "my-3", children: [
2224
- entry.title ? /* @__PURE__ */ jsx40("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
2225
- /* @__PURE__ */ jsx40(
2711
+ return /* @__PURE__ */ jsxs30("figure", { ref, className: "my-3", children: [
2712
+ entry.title ? /* @__PURE__ */ jsx45("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
2713
+ /* @__PURE__ */ jsx45(
2226
2714
  "div",
2227
2715
  {
2228
2716
  role: "img",
@@ -2233,11 +2721,11 @@ function MermaidView({ entry, load }) {
2233
2721
  )
2234
2722
  ] });
2235
2723
  }
2236
- return /* @__PURE__ */ jsx40(MermaidSource, { entry, ref });
2724
+ return /* @__PURE__ */ jsx45(MermaidSource, { entry, ref });
2237
2725
  }
2238
2726
 
2239
2727
  // src/interactive/island-renderers.tsx
2240
- import { jsx as jsx41 } from "react/jsx-runtime";
2728
+ import { jsx as jsx46 } from "react/jsx-runtime";
2241
2729
  var HLJS_ESM = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/es/highlight.min.js";
2242
2730
  var MERMAID_ESM = "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
2243
2731
  function cachedCdnModule(url) {
@@ -2254,7 +2742,7 @@ function cachedCdnModule(url) {
2254
2742
  var loadMermaidFromCdn = cachedCdnModule(MERMAID_ESM);
2255
2743
  var loadHljs = cachedCdnModule(HLJS_ESM);
2256
2744
  function CdnHighlightedCode({ entry }) {
2257
- const [html, setHtml] = useState7(null);
2745
+ const [html, setHtml] = useState9(null);
2258
2746
  useEffect8(() => {
2259
2747
  let cancelled = false;
2260
2748
  void (async () => {
@@ -2271,13 +2759,13 @@ function CdnHighlightedCode({ entry }) {
2271
2759
  cancelled = true;
2272
2760
  };
2273
2761
  }, [entry.content, entry.lang]);
2274
- return /* @__PURE__ */ jsx41(CodeFigure, { label: entry.label, children: html !== null ? /* @__PURE__ */ jsx41(
2762
+ return /* @__PURE__ */ jsx46(CodeFigure, { label: entry.label, children: html !== null ? /* @__PURE__ */ jsx46(
2275
2763
  "code",
2276
2764
  {
2277
2765
  className: cn("hljs font-mono text-xs", entry.lang && `language-${entry.lang}`),
2278
2766
  dangerouslySetInnerHTML: { __html: html }
2279
2767
  }
2280
- ) : /* @__PURE__ */ jsx41("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
2768
+ ) : /* @__PURE__ */ jsx46("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
2281
2769
  }
2282
2770
  function buildIslandRenderers(opts) {
2283
2771
  const renderers = {};
@@ -2291,12 +2779,13 @@ function buildIslandRenderers(opts) {
2291
2779
  }
2292
2780
 
2293
2781
  // src/interactive/ReportInteractiveIsland.tsx
2294
- import { jsx as jsx42 } from "react/jsx-runtime";
2782
+ import { jsx as jsx47 } from "react/jsx-runtime";
2295
2783
  function ReportInteractiveIsland({
2296
2784
  report,
2297
2785
  title,
2298
2786
  className,
2299
2787
  hideHeader,
2788
+ hideToc,
2300
2789
  syntaxHighlighting = true,
2301
2790
  mermaid = true
2302
2791
  }) {
@@ -2304,13 +2793,14 @@ function ReportInteractiveIsland({
2304
2793
  () => buildIslandRenderers({ syntaxHighlighting, mermaid }),
2305
2794
  [syntaxHighlighting, mermaid]
2306
2795
  );
2307
- return /* @__PURE__ */ jsx42(
2796
+ return /* @__PURE__ */ jsx47(
2308
2797
  ReportInteractive,
2309
2798
  {
2310
2799
  report,
2311
2800
  title,
2312
2801
  className,
2313
2802
  hideHeader,
2803
+ hideToc,
2314
2804
  renderers
2315
2805
  }
2316
2806
  );