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.
@@ -1,8 +1,10 @@
1
1
  "use client";
2
2
  "use strict";
3
+ var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
4
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
6
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
9
  var __export = (target, all) => {
8
10
  for (var name in all)
@@ -16,6 +18,14 @@ var __copyProps = (to, from, except, desc) => {
16
18
  }
17
19
  return to;
18
20
  };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
19
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
30
 
21
31
  // src/interactive/index.ts
@@ -37,7 +47,7 @@ __export(interactive_exports, {
37
47
  module.exports = __toCommonJS(interactive_exports);
38
48
 
39
49
  // src/interactive/ReportInteractive.tsx
40
- var import_react16 = require("react");
50
+ var import_react18 = require("react");
41
51
 
42
52
  // src/result.ts
43
53
  var ok = (data) => ({ ok: true, data });
@@ -136,11 +146,15 @@ var badgeVariants = (0, import_class_variance_authority.cva)(
136
146
  outline: "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
137
147
  ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
138
148
  link: "text-primary underline-offset-4 [a&]:hover:underline",
139
- passed: "border-pass-border bg-pass-bg text-pass font-mono uppercase",
149
+ // Pass/skip/pending are the quiet states: outline + coloured text, no
150
+ // fill, so a green report reads calm. Only `failed` keeps a tinted
151
+ // fill, making it the single loud badge on the page, so red pops the
152
+ // instant something breaks. Colour-as-accent, matching the KPI cards.
153
+ passed: "border-pass-border text-pass font-mono uppercase",
140
154
  failed: "border-fail-border bg-fail-bg text-fail font-mono uppercase",
141
- skipped: "border-skip-border bg-skip-bg text-skip font-mono uppercase",
142
- pending: "border-pend-border bg-pend-bg text-pend font-mono uppercase",
143
- tag: "border-pass-border bg-pass-bg text-pass font-mono font-medium"
155
+ skipped: "border-skip-border text-skip font-mono uppercase",
156
+ pending: "border-pend-border text-pend font-mono uppercase",
157
+ tag: "border-border text-muted-foreground font-mono font-medium [a&]:hover:bg-muted"
144
158
  }
145
159
  },
146
160
  defaultVariants: {
@@ -227,7 +241,7 @@ function DocTable({ entry }) {
227
241
  entry.label ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
228
242
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { tabIndex: 0, className: "overflow-x-auto rounded-md border border-border", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("table", { className: "w-full border-collapse text-sm", children: [
229
243
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("tr", { className: "bg-muted/50", children: entry.columns.map((c) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: c }, c)) }) }),
230
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("tbody", { children: entry.rows.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("tr", { children: row.map((cell, j) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("td", { className: "border-b border-border px-3 py-2 text-foreground", children: cell }, j)) }, i)) })
244
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("tbody", { children: entry.rows.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("tr", { children: row.map((cell2, j) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("td", { className: "border-b border-border px-3 py-2 text-foreground", children: cell2 }, j)) }, i)) })
231
245
  ] }) })
232
246
  ] });
233
247
  }
@@ -693,7 +707,7 @@ function ReportTrace({ spans }) {
693
707
  {
694
708
  className: "min-w-0 flex-1 truncate text-foreground",
695
709
  style: { paddingLeft: `${row.depth * 12}px` },
696
- title: row.statusMessage ? `${row.name} \u2014 ${row.statusMessage}` : row.name,
710
+ title: row.statusMessage ? `${row.name}: ${row.statusMessage}` : row.name,
697
711
  children: row.name
698
712
  }
699
713
  ),
@@ -832,12 +846,12 @@ function useCollapse() {
832
846
  }
833
847
  var STORAGE_KEY = "es-collapsed-ids";
834
848
  function readPersisted() {
835
- if (typeof localStorage === "undefined") return /* @__PURE__ */ new Set();
849
+ if (typeof localStorage === "undefined") return null;
836
850
  try {
837
851
  const raw = localStorage.getItem(STORAGE_KEY);
838
- return raw ? new Set(JSON.parse(raw)) : /* @__PURE__ */ new Set();
852
+ return raw ? new Set(JSON.parse(raw)) : null;
839
853
  } catch {
840
- return /* @__PURE__ */ new Set();
854
+ return null;
841
855
  }
842
856
  }
843
857
  function persist(ids) {
@@ -847,8 +861,10 @@ function persist(ids) {
847
861
  } catch {
848
862
  }
849
863
  }
