executable-stories-react 0.4.0 → 0.5.0

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