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.
package/dist/ssr.js CHANGED
@@ -99,11 +99,15 @@ var badgeVariants = cva(
99
99
  outline: "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
100
100
  ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
101
101
  link: "text-primary underline-offset-4 [a&]:hover:underline",
102
- passed: "border-pass-border bg-pass-bg text-pass font-mono uppercase",
102
+ // Pass/skip/pending are the quiet states: outline + coloured text, no
103
+ // fill, so a green report reads calm. Only `failed` keeps a tinted
104
+ // fill, making it the single loud badge on the page, so red pops the
105
+ // instant something breaks. Colour-as-accent, matching the KPI cards.
106
+ passed: "border-pass-border text-pass font-mono uppercase",
103
107
  failed: "border-fail-border bg-fail-bg text-fail font-mono uppercase",
104
- skipped: "border-skip-border bg-skip-bg text-skip font-mono uppercase",
105
- pending: "border-pend-border bg-pend-bg text-pend font-mono uppercase",
106
- tag: "border-pass-border bg-pass-bg text-pass font-mono font-medium"
108
+ skipped: "border-skip-border text-skip font-mono uppercase",
109
+ pending: "border-pend-border text-pend font-mono uppercase",
110
+ tag: "border-border text-muted-foreground font-mono font-medium [a&]:hover:bg-muted"
107
111
  }
108
112
  },