850
- function useCollapseState() {
851
- const [collapsed, setCollapsed] = (0, import_react6.useState)(readPersisted);
864
+ function useCollapseState(computeDefault) {
865
+ const [collapsed, setCollapsed] = (0, import_react6.useState)(
866
+ () => readPersisted() ?? computeDefault?.() ?? /* @__PURE__ */ new Set()
867
+ );
852
868
  const isCollapsed = (0, import_react6.useCallback)((id) => collapsed.has(id), [collapsed]);
853
869
  const toggle = (0, import_react6.useCallback)((id) => {
854
870
  setCollapsed((prev) => {
@@ -878,20 +894,118 @@ function useCollapseState() {
878
894
 
879
895
  // src/interactive/scenario-actions.tsx
880
896
  var import_react7 = require("react");
897
+
898
+ // ../executable-stories-core/dist/utils/scenario-markdown.js
899
+ function cell(value) {
900
+ return value.replaceAll("|", "\\|").replaceAll("\n", " ");
901
+ }
902
+ function stringify(value) {
903
+ if (typeof value === "string") return value;
904
+ try {
905
+ return JSON.stringify(value, null, 2) ?? String(value);
906
+ } catch {
907
+ return String(value);
908
+ }
909
+ }
910
+ function docEntryToMarkdown(entry) {
911
+ const lines = [];
912
+ switch (entry.kind) {
913
+ case "note":
914
+ lines.push(entry.text);
915
+ break;
916
+ case "tag":
917
+ lines.push(entry.names.map((n) => `\`@${n}\``).join(" "));
918
+ break;
919
+ case "kv":
920
+ lines.push(`**${entry.label}:** ${stringify(entry.value)}`);
921
+ break;
922
+ case "code":
923
+ lines.push(`**${entry.label}**`, "", "```" + (entry.lang ?? ""), entry.content, "```");
924
+ break;
925
+ case "table": {
926
+ lines.push(`**${entry.label}**`, "");
927
+ lines.push(`| ${entry.columns.map(cell).join(" | ")} |`);
928
+ lines.push(`| ${entry.columns.map(() => "---").join(" | ")} |`);
929
+ for (const row of entry.rows) lines.push(`| ${row.map(cell).join(" | ")} |`);
930
+ break;
931
+ }
932
+ case "link":
933
+ lines.push(`[${entry.label}](${entry.url})`);
934
+ break;
935
+ case "section":
936
+ lines.push(`### ${entry.title}`, "", entry.markdown);
937
+ break;
938
+ case "mermaid":
939
+ if (entry.title) lines.push(`**${entry.title}**`, "");
940
+ lines.push("```mermaid", entry.code, "```");
941
+ break;
942
+ case "screenshot":
943
+ lines.push(`![${entry.alt ?? "screenshot"}](${entry.path})`);
944
+ break;
945
+ case "video":
946
+ lines.push(`Video: ${entry.path}${entry.caption ? ` \u2014 ${entry.caption}` : ""}`);
947
+ break;
948
+ case "html": {
949
+ const htmlTitle = entry.title ?? "Embedded HTML";
950
+ if (entry.content) {
951
+ lines.push(`**${htmlTitle}**`, "", "```html", entry.content, "```");
952
+ } else if (entry.url) {
953
+ lines.push(`[${htmlTitle}](${entry.url})`);
954
+ } else {
955
+ lines.push(`Embedded HTML: ${entry.path ?? "(inline)"}`);
956
+ }
957
+ break;
958
+ }
959
+ case "custom":
960
+ lines.push(`**${entry.type}**`, "", "```json", stringify(entry.data), "```");
961
+ break;
962
+ }
963
+ for (const child of entry.children ?? []) {
964
+ lines.push("", ...docEntryToMarkdown(child));
965
+ }
966
+ return lines;
967
+ }
968
+ function stepLines(step, ordinal, full) {
969
+ const status = step.status === "passed" ? "" : ` \u2014 **${step.status}**`;
970
+ const lines = [`${ordinal}. **${step.keyword}** ${step.text}${status}`];
971
+ if (!full) return lines;
972
+ if (step.errorMessage) {
973
+ lines.push(...step.errorMessage.split("\n").map((l) => ` > ${l}`));
974
+ }
975
+ for (const entry of step.docEntries) {
976
+ lines.push("", ...docEntryToMarkdown(entry).map((l) => l === "" ? l : ` ${l}`));
977
+ }
978
+ return lines;
979
+ }
980
+ function scenarioToMarkdown(scenario, options = {}) {
981
+ const { variant = "full", meta = [] } = options;
982
+ const full = variant === "full";
983
+ const lines = full ? [`# ${scenario.title}`, ""] : [`## ${scenario.title} _(${scenario.status})_`, ""];
984
+ if (full && meta.length > 0) lines.push(...meta);
985
+ if (scenario.steps.length > 0) {
986
+ if (full) lines.push("", "## Steps", "");
987
+ scenario.steps.forEach((step, i) => lines.push(...stepLines(step, i + 1, full)));
988
+ }
989
+ if (scenario.errorMessage) {
990
+ if (full) lines.push("", "## Failure", "");
991
+ else lines.push("");
992
+ lines.push("```", scenario.errorMessage.trim(), "```");
993
+ }
994
+ if (full && scenario.docEntries.length > 0) {
995
+ lines.push("", "## Docs", "");
996
+ for (const entry of scenario.docEntries) lines.push(...docEntryToMarkdown(entry), "");
997
+ }
998
+ return lines.join("\n").replaceAll(/\n{3,}/g, "\n\n").trimEnd() + (full ? "\n" : "");
999
+ }
1000
+
1001
+ // src/interactive/scenario-actions.tsx
881
1002
  var ScenarioActionsContext = (0, import_react7.createContext)(null);
882
1003
  var ScenarioActionsProvider = ScenarioActionsContext.Provider;
883
1004
  function useScenarioActions() {
884
1005
  return (0, import_react7.useContext)(ScenarioActionsContext);
885
1006
  }
886
- function scenarioToMarkdown(scenario) {
887
- const lines = [`## ${scenario.title} _(${scenario.status})_`, ""];
888
- for (const step of scenario.steps) {
889
- lines.push(`- **${step.keyword}** ${step.text}`);
890
- }
891
- if (scenario.errorMessage) {
892
- lines.push("", "```", scenario.errorMessage.trim(), "```");
893
- }
894
- return lines.join("\n");
1007
+ function scenarioToMarkdown2(scenario) {
1008
+ return scenarioToMarkdown(scenario, { variant: "compact" });
895
1009
  }
896
1010
  function scenarioToPrompt(scenario) {
897
1011
  const lines = [
@@ -926,7 +1040,7 @@ function Card({ className, ...props }) {
926
1040
  {
927
1041
  "data-slot": "card",
928
1042
  className: cn(
929
- "flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm",
1043
+ "flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-xs",
930
1044
  className
931
1045
  ),
932
1046
  ...props
@@ -957,52 +1071,89 @@ function CardContent({ className, ...props }) {
957
1071
  );
958
1072
  }
959
1073
 
960
- // src/components/ReportScenario.tsx
1074
+ // src/components/ui/dropdown-menu.tsx
1075
+ var React3 = require("react");
1076
+ var import_menu = require("@base-ui/react/menu");
1077
+ var import_lucide_react = require("lucide-react");
961
1078
  var import_jsx_runtime22 = require("react/jsx-runtime");
962
- var STATUS_LABEL = {
963
- passed: "Passed",
964
- failed: "Failed",
965
- skipped: "Skipped",
966
- pending: "Pending"
967
- };
968
- var STATUS_GLYPH = {
969
- passed: "\u2713",
970
- failed: "\u2717",
971
- skipped: "\u25CB",
972
- pending: "\u25CB"
973
- };
974
- var STATUS_GLYPH_COLOR = {
975
- passed: "text-pass",
976
- failed: "text-fail",
977
- skipped: "text-skip",
978
- pending: "text-pend"
979
- };
980
- var STATUS_BADGE = {
981
- passed: "passed",
982
- failed: "failed",
983
- skipped: "skipped",
984
- pending: "pending"
1079
+ 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";
1080
+ function DropdownMenu(props) {
1081
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_menu.Menu.Root, { "data-slot": "dropdown-menu", ...props });
1082
+ }
1083
+ function DropdownMenuTrigger(props) {
1084
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_menu.Menu.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
1085
+ }
1086
+ function DropdownMenuContent({
1087
+ className,
1088
+ align = "end",
1089
+ sideOffset = 4,
1090
+ ...props
1091
+ }) {
1092
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_menu.Menu.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1093
+ import_menu.Menu.Positioner,
1094
+ {
1095
+ "data-slot": "dropdown-menu-positioner",
1096
+ className: "z-50",
1097
+ align,
1098
+ sideOffset,
1099
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1100
+ import_menu.Menu.Popup,
1101
+ {
1102
+ "data-slot": "dropdown-menu-content",
1103
+ className: cn(
1104
+ "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",
1105
+ className
1106
+ ),
1107
+ ...props
1108
+ }
1109
+ )
1110
+ }
1111
+ ) });
1112
+ }
1113
+ function DropdownMenuItem({
1114
+ className,
1115
+ ...props
1116
+ }) {
1117
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1118
+ import_menu.Menu.Item,
1119
+ {
1120
+ "data-slot": "dropdown-menu-item",
1121
+ className: cn(itemClasses, className),
1122
+ ...props
1123
+ }
1124
+ );
1125
+ }
1126
+
1127
+ // src/components/ReportScenario.tsx
1128
+ var import_lucide_react2 = require("lucide-react");
1129
+ var import_jsx_runtime23 = require("react/jsx-runtime");
1130
+ var STATUS_META = {
1131
+ passed: { label: "Passed", glyph: "\u2713", glyphColor: "text-pass", badgeVariant: "passed", showPill: false },
1132
+ failed: { label: "Failed", glyph: "\u2717", glyphColor: "text-fail", badgeVariant: "failed", showPill: true },
1133
+ skipped: { label: "Skipped", glyph: "\u25CB", glyphColor: "text-skip", badgeVariant: "skipped", showPill: true },
1134
+ pending: { label: "Pending", glyph: "\u25CB", glyphColor: "text-pend", badgeVariant: "pending", showPill: true }
985
1135
  };
986
1136
  function ReportScenario({ scenario, hideTitle = false }) {
987
1137
  const titleId = `${scenario.id}-title`;
988
1138
  const bodyId = `${scenario.id}-body`;
989
- const label = scenario.planned ? "Planned" : STATUS_LABEL[scenario.status];
1139
+ const meta = STATUS_META[scenario.status];
1140
+ const label = scenario.planned ? "Planned" : meta.label;
990
1141
  const collapse = useCollapse();
991
1142
  const collapsible = collapse !== null && !hideTitle;
992
1143
  const collapsed = collapsible ? collapse.isCollapsed(scenario.id) : false;
993
1144
  const actions = useScenarioActions();
994
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
1145
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
995
1146
  Card,
996
1147
  {
997
1148
  id: scenario.id,
998
1149
  "data-status": scenario.status,
999
- ...hideTitle ? { "aria-label": `${scenario.title} \u2014 ${label}` } : { "aria-labelledby": titleId },
1150
+ ...hideTitle ? { "aria-label": `${scenario.title}, ${label}` } : { "aria-labelledby": titleId },
1000
1151
  className: "gap-0 py-0",
1001
1152
  children: [
1002
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(CardHeader, { className: "px-4 py-3", children: [
1003
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
1004
- hideTitle ? null : /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1005
- collapsible ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1153
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(CardHeader, { className: "px-4 py-3", children: [
1154
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
1155
+ hideTitle ? null : /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1156
+ collapsible ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1006
1157
  "button",
1007
1158
  {
1008
1159
  type: "button",
@@ -1011,60 +1162,49 @@ function ReportScenario({ scenario, hideTitle = false }) {
1011
1162
  "aria-controls": bodyId,
1012
1163
  "aria-label": `Toggle ${scenario.title}`,
1013
1164
  className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
1014
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1165
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1015
1166
  }
1016
1167
  ) : null,
1017
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { "aria-hidden": true, className: cn("text-sm leading-none", STATUS_GLYPH_COLOR[scenario.status]), children: STATUS_GLYPH[scenario.status] }),
1018
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: scenario.title })
1168
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { "aria-hidden": true, className: cn("text-sm leading-none", meta.glyphColor), children: meta.glyph }),
1169
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { children: scenario.title })
1019
1170
  ] }),
1020
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex shrink-0 items-center gap-2", children: [
1021
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ScenarioRunHistory, { scenarioId: scenario.id }),
1022
- scenario.durationMs > 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(scenario.durationMs) }) : null,
1023
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Badge, { variant: STATUS_BADGE[scenario.status], "aria-label": `Status: ${label}`, children: label }),
1024
- actions ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-0.5", children: [
1025
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1026
- "button",
1171
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex shrink-0 items-center gap-2", children: [
1172
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ScenarioRunHistory, { scenarioId: scenario.id }),
1173
+ scenario.durationMs > 0 ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(scenario.durationMs) }) : null,
1174
+ meta.showPill ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Badge, { variant: meta.badgeVariant, "aria-label": `Status: ${label}`, children: label }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "sr-only", children: `Status: ${label}` }),
1175
+ actions ? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(DropdownMenu, { children: [
1176
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1177
+ DropdownMenuTrigger,
1027
1178
  {
1028
- type: "button",
1029
- onClick: () => actions.copyLink(scenario),
1030
- "aria-label": `Copy link to ${scenario.title}`,
1031
- title: "Copy permalink",
1032
- className: "cursor-pointer rounded px-1 py-0.5 font-mono text-[0.625rem] text-muted-foreground hover:bg-muted hover:text-foreground",
1033
- children: "link"
1179
+ "aria-label": `Actions for ${scenario.title}`,
1180
+ className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
1181
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react2.MoreHorizontalIcon, { "aria-hidden": true })
1034
1182
  }
1035
1183
  ),
1036
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1037
- "button",
1038
- {
1039
- type: "button",
1040
- onClick: () => actions.copyMarkdown(scenario),
1041
- "aria-label": `Copy ${scenario.title} as Markdown`,
1042
- title: "Copy as Markdown",
1043
- className: "cursor-pointer rounded px-1 py-0.5 font-mono text-[0.625rem] text-muted-foreground hover:bg-muted hover:text-foreground",
1044
- children: "md"
1045
- }
1046
- ),
1047
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1048
- "button",
1049
- {
1050
- type: "button",
1051
- onClick: () => actions.copyPrompt(scenario),
1052
- "aria-label": `Copy ${scenario.title} as an AI investigation prompt`,
1053
- title: "Copy as AI prompt",
1054
- className: "cursor-pointer rounded px-1 py-0.5 font-mono text-[0.625rem] text-muted-foreground hover:bg-muted hover:text-foreground",
1055
- children: "ai"
1056
- }
1057
- )
1184
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(DropdownMenuContent, { align: "end", children: [
1185
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
1186
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react2.LinkIcon, {}),
1187
+ " Copy link"
1188
+ ] }),
1189
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
1190
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react2.FileTextIcon, {}),
1191
+ " Copy as Markdown"
1192
+ ] }),
1193
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
1194
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react2.SparklesIcon, {}),
1195
+ " Explain with AI"
1196
+ ] })
1197
+ ] })
1058
1198
  ] }) : null
1059
1199
  ] })
1060
1200
  ] }),
1061
- scenario.tags.length > 0 || (scenario.tickets?.length ?? 0) > 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("ul", { "aria-label": "Tags and tickets", className: "flex flex-wrap gap-1.5", children: [
1062
- scenario.tags.map((t) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
1063
- (scenario.tickets ?? []).map((ticket) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("li", { children: ticket.url ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("a", { href: ticket.url, target: "_blank", rel: "noreferrer noopener", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Badge, { variant: "outline", children: ticket.id }) }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Badge, { variant: "outline", children: ticket.id }) }, `ticket-${ticket.id}`))
1201
+ scenario.tags.length > 0 || (scenario.tickets?.length ?? 0) > 0 ? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("ul", { "aria-label": "Tags and tickets", className: "flex flex-wrap gap-1.5", children: [
1202
+ scenario.tags.map((t) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
1203
+ (scenario.tickets ?? []).map((ticket) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("li", { children: ticket.url ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("a", { href: ticket.url, target: "_blank", rel: "noreferrer noopener", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Badge, { variant: "outline", children: ticket.id }) }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Badge, { variant: "outline", children: ticket.id }) }, `ticket-${ticket.id}`))
1064
1204
  ] }) : null
1065
1205
  ] }),
1066
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
1067
- scenario.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1206
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
1207
+ scenario.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1068
1208
  "pre",
1069
1209
  {
1070
1210
  role: "alert",
@@ -1072,10 +1212,10 @@ function ReportScenario({ scenario, hideTitle = false }) {
1072
1212
  children: scenario.errorMessage
1073
1213
  }
1074
1214
  ) : null,
1075
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ReportSteps, { scenario }),
1076
- scenario.docEntries.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { "data-es-docs": true, className: "mt-3", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
1077
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ReportAttachments, { attachments: scenario.attachments }),
1078
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ReportTrace, { spans: scenario.otelSpans })
1215
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ReportSteps, { scenario }),
1216
+ scenario.docEntries.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { "data-es-docs": true, className: "mt-3", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
1217
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ReportAttachments, { attachments: scenario.attachments }),
1218
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ReportTrace, { spans: scenario.otelSpans })
1079
1219
  ] })
1080
1220
  ]
1081
1221
  }
@@ -1083,13 +1223,13 @@ function ReportScenario({ scenario, hideTitle = false }) {
1083
1223
  }
1084
1224
 
1085
1225
  // src/components/ReportScenarioList.tsx
1086
- var import_jsx_runtime23 = require("react/jsx-runtime");
1226
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1087
1227
  function ReportScenarioList({ feature }) {
1088
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_jsx_runtime23.Fragment, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ReportScenario, { scenario }, scenario.id)) });
1228
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_jsx_runtime24.Fragment, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ReportScenario, { scenario }, scenario.id)) });
1089
1229
  }
1090
1230
 
1091
1231
  // src/components/ReportFeature.tsx
1092
- var import_jsx_runtime24 = require("react/jsx-runtime");
1232
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1093
1233
  function ReportFeature({ feature }) {
1094
1234
  const titleId = `${feature.id}-title`;
1095
1235
  const bodyId = `${feature.id}-body`;
@@ -1097,74 +1237,80 @@ function ReportFeature({ feature }) {
1097
1237
  const skipped = s.skipped + s.pending;
1098
1238
  const collapse = useCollapse();
1099
1239
  const collapsed = collapse?.isCollapsed(feature.id) ?? false;
1100
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
1101
- "section",
1102
- {
1103
- id: feature.id,
1104
- "data-slot": "feature",
1105
- "aria-labelledby": titleId,
1106
- className: "overflow-hidden rounded-xl border border-border bg-card shadow-sm",
1107
- children: [
1108
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex items-center justify-between gap-4 border-b border-border bg-muted/40 px-4 py-3", children: [
1109
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex min-w-0 items-center gap-2", children: [
1110
- collapse ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1111
- "button",
1240
+ return (
1241
+ // Suite = a section heading + a divider, NOT another bordered card. Drops a
1242
+ // layer of nesting so the scenario cards below read as the primary unit.
1243
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
1244
+ "section",
1245
+ {
1246
+ id: feature.id,
1247
+ "data-slot": "feature",
1248
+ "aria-labelledby": titleId,
1249
+ className: "flex flex-col gap-3",
1250
+ children: [
1251
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1252
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "min-w-0", children: [
1253
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("h2", { id: titleId, className: "text-base font-semibold tracking-tight text-foreground", children: collapse ? /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
1254
+ "button",
1255
+ {
1256
+ type: "button",
1257
+ onClick: () => collapse.toggle(feature.id),
1258
+ "aria-expanded": !collapsed,
1259
+ "aria-controls": bodyId,
1260
+ className: "flex w-full cursor-pointer items-center gap-2 text-left hover:text-foreground/80",
1261
+ children: [
1262
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { "aria-hidden": true, className: cn("shrink-0 text-xs text-muted-foreground transition-transform", !collapsed && "rotate-90"), children: "\u25B8" }),
1263
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "truncate", children: feature.title })
1264
+ ]
1265
+ }
1266
+ ) : feature.title }),
1267
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("p", { className: cn("mt-0.5 truncate font-mono text-xs text-muted-foreground", collapse && "pl-5"), children: feature.sourceFile })
1268
+ ] }),
1269
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
1270
+ "p",
1112
1271
  {
1113
- type: "button",
1114
- onClick: () => collapse.toggle(feature.id),
1115
- "aria-expanded": !collapsed,
1116
- "aria-controls": bodyId,
1117
- "aria-label": `Toggle ${feature.title}`,
1118
- className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
1119
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1272
+ className: "flex shrink-0 items-center gap-1.5 text-xs text-muted-foreground",
1273
+ "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
1274
+ children: [
1275
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("span", { children: [
1276
+ s.passed,
1277
+ " passed"
1278
+ ] }),
1279
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1280
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
1281
+ s.failed,
1282
+ " failed"
1283
+ ] }),
1284
+ skipped > 0 ? /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
1285
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1286
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("span", { children: [
1287
+ skipped,
1288
+ " skipped"
1289
+ ] })
1290
+ ] }) : null
1291
+ ]
1120
1292
  }
1121
- ) : null,
1122
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "min-w-0", children: [
1123
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h2", { id: titleId, className: "text-sm font-semibold tracking-tight text-foreground", children: feature.title }),
1124
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "mt-0.5 truncate font-mono text-xs text-muted-foreground", children: feature.sourceFile })
1125
- ] })
1293
+ )
1126
1294
  ] }),
1127
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
1128
- "div",
1129
- {
1130
- role: "img",
1131
- className: "flex shrink-0 items-center gap-2.5 font-mono text-xs font-medium",
1132
- "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
1133
- children: [
1134
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { "aria-hidden": true, className: "text-pass", children: [
1135
- "\u2713",
1136
- s.passed
1137
- ] }),
1138
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { "aria-hidden": true, className: "text-fail", children: [
1139
- "\u2717",
1140
- s.failed
1141
- ] }),
1142
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { "aria-hidden": true, className: "text-skip", children: [
1143
- "\u25CB",
1144
- skipped
1145
- ] })
1146
- ]
1147
- }
1148
- )
1149
- ] }),
1150
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-2 p-3", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ReportScenarioList, { feature }) })
1151
- ]
1152
- }
1295
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-3", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ReportScenarioList, { feature }) })
1296
+ ]
1297
+ }
1298
+ )
1153
1299
  );
1154
1300
  }
1155
1301
 
1156
1302
  // src/components/ReportFeatureList.tsx
1157
- var import_jsx_runtime25 = require("react/jsx-runtime");
1303
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1158
1304
  function ReportFeatureList() {
1159
1305
  const report = useReport();
1160
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_jsx_runtime25.Fragment, { children: report.features.map((feature) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ReportFeature, { feature }, feature.id)) });
1306
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "flex flex-col gap-8", children: report.features.map((feature) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ReportFeature, { feature }, feature.id)) });
1161
1307
  }
1162
1308
 
1163
1309
  // src/components/ui/empty.tsx
1164
1310
  var import_class_variance_authority2 = require("class-variance-authority");
1165
- var import_jsx_runtime26 = require("react/jsx-runtime");
1311
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1166
1312
  function Empty({ className, ...props }) {
1167
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1313
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1168
1314
  "div",
1169
1315
  {
1170
1316
  "data-slot": "empty",
@@ -1177,7 +1323,7 @@ function Empty({ className, ...props }) {
1177
1323
  );
1178
1324
  }
1179
1325
  function EmptyHeader({ className, ...props }) {
1180
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1326
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1181
1327
  "div",
1182
1328
  {
1183
1329
  "data-slot": "empty-header",
@@ -1204,7 +1350,7 @@ var emptyMediaVariants = (0, import_class_variance_authority2.cva)(
1204
1350
  }
1205
1351
  );
1206
1352
  function EmptyTitle({ className, ...props }) {
1207
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1353
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1208
1354
  "div",
1209
1355
  {
1210
1356
  "data-slot": "empty-title",
@@ -1215,16 +1361,16 @@ function EmptyTitle({ className, ...props }) {
1215
1361
  }
1216
1362
 
1217
1363
  // src/components/ReportEmpty.tsx
1218
- var import_jsx_runtime27 = require("react/jsx-runtime");
1364
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1219
1365
  function ReportEmpty({ message }) {
1220
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(EmptyHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1366
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(EmptyHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1221
1367
  }
1222
1368
 
1223
1369
  // src/components/ReportSummary.tsx
1224
- var import_jsx_runtime28 = require("react/jsx-runtime");
1370
+ var import_jsx_runtime29 = require("react/jsx-runtime");
1225
1371
  function ReportSummary({ className }) {
1226
1372
  const report = useReport();
1227
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1373
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1228
1374
  ReportSummaryView,
1229
1375
  {
1230
1376
  summary: report.summary,
@@ -1233,38 +1379,54 @@ function ReportSummary({ className }) {
1233
1379
  }
1234
1380
  );
1235
1381
  }
1382
+ var TONE = {
1383
+ total: { num: "text-foreground", dot: "bg-muted-foreground/50" },
1384
+ passed: { num: "text-pass", dot: "bg-pass" },
1385
+ failed: { num: "text-fail", dot: "bg-fail" },
1386
+ skipped: { num: "text-skip", dot: "bg-skip" },
1387
+ pending: { num: "text-pend", dot: "bg-pend" }
1388
+ };
1236
1389
  function ReportSummaryView({ summary, className, ariaLabel }) {
1237
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("p", { className: cn("text-sm text-muted-foreground", className), "aria-label": ariaLabel, children: [
1238
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { children: [
1239
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("strong", { className: "font-semibold text-foreground", children: summary.total }),
1240
- " scenario",
1241
- summary.total === 1 ? "" : "s"
1242
- ] }),
1243
- " \xB7 ",
1244
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { "data-status": "passed", className: "text-pass", children: [
1245
- summary.passed,
1246
- " passed"
1247
- ] }),
1248
- " \xB7 ",
1249
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { "data-status": "failed", className: "text-fail", children: [
1250
- summary.failed,
1251
- " failed"
1252
- ] }),
1253
- summary.skipped > 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
1254
- " \xB7 ",
1255
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { "data-status": "skipped", className: "text-skip", children: [
1256
- summary.skipped,
1257
- " skipped"
1258
- ] })
1259
- ] }) : null,
1260
- summary.pending > 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
1261
- " \xB7 ",
1262
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { "data-status": "pending", className: "text-pend", children: [
1263
- summary.pending,
1264
- " pending"
1265
- ] })
1266
- ] }) : null
1267
- ] });
1390
+ const cards = [
1391
+ { status: "total", label: "Total", value: summary.total },
1392
+ { status: "passed", label: "Passed", value: summary.passed },
1393
+ { status: "failed", label: "Failed", value: summary.failed },
1394
+ { status: "skipped", label: "Skipped", value: summary.skipped }
1395
+ ];
1396
+ if (summary.pending > 0) {
1397
+ cards.push({ status: "pending", label: "Pending", value: summary.pending });
1398
+ }
1399
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1400
+ "dl",
1401
+ {
1402
+ className: cn(
1403
+ "grid grid-cols-2 gap-3",
1404
+ // Columns follow the card count so the row is always full — 5 statuses
1405
+ // (pending present) never leave a lone orphan card below a row of four.
1406
+ cards.length === 5 ? "sm:grid-cols-5" : "sm:grid-cols-4",
1407
+ className
1408
+ ),
1409
+ "aria-label": ariaLabel,
1410
+ children: cards.map((c) => {
1411
+ const tone = TONE[c.status];
1412
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
1413
+ "div",
1414
+ {
1415
+ "data-status": c.status,
1416
+ className: "rounded-lg border bg-card px-4 py-3",
1417
+ children: [
1418
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("dt", { className: "flex items-center gap-1.5 font-mono text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: [
1419
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
1420
+ c.label
1421
+ ] }),
1422
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("dd", { className: cn("mt-2 text-3xl font-semibold leading-none tracking-tight tabular-nums", tone.num), children: c.value })
1423
+ ]
1424
+ },
1425
+ c.status
1426
+ );
1427
+ })
1428
+ }
1429
+ );
1268
1430
  }
1269
1431
 
1270
1432
  // src/lib/provenance.ts
@@ -1317,7 +1479,7 @@ function prUrl(ci) {
1317
1479
  }
1318
1480
 
1319
1481
  // src/components/ReportMeta.tsx
1320
- var import_jsx_runtime29 = require("react/jsx-runtime");
1482
+ var import_jsx_runtime30 = require("react/jsx-runtime");
1321
1483
  function ReportMeta() {
1322
1484
  const report = useReport();
1323
1485
  const items = [];
@@ -1340,13 +1502,13 @@ function ReportMeta() {
1340
1502
  const short = sha.slice(0, 8);
1341
1503
  items.push({
1342
1504
  label: "Commit",
1343
- value: url ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1505
+ value: url ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1344
1506
  });
1345
1507
  }
1346
1508
  if (report.ci) {
1347
1509
  items.push({
1348
1510
  label: "CI",
1349
- value: report.ci.url ? /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1511
+ value: report.ci.url ? /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1350
1512
  ciDisplayName(report.ci.name),
1351
1513
  report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
1352
1514
  ] }) : `${ciDisplayName(report.ci.name)}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
@@ -1357,59 +1519,172 @@ function ReportMeta() {
1357
1519
  const label = `#${report.ci.prNumber}`;
1358
1520
  items.push({
1359
1521
  label: "PR",
1360
- value: url ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1522
+ value: url ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1361
1523
  });
1362
1524
  }
1363
1525
  if (items.length === 0) return null;
1364
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("dl", { "aria-label": "Run metadata", className: "flex flex-wrap gap-x-6 gap-y-1 text-xs text-muted-foreground", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex gap-1.5", children: [
1365
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("dt", { className: "font-medium", children: item.label }),
1366
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("dd", { className: "font-mono text-foreground", children: item.value })
1367
- ] }, item.label)) });
1526
+ return (
1527
+ // Run metadata is useful but secondary to "what failed" — tuck it into a
1528
+ // collapsed disclosure so it doesn't push the result down. Native <details>
1529
+ // so it works with no JS (the SSR/no-hydration report still toggles).
1530
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("details", { "data-es-meta-card": true, className: "es-run-details group rounded-lg border border-border bg-card text-xs", children: [
1531
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("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: [
1532
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
1533
+ "Run details"
1534
+ ] }),
1535
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("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__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex items-baseline gap-1.5", children: [
1536
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("dt", { className: "font-medium", children: item.label }),
1537
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("dd", { className: "font-mono text-foreground", children: item.value })
1538
+ ] }, item.label)) })
1539
+ ] })
1540
+ );
1368
1541
  }
1369
1542
 
1370
1543
  // src/components/ReportSchemaError.tsx