109
113
  defaultVariants: {
@@ -656,7 +660,7 @@ function ReportTrace({ spans }) {
656
660
  {
657
661
  className: "min-w-0 flex-1 truncate text-foreground",
658
662
  style: { paddingLeft: `${row.depth * 12}px` },
659
- title: row.statusMessage ? `${row.name} \u2014 ${row.statusMessage}` : row.name,
663
+ title: row.statusMessage ? `${row.name}: ${row.statusMessage}` : row.name,
660
664
  children: row.name
661
665
  }
662
666
  ),
@@ -793,7 +797,7 @@ function Card({ className, ...props }) {
793
797
  {
794
798
  "data-slot": "card",
795
799
  className: cn(
796
- "flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm",
800
+ "flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-xs",
797
801
  className
798
802
  ),
799
803
  ...props
@@ -824,52 +828,89 @@ function CardContent({ className, ...props }) {
824
828
  );
825
829
  }
826
830
 
827
- // src/components/ReportScenario.tsx
831
+ // src/components/ui/dropdown-menu.tsx
832
+ import "react";
833
+ import { Menu as MenuPrimitive } from "@base-ui/react/menu";
834
+ import { CheckIcon } from "lucide-react";
828
835
  import { jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
829
- var STATUS_LABEL = {
830
- passed: "Passed",
831
- failed: "Failed",
832
- skipped: "Skipped",
833
- pending: "Pending"
834
- };
835
- var STATUS_GLYPH = {
836
- passed: "\u2713",
837
- failed: "\u2717",
838
- skipped: "\u25CB",
839
- pending: "\u25CB"
840
- };
841
- var STATUS_GLYPH_COLOR = {
842
- passed: "text-pass",
843
- failed: "text-fail",
844
- skipped: "text-skip",
845
- pending: "text-pend"
846
- };
847
- var STATUS_BADGE = {
848
- passed: "passed",
849
- failed: "failed",
850
- skipped: "skipped",
851
- pending: "pending"
836
+ 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";
837
+ function DropdownMenu(props) {
838
+ return /* @__PURE__ */ jsx22(MenuPrimitive.Root, { "data-slot": "dropdown-menu", ...props });
839
+ }
840
+ function DropdownMenuTrigger(props) {
841
+ return /* @__PURE__ */ jsx22(MenuPrimitive.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
842
+ }
843
+ function DropdownMenuContent({
844
+ className,
845
+ align = "end",
846
+ sideOffset = 4,
847
+ ...props
848
+ }) {
849
+ return /* @__PURE__ */ jsx22(MenuPrimitive.Portal, { children: /* @__PURE__ */ jsx22(
850
+ MenuPrimitive.Positioner,
851
+ {
852
+ "data-slot": "dropdown-menu-positioner",
853
+ className: "z-50",
854
+ align,
855
+ sideOffset,
856
+ children: /* @__PURE__ */ jsx22(
857
+ MenuPrimitive.Popup,
858
+ {
859
+ "data-slot": "dropdown-menu-content",
860
+ className: cn(
861
+ "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",
862
+ className
863
+ ),
864
+ ...props
865
+ }
866
+ )
867
+ }
868
+ ) });
869
+ }
870
+ function DropdownMenuItem({
871
+ className,
872
+ ...props
873
+ }) {
874
+ return /* @__PURE__ */ jsx22(
875
+ MenuPrimitive.Item,
876
+ {
877
+ "data-slot": "dropdown-menu-item",
878
+ className: cn(itemClasses, className),
879
+ ...props
880
+ }
881
+ );
882
+ }
883
+
884
+ // src/components/ReportScenario.tsx
885
+ import { LinkIcon, FileTextIcon, SparklesIcon, MoreHorizontalIcon } from "lucide-react";
886
+ import { jsx as jsx23, jsxs as jsxs15 } from "react/jsx-runtime";
887
+ var STATUS_META = {
888
+ passed: { label: "Passed", glyph: "\u2713", glyphColor: "text-pass", badgeVariant: "passed", showPill: false },
889
+ failed: { label: "Failed", glyph: "\u2717", glyphColor: "text-fail", badgeVariant: "failed", showPill: true },
890
+ skipped: { label: "Skipped", glyph: "\u25CB", glyphColor: "text-skip", badgeVariant: "skipped", showPill: true },
891
+ pending: { label: "Pending", glyph: "\u25CB", glyphColor: "text-pend", badgeVariant: "pending", showPill: true }
852
892
  };
853
893
  function ReportScenario({ scenario, hideTitle = false }) {
854
894
  const titleId = `${scenario.id}-title`;
855
895
  const bodyId = `${scenario.id}-body`;
856
- const label = scenario.planned ? "Planned" : STATUS_LABEL[scenario.status];
896
+ const meta = STATUS_META[scenario.status];
897
+ const label = scenario.planned ? "Planned" : meta.label;
857
898
  const collapse = useCollapse();
858
899
  const collapsible = collapse !== null && !hideTitle;
859
900
  const collapsed = collapsible ? collapse.isCollapsed(scenario.id) : false;
860
901
  const actions = useScenarioActions();
861
- return /* @__PURE__ */ jsxs14(
902
+ return /* @__PURE__ */ jsxs15(
862
903
  Card,
863
904
  {
864
905
  id: scenario.id,
865
906
  "data-status": scenario.status,
866
- ...hideTitle ? { "aria-label": `${scenario.title} \u2014 ${label}` } : { "aria-labelledby": titleId },
907
+ ...hideTitle ? { "aria-label": `${scenario.title}, ${label}` } : { "aria-labelledby": titleId },
867
908
  className: "gap-0 py-0",
868
909
  children: [
869
- /* @__PURE__ */ jsxs14(CardHeader, { className: "px-4 py-3", children: [
870
- /* @__PURE__ */ jsxs14("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
871
- hideTitle ? null : /* @__PURE__ */ jsxs14("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
872
- collapsible ? /* @__PURE__ */ jsx22(
910
+ /* @__PURE__ */ jsxs15(CardHeader, { className: "px-4 py-3", children: [
911
+ /* @__PURE__ */ jsxs15("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
912
+ hideTitle ? null : /* @__PURE__ */ jsxs15("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
913
+ collapsible ? /* @__PURE__ */ jsx23(
873
914
  "button",
874
915
  {
875
916
  type: "button",
@@ -878,60 +919,49 @@ function ReportScenario({ scenario, hideTitle = false }) {
878
919
  "aria-controls": bodyId,
879
920
  "aria-label": `Toggle ${scenario.title}`,
880
921
  className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
881
- children: /* @__PURE__ */ jsx22("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
922
+ children: /* @__PURE__ */ jsx23("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
882
923
  }
883
924
  ) : null,
884
- /* @__PURE__ */ jsx22("span", { "aria-hidden": true, className: cn("text-sm leading-none", STATUS_GLYPH_COLOR[scenario.status]), children: STATUS_GLYPH[scenario.status] }),
885
- /* @__PURE__ */ jsx22("span", { children: scenario.title })
925
+ /* @__PURE__ */ jsx23("span", { "aria-hidden": true, className: cn("text-sm leading-none", meta.glyphColor), children: meta.glyph }),
926
+ /* @__PURE__ */ jsx23("span", { children: scenario.title })
886
927
  ] }),
887
- /* @__PURE__ */ jsxs14("div", { className: "flex shrink-0 items-center gap-2", children: [
888
- /* @__PURE__ */ jsx22(ScenarioRunHistory, { scenarioId: scenario.id }),
889
- scenario.durationMs > 0 ? /* @__PURE__ */ jsx22("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(scenario.durationMs) }) : null,
890
- /* @__PURE__ */ jsx22(Badge, { variant: STATUS_BADGE[scenario.status], "aria-label": `Status: ${label}`, children: label }),
891
- actions ? /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-0.5", children: [
892
- /* @__PURE__ */ jsx22(
893
- "button",
894
- {
895
- type: "button",
896
- onClick: () => actions.copyLink(scenario),
897
- "aria-label": `Copy link to ${scenario.title}`,
898
- title: "Copy permalink",
899
- className: "cursor-pointer rounded px-1 py-0.5 font-mono text-[0.625rem] text-muted-foreground hover:bg-muted hover:text-foreground",
900
- children: "link"
901
- }
902
- ),
903
- /* @__PURE__ */ jsx22(
904
- "button",
928
+ /* @__PURE__ */ jsxs15("div", { className: "flex shrink-0 items-center gap-2", children: [
929
+ /* @__PURE__ */ jsx23(ScenarioRunHistory, { scenarioId: scenario.id }),
930
+ scenario.durationMs > 0 ? /* @__PURE__ */ jsx23("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(scenario.durationMs) }) : null,
931
+ meta.showPill ? /* @__PURE__ */ jsx23(Badge, { variant: meta.badgeVariant, "aria-label": `Status: ${label}`, children: label }) : /* @__PURE__ */ jsx23("span", { className: "sr-only", children: `Status: ${label}` }),
932
+ actions ? /* @__PURE__ */ jsxs15(DropdownMenu, { children: [
933
+ /* @__PURE__ */ jsx23(
934
+ DropdownMenuTrigger,
905
935
  {
906
- type: "button",
907
- onClick: () => actions.copyMarkdown(scenario),
908
- "aria-label": `Copy ${scenario.title} as Markdown`,
909
- title: "Copy as Markdown",
910
- className: "cursor-pointer rounded px-1 py-0.5 font-mono text-[0.625rem] text-muted-foreground hover:bg-muted hover:text-foreground",
911
- children: "md"
936
+ "aria-label": `Actions for ${scenario.title}`,
937
+ className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
938
+ children: /* @__PURE__ */ jsx23(MoreHorizontalIcon, { "aria-hidden": true })
912
939
  }
913
940
  ),
914
- /* @__PURE__ */ jsx22(
915
- "button",
916
- {
917
- type: "button",
918
- onClick: () => actions.copyPrompt(scenario),
919
- "aria-label": `Copy ${scenario.title} as an AI investigation prompt`,
920
- title: "Copy as AI prompt",
921
- className: "cursor-pointer rounded px-1 py-0.5 font-mono text-[0.625rem] text-muted-foreground hover:bg-muted hover:text-foreground",
922
- children: "ai"
923
- }
924
- )
941
+ /* @__PURE__ */ jsxs15(DropdownMenuContent, { align: "end", children: [
942
+ /* @__PURE__ */ jsxs15(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
943
+ /* @__PURE__ */ jsx23(LinkIcon, {}),
944
+ " Copy link"
945
+ ] }),
946
+ /* @__PURE__ */ jsxs15(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
947
+ /* @__PURE__ */ jsx23(FileTextIcon, {}),
948
+ " Copy as Markdown"
949
+ ] }),
950
+ /* @__PURE__ */ jsxs15(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
951
+ /* @__PURE__ */ jsx23(SparklesIcon, {}),
952
+ " Explain with AI"
953
+ ] })
954
+ ] })
925
955
  ] }) : null
926
956
  ] })
927
957
  ] }),
928
- 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: [
929
- scenario.tags.map((t) => /* @__PURE__ */ jsx22("li", { children: /* @__PURE__ */ jsx22(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
930
- (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}`))
958
+ 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: [
959
+ scenario.tags.map((t) => /* @__PURE__ */ jsx23("li", { children: /* @__PURE__ */ jsx23(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
960
+ (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}`))
931
961
  ] }) : null
932
962
  ] }),
933
- /* @__PURE__ */ jsxs14(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
934
- scenario.errorMessage ? /* @__PURE__ */ jsx22(
963
+ /* @__PURE__ */ jsxs15(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
964
+ scenario.errorMessage ? /* @__PURE__ */ jsx23(
935
965
  "pre",
936
966
  {
937
967
  role: "alert",
@@ -939,10 +969,10 @@ function ReportScenario({ scenario, hideTitle = false }) {
939
969
  children: scenario.errorMessage
940
970
  }
941
971
  ) : null,
942
- /* @__PURE__ */ jsx22(ReportSteps, { scenario }),
943
- scenario.docEntries.length > 0 ? /* @__PURE__ */ jsx22("div", { "data-es-docs": true, className: "mt-3", children: /* @__PURE__ */ jsx22(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
944
- /* @__PURE__ */ jsx22(ReportAttachments, { attachments: scenario.attachments }),
945
- /* @__PURE__ */ jsx22(ReportTrace, { spans: scenario.otelSpans })
972
+ /* @__PURE__ */ jsx23(ReportSteps, { scenario }),
973
+ scenario.docEntries.length > 0 ? /* @__PURE__ */ jsx23("div", { "data-es-docs": true, className: "mt-3", children: /* @__PURE__ */ jsx23(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
974
+ /* @__PURE__ */ jsx23(ReportAttachments, { attachments: scenario.attachments }),
975
+ /* @__PURE__ */ jsx23(ReportTrace, { spans: scenario.otelSpans })
946
976
  ] })
947
977
  ]
948
978
  }
@@ -950,13 +980,13 @@ function ReportScenario({ scenario, hideTitle = false }) {
950
980
  }
951
981
 
952
982
  // src/components/ReportScenarioList.tsx
953
- import { Fragment as Fragment8, jsx as jsx23 } from "react/jsx-runtime";
983
+ import { Fragment as Fragment8, jsx as jsx24 } from "react/jsx-runtime";
954
984
  function ReportScenarioList({ feature }) {
955
- return /* @__PURE__ */ jsx23(Fragment8, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ jsx23(ReportScenario, { scenario }, scenario.id)) });
985
+ return /* @__PURE__ */ jsx24(Fragment8, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ jsx24(ReportScenario, { scenario }, scenario.id)) });
956
986
  }
957
987
 
958
988
  // src/components/ReportFeature.tsx
959
- import { jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
989
+ import { Fragment as Fragment9, jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
960
990
  function ReportFeature({ feature }) {
961
991
  const titleId = `${feature.id}-title`;
962
992
  const bodyId = `${feature.id}-body`;
@@ -964,74 +994,80 @@ function ReportFeature({ feature }) {
964
994
  const skipped = s.skipped + s.pending;
965
995
  const collapse = useCollapse();
966
996
  const collapsed = collapse?.isCollapsed(feature.id) ?? false;
967
- return /* @__PURE__ */ jsxs15(
968
- "section",
969
- {
970
- id: feature.id,
971
- "data-slot": "feature",
972
- "aria-labelledby": titleId,
973
- className: "overflow-hidden rounded-xl border border-border bg-card shadow-sm",
974
- children: [
975
- /* @__PURE__ */ jsxs15("div", { className: "flex items-center justify-between gap-4 border-b border-border bg-muted/40 px-4 py-3", children: [
976
- /* @__PURE__ */ jsxs15("div", { className: "flex min-w-0 items-center gap-2", children: [
977
- collapse ? /* @__PURE__ */ jsx24(
978
- "button",
997
+ return (
998
+ // Suite = a section heading + a divider, NOT another bordered card. Drops a
999
+ // layer of nesting so the scenario cards below read as the primary unit.
1000
+ /* @__PURE__ */ jsxs16(
1001
+ "section",
1002
+ {
1003
+ id: feature.id,
1004
+ "data-slot": "feature",
1005
+ "aria-labelledby": titleId,
1006
+ className: "flex flex-col gap-3",
1007
+ children: [
1008
+ /* @__PURE__ */ jsxs16("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1009
+ /* @__PURE__ */ jsxs16("div", { className: "min-w-0", children: [
1010
+ /* @__PURE__ */ jsx25("h2", { id: titleId, className: "text-base font-semibold tracking-tight text-foreground", children: collapse ? /* @__PURE__ */ jsxs16(
1011
+ "button",
1012
+ {
1013
+ type: "button",
1014
+ onClick: () => collapse.toggle(feature.id),
1015
+ "aria-expanded": !collapsed,
1016
+ "aria-controls": bodyId,
1017
+ className: "flex w-full cursor-pointer items-center gap-2 text-left hover:text-foreground/80",
1018
+ children: [
1019
+ /* @__PURE__ */ jsx25("span", { "aria-hidden": true, className: cn("shrink-0 text-xs text-muted-foreground transition-transform", !collapsed && "rotate-90"), children: "\u25B8" }),
1020
+ /* @__PURE__ */ jsx25("span", { className: "truncate", children: feature.title })
1021
+ ]
1022
+ }
1023
+ ) : feature.title }),
1024
+ /* @__PURE__ */ jsx25("p", { className: cn("mt-0.5 truncate font-mono text-xs text-muted-foreground", collapse && "pl-5"), children: feature.sourceFile })
1025
+ ] }),
1026
+ /* @__PURE__ */ jsxs16(
1027
+ "p",
979
1028
  {
980
- type: "button",
981
- onClick: () => collapse.toggle(feature.id),
982
- "aria-expanded": !collapsed,
983
- "aria-controls": bodyId,
984
- "aria-label": `Toggle ${feature.title}`,
985
- className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
986
- children: /* @__PURE__ */ jsx24("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1029
+ className: "flex shrink-0 items-center gap-1.5 text-xs text-muted-foreground",
1030
+ "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
1031
+ children: [
1032
+ /* @__PURE__ */ jsxs16("span", { children: [
1033
+ s.passed,
1034
+ " passed"
1035
+ ] }),
1036
+ /* @__PURE__ */ jsx25("span", { "aria-hidden": true, children: "\xB7" }),
1037
+ /* @__PURE__ */ jsxs16("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
1038
+ s.failed,
1039
+ " failed"
1040
+ ] }),
1041
+ skipped > 0 ? /* @__PURE__ */ jsxs16(Fragment9, { children: [
1042
+ /* @__PURE__ */ jsx25("span", { "aria-hidden": true, children: "\xB7" }),
1043
+ /* @__PURE__ */ jsxs16("span", { children: [
1044
+ skipped,
1045
+ " skipped"
1046
+ ] })
1047
+ ] }) : null
1048
+ ]
987
1049
  }
988
- ) : null,
989
- /* @__PURE__ */ jsxs15("div", { className: "min-w-0", children: [
990
- /* @__PURE__ */ jsx24("h2", { id: titleId, className: "text-sm font-semibold tracking-tight text-foreground", children: feature.title }),
991
- /* @__PURE__ */ jsx24("p", { className: "mt-0.5 truncate font-mono text-xs text-muted-foreground", children: feature.sourceFile })
992
- ] })
1050
+ )
993
1051
  ] }),
994
- /* @__PURE__ */ jsxs15(
995
- "div",
996
- {
997
- role: "img",
998
- className: "flex shrink-0 items-center gap-2.5 font-mono text-xs font-medium",
999
- "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
1000
- children: [
1001
- /* @__PURE__ */ jsxs15("span", { "aria-hidden": true, className: "text-pass", children: [
1002
- "\u2713",
1003
- s.passed
1004
- ] }),
1005
- /* @__PURE__ */ jsxs15("span", { "aria-hidden": true, className: "text-fail", children: [
1006
- "\u2717",
1007
- s.failed
1008
- ] }),
1009
- /* @__PURE__ */ jsxs15("span", { "aria-hidden": true, className: "text-skip", children: [
1010
- "\u25CB",
1011
- skipped
1012
- ] })
1013
- ]
1014
- }
1015
- )
1016
- ] }),
1017
- /* @__PURE__ */ jsx24("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-2 p-3", children: /* @__PURE__ */ jsx24(ReportScenarioList, { feature }) })
1018
- ]
1019
- }
1052
+ /* @__PURE__ */ jsx25("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-3", children: /* @__PURE__ */ jsx25(ReportScenarioList, { feature }) })
1053
+ ]
1054
+ }
1055
+ )
1020
1056
  );
1021
1057
  }
1022
1058
 
1023
1059
  // src/components/ReportFeatureList.tsx
1024
- import { Fragment as Fragment9, jsx as jsx25 } from "react/jsx-runtime";
1060
+ import { jsx as jsx26 } from "react/jsx-runtime";
1025
1061
  function ReportFeatureList() {
1026
1062
  const report = useReport();
1027
- return /* @__PURE__ */ jsx25(Fragment9, { children: report.features.map((feature) => /* @__PURE__ */ jsx25(ReportFeature, { feature }, feature.id)) });
1063
+ return /* @__PURE__ */ jsx26("div", { className: "flex flex-col gap-8", children: report.features.map((feature) => /* @__PURE__ */ jsx26(ReportFeature, { feature }, feature.id)) });
1028
1064
  }
1029
1065
 
1030
1066
  // src/components/ui/empty.tsx
1031
1067
  import { cva as cva2 } from "class-variance-authority";
1032
- import { jsx as jsx26 } from "react/jsx-runtime";
1068
+ import { jsx as jsx27 } from "react/jsx-runtime";
1033
1069
  function Empty({ className, ...props }) {
1034
- return /* @__PURE__ */ jsx26(
1070
+ return /* @__PURE__ */ jsx27(
1035
1071
  "div",
1036
1072
  {
1037
1073
  "data-slot": "empty",
@@ -1044,7 +1080,7 @@ function Empty({ className, ...props }) {
1044
1080
  );
1045
1081
  }
1046
1082
  function EmptyHeader({ className, ...props }) {
1047
- return /* @__PURE__ */ jsx26(
1083
+ return /* @__PURE__ */ jsx27(
1048
1084
  "div",
1049
1085
  {
1050
1086
  "data-slot": "empty-header",
@@ -1071,7 +1107,7 @@ var emptyMediaVariants = cva2(
1071
1107
  }
1072
1108
  );
1073
1109
  function EmptyTitle({ className, ...props }) {
1074
- return /* @__PURE__ */ jsx26(
1110
+ return /* @__PURE__ */ jsx27(
1075
1111
  "div",
1076
1112
  {
1077
1113
  "data-slot": "empty-title",
@@ -1082,16 +1118,16 @@ function EmptyTitle({ className, ...props }) {
1082
1118
  }
1083
1119
 
1084
1120
  // src/components/ReportEmpty.tsx
1085
- import { jsx as jsx27 } from "react/jsx-runtime";
1121
+ import { jsx as jsx28 } from "react/jsx-runtime";
1086
1122
  function ReportEmpty({ message }) {
1087
- return /* @__PURE__ */ jsx27(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ jsx27(EmptyHeader, { children: /* @__PURE__ */ jsx27(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1123
+ return /* @__PURE__ */ jsx28(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ jsx28(EmptyHeader, { children: /* @__PURE__ */ jsx28(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1088
1124
  }
1089
1125
 
1090
1126
  // src/components/ReportSummary.tsx
1091
- import { Fragment as Fragment10, jsx as jsx28, jsxs as jsxs16 } from "react/jsx-runtime";
1127
+ import { jsx as jsx29, jsxs as jsxs17 } from "react/jsx-runtime";
1092
1128
  function ReportSummary({ className }) {
1093
1129
  const report = useReport();
1094
- return /* @__PURE__ */ jsx28(
1130
+ return /* @__PURE__ */ jsx29(
1095
1131
  ReportSummaryView,
1096
1132
  {
1097
1133
  summary: report.summary,
@@ -1100,38 +1136,54 @@ function ReportSummary({ className }) {
1100
1136
  }
1101
1137
  );
1102
1138
  }
1139
+ var TONE = {
1140
+ total: { num: "text-foreground", dot: "bg-muted-foreground/50" },
1141
+ passed: { num: "text-pass", dot: "bg-pass" },
1142
+ failed: { num: "text-fail", dot: "bg-fail" },
1143
+ skipped: { num: "text-skip", dot: "bg-skip" },
1144
+ pending: { num: "text-pend", dot: "bg-pend" }
1145
+ };
1103
1146
  function ReportSummaryView({ summary, className, ariaLabel }) {
1104
- return /* @__PURE__ */ jsxs16("p", { className: cn("text-sm text-muted-foreground", className), "aria-label": ariaLabel, children: [
1105
- /* @__PURE__ */ jsxs16("span", { children: [
1106
- /* @__PURE__ */ jsx28("strong", { className: "font-semibold text-foreground", children: summary.total }),
1107
- " scenario",
1108
- summary.total === 1 ? "" : "s"
1109
- ] }),
1110
- " \xB7 ",
1111
- /* @__PURE__ */ jsxs16("span", { "data-status": "passed", className: "text-pass", children: [
1112
- summary.passed,
1113
- " passed"
1114
- ] }),
1115
- " \xB7 ",
1116
- /* @__PURE__ */ jsxs16("span", { "data-status": "failed", className: "text-fail", children: [
1117
- summary.failed,
1118
- " failed"
1119
- ] }),
1120
- summary.skipped > 0 ? /* @__PURE__ */ jsxs16(Fragment10, { children: [
1121
- " \xB7 ",
1122
- /* @__PURE__ */ jsxs16("span", { "data-status": "skipped", className: "text-skip", children: [
1123
- summary.skipped,
1124
- " skipped"
1125
- ] })
1126
- ] }) : null,
1127
- summary.pending > 0 ? /* @__PURE__ */ jsxs16(Fragment10, { children: [
1128
- " \xB7 ",
1129
- /* @__PURE__ */ jsxs16("span", { "data-status": "pending", className: "text-pend", children: [
1130
- summary.pending,
1131
- " pending"
1132
- ] })
1133
- ] }) : null
1134
- ] });
1147
+ const cards = [
1148
+ { status: "total", label: "Total", value: summary.total },
1149
+ { status: "passed", label: "Passed", value: summary.passed },
1150
+ { status: "failed", label: "Failed", value: summary.failed },
1151
+ { status: "skipped", label: "Skipped", value: summary.skipped }
1152
+ ];
1153
+ if (summary.pending > 0) {
1154
+ cards.push({ status: "pending", label: "Pending", value: summary.pending });
1155
+ }
1156
+ return /* @__PURE__ */ jsx29(
1157
+ "dl",
1158
+ {
1159
+ className: cn(
1160
+ "grid grid-cols-2 gap-3",
1161
+ // Columns follow the card count so the row is always full — 5 statuses
1162
+ // (pending present) never leave a lone orphan card below a row of four.
1163
+ cards.length === 5 ? "sm:grid-cols-5" : "sm:grid-cols-4",
1164
+ className
1165
+ ),
1166
+ "aria-label": ariaLabel,
1167
+ children: cards.map((c) => {
1168
+ const tone = TONE[c.status];
1169
+ return /* @__PURE__ */ jsxs17(
1170
+ "div",
1171
+ {
1172
+ "data-status": c.status,
1173
+ className: "rounded-lg border bg-card px-4 py-3",
1174
+ children: [
1175
+ /* @__PURE__ */ jsxs17("dt", { className: "flex items-center gap-1.5 font-mono text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: [
1176
+ /* @__PURE__ */ jsx29("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
1177
+ c.label
1178
+ ] }),
1179
+ /* @__PURE__ */ jsx29("dd", { className: cn("mt-2 text-3xl font-semibold leading-none tracking-tight tabular-nums", tone.num), children: c.value })
1180
+ ]
1181
+ },
1182
+ c.status
1183
+ );
1184
+ })
1185
+ }
1186
+ );
1135
1187
  }
1136
1188
 
1137
1189
  // src/lib/provenance.ts
@@ -1165,7 +1217,7 @@ function prUrl(ci) {
1165
1217
  }
1166
1218
 
1167
1219
  // src/components/ReportMeta.tsx
1168
- import { jsx as jsx29, jsxs as jsxs17 } from "react/jsx-runtime";
1220
+ import { jsx as jsx30, jsxs as jsxs18 } from "react/jsx-runtime";
1169
1221
  function ReportMeta() {
1170
1222
  const report = useReport();
1171
1223
  const items = [];
@@ -1188,13 +1240,13 @@ function ReportMeta() {
1188
1240
  const short = sha.slice(0, 8);
1189
1241
  items.push({
1190
1242
  label: "Commit",
1191
- value: url ? /* @__PURE__ */ jsx29("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1243
+ value: url ? /* @__PURE__ */ jsx30("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1192
1244
  });
1193
1245
  }
1194
1246
  if (report.ci) {
1195
1247
  items.push({
1196
1248
  label: "CI",
1197
- value: report.ci.url ? /* @__PURE__ */ jsxs17("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1249
+ value: report.ci.url ? /* @__PURE__ */ jsxs18("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1198
1250
  ciDisplayName(report.ci.name),
1199
1251
  report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
1200
1252
  ] }) : `${ciDisplayName(report.ci.name)}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
@@ -1205,45 +1257,56 @@ function ReportMeta() {
1205
1257
  const label = `#${report.ci.prNumber}`;
1206
1258
  items.push({
1207
1259
  label: "PR",
1208
- value: url ? /* @__PURE__ */ jsx29("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1260
+ value: url ? /* @__PURE__ */ jsx30("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1209
1261
  });
1210
1262
  }
1211
1263
  if (items.length === 0) return null;
1212
- 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: [
1213
- /* @__PURE__ */ jsx29("dt", { className: "font-medium", children: item.label }),
1214
- /* @__PURE__ */ jsx29("dd", { className: "font-mono text-foreground", children: item.value })
1215
- ] }, item.label)) });
1264
+ return (
1265
+ // Run metadata is useful but secondary to "what failed" — tuck it into a
1266
+ // collapsed disclosure so it doesn't push the result down. Native <details>
1267
+ // so it works with no JS (the SSR/no-hydration report still toggles).
1268
+ /* @__PURE__ */ jsxs18("details", { "data-es-meta-card": true, className: "es-run-details group rounded-lg border border-border bg-card text-xs", children: [
1269
+ /* @__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: [
1270
+ /* @__PURE__ */ jsx30("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
1271
+ "Run details"
1272
+ ] }),
1273
+ /* @__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: [
1274
+ /* @__PURE__ */ jsx30("dt", { className: "font-medium", children: item.label }),
1275
+ /* @__PURE__ */ jsx30("dd", { className: "font-mono text-foreground", children: item.value })
1276
+ ] }, item.label)) })
1277
+ ] })
1278
+ );
1216
1279
  }
1217
1280
 
1218
1281
  // src/components/ReportSchemaError.tsx
1219
- import { jsx as jsx30, jsxs as jsxs18 } from "react/jsx-runtime";
1282
+ import { jsx as jsx31, jsxs as jsxs19 } from "react/jsx-runtime";
1220
1283
  function ReportSchemaError({ error }) {
1221
- return /* @__PURE__ */ jsxs18("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1222
- /* @__PURE__ */ jsx30("p", { children: /* @__PURE__ */ jsx30("strong", { children: "Report could not be displayed." }) }),
1223
- /* @__PURE__ */ jsx30("p", { children: error.message }),
1224
- error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs18("p", { children: [
1284
+ return /* @__PURE__ */ jsxs19("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1285
+ /* @__PURE__ */ jsx31("p", { children: /* @__PURE__ */ jsx31("strong", { children: "Report could not be displayed." }) }),
1286
+ /* @__PURE__ */ jsx31("p", { children: error.message }),
1287
+ error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs19("p", { children: [
1225
1288
  "The report bundle is newer than this version of ",
1226
- /* @__PURE__ */ jsx30("code", { children: "executable-stories-react" }),
1289
+ /* @__PURE__ */ jsx31("code", { children: "executable-stories-react" }),
1227
1290
  ". Upgrade the package, or regenerate the report with an older formatters CLI."
1228
1291
  ] }) : null,
1229
- error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs18("details", { children: [
1230
- /* @__PURE__ */ jsxs18("summary", { children: [
1292
+ error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs19("details", { children: [
1293
+ /* @__PURE__ */ jsxs19("summary", { children: [
1231
1294
  error.issues.length,
1232
1295
  " validation issue",
1233
1296
  error.issues.length === 1 ? "" : "s"
1234
1297
  ] }),
1235
- /* @__PURE__ */ jsx30("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1298
+ /* @__PURE__ */ jsx31("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1236
1299
  ] }) : null
1237
1300
  ] });
1238
1301
  }
1239
1302
 
1240
1303
  // src/components/ReportShell.tsx
1241
- import { Fragment as Fragment11, jsx as jsx31, jsxs as jsxs19 } from "react/jsx-runtime";
1304
+ import { Fragment as Fragment10, jsx as jsx32, jsxs as jsxs20 } from "react/jsx-runtime";
1242
1305
  function ReportTitleBlock({ title }) {
1243
- return /* @__PURE__ */ jsxs19(Fragment11, { children: [
1244
- /* @__PURE__ */ jsx31("h1", { children: title ?? "Story Report" }),
1245
- /* @__PURE__ */ jsx31(ReportSummary, {}),
1246
- /* @__PURE__ */ jsx31(ReportMeta, {})
1306
+ return /* @__PURE__ */ jsxs20(Fragment10, { children: [
1307
+ /* @__PURE__ */ jsx32("h1", { children: title ?? "Story Report" }),
1308
+ /* @__PURE__ */ jsx32(ReportSummary, {}),
1309
+ /* @__PURE__ */ jsx32(ReportMeta, {})
1247
1310
  ] });
1248
1311
  }
1249
1312
  function ReportErrorShell({
@@ -1252,22 +1315,22 @@ function ReportErrorShell({
1252
1315
  title,
1253
1316
  dataTheme
1254
1317
  }) {
1255
- return /* @__PURE__ */ jsx31("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ jsx31(ReportSchemaError, { error }) });
1318
+ return /* @__PURE__ */ jsx32("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ jsx32(ReportSchemaError, { error }) });
1256
1319
  }
1257
1320
 
1258
1321
  // src/components/Report.tsx
1259
- import { jsx as jsx32, jsxs as jsxs20 } from "react/jsx-runtime";
1322
+ import { jsx as jsx33, jsxs as jsxs21 } from "react/jsx-runtime";
1260
1323
  function Report(props) {
1261
1324
  const { report, customRenderers, renderers, className, title, dataTheme } = props;
1262
1325
  const result = unwrapReport(report);
1263
1326
  if (!result.ok) {
1264
- return /* @__PURE__ */ jsx32(ReportErrorShell, { error: result.error, className, title, dataTheme });
1327
+ return /* @__PURE__ */ jsx33(ReportErrorShell, { error: result.error, className, title, dataTheme });
1265
1328
  }
1266
1329
  const data = result.data;
1267
1330
  const hasContent = data.features.length > 0;
1268
- return /* @__PURE__ */ jsx32(ReportRoot, { report: data, customRenderers, renderers, children: /* @__PURE__ */ jsxs20("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: [
1269
- /* @__PURE__ */ jsx32("header", { className: "es-report-header", children: /* @__PURE__ */ jsx32(ReportTitleBlock, { title }) }),
1270
- hasContent ? /* @__PURE__ */ jsx32(ReportFeatureList, {}) : /* @__PURE__ */ jsx32(ReportEmpty, {})
1331
+ return /* @__PURE__ */ jsx33(ReportRoot, { report: data, customRenderers, renderers, children: /* @__PURE__ */ jsxs21("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: [
1332
+ /* @__PURE__ */ jsx33("header", { className: "es-report-header", children: /* @__PURE__ */ jsx33(ReportTitleBlock, { title }) }),
1333
+ hasContent ? /* @__PURE__ */ jsx33(ReportFeatureList, {}) : /* @__PURE__ */ jsx33(ReportEmpty, {})
1271
1334
  ] }) });
1272
1335
  }
1273
1336