1371
- var import_jsx_runtime30 = require("react/jsx-runtime");
1544
+ var import_jsx_runtime31 = require("react/jsx-runtime");
1372
1545
  function ReportSchemaError({ error }) {
1373
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1374
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("strong", { children: "Report could not be displayed." }) }),
1375
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { children: error.message }),
1376
- error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("p", { children: [
1546
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1547
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("strong", { children: "Report could not be displayed." }) }),
1548
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { children: error.message }),
1549
+ error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("p", { children: [
1377
1550
  "The report bundle is newer than this version of ",
1378
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("code", { children: "executable-stories-react" }),
1551
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("code", { children: "executable-stories-react" }),
1379
1552
  ". Upgrade the package, or regenerate the report with an older formatters CLI."
1380
1553
  ] }) : null,
1381
- error.issues && error.issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("details", { children: [
1382
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("summary", { children: [
1554
+ error.issues && error.issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("details", { children: [
1555
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("summary", { children: [
1383
1556
  error.issues.length,
1384
1557
  " validation issue",
1385
1558
  error.issues.length === 1 ? "" : "s"
1386
1559
  ] }),
1387
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1560
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1388
1561
  ] }) : null
1389
1562
  ] });
1390
1563
  }
1391
1564
 
1392
1565
  // src/components/ReportShell.tsx
1393
- var import_jsx_runtime31 = require("react/jsx-runtime");
1394
- function ReportTitleBlock({ title }) {
1395
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_jsx_runtime31.Fragment, { children: [
1396
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("h1", { children: title ?? "Story Report" }),
1397
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ReportSummary, {}),
1398
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ReportMeta, {})
1399
- ] });
1400
- }
1566
+ var import_jsx_runtime32 = require("react/jsx-runtime");
1401
1567
  function ReportErrorShell({
1402
1568
  error,
1403
1569
  className,
1404
1570
  title,
1405
1571
  dataTheme
1406
1572
  }) {
1407
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ReportSchemaError, { error }) });
1573
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ReportSchemaError, { error }) });
1574
+ }
1575
+
1576
+ // src/interactive/ReportInteractive.tsx
1577
+ var import_lucide_react5 = require("lucide-react");
1578
+
1579
+ // src/components/ui/switch.tsx
1580
+ var React4 = require("react");
1581
+ var import_switch = require("@base-ui/react/switch");
1582
+ var import_jsx_runtime33 = require("react/jsx-runtime");
1583
+ function Switch({
1584
+ className,
1585
+ ...props
1586
+ }) {
1587
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
1588
+ import_switch.Switch.Root,
1589
+ {
1590
+ "data-slot": "switch",
1591
+ className: cn(
1592
+ "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",
1593
+ className
1594
+ ),
1595
+ ...props,
1596
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
1597
+ import_switch.Switch.Thumb,
1598
+ {
1599
+ "data-slot": "switch-thumb",
1600
+ 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"
1601
+ }
1602
+ )
1603
+ }
1604
+ );
1605
+ }
1606
+
1607
+ // src/components/ui/toggle-group.tsx
1608
+ var React5 = __toESM(require("react"), 1);
1609
+ var import_toggle_group = require("@base-ui/react/toggle-group");
1610
+ var import_toggle = require("@base-ui/react/toggle");
1611
+ var import_class_variance_authority3 = require("class-variance-authority");
1612
+ var import_jsx_runtime34 = require("react/jsx-runtime");
1613
+ var toggleItemVariants = (0, import_class_variance_authority3.cva)(
1614
+ "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",
1615
+ {
1616
+ variants: {
1617
+ size: {
1618
+ default: "h-9 px-3 text-sm",
1619
+ sm: "h-8 px-2.5 text-[0.82rem]"
1620
+ }
1621
+ },
1622
+ defaultVariants: { size: "default" }
1623
+ }
1624
+ );
1625
+ var ToggleGroupContext = React5.createContext({
1626
+ size: "default"
1627
+ });
1628
+ function ToggleGroup({
1629
+ className,
1630
+ size,
1631
+ children,
1632
+ ...props
1633
+ }) {
1634
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
1635
+ import_toggle_group.ToggleGroup,
1636
+ {
1637
+ "data-slot": "toggle-group",
1638
+ className: cn("flex w-fit items-center gap-0.5 rounded-lg bg-muted p-0.5", className),
1639
+ ...props,
1640
+ children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ToggleGroupContext.Provider, { value: { size }, children })
1641
+ }
1642
+ );
1643
+ }
1644
+ function ToggleGroupItem({
1645
+ className,
1646
+ children,
1647
+ size,
1648
+ ...props
1649
+ }) {
1650
+ const context = React5.useContext(ToggleGroupContext);
1651
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
1652
+ import_toggle.Toggle,
1653
+ {
1654
+ "data-slot": "toggle-group-item",
1655
+ className: cn(toggleItemVariants({ size: context.size ?? size }), "flex-1 shrink-0", className),
1656
+ ...props,
1657
+ children
1658
+ }
1659
+ );
1408
1660
  }
1409
1661
 
1410
1662
  // src/interactive/ReportSearch.tsx
1411
1663
  var import_react8 = require("react");
1412
- var import_jsx_runtime32 = require("react/jsx-runtime");
1664
+ var import_lucide_react3 = require("lucide-react");
1665
+
1666
+ // src/components/ui/input.tsx
1667
+ var React6 = require("react");
1668
+ var import_jsx_runtime35 = require("react/jsx-runtime");
1669
+ function Input({ className, type, ...props }) {
1670
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
1671
+ "input",
1672
+ {
1673
+ type,
1674
+ "data-slot": "input",
1675
+ className: cn(
1676
+ "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",
1677
+ "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
1678
+ "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
1679
+ className
1680
+ ),
1681
+ ...props
1682
+ }
1683
+ );
1684
+ }
1685
+
1686
+ // src/interactive/ReportSearch.tsx
1687
+ var import_jsx_runtime36 = require("react/jsx-runtime");
1413
1688
  var ReportSearch = (0, import_react8.forwardRef)(
1414
1689
  function ReportSearch2(props, ref) {
1415
1690
  const {
@@ -1417,11 +1692,11 @@ var ReportSearch = (0, import_react8.forwardRef)(
1417
1692
  onChange,
1418
1693
  matchedCount,
1419
1694
  totalCount,
1420
- placeholder = "Search scenarios, tags, or step text\u2026",
1695
+ placeholder = "Search scenarios or tags\u2026",
1421
1696
  className
1422
1697
  } = props;
1423
1698
  const inputId = (0, import_react8.useId)();
1424
- const showCounts = typeof matchedCount === "number" && typeof totalCount === "number";
1699
+ const showCounts = value.trim().length > 0 && typeof matchedCount === "number" && typeof totalCount === "number";
1425
1700
  function handleChange(e) {
1426
1701
  onChange(e.target.value);
1427
1702
  }
@@ -1431,24 +1706,46 @@ var ReportSearch = (0, import_react8.forwardRef)(
1431
1706
  e.preventDefault();
1432
1707
  }
1433
1708
  }
1434
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: ["es-search", className].filter(Boolean).join(" "), children: [
1435
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("label", { htmlFor: inputId, className: "es-search-label", children: "Search" }),
1436
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1437
- "input",
1709
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: cn("es-search flex flex-col gap-1", className), children: [
1710
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("label", { htmlFor: inputId, className: "sr-only", children: "Search" }),
1711
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "relative w-full sm:w-72", children: [
1712
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
1713
+ import_lucide_react3.Search,
1714
+ {
1715
+ "aria-hidden": true,
1716
+ className: "pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground"
1717
+ }
1718
+ ),
1719
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
1720
+ Input,
1721
+ {
1722
+ ref,
1723
+ id: inputId,
1724
+ type: "search",
1725
+ value,
1726
+ onChange: handleChange,
1727
+ onKeyDown: handleKeyDown,
1728
+ placeholder,
1729
+ autoComplete: "off",
1730
+ spellCheck: false,
1731
+ "aria-keyshortcuts": "/",
1732
+ className: "pl-8"
1733
+ }
1734
+ )
1735
+ ] }),
1736
+ showCounts ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
1737
+ "span",
1438
1738
  {
1439
- ref,
1440
- id: inputId,
1441
- type: "search",
1442
- value,
1443
- onChange: handleChange,
1444
- onKeyDown: handleKeyDown,
1445
- placeholder,
1446
- autoComplete: "off",
1447
- spellCheck: false,
1448
- "aria-keyshortcuts": "/"
1739
+ className: "text-xs tabular-nums text-muted-foreground",
1740
+ "aria-live": "polite",
1741
+ children: [
1742
+ matchedCount,
1743
+ " of ",
1744
+ totalCount,
1745
+ " scenarios"
1746
+ ]
1449
1747
  }
1450
- ),
1451
- showCounts ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "es-search-counts", "aria-live": "polite", children: value ? `${matchedCount} of ${totalCount}` : `${totalCount} total` }) : null
1748
+ ) : null
1452
1749
  ] });
1453
1750
  }
1454
1751
  );
@@ -1477,7 +1774,7 @@ function scrollToScenarioId(scenarioId, options = {}) {
1477
1774
  }
1478
1775
 
1479
1776
  // src/interactive/ReportFailureBanner.tsx
1480
- var import_jsx_runtime33 = require("react/jsx-runtime");
1777
+ var import_jsx_runtime37 = require("react/jsx-runtime");
1481
1778
  function ReportFailureBanner({ failures }) {
1482
1779
  const first = failures[0];
1483
1780
  const jumpToFirst = (0, import_react9.useCallback)(() => {
@@ -1485,7 +1782,7 @@ function ReportFailureBanner({ failures }) {
1485
1782
  scrollToScenarioId(first.scenarioId);
1486
1783
  }, [first]);
1487
1784
  if (failures.length === 0) return null;
1488
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
1785
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
1489
1786
  "div",
1490
1787
  {
1491
1788
  className: "es-failure-banner",
@@ -1493,20 +1790,20 @@ function ReportFailureBanner({ failures }) {
1493
1790
  "aria-live": "polite",
1494
1791
  "aria-label": "Failure summary",
1495
1792
  children: [
1496
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("span", { className: "es-failure-banner-text", children: [
1497
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("strong", { children: failures.length }),
1793
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { className: "es-failure-banner-text", children: [
1794
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("strong", { children: failures.length }),
1498
1795
  " ",
1499
1796
  "failure",
1500
1797
  failures.length === 1 ? "" : "s"
1501
1798
  ] }),
1502
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
1799
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
1503
1800
  "button",
1504
1801
  {
1505
1802
  type: "button",
1506
1803
  className: "es-failure-banner-jump",
1507
1804
  onClick: jumpToFirst,
1508
- "aria-label": "Jump to first failure",
1509
- children: "Jump to first \u2193"
1805
+ "aria-label": "View first failure",
1806
+ children: "View first failure \u2193"
1510
1807
  }
1511
1808
  )
1512
1809
  ]
@@ -1516,7 +1813,7 @@ function ReportFailureBanner({ failures }) {
1516
1813
 
1517
1814
  // src/interactive/ReportFreshness.tsx
1518
1815
  var import_react10 = require("react");
1519
- var import_jsx_runtime34 = require("react/jsx-runtime");
1816
+ var import_jsx_runtime38 = require("react/jsx-runtime");
1520
1817
  function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
1521
1818
  const [now, setNow] = (0, import_react10.useState)(nowMs);
1522
1819
  (0, import_react10.useEffect)(() => {
@@ -1524,7 +1821,7 @@ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
1524
1821
  }, [nowMs]);
1525
1822
  if (lastRunMs === void 0 || now === void 0) return null;
1526
1823
  const age = formatRelativeAge(lastRunMs, now);
1527
- const runLink = ciUrl ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
1824
+ const runLink = ciUrl ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
1528
1825
  "a",
1529
1826
  {
1530
1827
  href: ciUrl,
@@ -1535,22 +1832,22 @@ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
1535
1832
  }
1536
1833
  ) : null;
1537
1834
  if (isReportStale(lastRunMs, staleAfterDays, now)) {
1538
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "es-stale-banner", role: "status", "aria-label": "Report freshness", children: [
1539
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { children: [
1540
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("strong", { children: [
1835
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "es-stale-banner", role: "status", "aria-label": "Report freshness", children: [
1836
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("span", { children: [
1837
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("strong", { children: [
1541
1838
  "Last verified ",
1542
1839
  age,
1543
1840
  "."
1544
1841
  ] }),
1545
- " This report may no longer match the current behavior \u2014 look for a newer run."
1842
+ " This report may no longer match the current behavior. Look for a newer run."
1546
1843
  ] }),
1547
1844
  runLink
1548
1845
  ] });
1549
1846
  }
1550
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("p", { className: "es-freshness", "aria-label": "Report freshness", children: [
1847
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("p", { className: "es-freshness", "aria-label": "Report freshness", children: [
1551
1848
  "Verified ",
1552
1849
  age,
1553
- runLink ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
1850
+ runLink ? /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
1554
1851
  " \xB7 ",
1555
1852
  runLink
1556
1853
  ] }) : null
@@ -1558,15 +1855,15 @@ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
1558
1855
  }
1559
1856
 
1560
1857
  // src/interactive/ReportLastRunDelta.tsx
1561
- var import_jsx_runtime35 = require("react/jsx-runtime");
1858
+ var import_jsx_runtime39 = require("react/jsx-runtime");
1562
1859
  var MAX_LINKS = 5;
1563
1860
  function ScenarioLinks({ scenarios, tone }) {
1564
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
1565
- scenarios.slice(0, MAX_LINKS).map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { children: [
1861
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
1862
+ scenarios.slice(0, MAX_LINKS).map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("span", { children: [
1566
1863
  i > 0 ? ", " : "",
1567
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("a", { href: `#${s.id}`, className: cn("underline underline-offset-2 hover:no-underline", tone), children: s.title })
1864
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("a", { href: `#${s.id}`, className: cn("underline underline-offset-2 hover:no-underline", tone), children: s.title })
1568
1865
  ] }, s.id)),
1569
- scenarios.length > MAX_LINKS ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { children: [
1866
+ scenarios.length > MAX_LINKS ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("span", { children: [
1570
1867
  " and ",
1571
1868
  scenarios.length - MAX_LINKS,
1572
1869
  " more"
@@ -1583,23 +1880,23 @@ function ReportLastRunDelta({
1583
1880
  if (!delta) return null;
1584
1881
  const { newlyFailing, newlyPassing, added } = delta;
1585
1882
  if (newlyFailing.length === 0 && newlyPassing.length === 0 && added.length === 0) {
1586
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: "Since last run: no behavior changes." });
1883
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: "Since last run: no behavior changes." });
1587
1884
  }
1588
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: [
1589
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "font-medium text-foreground", children: "Since last run:" }),
1590
- newlyFailing.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "ml-2 text-fail", children: [
1885
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: [
1886
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "font-medium text-foreground", children: "Since last run:" }),
1887
+ newlyFailing.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("span", { className: "ml-2 text-fail", children: [
1591
1888
  newlyFailing.length,
1592
1889
  " newly failing (",
1593
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ScenarioLinks, { scenarios: newlyFailing, tone: "text-fail" }),
1890
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ScenarioLinks, { scenarios: newlyFailing, tone: "text-fail" }),
1594
1891
  ")"
1595
1892
  ] }) : null,
1596
- newlyPassing.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "ml-2 text-pass", children: [
1893
+ newlyPassing.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("span", { className: "ml-2 text-pass", children: [
1597
1894
  newlyPassing.length,
1598
1895
  " fixed (",
1599
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ScenarioLinks, { scenarios: newlyPassing, tone: "text-pass" }),
1896
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ScenarioLinks, { scenarios: newlyPassing, tone: "text-pass" }),
1600
1897
  ")"
1601
1898
  ] }) : null,
1602
- added.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "ml-2", children: [
1899
+ added.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("span", { className: "ml-2", children: [
1603
1900
  added.length,
1604
1901
  " new"
1605
1902
  ] }) : null
@@ -1608,14 +1905,78 @@ function ReportLastRunDelta({
1608
1905
 
1609
1906
  // src/interactive/ReportShortcutsHelp.tsx
1610
1907
  var import_react11 = require("react");
1611
- var import_jsx_runtime36 = require("react/jsx-runtime");
1908
+
1909
+ // src/components/ui/button.tsx
1910
+ var React7 = require("react");
1911
+ var import_class_variance_authority4 = require("class-variance-authority");
1912
+ var import_use_render2 = require("@base-ui/react/use-render");
1913
+ var buttonVariants = (0, import_class_variance_authority4.cva)(
1914
+ "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",
1915
+ {
1916
+ variants: {
1917
+ variant: {
1918
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
1919
+ destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
1920
+ 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",
1921
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
1922
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
1923
+ link: "text-primary underline-offset-4 hover:underline"
1924
+ },
1925
+ size: {
1926
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
1927
+ xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
1928
+ sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
1929
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
1930
+ icon: "size-9",
1931
+ "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
1932
+ "icon-sm": "size-8",
1933
+ "icon-lg": "size-10"
1934
+ }
1935
+ },
1936
+ defaultVariants: {
1937
+ variant: "default",
1938
+ size: "default"
1939
+ }
1940
+ }
1941
+ );
1942
+ function Button({
1943
+ className,
1944
+ variant = "default",
1945
+ size = "default",
1946
+ render,
1947
+ ...props
1948
+ }) {
1949
+ return (0, import_use_render2.useRender)({
1950
+ defaultTagName: "button",
1951
+ render,
1952
+ props: {
1953
+ "data-slot": "button",
1954
+ "data-variant": variant,
1955
+ "data-size": size,
1956
+ className: cn(buttonVariants({ variant, size, className })),
1957
+ ...props
1958
+ }
1959
+ });
1960
+ }
1961
+
1962
+ // src/interactive/ReportShortcutsHelp.tsx
1963
+ var import_jsx_runtime40 = require("react/jsx-runtime");
1612
1964
  var SHORTCUTS = [
1613
1965
  { keys: "/", description: "Focus search" },
1614
1966
  { keys: "f", description: "Jump to next failure" },
1615
- { keys: "Shift+F", description: "Jump to previous failure" },
1616
- { keys: "Esc", description: "Clear search / close dialog" },
1617
- { keys: "?", description: "Toggle this help" }
1967
+ { keys: "Shift F", description: "Jump to previous failure" },
1968
+ { keys: "e", description: "Expand all" },
1969
+ { keys: "c", description: "Collapse all" },
1970
+ { keys: "?", description: "Toggle this help" },
1971
+ { keys: "Esc", description: "Clear search / close" }
1618
1972
  ];
1973
+ function Keys({ keys }) {
1974
+ const parts = keys.split(" ");
1975
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "inline-flex items-center gap-1", children: parts.map((k, i) => /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "inline-flex items-center gap-1", children: [
1976
+ i > 0 ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "text-xs text-muted-foreground", children: "+" }) : null,
1977
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("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 })
1978
+ ] }, k)) });
1979
+ }
1619
1980
  function ReportShortcutsHelp({ open, onClose }) {
1620
1981
  const dialogRef = (0, import_react11.useRef)(null);
1621
1982
  (0, import_react11.useEffect)(() => {
@@ -1624,20 +1985,20 @@ function ReportShortcutsHelp({ open, onClose }) {
1624
1985
  if (open && !dialog.open) dialog.showModal();
1625
1986
  else if (!open && dialog.open) dialog.close();
1626
1987
  }, [open]);
1627
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
1988
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
1628
1989
  "dialog",
1629
1990
  {
1630
1991
  ref: dialogRef,
1631
- className: "es-shortcuts-help",
1992
+ 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",
1632
1993
  "aria-label": "Keyboard shortcuts",
1633
1994
  onClose,
1634
1995
  children: [
1635
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("h2", { children: "Keyboard shortcuts" }),
1636
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("dl", { children: SHORTCUTS.map((s) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { children: [
1637
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("dt", { children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("kbd", { children: s.keys }) }),
1638
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("dd", { children: s.description })
1996
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("h2", { className: "text-base font-semibold", children: "Keyboard shortcuts" }),
1997
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("dl", { className: "mt-4 grid grid-cols-[auto_1fr] items-center gap-x-4 gap-y-2.5", children: SHORTCUTS.map((s) => /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "col-span-2 grid grid-cols-subgrid items-center", children: [
1998
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("dt", { className: "justify-self-start", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Keys, { keys: s.keys }) }),
1999
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("dd", { className: "text-sm text-muted-foreground", children: s.description })
1639
2000
  ] }, s.keys)) }),
1640
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("form", { method: "dialog", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("button", { type: "submit", className: "es-shortcuts-close", children: "Close" }) })
2001
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("form", { method: "dialog", className: "mt-6", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Button, { type: "submit", variant: "outline", size: "sm", children: "Close" }) })
1641
2002
  ]
1642
2003
  }
1643
2004
  );
@@ -1809,8 +2170,37 @@ function listFailures(report) {
1809
2170
  return out;
1810
2171
  }
1811
2172
 
2173
+ // src/interactive/triage-default.ts
2174
+ function needsAttention(status) {
2175
+ return status === "failed";
2176
+ }
2177
+ function defaultCollapsedIds(report) {
2178
+ const collapsed = /* @__PURE__ */ new Set();
2179
+ if (report.summary.failed === 0) return collapsed;
2180
+ for (const feature of report.features) {
2181
+ const featureHasFailure = feature.scenarios.some((s) => needsAttention(s.status));
2182
+ if (!featureHasFailure) collapsed.add(feature.id);
2183
+ for (const scenario of feature.scenarios) {
2184
+ if (!needsAttention(scenario.status)) collapsed.add(scenario.id);
2185
+ }
2186
+ }
2187
+ return collapsed;
2188
+ }
2189
+ function failuresFirst(report) {
2190
+ if (report.summary.failed === 0) return report;
2191
+ const withFailures = [];
2192
+ const rest = [];
2193
+ for (const feature of report.features) {
2194
+ (feature.scenarios.some((s) => needsAttention(s.status)) ? withFailures : rest).push(feature);
2195
+ }
2196
+ if (withFailures.length === 0) return report;
2197
+ return { ...report, features: [...withFailures, ...rest] };
2198
+ }
2199
+
1812
2200
  // src/interactive/ReportFilters.tsx
1813
- var import_jsx_runtime37 = require("react/jsx-runtime");
2201
+ var import_react14 = require("react");
2202
+ var import_jsx_runtime41 = require("react/jsx-runtime");
2203
+ var TAG_LIMIT = 12;
1814
2204
  function ReportFilters({
1815
2205
  statuses,
1816
2206
  status,
@@ -1819,11 +2209,14 @@ function ReportFilters({
1819
2209
  activeTags,
1820
2210
  onToggleTag
1821
2211
  }) {
2212
+ const [showAllTags, setShowAllTags] = (0, import_react14.useState)(false);
1822
2213
  if (statuses.length === 0 && tags.length === 0) return null;
1823
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "flex flex-col gap-2", children: [
1824
- statuses.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { role: "group", "aria-label": "Filter by status", className: "flex flex-wrap gap-1", children: statuses.map((opt) => {
2214
+ const tagsCollapsed = tags.length > TAG_LIMIT && !showAllTags;
2215
+ const visibleTags = tagsCollapsed ? tags.slice(0, TAG_LIMIT) : tags;
2216
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "flex flex-col gap-2", children: [
2217
+ statuses.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { role: "group", "aria-label": "Filter by status", className: "flex flex-wrap gap-1", children: statuses.map((opt) => {
1825
2218
  const active = status === opt.key;
1826
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
2219
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
1827
2220
  "button",
1828
2221
  {
1829
2222
  type: "button",
@@ -1835,45 +2228,66 @@ function ReportFilters({
1835
2228
  ),
1836
2229
  children: [
1837
2230
  opt.label,
1838
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "ml-1 font-mono opacity-70", children: opt.count })
2231
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "ml-1 font-mono opacity-70", children: opt.count })
1839
2232
  ]
1840
2233
  },
1841
2234
  opt.key
1842
2235
  );
1843
2236
  }) }) : null,
1844
- tags.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("ul", { "aria-label": "Filter by tag", className: "flex flex-wrap gap-1.5", children: tags.map((tag) => {
1845
- const active = activeTags.includes(tag);
1846
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
2237
+ tags.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("ul", { "aria-label": "Filter by tag", className: "flex flex-wrap items-center gap-1.5", children: [
2238
+ visibleTags.map((tag) => {
2239
+ const active = activeTags.includes(tag);
2240
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
2241
+ "button",
2242
+ {
2243
+ type: "button",
2244
+ "aria-pressed": active,
2245
+ onClick: () => onToggleTag(tag),
2246
+ className: "cursor-pointer",
2247
+ children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
2248
+ Badge,
2249
+ {
2250
+ variant: "tag",
2251
+ className: cn(
2252
+ active && "border-primary bg-primary text-primary-foreground"
2253
+ ),
2254
+ children: tag
2255
+ }
2256
+ )
2257
+ }
2258
+ ) }, tag);
2259
+ }),
2260
+ tags.length > TAG_LIMIT ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
1847
2261
  "button",
1848
2262
  {
1849
2263
  type: "button",
1850
- "aria-pressed": active,
1851
- onClick: () => onToggleTag(tag),
1852
- className: "cursor-pointer",
1853
- children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Badge, { variant: active ? "default" : "tag", children: tag })
2264
+ "aria-expanded": showAllTags,
2265
+ onClick: () => setShowAllTags((v) => !v),
2266
+ className: "cursor-pointer rounded-full px-2 py-0.5 text-xs font-medium text-muted-foreground hover:text-foreground",
2267
+ children: tagsCollapsed ? `+${tags.length - TAG_LIMIT} more` : "Show fewer"
1854
2268
  }
1855
- ) }, tag);
1856
- }) }) : null
2269
+ ) }) : null
2270
+ ] }) : null
1857
2271
  ] });
1858
2272
  }
1859
2273
 
1860
2274
  // src/interactive/ReportToc.tsx
1861
- var import_react14 = require("react");
1862
- var import_jsx_runtime38 = require("react/jsx-runtime");
2275
+ var import_react15 = require("react");
2276
+ var import_jsx_runtime42 = require("react/jsx-runtime");
1863
2277
  var DOT = {
1864
- passed: "text-pass",
1865
- failed: "text-fail",
1866
- skipped: "text-skip",
1867
- pending: "text-pend"
2278
+ passed: "bg-pass",
2279
+ failed: "bg-fail",
2280
+ skipped: "bg-skip",
2281
+ pending: "bg-pend"
1868
2282
  };
1869
- function ReportToc() {
2283
+ function TocContent({ onNavigate }) {
1870
2284
  const report = useReport();
1871
- const [activeId, setActiveId] = (0, import_react14.useState)(null);
1872
- const scenarioIds = (0, import_react14.useMemo)(
2285
+ const [activeId, setActiveId] = (0, import_react15.useState)(null);
2286
+ const scenarioIds = (0, import_react15.useMemo)(
1873
2287
  () => report.features.flatMap((f) => f.scenarios.map((s) => s.id)),
1874
2288
  [report]
1875
2289
  );
1876
- (0, import_react14.useEffect)(() => {
2290
+ (0, import_react15.useEffect)(() => {
1877
2291
  if (typeof IntersectionObserver === "undefined") return;
1878
2292
  const els = scenarioIds.map((id) => document.getElementById(id)).filter((el) => el !== null);
1879
2293
  if (els.length === 0) return;
@@ -1888,91 +2302,166 @@ function ReportToc() {
1888
2302
  return () => observer.disconnect();
1889
2303
  }, [scenarioIds]);
1890
2304
  if (report.features.length === 0) return null;
1891
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2305
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("ul", { className: "flex list-none flex-col gap-4 pl-0", children: report.features.map((feature) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("li", { children: [
2306
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "mb-1.5 font-semibold leading-snug text-foreground", children: feature.title }),
2307
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("ul", { className: "flex list-none flex-col gap-0.5 pl-0", children: feature.scenarios.map((scenario) => {
2308
+ const active = activeId === scenario.id;
2309
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
2310
+ "a",
2311
+ {
2312
+ href: `#${scenario.id}`,
2313
+ onClick: (e) => {
2314
+ e.preventDefault();
2315
+ scrollToScenarioId(scenario.id, { updateHash: true });
2316
+ onNavigate?.();
2317
+ },
2318
+ "aria-current": active ? "true" : void 0,
2319
+ className: cn(
2320
+ "flex items-start gap-2 border-l-2 py-1 pl-2.5 leading-snug hover:text-foreground",
2321
+ active ? "border-primary font-medium text-foreground" : "border-border text-muted-foreground"
2322
+ ),
2323
+ children: [
2324
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2325
+ "span",
2326
+ {
2327
+ "aria-hidden": true,
2328
+ className: cn("mt-1 size-1.5 shrink-0 rounded-full", DOT[scenario.status])
2329
+ }
2330
+ ),
2331
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "break-words", children: scenario.title })
2332
+ ]
2333
+ }
2334
+ ) }, scenario.id);
2335
+ }) })
2336
+ ] }, feature.id)) });
2337
+ }
2338
+ function ReportToc() {
2339
+ const report = useReport();
2340
+ if (report.features.length === 0) return null;
2341
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
1892
2342
  "nav",
1893
2343
  {
1894
2344
  "aria-label": "Table of contents",
1895
- className: "sticky top-4 hidden h-fit w-56 shrink-0 self-start text-xs lg:block",
1896
- children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("ul", { className: "flex flex-col gap-3", children: report.features.map((feature) => /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("li", { children: [
1897
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { className: "mb-1 truncate font-semibold text-foreground", children: feature.title }),
1898
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("ul", { className: "flex flex-col", children: feature.scenarios.map((scenario) => {
1899
- const active = activeId === scenario.id;
1900
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
1901
- "a",
1902
- {
1903
- href: `#${scenario.id}`,
1904
- onClick: (e) => {
1905
- e.preventDefault();
1906
- scrollToScenarioId(scenario.id, { updateHash: true });
1907
- },
1908
- "aria-current": active ? "true" : void 0,
1909
- className: cn(
1910
- "flex items-center gap-1.5 truncate border-l-2 py-0.5 pl-2 hover:text-foreground",
1911
- active ? "border-primary font-medium text-foreground" : "border-border text-muted-foreground"
1912
- ),
1913
- children: [
1914
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { "aria-hidden": true, className: cn("shrink-0", DOT[scenario.status]), children: "\u2022" }),
1915
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "truncate", children: scenario.title })
1916
- ]
1917
- }
1918
- ) }, scenario.id);
1919
- }) })
1920
- ] }, feature.id)) })
2345
+ className: "sticky top-4 hidden h-fit w-64 shrink-0 self-start text-xs lg:block",
2346
+ children: [
2347
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "mb-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Contents" }),
2348
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TocContent, {})
2349
+ ]
1921
2350
  }
1922
2351
  );
1923
2352
  }
1924
2353
 
2354
+ // src/interactive/ReportTocDrawer.tsx
2355
+ var import_react16 = require("react");
2356
+ var import_dialog = require("@base-ui/react/dialog");
2357
+ var import_lucide_react4 = require("lucide-react");
2358
+ var import_jsx_runtime43 = require("react/jsx-runtime");
2359
+ function ReportTocDrawer() {
2360
+ const report = useReport();
2361
+ const [open, setOpen] = (0, import_react16.useState)(false);
2362
+ if (report.features.length === 0) return null;
2363
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(import_dialog.Dialog.Root, { open, onOpenChange: setOpen, children: [
2364
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(import_dialog.Dialog.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: [
2365
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react4.ListIcon, { "aria-hidden": true }),
2366
+ " Contents"
2367
+ ] }),
2368
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(import_dialog.Dialog.Portal, { children: [
2369
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_dialog.Dialog.Backdrop, { className: "fixed inset-0 z-50 bg-black/50 transition-opacity duration-200 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0" }),
2370
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(import_dialog.Dialog.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: [
2371
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "flex items-center justify-between", children: [
2372
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_dialog.Dialog.Title, { className: "text-xs font-semibold tracking-wide text-muted-foreground uppercase", children: "Contents" }),
2373
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
2374
+ import_dialog.Dialog.Close,
2375
+ {
2376
+ "aria-label": "Close contents",
2377
+ className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
2378
+ children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react4.XIcon, { "aria-hidden": true })
2379
+ }
2380
+ )
2381
+ ] }),
2382
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(TocContent, { onNavigate: () => setOpen(false) })
2383
+ ] })
2384
+ ] })
2385
+ ] });
2386
+ }
2387
+
1925
2388
  // src/interactive/use-theme.ts
1926
- var import_react15 = require("react");
2389
+ var import_react17 = require("react");
1927
2390
  var KEY = "es-theme";
1928
- function applyTheme(theme) {
2391
+ function systemDark() {
2392
+ return typeof matchMedia !== "undefined" && matchMedia("(prefers-color-scheme: dark)").matches;
2393
+ }
2394
+ function resolve(pref) {
2395
+ return pref === "system" ? systemDark() ? "dark" : "light" : pref;
2396
+ }
2397
+ function applyTheme(pref) {
1929
2398
  if (typeof document !== "undefined") {
1930
- document.documentElement.setAttribute("data-theme", theme);
2399
+ document.documentElement.setAttribute("data-theme", resolve(pref));
1931
2400
  }
1932
2401
  }
1933
- function initialTheme() {
2402
+ function initialPref() {
1934
2403
  if (typeof localStorage !== "undefined") {
1935
2404
  const stored = localStorage.getItem(KEY);
1936
- if (stored === "light" || stored === "dark") return stored;
1937
- }
1938
- if (typeof document !== "undefined" && document.documentElement.getAttribute("data-theme") === "dark") {
1939
- return "dark";
1940
- }
1941
- if (typeof matchMedia !== "undefined" && matchMedia("(prefers-color-scheme: dark)").matches) {
1942
- return "dark";
2405
+ if (stored === "light" || stored === "dark" || stored === "system") return stored;
1943
2406
  }
1944
- return "light";
2407
+ return "system";
1945
2408
  }
1946
2409
  function useTheme() {
1947
- const [theme, setTheme] = (0, import_react15.useState)("light");
1948
- (0, import_react15.useEffect)(() => {
1949
- const t = initialTheme();
1950
- setTheme(t);
1951
- applyTheme(t);
2410
+ const [pref, setPrefState] = (0, import_react17.useState)("system");
2411
+ (0, import_react17.useEffect)(() => {
2412
+ const p = initialPref();
2413
+ setPrefState(p);
2414
+ applyTheme(p);
1952
2415
  }, []);
1953
- const toggle = (0, import_react15.useCallback)(() => {
1954
- setTheme((prev) => {
1955
- const next = prev === "dark" ? "light" : "dark";
1956
- applyTheme(next);
1957
- try {
1958
- localStorage.setItem(KEY, next);
1959
- } catch {
1960
- }
1961
- return next;
1962
- });
2416
+ (0, import_react17.useEffect)(() => {
2417
+ if (typeof matchMedia === "undefined") return;
2418
+ const mq = matchMedia("(prefers-color-scheme: dark)");
2419
+ const onChange = () => {
2420
+ if (pref === "system") applyTheme("system");
2421
+ };
2422
+ mq.addEventListener("change", onChange);
2423
+ return () => mq.removeEventListener("change", onChange);
2424
+ }, [pref]);
2425
+ const setPref = (0, import_react17.useCallback)((p) => {
2426
+ setPrefState(p);
2427
+ applyTheme(p);
2428
+ try {
2429
+ localStorage.setItem(KEY, p);
2430
+ } catch {
2431
+ }
1963
2432
  }, []);
1964
- return { theme, toggle };
2433
+ return { pref, setPref };
1965
2434
  }
1966
2435
 
1967
2436
  // src/interactive/ReportInteractive.tsx
1968
- var import_jsx_runtime39 = require("react/jsx-runtime");
2437
+ var import_jsx_runtime44 = require("react/jsx-runtime");
1969
2438
  function ReportInteractive(props) {
1970
2439
  const { report, className, title, dataTheme } = props;
1971
2440
  const result = unwrapReport(report);
1972
2441
  if (!result.ok) {
1973
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportErrorShell, { error: result.error, className, title, dataTheme });
2442
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ReportErrorShell, { error: result.error, className, title, dataTheme });
1974
2443
  }
1975
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportInteractiveView, { ...props, report: result.data });
2444
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ReportInteractiveView, { ...props, report: result.data });
2445
+ }
2446
+ var THEME_OPTIONS = [
2447
+ { value: "light", label: "Light", Icon: import_lucide_react5.Sun },
2448
+ { value: "system", label: "System", Icon: import_lucide_react5.Monitor },
2449
+ { value: "dark", label: "Dark", Icon: import_lucide_react5.Moon }
2450
+ ];
2451
+ function ThemeSegment({ pref, onPref }) {
2452
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
2453
+ ToggleGroup,
2454
+ {
2455
+ size: "sm",
2456
+ value: [pref],
2457
+ onValueChange: (v) => {
2458
+ const next = v[0];
2459
+ if (next) onPref(next);
2460
+ },
2461
+ "aria-label": "Color theme",
2462
+ children: THEME_OPTIONS.map(({ value, label, Icon }) => /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ToggleGroupItem, { value, "aria-label": label, title: label, className: "px-2", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(Icon, {}) }, value))
2463
+ }
2464
+ );
1976
2465
  }
1977
2466
  function ReportInteractiveView({
1978
2467
  report,
@@ -1982,24 +2471,29 @@ function ReportInteractiveView({
1982
2471
  title,
1983
2472
  dataTheme,
1984
2473
  hideHeader = false,
2474
+ hideToc = false,
1985
2475
  staleAfterDays = 7,
1986
2476
  scenarioHistory
1987
2477
  }) {
1988
- const [query, setQuery] = (0, import_react16.useState)("");
1989
- const [statusFilter, setStatusFilter] = (0, import_react16.useState)("all");
1990
- const [activeTags, setActiveTags] = (0, import_react16.useState)([]);
1991
- const [detail, setDetail] = (0, import_react16.useState)("full");
1992
- const [helpOpen, setHelpOpen] = (0, import_react16.useState)(false);
1993
- const { theme, toggle: toggleTheme } = useTheme();
1994
- const searchRef = (0, import_react16.useRef)(null);
1995
- const deferredQuery = (0, import_react16.useDeferredValue)(query);
1996
- const failures = (0, import_react16.useMemo)(() => listFailures(report), [report]);
1997
- const filtered = (0, import_react16.useMemo)(
1998
- () => filterReport(report, { query: deferredQuery, status: statusFilter, tags: activeTags }),
2478
+ const [query, setQuery] = (0, import_react18.useState)("");
2479
+ const [statusFilter, setStatusFilter] = (0, import_react18.useState)("all");
2480
+ const [activeTags, setActiveTags] = (0, import_react18.useState)([]);
2481
+ const [detail, setDetail] = (0, import_react18.useState)("full");
2482
+ const [helpOpen, setHelpOpen] = (0, import_react18.useState)(false);
2483
+ const { pref: themePref, setPref: setThemePref } = useTheme();
2484
+ const searchRef = (0, import_react18.useRef)(null);
2485
+ const expandId = (0, import_react18.useId)();
2486
+ const docsId = (0, import_react18.useId)();
2487
+ const deferredQuery = (0, import_react18.useDeferredValue)(query);
2488
+ const failures = (0, import_react18.useMemo)(() => listFailures(report), [report]);
2489
+ const filtered = (0, import_react18.useMemo)(
2490
+ // Features carrying failures float to the top so the first thing on screen
2491
+ // is the thing that needs attention; an all-green run keeps source order.
2492
+ () => failuresFirst(filterReport(report, { query: deferredQuery, status: statusFilter, tags: activeTags })),
1999
2493
  [report, deferredQuery, statusFilter, activeTags]
2000
2494
  );
2001
2495
  const isFiltering = query !== deferredQuery;
2002
- const statusOptions = (0, import_react16.useMemo)(() => {
2496
+ const statusOptions = (0, import_react18.useMemo)(() => {
2003
2497
  const s = report.summary;
2004
2498
  const opts = [
2005
2499
  { key: "all", label: "All", count: s.total }
@@ -2010,19 +2504,19 @@ function ReportInteractiveView({
2010
2504
  if (s.pending) opts.push({ key: "pending", label: "Pending", count: s.pending });
2011
2505
  return opts;
2012
2506
  }, [report]);
2013
- const tagOptions = (0, import_react16.useMemo)(() => allTags(report), [report]);
2014
- const toggleTag = (0, import_react16.useCallback)(
2507
+ const tagOptions = (0, import_react18.useMemo)(() => allTags(report), [report]);
2508
+ const toggleTag = (0, import_react18.useCallback)(
2015
2509
  (tag) => setActiveTags((prev) => prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag]),
2016
2510
  []
2017
2511
  );
2018
- const [toast, setToast] = (0, import_react16.useState)(null);
2019
- const toastTimer = (0, import_react16.useRef)(null);
2020
- const showToast = (0, import_react16.useCallback)((msg) => {
2512
+ const [toast, setToast] = (0, import_react18.useState)(null);
2513
+ const toastTimer = (0, import_react18.useRef)(null);
2514
+ const showToast = (0, import_react18.useCallback)((msg) => {
2021
2515
  setToast(msg);
2022
2516
  if (toastTimer.current) clearTimeout(toastTimer.current);
2023
2517
  toastTimer.current = setTimeout(() => setToast(null), 1800);
2024
2518
  }, []);
2025
- const copy = (0, import_react16.useCallback)(
2519
+ const copy = (0, import_react18.useCallback)(
2026
2520
  (text, msg) => {
2027
2521
  if (typeof navigator !== "undefined" && navigator.clipboard) {
2028
2522
  navigator.clipboard.writeText(text).then(() => showToast(msg), () => showToast("Copy failed"));
@@ -2030,25 +2524,34 @@ function ReportInteractiveView({
2030
2524
  },
2031
2525
  [showToast]
2032
2526
  );
2033
- const scenarioActions = (0, import_react16.useMemo)(
2527
+ const scenarioActions = (0, import_react18.useMemo)(
2034
2528
  () => ({
2035
2529
  copyLink: (s) => copy(scenarioPermalink(s), "Link copied"),
2036
- copyMarkdown: (s) => copy(scenarioToMarkdown(s), "Markdown copied"),
2530
+ copyMarkdown: (s) => copy(scenarioToMarkdown2(s), "Markdown copied"),
2037
2531
  copyPrompt: (s) => copy(scenarioToPrompt(s), "Prompt copied")
2038
2532
  }),
2039
2533
  [copy]
2040
2534
  );
2041
- const failureIndexRef = (0, import_react16.useRef)(0);
2042
- const collapse = useCollapseState();
2043
- const allCollapsibleIds = (0, import_react16.useMemo)(
2535
+ const failureIndexRef = (0, import_react18.useRef)(0);
2536
+ const allCollapsibleIds = (0, import_react18.useMemo)(
2044
2537
  () => report.features.flatMap((f) => [f.id, ...f.scenarios.map((s) => s.id)]),
2045
2538
  [report]
2046
2539
  );
2047
- const collapseAll = (0, import_react16.useCallback)(() => collapse.collapseAll(allCollapsibleIds), [collapse, allCollapsibleIds]);
2048
- const focusSearch = (0, import_react16.useCallback)(() => {
2540
+ const collapse = useCollapseState((0, import_react18.useCallback)(() => defaultCollapsedIds(report), [report]));
2541
+ const collapseAll = (0, import_react18.useCallback)(() => collapse.collapseAll(allCollapsibleIds), [collapse, allCollapsibleIds]);
2542
+ const [expandedAll, setExpandedAll] = (0, import_react18.useState)(() => report.summary.failed === 0);
2543
+ const setExpanded = (0, import_react18.useCallback)(
2544
+ (expanded) => {
2545
+ if (expanded) collapse.expandAll();
2546
+ else collapseAll();
2547
+ setExpandedAll(expanded);
2548
+ },
2549
+ [collapse, collapseAll]
2550
+ );
2551
+ const focusSearch = (0, import_react18.useCallback)(() => {
2049
2552
  searchRef.current?.focus();
2050
2553
  }, []);
2051
- const stepFailure = (0, import_react16.useCallback)(
2554
+ const stepFailure = (0, import_react18.useCallback)(
2052
2555
  (direction) => {
2053
2556
  if (failures.length === 0) return;
2054
2557
  failureIndexRef.current = (failureIndexRef.current + direction + failures.length) % failures.length;
@@ -2057,10 +2560,10 @@ function ReportInteractiveView({
2057
2560
  },
2058
2561
  [failures]
2059
2562
  );
2060
- const toggleHelp = (0, import_react16.useCallback)(() => {
2563
+ const toggleHelp = (0, import_react18.useCallback)(() => {
2061
2564
  setHelpOpen((v) => !v);
2062
2565
  }, []);
2063
- const escape = (0, import_react16.useCallback)(() => {
2566
+ const escape = (0, import_react18.useCallback)(() => {
2064
2567
  if (helpOpen) setHelpOpen(false);
2065
2568
  else if (query !== "") setQuery("");
2066
2569
  }, [helpOpen, query]);
@@ -2070,20 +2573,21 @@ function ReportInteractiveView({
2070
2573
  onPrevFailure: () => stepFailure(-1),
2071
2574
  onToggleHelp: toggleHelp,
2072
2575
  onEscape: escape,
2073
- onExpandAll: collapse.expandAll,
2074
- onCollapseAll: collapseAll
2576
+ onExpandAll: () => setExpanded(true),
2577
+ onCollapseAll: () => setExpanded(false)
2075
2578
  });
2076
2579
  useDeepLinkScroll();
2077
2580
  const hasContent = filtered.features.length > 0;
2078
2581
  const totalScenarios = report.summary.total;
2079
2582
  const matchedScenarios = filtered.summary.total;
2080
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(CollapseProvider, { value: collapse.api, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ScenarioActionsProvider, { value: scenarioActions, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ScenarioHistoryProvider, { value: scenarioHistory ?? null, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2583
+ const showToc = !hideToc && totalScenarios > 3;
2584
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(CollapseProvider, { value: collapse.api, children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ScenarioActionsProvider, { value: scenarioActions, children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ScenarioHistoryProvider, { value: scenarioHistory ?? null, children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
2081
2585
  ReportRoot,
2082
2586
  {
2083
2587
  report: filtered,
2084
2588
  customRenderers,
2085
2589
  renderers,
2086
- children: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
2590
+ children: /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
2087
2591
  "main",
2088
2592
  {
2089
2593
  className: cn("es-report", "es-report-interactive", className),
@@ -2092,9 +2596,38 @@ function ReportInteractiveView({
2092
2596
  "data-theme": dataTheme,
2093
2597
  "data-detail-level": detail,
2094
2598
  children: [
2095
- /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("header", { className: "es-report-header", children: [
2096
- hideHeader ? null : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportTitleBlock, { title }),
2097
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2599
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("header", { className: "es-report-header", children: [
2600
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
2601
+ "div",
2602
+ {
2603
+ "data-es-navbar": true,
2604
+ className: cn(
2605
+ "flex flex-wrap items-center gap-4",
2606
+ hideHeader ? "justify-end" : "justify-between"
2607
+ ),
2608
+ children: [
2609
+ hideHeader ? null : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("h1", { className: "min-w-0", children: title ?? "Story Report" }),
2610
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "flex items-center gap-2", children: [
2611
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
2612
+ ReportSearch,
2613
+ {
2614
+ ref: searchRef,
2615
+ value: query,
2616
+ onChange: setQuery,
2617
+ matchedCount: matchedScenarios,
2618
+ totalCount: totalScenarios
2619
+ }
2620
+ ),
2621
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ThemeSegment, { pref: themePref, onPref: setThemePref })
2622
+ ] })
2623
+ ]
2624
+ }
2625
+ ),
2626
+ hideHeader ? null : /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_jsx_runtime44.Fragment, { children: [
2627
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ReportSummary, {}),
2628
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ReportMeta, {})
2629
+ ] }),
2630
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
2098
2631
  ReportFreshness,
2099
2632
  {
2100
2633
  lastRunMs: reportLastRunMs(report),
@@ -2102,62 +2635,8 @@ function ReportInteractiveView({
2102
2635
  staleAfterDays
2103
2636
  }
2104
2637
  ),
2105
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportLastRunDelta, { history: scenarioHistory, report }),
2106
- /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
2107
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2108
- ReportSearch,
2109
- {
2110
- ref: searchRef,
2111
- value: query,
2112
- onChange: setQuery,
2113
- matchedCount: matchedScenarios,
2114
- totalCount: totalScenarios
2115
- }
2116
- ),
2117
- /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex shrink-0 gap-1", children: [
2118
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2119
- "button",
2120
- {
2121
- type: "button",
2122
- onClick: collapse.expandAll,
2123
- "aria-keyshortcuts": "e",
2124
- className: "cursor-pointer rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground",
2125
- children: "Expand all"
2126
- }
2127
- ),
2128
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2129
- "button",
2130
- {
2131
- type: "button",
2132
- onClick: collapseAll,
2133
- "aria-keyshortcuts": "c",
2134
- className: "cursor-pointer rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground",
2135
- children: "Collapse all"
2136
- }
2137
- ),
2138
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2139
- "button",
2140
- {
2141
- type: "button",
2142
- onClick: () => setDetail((d) => d === "full" ? "minimal" : "full"),
2143
- "aria-pressed": detail === "minimal",
2144
- className: "cursor-pointer rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground",
2145
- children: detail === "full" ? "Hide docs" : "Show docs"
2146
- }
2147
- ),
2148
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2149
- "button",
2150
- {
2151
- type: "button",
2152
- onClick: toggleTheme,
2153
- "aria-label": `Switch to ${theme === "dark" ? "light" : "dark"} theme`,
2154
- className: "cursor-pointer rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground",
2155
- children: theme === "dark" ? "\u2600 Light" : "\u263E Dark"
2156
- }
2157
- )
2158
- ] })
2159
- ] }),
2160
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2638
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ReportLastRunDelta, { history: scenarioHistory, report }),
2639
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
2161
2640
  ReportFilters,
2162
2641
  {
2163
2642
  statuses: statusOptions,
@@ -2167,26 +2646,44 @@ function ReportInteractiveView({
2167
2646
  activeTags,
2168
2647
  onToggleTag: toggleTag
2169
2648
  }
2170
- )
2649
+ ),
2650
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "flex flex-wrap items-center gap-x-6 gap-y-2 border-t border-border/60 pt-3", children: [
2651
+ showToc ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ReportTocDrawer, {}) : null,
2652
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "flex items-center gap-2", children: [
2653
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(Switch, { id: expandId, checked: expandedAll, onCheckedChange: setExpanded }),
2654
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("label", { htmlFor: expandId, className: "cursor-pointer text-sm text-muted-foreground select-none", children: "Expand all" })
2655
+ ] }),
2656
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "flex items-center gap-2", children: [
2657
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
2658
+ Switch,
2659
+ {
2660
+ id: docsId,
2661
+ checked: detail === "full",
2662
+ onCheckedChange: (checked) => setDetail(checked ? "full" : "minimal")
2663
+ }
2664
+ ),
2665
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("label", { htmlFor: docsId, className: "cursor-pointer text-sm text-muted-foreground select-none", children: "Show documentation" })
2666
+ ] })
2667
+ ] })
2171
2668
  ] }),
2172
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportFailureBanner, { failures }),
2173
- hasContent ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex gap-6", children: [
2174
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportToc, {}),
2175
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "flex min-w-0 flex-1 flex-col gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportFeatureList, {}) })
2176
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportEmpty, { message: query ? "No scenarios match the search." : void 0 }),
2177
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2669
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ReportFailureBanner, { failures }),
2670
+ hasContent ? showToc ? /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "flex gap-6", children: [
2671
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ReportToc, {}),
2672
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "flex min-w-0 flex-1 flex-col gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ReportFeatureList, {}) })
2673
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "flex flex-col gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ReportFeatureList, {}) }) : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ReportEmpty, { message: query ? "No scenarios match the search." : void 0 }),
2674
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
2178
2675
  "button",
2179
2676
  {
2180
2677
  type: "button",
2181
- className: "es-shortcuts-trigger",
2678
+ 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",
2182
2679
  "aria-label": "Keyboard shortcuts",
2183
- "aria-keyshortcuts": "Shift+?",
2680
+ "aria-keyshortcuts": "?",
2184
2681
  onClick: toggleHelp,
2185
2682
  children: "?"
2186
2683
  }
2187
2684
  ),
2188
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ReportShortcutsHelp, { open: helpOpen, onClose: () => setHelpOpen(false) }),
2189
- toast ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2685
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ReportShortcutsHelp, { open: helpOpen, onClose: () => setHelpOpen(false) }),
2686
+ toast ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
2190
2687
  "div",
2191
2688
  {
2192
2689
  role: "status",
@@ -2203,14 +2700,14 @@ function ReportInteractiveView({
2203
2700
  }
2204
2701
 
2205
2702
  // src/interactive/ReportInteractiveIsland.tsx
2206
- var import_react19 = require("react");
2703
+ var import_react21 = require("react");
2207
2704
 
2208
2705
  // src/interactive/island-renderers.tsx
2209
- var import_react18 = require("react");
2706
+ var import_react20 = require("react");
2210
2707
 
2211
2708
  // src/components/doc/MermaidView.tsx
2212
- var import_react17 = require("react");
2213
- var import_jsx_runtime40 = require("react/jsx-runtime");
2709
+ var import_react19 = require("react");
2710
+ var import_jsx_runtime45 = require("react/jsx-runtime");
2214
2711
  function prefersDark(el) {
2215
2712
  const themed = el?.closest("[data-theme]");
2216
2713
  if (themed?.dataset.theme === "dark") return true;
@@ -2218,9 +2715,9 @@ function prefersDark(el) {
2218
2715
  return typeof window !== "undefined" && !!window.matchMedia?.("(prefers-color-scheme: dark)").matches;
2219
2716
  }
2220
2717
  function MermaidView({ entry, load }) {
2221
- const ref = (0, import_react17.useRef)(null);
2222
- const [svg, setSvg] = (0, import_react17.useState)(null);
2223
- (0, import_react17.useEffect)(() => {
2718
+ const ref = (0, import_react19.useRef)(null);
2719
+ const [svg, setSvg] = (0, import_react19.useState)(null);
2720
+ (0, import_react19.useEffect)(() => {
2224
2721
  let cancelled = false;
2225
2722
  const draw = async () => {
2226
2723
  try {
@@ -2247,9 +2744,9 @@ function MermaidView({ entry, load }) {
2247
2744
  };
2248
2745
  }, [entry.code, load]);
2249
2746
  if (svg) {
2250
- return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("figure", { ref, className: "my-3", children: [
2251
- entry.title ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
2252
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2747
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("figure", { ref, className: "my-3", children: [
2748
+ entry.title ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
2749
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
2253
2750
  "div",
2254
2751
  {
2255
2752
  role: "img",
@@ -2260,11 +2757,11 @@ function MermaidView({ entry, load }) {
2260
2757
  )
2261
2758
  ] });
2262
2759
  }
2263
- return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(MermaidSource, { entry, ref });
2760
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(MermaidSource, { entry, ref });
2264
2761
  }
2265
2762
 
2266
2763
  // src/interactive/island-renderers.tsx
2267
- var import_jsx_runtime41 = require("react/jsx-runtime");
2764
+ var import_jsx_runtime46 = require("react/jsx-runtime");
2268
2765
  var HLJS_ESM = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/es/highlight.min.js";
2269
2766
  var MERMAID_ESM = "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
2270
2767
  function cachedCdnModule(url) {
@@ -2281,8 +2778,8 @@ function cachedCdnModule(url) {
2281
2778
  var loadMermaidFromCdn = cachedCdnModule(MERMAID_ESM);
2282
2779
  var loadHljs = cachedCdnModule(HLJS_ESM);
2283
2780
  function CdnHighlightedCode({ entry }) {
2284
- const [html, setHtml] = (0, import_react18.useState)(null);
2285
- (0, import_react18.useEffect)(() => {
2781
+ const [html, setHtml] = (0, import_react20.useState)(null);
2782
+ (0, import_react20.useEffect)(() => {
2286
2783
  let cancelled = false;
2287
2784
  void (async () => {
2288
2785
  try {
@@ -2298,46 +2795,48 @@ function CdnHighlightedCode({ entry }) {
2298
2795
  cancelled = true;
2299
2796
  };
2300
2797
  }, [entry.content, entry.lang]);
2301
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(CodeFigure, { label: entry.label, children: html !== null ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
2798
+ return /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(CodeFigure, { label: entry.label, children: html !== null ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
2302
2799
  "code",
2303
2800
  {
2304
2801
  className: cn("hljs font-mono text-xs", entry.lang && `language-${entry.lang}`),
2305
2802
  dangerouslySetInnerHTML: { __html: html }
2306
2803
  }
2307
- ) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
2804
+ ) : /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
2308
2805
  }
2309
2806
  function buildIslandRenderers(opts) {
2310
2807
  const renderers = {};
2311
2808
  if (opts.mermaid) {
2312
- renderers.mermaid = (entry) => (0, import_react18.createElement)(MermaidView, { entry, load: loadMermaidFromCdn });
2809
+ renderers.mermaid = (entry) => (0, import_react20.createElement)(MermaidView, { entry, load: loadMermaidFromCdn });
2313
2810
  }
2314
2811
  if (opts.syntaxHighlighting) {
2315
- renderers.code = (entry) => (0, import_react18.createElement)(CdnHighlightedCode, { entry });
2812
+ renderers.code = (entry) => (0, import_react20.createElement)(CdnHighlightedCode, { entry });
2316
2813
  }
2317
2814
  return renderers;
2318
2815
  }
2319
2816
 
2320
2817
  // src/interactive/ReportInteractiveIsland.tsx
2321
- var import_jsx_runtime42 = require("react/jsx-runtime");
2818
+ var import_jsx_runtime47 = require("react/jsx-runtime");
2322
2819
  function ReportInteractiveIsland({
2323
2820
  report,
2324
2821
  title,
2325
2822
  className,
2326
2823
  hideHeader,
2824
+ hideToc,
2327
2825
  syntaxHighlighting = true,
2328
2826
  mermaid = true
2329
2827
  }) {
2330
- const renderers = (0, import_react19.useMemo)(
2828
+ const renderers = (0, import_react21.useMemo)(
2331
2829
  () => buildIslandRenderers({ syntaxHighlighting, mermaid }),
2332
2830
  [syntaxHighlighting, mermaid]
2333
2831
  );
2334
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2832
+ return /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
2335
2833
  ReportInteractive,
2336
2834
  {
2337
2835
  report,
2338
2836
  title,
2339
2837
  className,
2340
2838
  hideHeader,
2839
+ hideToc,
2341
2840
  renderers
2342
2841
  }
2343
2842
  );