executable-stories-react 0.1.16 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,15 @@
1
1
  "use client";
2
2
 
3
+ // src/result.ts
4
+ var ok = (data) => ({ ok: true, data });
5
+ var err = (error) => ({ ok: false, error });
6
+ function unwrapReport(value) {
7
+ if (typeof value === "object" && value !== null && "ok" in value && typeof value.ok === "boolean") {
8
+ return value;
9
+ }
10
+ return ok(value);
11
+ }
12
+
3
13
  // src/context/ReportRoot.tsx
4
14
  import { useMemo } from "react";
5
15
 
@@ -40,74 +50,80 @@ function useReport() {
40
50
  return ctx.report;
41
51
  }
42
52
 
43
- // src/components/ReportSummary.tsx
44
- import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
45
- function ReportSummary({ className }) {
46
- const report = useReport();
47
- return /* @__PURE__ */ jsx2(
48
- ReportSummaryView,
49
- {
50
- summary: report.summary,
51
- ...className !== void 0 && { className },
52
- ariaLabel: "Run summary"
53
- }
54
- );
55
- }
56
- function ReportSummaryView({ summary, className, ariaLabel }) {
57
- return /* @__PURE__ */ jsxs(
58
- "p",
59
- {
60
- className: ["es-report-summary", className].filter(Boolean).join(" "),
61
- "aria-label": ariaLabel,
62
- children: [
63
- /* @__PURE__ */ jsxs("span", { children: [
64
- /* @__PURE__ */ jsx2("strong", { children: summary.total }),
65
- " scenario",
66
- summary.total === 1 ? "" : "s"
67
- ] }),
68
- " \xB7 ",
69
- /* @__PURE__ */ jsxs("span", { "data-status": "passed", children: [
70
- summary.passed,
71
- " passed"
72
- ] }),
73
- " \xB7 ",
74
- /* @__PURE__ */ jsxs("span", { "data-status": "failed", children: [
75
- summary.failed,
76
- " failed"
77
- ] }),
78
- summary.skipped > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
79
- " \xB7 ",
80
- /* @__PURE__ */ jsxs("span", { "data-status": "skipped", children: [
81
- summary.skipped,
82
- " skipped"
83
- ] })
84
- ] }) : null,
85
- summary.pending > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
86
- " \xB7 ",
87
- /* @__PURE__ */ jsxs("span", { "data-status": "pending", children: [
88
- summary.pending,
89
- " pending"
90
- ] })
91
- ] }) : null
92
- ]
93
- }
94
- );
95
- }
53
+ // src/components/ReportScenario.tsx
54
+ import { formatDuration as formatDuration3 } from "executable-stories-core/utils/duration";
55
+
56
+ // src/components/ReportSteps.tsx
57
+ import { formatDuration } from "executable-stories-core/utils/duration";
96
58
 
97
59
  // src/components/doc/DocNote.tsx
98
- import { jsx as jsx3 } from "react/jsx-runtime";
60
+ import { jsx as jsx2 } from "react/jsx-runtime";
99
61
  function DocNote({ entry }) {
100
- return /* @__PURE__ */ jsx3("p", { className: "es-doc es-doc-note", children: entry.text });
62
+ return /* @__PURE__ */ jsx2("p", { className: "my-2 text-sm text-muted-foreground", children: entry.text });
63
+ }
64
+
65
+ // src/components/ui/badge.tsx
66
+ import "react";
67
+ import { cva } from "class-variance-authority";
68
+ import { useRender } from "@base-ui/react/use-render";
69
+
70
+ // src/lib/utils.ts
71
+ import { clsx } from "clsx";
72
+ import { twMerge } from "tailwind-merge";
73
+ function cn(...inputs) {
74
+ return twMerge(clsx(inputs));
75
+ }
76
+
77
+ // src/components/ui/badge.tsx
78
+ var badgeVariants = cva(
79
+ "inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3",
80
+ {
81
+ variants: {
82
+ variant: {
83
+ default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
84
+ secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
85
+ destructive: "bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90",
86
+ outline: "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
87
+ ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
88
+ link: "text-primary underline-offset-4 [a&]:hover:underline",
89
+ passed: "border-pass-border bg-pass-bg text-pass font-mono uppercase",
90
+ failed: "border-fail-border bg-fail-bg text-fail font-mono uppercase",
91
+ skipped: "border-skip-border bg-skip-bg text-skip font-mono uppercase",
92
+ pending: "border-pend-border bg-pend-bg text-pend font-mono uppercase",
93
+ tag: "border-pass-border bg-pass-bg text-pass font-mono font-medium"
94
+ }
95
+ },
96
+ defaultVariants: {
97
+ variant: "default"
98
+ }
99
+ }
100
+ );
101
+ function Badge({
102
+ className,
103
+ variant = "default",
104
+ render,
105
+ ...props
106
+ }) {
107
+ return useRender({
108
+ defaultTagName: "span",
109
+ render,
110
+ props: {
111
+ "data-slot": "badge",
112
+ "data-variant": variant,
113
+ className: cn(badgeVariants({ variant }), className),
114
+ ...props
115
+ }
116
+ });
101
117
  }
102
118
 
103
119
  // src/components/doc/DocTag.tsx
104
- import { jsx as jsx4 } from "react/jsx-runtime";
120
+ import { jsx as jsx3 } from "react/jsx-runtime";
105
121
  function DocTag({ entry }) {
106
- return /* @__PURE__ */ jsx4("ul", { className: "es-doc es-tags", "aria-label": "Tags", children: entry.names.map((n) => /* @__PURE__ */ jsx4("li", { children: n }, n)) });
122
+ return /* @__PURE__ */ jsx3("ul", { className: "my-2 flex flex-wrap gap-1.5", "aria-label": "Tags", children: entry.names.map((n) => /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsx3(Badge, { variant: "tag", children: n }) }, n)) });
107
123
  }
108
124
 
109
125
  // src/components/doc/DocKv.tsx
110
- import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
126
+ import { jsx as jsx4, jsxs } from "react/jsx-runtime";
111
127
  function formatValue(value) {
112
128
  if (value === null) return "null";
113
129
  if (typeof value === "string") return value;
@@ -119,9 +135,9 @@ function formatValue(value) {
119
135
  }
120
136
  }
121
137
  function DocKv({ entry }) {
122
- return /* @__PURE__ */ jsxs2("dl", { className: "es-doc es-doc-kv", children: [
123
- /* @__PURE__ */ jsx5("dt", { children: entry.label }),
124
- /* @__PURE__ */ jsx5("dd", { children: formatValue(entry.value) })
138
+ return /* @__PURE__ */ jsxs("dl", { className: "my-2 flex flex-wrap items-baseline gap-x-2 text-sm", children: [
139
+ /* @__PURE__ */ jsx4("dt", { className: "font-semibold text-muted-foreground", children: entry.label }),
140
+ /* @__PURE__ */ jsx4("dd", { className: "font-mono text-foreground", children: formatValue(entry.value) })
125
141
  ] });
126
142
  }
127
143
 
@@ -139,37 +155,40 @@ function useBuiltinRenderers() {
139
155
  }
140
156
 
141
157
  // src/components/doc/DocCode.tsx
142
- import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
158
+ import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
159
+ function CodeFigure({ label, children }) {
160
+ return /* @__PURE__ */ jsxs2("figure", { className: "my-3", children: [
161
+ label ? /* @__PURE__ */ jsx5("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: label }) : null,
162
+ /* @__PURE__ */ jsx5("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3", children })
163
+ ] });
164
+ }
143
165
  function DocCode({ entry }) {
144
166
  const renderers = useBuiltinRenderers();
145
167
  if (renderers.code) {
146
- return /* @__PURE__ */ jsx6(Fragment2, { children: renderers.code(entry) });
168
+ return /* @__PURE__ */ jsx5(Fragment, { children: renderers.code(entry) });
147
169
  }
148
- return /* @__PURE__ */ jsxs3("figure", { className: "es-doc es-doc-code", children: [
149
- /* @__PURE__ */ jsx6("figcaption", { children: entry.label }),
150
- /* @__PURE__ */ jsx6("pre", { children: /* @__PURE__ */ jsx6("code", { className: entry.lang ? `language-${entry.lang}` : void 0, children: entry.content }) })
151
- ] });
170
+ return /* @__PURE__ */ jsx5(CodeFigure, { label: entry.label, children: /* @__PURE__ */ jsx5("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
152
171
  }
153
172
 
154
173
  // src/components/doc/DocTable.tsx
155
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
174
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
156
175
  function DocTable({ entry }) {
157
- return /* @__PURE__ */ jsxs4("figure", { className: "es-doc es-doc-table", children: [
158
- /* @__PURE__ */ jsx7("figcaption", { children: entry.label }),
159
- /* @__PURE__ */ jsxs4("table", { children: [
160
- /* @__PURE__ */ jsx7("thead", { children: /* @__PURE__ */ jsx7("tr", { children: entry.columns.map((c) => /* @__PURE__ */ jsx7("th", { scope: "col", children: c }, c)) }) }),
161
- /* @__PURE__ */ jsx7("tbody", { children: entry.rows.map((row, i) => /* @__PURE__ */ jsx7("tr", { children: row.map((cell, j) => /* @__PURE__ */ jsx7("td", { children: cell }, j)) }, i)) })
162
- ] })
176
+ return /* @__PURE__ */ jsxs3("figure", { className: "my-3", children: [
177
+ entry.label ? /* @__PURE__ */ jsx6("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
178
+ /* @__PURE__ */ jsx6("div", { tabIndex: 0, className: "overflow-x-auto rounded-md border border-border", children: /* @__PURE__ */ jsxs3("table", { className: "w-full border-collapse text-sm", children: [
179
+ /* @__PURE__ */ jsx6("thead", { children: /* @__PURE__ */ jsx6("tr", { className: "bg-muted/50", children: entry.columns.map((c) => /* @__PURE__ */ jsx6("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: c }, c)) }) }),
180
+ /* @__PURE__ */ jsx6("tbody", { children: entry.rows.map((row, i) => /* @__PURE__ */ jsx6("tr", { children: row.map((cell, j) => /* @__PURE__ */ jsx6("td", { className: "border-b border-border px-3 py-2 text-foreground", children: cell }, j)) }, i)) })
181
+ ] }) })
163
182
  ] });
164
183
  }
165
184
 
166
185
  // src/components/doc/DocLink.tsx
167
- import { jsx as jsx8 } from "react/jsx-runtime";
186
+ import { jsx as jsx7 } from "react/jsx-runtime";
168
187
  function DocLink({ entry }) {
169
- return /* @__PURE__ */ jsx8(
188
+ return /* @__PURE__ */ jsx7(
170
189
  "a",
171
190
  {
172
- className: "es-doc es-doc-link",
191
+ className: "text-sm font-medium text-primary underline underline-offset-2 hover:no-underline",
173
192
  href: entry.url,
174
193
  rel: "noreferrer noopener",
175
194
  target: "_blank",
@@ -180,23 +199,37 @@ function DocLink({ entry }) {
180
199
 
181
200
  // src/components/doc/DocSection.tsx
182
201
  import { marked } from "marked";
183
- import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
202
+
203
+ // src/lib/url.ts
204
+ function safeUrl(value) {
205
+ if (!value) return void 0;
206
+ const trimmed = value.trim();
207
+ const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(trimmed);
208
+ if (scheme && !/^https?$/i.test(scheme[1] ?? "")) return void 0;
209
+ return trimmed;
210
+ }
211
+
212
+ // src/components/doc/DocSection.tsx
213
+ import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
214
+ function neutralizeUrl(value) {
215
+ return safeUrl(value) ?? "#";
216
+ }
184
217
  function safeMarkdownHtml(markdown) {
185
218
  const raw = marked.parse(markdown, { async: false });
186
- return raw.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, "").replace(/\son[a-z]+\s*=\s*"[^"]*"/gi, "").replace(/\son[a-z]+\s*=\s*'[^']*'/gi, "").replace(/\son[a-z]+\s*=\s*[^\s>]+/gi, "").replace(/(href|src)\s*=\s*"\s*javascript:[^"]*"/gi, '$1="#"').replace(/(href|src)\s*=\s*'\s*javascript:[^']*'/gi, "$1='#'");
219
+ return raw.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<\/?(?:iframe|object|embed|form|base)\b[^>]*>/gi, "").replace(/\son[a-z]+\s*=\s*"[^"]*"/gi, "").replace(/\son[a-z]+\s*=\s*'[^']*'/gi, "").replace(/\son[a-z]+\s*=\s*[^\s>]+/gi, "").replace(/(href|src)\s*=\s*"([^"]*)"/gi, (_m, attr, val) => `${attr}="${neutralizeUrl(val)}"`).replace(/(href|src)\s*=\s*'([^']*)'/gi, (_m, attr, val) => `${attr}='${neutralizeUrl(val)}'`);
187
220
  }
188
221
  function DocSection({ entry }) {
189
222
  const renderers = useBuiltinRenderers();
190
223
  if (renderers.section) {
191
- return /* @__PURE__ */ jsx9(Fragment3, { children: renderers.section(entry) });
224
+ return /* @__PURE__ */ jsx8(Fragment2, { children: renderers.section(entry) });
192
225
  }
193
226
  const html = safeMarkdownHtml(entry.markdown);
194
- return /* @__PURE__ */ jsxs5("section", { className: "es-doc es-doc-section", "aria-label": entry.title, children: [
195
- entry.title ? /* @__PURE__ */ jsx9("h4", { className: "es-doc-section-title", children: entry.title }) : null,
196
- /* @__PURE__ */ jsx9(
227
+ return /* @__PURE__ */ jsxs4("section", { className: "my-3 text-sm", "aria-label": entry.title, children: [
228
+ entry.title ? /* @__PURE__ */ jsx8("h4", { className: "mb-1 font-semibold text-foreground", children: entry.title }) : null,
229
+ /* @__PURE__ */ jsx8(
197
230
  "div",
198
231
  {
199
- className: "es-doc-section-content",
232
+ className: "es-doc-prose text-muted-foreground",
200
233
  dangerouslySetInnerHTML: { __html: html }
201
234
  }
202
235
  )
@@ -204,45 +237,114 @@ function DocSection({ entry }) {
204
237
  }
205
238
 
206
239
  // src/components/doc/DocMermaid.tsx
207
- import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
240
+ import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
241
+ function MermaidSource({ entry, ref }) {
242
+ return /* @__PURE__ */ jsxs5("figure", { ref, className: "my-3", "aria-label": entry.title ?? "Diagram", children: [
243
+ entry.title ? /* @__PURE__ */ jsx9("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
244
+ /* @__PURE__ */ jsx9(
245
+ "pre",
246
+ {
247
+ "data-mermaid": true,
248
+ tabIndex: 0,
249
+ className: "overflow-x-auto rounded-md bg-muted p-3 font-mono text-xs text-foreground",
250
+ children: entry.code
251
+ }
252
+ )
253
+ ] });
254
+ }
208
255
  function DocMermaid({ entry }) {
209
256
  const renderers = useBuiltinRenderers();
210
257
  if (renderers.mermaid) {
211
- return /* @__PURE__ */ jsx10(Fragment4, { children: renderers.mermaid(entry) });
258
+ return /* @__PURE__ */ jsx9(Fragment3, { children: renderers.mermaid(entry) });
212
259
  }
213
- return /* @__PURE__ */ jsxs6(
214
- "figure",
215
- {
216
- className: "es-doc es-doc-mermaid",
217
- "aria-label": entry.title ?? "Diagram",
218
- children: [
219
- entry.title ? /* @__PURE__ */ jsx10("figcaption", { children: entry.title }) : null,
220
- /* @__PURE__ */ jsx10("pre", { "data-mermaid": true, children: entry.code })
221
- ]
222
- }
223
- );
260
+ return /* @__PURE__ */ jsx9(MermaidSource, { entry });
224
261
  }
225
262
 
226
263
  // src/components/doc/DocScreenshot.tsx
227
- import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
264
+ import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
228
265
  function DocScreenshot({ entry }) {
229
- return /* @__PURE__ */ jsxs7("figure", { className: "es-doc es-doc-screenshot", children: [
230
- /* @__PURE__ */ jsx11("img", { src: entry.path, alt: entry.alt ?? "", loading: "lazy" }),
231
- entry.alt ? /* @__PURE__ */ jsx11("figcaption", { children: entry.alt }) : null
266
+ return /* @__PURE__ */ jsxs6("figure", { className: "my-3", children: [
267
+ /* @__PURE__ */ jsx10(
268
+ "img",
269
+ {
270
+ src: entry.path,
271
+ alt: entry.alt ?? "",
272
+ loading: "lazy",
273
+ className: "max-w-full rounded-md border border-border"
274
+ }
275
+ ),
276
+ entry.alt ? /* @__PURE__ */ jsx10("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: entry.alt }) : null
277
+ ] });
278
+ }
279
+
280
+ // src/components/doc/DocVideo.tsx
281
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
282
+ function DocVideo({ entry }) {
283
+ const src = safeUrl(entry.path);
284
+ const poster = safeUrl(entry.poster);
285
+ return /* @__PURE__ */ jsxs7("figure", { className: "my-3", children: [
286
+ /* @__PURE__ */ jsx11(
287
+ "video",
288
+ {
289
+ controls: true,
290
+ preload: "metadata",
291
+ ...src ? { src } : {},
292
+ ...poster ? { poster } : {},
293
+ className: "max-w-full rounded-md border border-border"
294
+ }
295
+ ),
296
+ entry.caption ? /* @__PURE__ */ jsx11("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: entry.caption }) : null
297
+ ] });
298
+ }
299
+
300
+ // src/components/doc/DocHtml.tsx
301
+ import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
302
+ function DocHtml({ entry }) {
303
+ const height = typeof entry.height === "number" ? `${entry.height}px` : entry.height ?? "400px";
304
+ const title = entry.title ?? "Embedded HTML";
305
+ const safeHref = safeUrl(entry.url ?? entry.path);
306
+ const frameProps = entry.content !== void 0 ? { srcDoc: entry.content } : { src: safeHref ?? "" };
307
+ return /* @__PURE__ */ jsxs8("figure", { className: "my-3 overflow-hidden rounded-md border border-border", children: [
308
+ /* @__PURE__ */ jsxs8("figcaption", { className: "flex items-center justify-between border-b border-border bg-muted/40 px-3 py-1.5 text-xs font-medium text-muted-foreground", children: [
309
+ /* @__PURE__ */ jsx12("span", { children: entry.title ?? "HTML" }),
310
+ safeHref ? /* @__PURE__ */ jsx12(
311
+ "a",
312
+ {
313
+ href: safeHref,
314
+ target: "_blank",
315
+ rel: "noreferrer noopener",
316
+ "aria-label": "Open in new tab",
317
+ title: "Open in new tab",
318
+ className: "text-primary",
319
+ children: "\u2197"
320
+ }
321
+ ) : null
322
+ ] }),
323
+ /* @__PURE__ */ jsx12(
324
+ "iframe",
325
+ {
326
+ ...frameProps,
327
+ sandbox: "allow-scripts",
328
+ loading: "lazy",
329
+ title,
330
+ className: "block w-full border-0",
331
+ style: { height }
332
+ }
333
+ )
232
334
  ] });
233
335
  }
234
336
 
235
337
  // src/components/doc/DocCustom.tsx
236
- import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
338
+ import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
237
339
  function DocCustom({ entry }) {
238
340
  const renderers = useCustomRenderers();
239
341
  const renderer = renderers[entry.type];
240
342
  if (renderer) {
241
- return /* @__PURE__ */ jsx12(Fragment5, { children: renderer(entry) });
343
+ return /* @__PURE__ */ jsx13(Fragment4, { children: renderer(entry) });
242
344
  }
243
- return /* @__PURE__ */ jsxs8("div", { className: "es-doc es-doc-custom", "data-type": entry.type, children: [
244
- /* @__PURE__ */ jsx12("p", { className: "es-doc-custom-type", children: entry.type }),
245
- /* @__PURE__ */ jsx12("pre", { children: safeStringify(entry.data) })
345
+ return /* @__PURE__ */ jsxs9("div", { className: "my-2", "data-type": entry.type, children: [
346
+ /* @__PURE__ */ jsx13("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: entry.type }),
347
+ /* @__PURE__ */ jsx13("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3 font-mono text-xs text-foreground", children: safeStringify(entry.data) })
246
348
  ] });
247
349
  }
248
350
  function safeStringify(value) {
@@ -254,231 +356,998 @@ function safeStringify(value) {
254
356
  }
255
357
 
256
358
  // src/components/doc/DocEntry.tsx
257
- import { jsx as jsx13 } from "react/jsx-runtime";
359
+ import { jsx as jsx14 } from "react/jsx-runtime";
258
360
  function DocEntry({ entry }) {
259
361
  switch (entry.kind) {
260
362
  case "note":
261
- return /* @__PURE__ */ jsx13(DocNote, { entry });
363
+ return /* @__PURE__ */ jsx14(DocNote, { entry });
262
364
  case "tag":
263
- return /* @__PURE__ */ jsx13(DocTag, { entry });
365
+ return /* @__PURE__ */ jsx14(DocTag, { entry });
264
366
  case "kv":
265
- return /* @__PURE__ */ jsx13(DocKv, { entry });
367
+ return /* @__PURE__ */ jsx14(DocKv, { entry });
266
368
  case "code":
267
- return /* @__PURE__ */ jsx13(DocCode, { entry });
369
+ return /* @__PURE__ */ jsx14(DocCode, { entry });
268
370
  case "table":
269
- return /* @__PURE__ */ jsx13(DocTable, { entry });
371
+ return /* @__PURE__ */ jsx14(DocTable, { entry });
270
372
  case "link":
271
- return /* @__PURE__ */ jsx13(DocLink, { entry });
373
+ return /* @__PURE__ */ jsx14(DocLink, { entry });
272
374
  case "section":
273
- return /* @__PURE__ */ jsx13(DocSection, { entry });
375
+ return /* @__PURE__ */ jsx14(DocSection, { entry });
274
376
  case "mermaid":
275
- return /* @__PURE__ */ jsx13(DocMermaid, { entry });
377
+ return /* @__PURE__ */ jsx14(DocMermaid, { entry });
276
378
  case "screenshot":
277
- return /* @__PURE__ */ jsx13(DocScreenshot, { entry });
379
+ return /* @__PURE__ */ jsx14(DocScreenshot, { entry });
380
+ case "video":
381
+ return /* @__PURE__ */ jsx14(DocVideo, { entry });
382
+ case "html":
383
+ return /* @__PURE__ */ jsx14(DocHtml, { entry });
278
384
  case "custom":
279
- return /* @__PURE__ */ jsx13(DocCustom, { entry });
385
+ return /* @__PURE__ */ jsx14(DocCustom, { entry });
280
386
  }
281
387
  }
282
388
 
283
389
  // src/components/ReportDocEntries.tsx
284
- import { Fragment as Fragment6, jsx as jsx14 } from "react/jsx-runtime";
390
+ import { Fragment as Fragment5, jsx as jsx15 } from "react/jsx-runtime";
285
391
  function ReportDocEntries({ entries }) {
286
392
  if (entries.length === 0) return null;
287
- return /* @__PURE__ */ jsx14(Fragment6, { children: entries.map((entry, i) => /* @__PURE__ */ jsx14(DocEntry, { entry }, i)) });
393
+ return /* @__PURE__ */ jsx15(Fragment5, { children: entries.map((entry, i) => /* @__PURE__ */ jsx15(DocEntry, { entry }, i)) });
394
+ }
395
+
396
+ // src/lib/step-params.tsx
397
+ import { jsx as jsx16 } from "react/jsx-runtime";
398
+ var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.-])\d+(?:\.\d+)?(?![\w.-])/g;
399
+ function highlightStepParams(text) {
400
+ const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
401
+ if (matches.length === 0) return text;
402
+ const nodes = [];
403
+ let lastIndex = 0;
404
+ matches.forEach((match, i) => {
405
+ const start = match.index;
406
+ const end = start + match[0].length;
407
+ if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
408
+ nodes.push(
409
+ /* @__PURE__ */ jsx16("span", { className: "font-medium text-step-param italic", children: match[0] }, i)
410
+ );
411
+ lastIndex = end;
412
+ });
413
+ if (lastIndex < text.length) nodes.push(text.slice(lastIndex));
414
+ return nodes;
288
415
  }
289
416
 
290
417
  // src/components/ReportSteps.tsx
291
- import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
418
+ import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
419
+ var STEP_GLYPH = {
420
+ passed: "\u2713",
421
+ failed: "\u2717",
422
+ skipped: "\u25CB",
423
+ pending: "\u25CB"
424
+ };
425
+ var STEP_GLYPH_COLOR = {
426
+ passed: "text-pass",
427
+ failed: "text-fail",
428
+ skipped: "text-skip",
429
+ pending: "text-pend"
430
+ };
292
431
  function ReportSteps({ scenario }) {
293
432
  if (scenario.steps.length === 0) return null;
294
- return /* @__PURE__ */ jsx15("ol", { className: "es-steps", children: scenario.steps.map((step) => /* @__PURE__ */ jsx15(ReportStepItem, { step }, step.id)) });
433
+ return /* @__PURE__ */ jsx17("ol", { "data-slot": "steps", className: "flex flex-col", children: scenario.steps.map((step) => /* @__PURE__ */ jsx17(ReportStepItem, { step }, step.id)) });
295
434
  }
296
435
  function ReportStepItem({ step }) {
297
- return /* @__PURE__ */ jsxs9(
436
+ const kw = step.keyword.toLowerCase();
437
+ const continuation = kw === "and" || kw === "but";
438
+ return /* @__PURE__ */ jsxs10(
298
439
  "li",
299
440
  {
300
441
  id: step.id,
301
- className: `es-step es-step-${step.status}`,
302
442
  "data-status": step.status,
443
+ className: cn("py-1.5 text-[0.8125rem] leading-normal", continuation && "pl-5"),
303
444
  children: [
304
- /* @__PURE__ */ jsx15("span", { className: "es-step-keyword", children: step.keyword }),
305
- /* @__PURE__ */ jsx15("span", { className: "es-step-text", children: step.text }),
306
- step.errorMessage ? /* @__PURE__ */ jsx15("pre", { className: "es-scenario-error", role: "alert", children: step.errorMessage }) : null,
307
- step.docEntries.length > 0 ? /* @__PURE__ */ jsx15("div", { className: "es-step-docs", children: /* @__PURE__ */ jsx15(ReportDocEntries, { entries: step.docEntries }) }) : null
445
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-baseline gap-2", children: [
446
+ /* @__PURE__ */ jsx17("span", { "aria-hidden": true, className: cn("w-4 shrink-0 text-center text-xs", STEP_GLYPH_COLOR[step.status]), children: STEP_GLYPH[step.status] ?? "\u25CB" }),
447
+ /* @__PURE__ */ jsx17(
448
+ "span",
449
+ {
450
+ className: cn(
451
+ "min-w-[52px] shrink-0 font-mono text-xs font-semibold capitalize",
452
+ continuation ? "font-medium text-muted-foreground" : "text-keyword"
453
+ ),
454
+ children: step.keyword
455
+ }
456
+ ),
457
+ /* @__PURE__ */ jsx17("span", { className: "flex-1 text-foreground", children: highlightStepParams(step.text) }),
458
+ step.durationMs > 0 ? /* @__PURE__ */ jsx17("span", { className: "shrink-0 whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(step.durationMs) }) : null
459
+ ] }),
460
+ step.errorMessage ? /* @__PURE__ */ jsx17(
461
+ "pre",
462
+ {
463
+ role: "alert",
464
+ className: "mt-2.5 ml-6 overflow-x-auto rounded-md border border-fail-border bg-fail-bg px-4 py-3.5 font-mono text-xs leading-relaxed whitespace-pre-wrap text-fail",
465
+ children: step.errorMessage
466
+ }
467
+ ) : null,
468
+ step.docEntries.length > 0 ? /* @__PURE__ */ jsx17("div", { "data-es-docs": true, className: "mt-2 ml-6", children: /* @__PURE__ */ jsx17(ReportDocEntries, { entries: step.docEntries }) }) : null
308
469
  ]
309
470
  }
310
471
  );
311
472
  }
312
473
 
474
+ // src/components/ReportAttachments.tsx
475
+ import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
476
+ function ReportAttachments({ attachments }) {
477
+ if (attachments.length === 0) return null;
478
+ return (
479
+ // role="group" (not a labelled <section>) so multiple scenarios' attachment
480
+ // blocks don't create duplicate "Attachments" region landmarks.
481
+ /* @__PURE__ */ jsx18("div", { role: "group", "aria-label": "Attachments", className: "mt-3 flex flex-col gap-2", children: attachments.map((att, i) => {
482
+ const dataUri = att.contentEncoding === "BASE64" ? `data:${att.mediaType};base64,${att.body}` : `data:${att.mediaType},${encodeURIComponent(att.body)}`;
483
+ const isImage = att.mediaType.startsWith("image/");
484
+ return /* @__PURE__ */ jsx18("figure", { className: "m-0", children: isImage ? /* @__PURE__ */ jsxs11(Fragment6, { children: [
485
+ /* @__PURE__ */ jsx18(
486
+ "img",
487
+ {
488
+ src: dataUri,
489
+ alt: att.name,
490
+ loading: "lazy",
491
+ className: "max-w-full rounded-md border border-border"
492
+ }
493
+ ),
494
+ /* @__PURE__ */ jsx18("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: att.name })
495
+ ] }) : /* @__PURE__ */ jsxs11(
496
+ "a",
497
+ {
498
+ href: dataUri,
499
+ download: att.name,
500
+ className: "text-xs text-link underline underline-offset-2",
501
+ children: [
502
+ att.name,
503
+ " ",
504
+ /* @__PURE__ */ jsxs11("span", { className: "text-muted-foreground", children: [
505
+ "(",
506
+ att.mediaType,
507
+ ")"
508
+ ] })
509
+ ]
510
+ }
511
+ ) }, `${att.name}-${i}`);
512
+ }) })
513
+ );
514
+ }
515
+
516
+ // src/components/ReportTrace.tsx
517
+ import { formatDuration as formatDuration2 } from "executable-stories-core/utils/duration";
518
+
519
+ // src/lib/trace.ts
520
+ var VALID = /* @__PURE__ */ new Set(["ok", "error", "unset"]);
521
+ var safeStatus = (s) => VALID.has(s) ? s : "unset";
522
+ function normalizeSpans(spans) {
523
+ const out = [];
524
+ for (const span of spans) {
525
+ if (!span || typeof span.spanId !== "string" || typeof span.name !== "string") continue;
526
+ let startTimeMs;
527
+ let durationMs;
528
+ if (span.startTimeMs != null && span.durationMs != null) {
529
+ startTimeMs = span.startTimeMs;
530
+ durationMs = span.durationMs;
531
+ } else if (span.startTimeUnixNano != null && span.endTimeUnixNano != null) {
532
+ startTimeMs = span.startTimeUnixNano / 1e6;
533
+ durationMs = (span.endTimeUnixNano - span.startTimeUnixNano) / 1e6;
534
+ } else {
535
+ continue;
536
+ }
537
+ durationMs = Math.max(0, durationMs);
538
+ if (!isFinite(startTimeMs) || !isFinite(durationMs)) continue;
539
+ out.push({
540
+ spanId: span.spanId,
541
+ parentSpanId: span.parentSpanId,
542
+ name: span.name,
543
+ startTimeMs,
544
+ durationMs,
545
+ status: safeStatus(span.status),
546
+ statusMessage: span.statusMessage
547
+ });
548
+ }
549
+ return out;
550
+ }
551
+ function toTraceRows(spans) {
552
+ const normalized = normalizeSpans(spans);
553
+ if (normalized.length === 0) return [];
554
+ const byId = /* @__PURE__ */ new Map();
555
+ for (const span of normalized) {
556
+ let key = span.spanId;
557
+ let suffix = 2;
558
+ while (byId.has(key)) key = `${span.spanId}__dup${suffix++}`;
559
+ byId.set(key, { span: { ...span, spanId: key }, children: [], depth: 0 });
560
+ }
561
+ const roots = [];
562
+ for (const node of byId.values()) {
563
+ const parent = node.span.parentSpanId ? byId.get(node.span.parentSpanId) : void 0;
564
+ if (parent && parent !== node) parent.children.push(node);
565
+ else roots.push(node);
566
+ }
567
+ for (const node of byId.values()) node.children.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
568
+ roots.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
569
+ const rows = [];
570
+ const seen = /* @__PURE__ */ new Set();
571
+ const walk = (node, depth) => {
572
+ if (seen.has(node.span.spanId)) return;
573
+ seen.add(node.span.spanId);
574
+ rows.push({ ...node.span, depth });
575
+ for (const child of node.children) walk(child, depth + 1);
576
+ };
577
+ for (const root of roots) walk(root, 0);
578
+ for (const node of byId.values()) {
579
+ if (!seen.has(node.span.spanId)) walk(node, 0);
580
+ }
581
+ return rows;
582
+ }
583
+ function traceWindow(rows) {
584
+ let min = Infinity;
585
+ let max = -Infinity;
586
+ for (const r of rows) {
587
+ min = Math.min(min, r.startTimeMs);
588
+ max = Math.max(max, r.startTimeMs + r.durationMs);
589
+ }
590
+ const total = max - min;
591
+ return { minStartMs: isFinite(min) ? min : 0, totalMs: total > 0 ? total : 1 };
592
+ }
593
+
594
+ // src/components/ReportTrace.tsx
595
+ import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
596
+ var BAR_COLOR = {
597
+ ok: "bg-pass",
598
+ error: "bg-fail",
599
+ unset: "bg-skip"
600
+ };
601
+ function ReportTrace({ spans }) {
602
+ if (!spans || spans.length === 0) return null;
603
+ const rows = toTraceRows(spans);
604
+ if (rows.length === 0) return null;
605
+ const { minStartMs, totalMs } = traceWindow(rows);
606
+ return /* @__PURE__ */ jsxs12("details", { className: "mt-3 rounded-md border border-border bg-muted/30", children: [
607
+ /* @__PURE__ */ jsxs12("summary", { className: "cursor-pointer px-3 py-2 text-xs font-medium text-muted-foreground", children: [
608
+ "Trace (",
609
+ rows.length,
610
+ " span",
611
+ rows.length === 1 ? "" : "s",
612
+ ", ",
613
+ formatDuration2(totalMs),
614
+ ")"
615
+ ] }),
616
+ /* @__PURE__ */ jsx19("ul", { className: "flex flex-col gap-0.5 px-3 pb-3 font-mono text-[0.6875rem]", children: rows.map((row, i) => {
617
+ const leftPct = (row.startTimeMs - minStartMs) / totalMs * 100;
618
+ const widthPct = Math.max(row.durationMs / totalMs * 100, 0.5);
619
+ return /* @__PURE__ */ jsxs12("li", { className: "flex items-center gap-2", children: [
620
+ /* @__PURE__ */ jsx19(
621
+ "span",
622
+ {
623
+ className: "min-w-0 flex-1 truncate text-foreground",
624
+ style: { paddingLeft: `${row.depth * 12}px` },
625
+ title: row.statusMessage ? `${row.name} \u2014 ${row.statusMessage}` : row.name,
626
+ children: row.name
627
+ }
628
+ ),
629
+ /* @__PURE__ */ jsx19("span", { className: "relative h-3 w-1/2 shrink-0 overflow-hidden rounded-sm bg-border/40", children: /* @__PURE__ */ jsx19(
630
+ "span",
631
+ {
632
+ className: cn("absolute top-0 h-full rounded-sm", BAR_COLOR[row.status]),
633
+ style: { left: `${leftPct}%`, width: `${widthPct}%` }
634
+ }
635
+ ) }),
636
+ /* @__PURE__ */ jsx19("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: formatDuration2(row.durationMs) })
637
+ ] }, `${row.spanId}-${i}`);
638
+ }) })
639
+ ] });
640
+ }
641
+
642
+ // src/interactive/collapse-context.ts
643
+ import { createContext as createContext2, useCallback, useContext as useContext3, useState } from "react";
644
+ var CollapseContext = createContext2(null);
645
+ var CollapseProvider = CollapseContext.Provider;
646
+ function useCollapse() {
647
+ return useContext3(CollapseContext);
648
+ }
649
+
650
+ // src/interactive/scenario-actions.tsx
651
+ import { createContext as createContext3, useContext as useContext4 } from "react";
652
+ var ScenarioActionsContext = createContext3(null);
653
+ var ScenarioActionsProvider = ScenarioActionsContext.Provider;
654
+ function useScenarioActions() {
655
+ return useContext4(ScenarioActionsContext);
656
+ }
657
+
658
+ // src/components/ui/card.tsx
659
+ import "react";
660
+ import { jsx as jsx20 } from "react/jsx-runtime";
661
+ function Card({ className, ...props }) {
662
+ return /* @__PURE__ */ jsx20(
663
+ "div",
664
+ {
665
+ "data-slot": "card",
666
+ className: cn(
667
+ "flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm",
668
+ className
669
+ ),
670
+ ...props
671
+ }
672
+ );
673
+ }
674
+ function CardHeader({ className, ...props }) {
675
+ return /* @__PURE__ */ jsx20(
676
+ "div",
677
+ {
678
+ "data-slot": "card-header",
679
+ className: cn(
680
+ "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
681
+ className
682
+ ),
683
+ ...props
684
+ }
685
+ );
686
+ }
687
+ function CardContent({ className, ...props }) {
688
+ return /* @__PURE__ */ jsx20(
689
+ "div",
690
+ {
691
+ "data-slot": "card-content",
692
+ className: cn("px-6", className),
693
+ ...props
694
+ }
695
+ );
696
+ }
697
+
313
698
  // src/components/ReportScenario.tsx
314
- import { jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
699
+ import { jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
315
700
  var STATUS_LABEL = {
316
701
  passed: "Passed",
317
702
  failed: "Failed",
318
703
  skipped: "Skipped",
319
704
  pending: "Pending"
320
705
  };
321
- function ReportScenario({ scenario }) {
706
+ var STATUS_GLYPH = {
707
+ passed: "\u2713",
708
+ failed: "\u2717",
709
+ skipped: "\u25CB",
710
+ pending: "\u25CB"
711
+ };
712
+ var STATUS_GLYPH_COLOR = {
713
+ passed: "text-pass",
714
+ failed: "text-fail",
715
+ skipped: "text-skip",
716
+ pending: "text-pend"
717
+ };
718
+ var STATUS_BADGE = {
719
+ passed: "passed",
720
+ failed: "failed",
721
+ skipped: "skipped",
722
+ pending: "pending"
723
+ };
724
+ function ReportScenario({ scenario, hideTitle = false }) {
322
725
  const titleId = `${scenario.id}-title`;
323
- return /* @__PURE__ */ jsxs10(
324
- "article",
726
+ const bodyId = `${scenario.id}-body`;
727
+ const label = STATUS_LABEL[scenario.status];
728
+ const collapse = useCollapse();
729
+ const collapsible = collapse !== null && !hideTitle;
730
+ const collapsed = collapsible ? collapse.isCollapsed(scenario.id) : false;
731
+ const actions = useScenarioActions();
732
+ return /* @__PURE__ */ jsxs13(
733
+ Card,
325
734
  {
326
735
  id: scenario.id,
327
- className: `es-scenario es-status-${scenario.status}`,
328
- "aria-labelledby": titleId,
329
736
  "data-status": scenario.status,
737
+ ...hideTitle ? { "aria-label": `${scenario.title} \u2014 ${label}` } : { "aria-labelledby": titleId },
738
+ className: "gap-0 py-0",
330
739
  children: [
331
- /* @__PURE__ */ jsxs10("h3", { id: titleId, className: "es-scenario-title", children: [
332
- /* @__PURE__ */ jsx16("span", { children: scenario.title }),
333
- /* @__PURE__ */ jsx16("span", { className: "es-scenario-status", "aria-label": `Status: ${STATUS_LABEL[scenario.status]}`, children: STATUS_LABEL[scenario.status] })
740
+ /* @__PURE__ */ jsxs13(CardHeader, { className: "px-4 py-3", children: [
741
+ /* @__PURE__ */ jsxs13("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
742
+ hideTitle ? null : /* @__PURE__ */ jsxs13("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
743
+ collapsible ? /* @__PURE__ */ jsx21(
744
+ "button",
745
+ {
746
+ type: "button",
747
+ onClick: () => collapse.toggle(scenario.id),
748
+ "aria-expanded": !collapsed,
749
+ "aria-controls": bodyId,
750
+ "aria-label": `Toggle ${scenario.title}`,
751
+ className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
752
+ children: /* @__PURE__ */ jsx21("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
753
+ }
754
+ ) : null,
755
+ /* @__PURE__ */ jsx21("span", { "aria-hidden": true, className: cn("text-sm leading-none", STATUS_GLYPH_COLOR[scenario.status]), children: STATUS_GLYPH[scenario.status] }),
756
+ /* @__PURE__ */ jsx21("span", { children: scenario.title })
757
+ ] }),
758
+ /* @__PURE__ */ jsxs13("div", { className: "flex shrink-0 items-center gap-2", children: [
759
+ scenario.durationMs > 0 ? /* @__PURE__ */ jsx21("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration3(scenario.durationMs) }) : null,
760
+ /* @__PURE__ */ jsx21(Badge, { variant: STATUS_BADGE[scenario.status], "aria-label": `Status: ${label}`, children: label }),
761
+ actions ? /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-0.5", children: [
762
+ /* @__PURE__ */ jsx21(
763
+ "button",
764
+ {
765
+ type: "button",
766
+ onClick: () => actions.copyLink(scenario),
767
+ "aria-label": `Copy link to ${scenario.title}`,
768
+ title: "Copy permalink",
769
+ className: "cursor-pointer rounded px-1 py-0.5 font-mono text-[0.625rem] text-muted-foreground hover:bg-muted hover:text-foreground",
770
+ children: "link"
771
+ }
772
+ ),
773
+ /* @__PURE__ */ jsx21(
774
+ "button",
775
+ {
776
+ type: "button",
777
+ onClick: () => actions.copyMarkdown(scenario),
778
+ "aria-label": `Copy ${scenario.title} as Markdown`,
779
+ title: "Copy as Markdown",
780
+ className: "cursor-pointer rounded px-1 py-0.5 font-mono text-[0.625rem] text-muted-foreground hover:bg-muted hover:text-foreground",
781
+ children: "md"
782
+ }
783
+ ),
784
+ /* @__PURE__ */ jsx21(
785
+ "button",
786
+ {
787
+ type: "button",
788
+ onClick: () => actions.copyPrompt(scenario),
789
+ "aria-label": `Copy ${scenario.title} as an AI investigation prompt`,
790
+ title: "Copy as AI prompt",
791
+ className: "cursor-pointer rounded px-1 py-0.5 font-mono text-[0.625rem] text-muted-foreground hover:bg-muted hover:text-foreground",
792
+ children: "ai"
793
+ }
794
+ )
795
+ ] }) : null
796
+ ] })
797
+ ] }),
798
+ scenario.tags.length > 0 || (scenario.tickets?.length ?? 0) > 0 ? /* @__PURE__ */ jsxs13("ul", { "aria-label": "Tags and tickets", className: "flex flex-wrap gap-1.5", children: [
799
+ scenario.tags.map((t) => /* @__PURE__ */ jsx21("li", { children: /* @__PURE__ */ jsx21(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
800
+ (scenario.tickets ?? []).map((ticket) => /* @__PURE__ */ jsx21("li", { children: ticket.url ? /* @__PURE__ */ jsx21("a", { href: ticket.url, target: "_blank", rel: "noreferrer noopener", children: /* @__PURE__ */ jsx21(Badge, { variant: "outline", children: ticket.id }) }) : /* @__PURE__ */ jsx21(Badge, { variant: "outline", children: ticket.id }) }, `ticket-${ticket.id}`))
801
+ ] }) : null
334
802
  ] }),
335
- scenario.tags.length > 0 ? /* @__PURE__ */ jsx16("ul", { className: "es-tags", "aria-label": "Tags", children: scenario.tags.map((t) => /* @__PURE__ */ jsx16("li", { children: t }, t)) }) : null,
336
- scenario.errorMessage ? /* @__PURE__ */ jsx16("pre", { className: "es-scenario-error", role: "alert", children: scenario.errorMessage }) : null,
337
- scenario.docEntries.length > 0 ? /* @__PURE__ */ jsx16("div", { className: "es-scenario-docs", children: /* @__PURE__ */ jsx16(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
338
- /* @__PURE__ */ jsx16(ReportSteps, { scenario })
803
+ /* @__PURE__ */ jsxs13(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
804
+ scenario.errorMessage ? /* @__PURE__ */ jsx21(
805
+ "pre",
806
+ {
807
+ role: "alert",
808
+ className: "mb-1 overflow-x-auto rounded-md border border-fail-border bg-fail-bg px-4 py-3.5 font-mono text-xs leading-relaxed whitespace-pre-wrap text-fail",
809
+ children: scenario.errorMessage
810
+ }
811
+ ) : null,
812
+ /* @__PURE__ */ jsx21(ReportSteps, { scenario }),
813
+ scenario.docEntries.length > 0 ? /* @__PURE__ */ jsx21("div", { "data-es-docs": true, className: "mt-3", children: /* @__PURE__ */ jsx21(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
814
+ /* @__PURE__ */ jsx21(ReportAttachments, { attachments: scenario.attachments }),
815
+ /* @__PURE__ */ jsx21(ReportTrace, { spans: scenario.otelSpans })
816
+ ] })
339
817
  ]
340
818
  }
341
819
  );
342
820
  }
343
821
 
344
822
  // src/components/ReportScenarioList.tsx
345
- import { Fragment as Fragment7, jsx as jsx17 } from "react/jsx-runtime";
823
+ import { Fragment as Fragment7, jsx as jsx22 } from "react/jsx-runtime";
346
824
  function ReportScenarioList({ feature }) {
347
- return /* @__PURE__ */ jsx17(Fragment7, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ jsx17(ReportScenario, { scenario }, scenario.id)) });
825
+ return /* @__PURE__ */ jsx22(Fragment7, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ jsx22(ReportScenario, { scenario }, scenario.id)) });
348
826
  }
349
827
 
350
828
  // src/components/ReportFeature.tsx
351
- import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
829
+ import { jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
352
830
  function ReportFeature({ feature }) {
353
831
  const titleId = `${feature.id}-title`;
354
- return /* @__PURE__ */ jsxs11(
832
+ const bodyId = `${feature.id}-body`;
833
+ const s = feature.summary;
834
+ const skipped = s.skipped + s.pending;
835
+ const collapse = useCollapse();
836
+ const collapsed = collapse?.isCollapsed(feature.id) ?? false;
837
+ return /* @__PURE__ */ jsxs14(
355
838
  "section",
356
839
  {
357
840
  id: feature.id,
358
- className: "es-feature",
841
+ "data-slot": "feature",
359
842
  "aria-labelledby": titleId,
843
+ className: "overflow-hidden rounded-xl border border-border bg-card shadow-sm",
360
844
  children: [
361
- /* @__PURE__ */ jsx18("h2", { id: titleId, className: "es-feature-title", children: feature.title }),
362
- /* @__PURE__ */ jsx18("p", { className: "es-feature-source", children: feature.sourceFile }),
363
- /* @__PURE__ */ jsx18(ReportSummaryView, { summary: feature.summary, className: "es-feature-summary" }),
364
- /* @__PURE__ */ jsx18(ReportScenarioList, { feature })
845
+ /* @__PURE__ */ jsxs14("div", { className: "flex items-center justify-between gap-4 border-b border-border bg-muted/40 px-4 py-3", children: [
846
+ /* @__PURE__ */ jsxs14("div", { className: "flex min-w-0 items-center gap-2", children: [
847
+ collapse ? /* @__PURE__ */ jsx23(
848
+ "button",
849
+ {
850
+ type: "button",
851
+ onClick: () => collapse.toggle(feature.id),
852
+ "aria-expanded": !collapsed,
853
+ "aria-controls": bodyId,
854
+ "aria-label": `Toggle ${feature.title}`,
855
+ className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
856
+ children: /* @__PURE__ */ jsx23("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
857
+ }
858
+ ) : null,
859
+ /* @__PURE__ */ jsxs14("div", { className: "min-w-0", children: [
860
+ /* @__PURE__ */ jsx23("h2", { id: titleId, className: "text-sm font-semibold tracking-tight text-foreground", children: feature.title }),
861
+ /* @__PURE__ */ jsx23("p", { className: "mt-0.5 truncate font-mono text-xs text-muted-foreground", children: feature.sourceFile })
862
+ ] })
863
+ ] }),
864
+ /* @__PURE__ */ jsxs14(
865
+ "div",
866
+ {
867
+ role: "img",
868
+ className: "flex shrink-0 items-center gap-2.5 font-mono text-xs font-medium",
869
+ "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
870
+ children: [
871
+ /* @__PURE__ */ jsxs14("span", { "aria-hidden": true, className: "text-pass", children: [
872
+ "\u2713",
873
+ s.passed
874
+ ] }),
875
+ /* @__PURE__ */ jsxs14("span", { "aria-hidden": true, className: "text-fail", children: [
876
+ "\u2717",
877
+ s.failed
878
+ ] }),
879
+ /* @__PURE__ */ jsxs14("span", { "aria-hidden": true, className: "text-skip", children: [
880
+ "\u25CB",
881
+ skipped
882
+ ] })
883
+ ]
884
+ }
885
+ )
886
+ ] }),
887
+ /* @__PURE__ */ jsx23("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-2 p-3", children: /* @__PURE__ */ jsx23(ReportScenarioList, { feature }) })
365
888
  ]
366
889
  }
367
890
  );
368
891
  }
369
892
 
370
893
  // src/components/ReportFeatureList.tsx
371
- import { Fragment as Fragment8, jsx as jsx19 } from "react/jsx-runtime";
894
+ import { Fragment as Fragment8, jsx as jsx24 } from "react/jsx-runtime";
372
895
  function ReportFeatureList() {
373
896
  const report = useReport();
374
- return /* @__PURE__ */ jsx19(Fragment8, { children: report.features.map((feature) => /* @__PURE__ */ jsx19(ReportFeature, { feature }, feature.id)) });
897
+ return /* @__PURE__ */ jsx24(Fragment8, { children: report.features.map((feature) => /* @__PURE__ */ jsx24(ReportFeature, { feature }, feature.id)) });
898
+ }
899
+
900
+ // src/components/ui/empty.tsx
901
+ import { cva as cva2 } from "class-variance-authority";
902
+ import { jsx as jsx25 } from "react/jsx-runtime";
903
+ function Empty({ className, ...props }) {
904
+ return /* @__PURE__ */ jsx25(
905
+ "div",
906
+ {
907
+ "data-slot": "empty",
908
+ className: cn(
909
+ "flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12",
910
+ className
911
+ ),
912
+ ...props
913
+ }
914
+ );
915
+ }
916
+ function EmptyHeader({ className, ...props }) {
917
+ return /* @__PURE__ */ jsx25(
918
+ "div",
919
+ {
920
+ "data-slot": "empty-header",
921
+ className: cn(
922
+ "flex max-w-sm flex-col items-center gap-2 text-center",
923
+ className
924
+ ),
925
+ ...props
926
+ }
927
+ );
928
+ }
929
+ var emptyMediaVariants = cva2(
930
+ "mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
931
+ {
932
+ variants: {
933
+ variant: {
934
+ default: "bg-transparent",
935
+ icon: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-6"
936
+ }
937
+ },
938
+ defaultVariants: {
939
+ variant: "default"
940
+ }
941
+ }
942
+ );
943
+ function EmptyTitle({ className, ...props }) {
944
+ return /* @__PURE__ */ jsx25(
945
+ "div",
946
+ {
947
+ "data-slot": "empty-title",
948
+ className: cn("text-lg font-medium tracking-tight", className),
949
+ ...props
950
+ }
951
+ );
375
952
  }
376
953
 
377
954
  // src/components/ReportEmpty.tsx
378
- import { jsx as jsx20 } from "react/jsx-runtime";
955
+ import { jsx as jsx26 } from "react/jsx-runtime";
379
956
  function ReportEmpty({ message }) {
380
- return /* @__PURE__ */ jsx20("section", { className: "es-empty", "aria-live": "polite", children: /* @__PURE__ */ jsx20("p", { children: message ?? "No scenarios in this report." }) });
957
+ return /* @__PURE__ */ jsx26(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ jsx26(EmptyHeader, { children: /* @__PURE__ */ jsx26(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
958
+ }
959
+
960
+ // src/components/ReportSummary.tsx
961
+ import { Fragment as Fragment9, jsx as jsx27, jsxs as jsxs15 } from "react/jsx-runtime";
962
+ function ReportSummary({ className }) {
963
+ const report = useReport();
964
+ return /* @__PURE__ */ jsx27(
965
+ ReportSummaryView,
966
+ {
967
+ summary: report.summary,
968
+ ...className !== void 0 && { className },
969
+ ariaLabel: "Run summary"
970
+ }
971
+ );
972
+ }
973
+ function ReportSummaryView({ summary, className, ariaLabel }) {
974
+ return /* @__PURE__ */ jsxs15("p", { className: cn("text-sm text-muted-foreground", className), "aria-label": ariaLabel, children: [
975
+ /* @__PURE__ */ jsxs15("span", { children: [
976
+ /* @__PURE__ */ jsx27("strong", { className: "font-semibold text-foreground", children: summary.total }),
977
+ " scenario",
978
+ summary.total === 1 ? "" : "s"
979
+ ] }),
980
+ " \xB7 ",
981
+ /* @__PURE__ */ jsxs15("span", { "data-status": "passed", className: "text-pass", children: [
982
+ summary.passed,
983
+ " passed"
984
+ ] }),
985
+ " \xB7 ",
986
+ /* @__PURE__ */ jsxs15("span", { "data-status": "failed", className: "text-fail", children: [
987
+ summary.failed,
988
+ " failed"
989
+ ] }),
990
+ summary.skipped > 0 ? /* @__PURE__ */ jsxs15(Fragment9, { children: [
991
+ " \xB7 ",
992
+ /* @__PURE__ */ jsxs15("span", { "data-status": "skipped", className: "text-skip", children: [
993
+ summary.skipped,
994
+ " skipped"
995
+ ] })
996
+ ] }) : null,
997
+ summary.pending > 0 ? /* @__PURE__ */ jsxs15(Fragment9, { children: [
998
+ " \xB7 ",
999
+ /* @__PURE__ */ jsxs15("span", { "data-status": "pending", className: "text-pend", children: [
1000
+ summary.pending,
1001
+ " pending"
1002
+ ] })
1003
+ ] }) : null
1004
+ ] });
1005
+ }
1006
+
1007
+ // src/components/ReportMeta.tsx
1008
+ import { formatDuration as formatDuration4 } from "executable-stories-core/utils/duration";
1009
+ import { jsx as jsx28, jsxs as jsxs16 } from "react/jsx-runtime";
1010
+ function ReportMeta() {
1011
+ const report = useReport();
1012
+ const items = [];
1013
+ if (report.startedAtMs) {
1014
+ items.push({ label: "Started", value: new Date(report.startedAtMs).toLocaleString() });
1015
+ }
1016
+ if (report.durationMs) {
1017
+ items.push({ label: "Duration", value: formatDuration4(report.durationMs) });
1018
+ }
1019
+ if (report.packageVersion) {
1020
+ items.push({ label: "Version", value: report.packageVersion });
1021
+ }
1022
+ if (report.gitSha) {
1023
+ items.push({ label: "Commit", value: report.gitSha.slice(0, 8) });
1024
+ }
1025
+ if (report.ci) {
1026
+ items.push({
1027
+ label: "CI",
1028
+ value: report.ci.url ? /* @__PURE__ */ jsxs16("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: "text-primary underline underline-offset-2", children: [
1029
+ report.ci.name,
1030
+ report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
1031
+ ] }) : `${report.ci.name}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
1032
+ });
1033
+ }
1034
+ if (items.length === 0) return null;
1035
+ return /* @__PURE__ */ jsx28("dl", { "aria-label": "Run metadata", className: "flex flex-wrap gap-x-6 gap-y-1 text-xs text-muted-foreground", children: items.map((item) => /* @__PURE__ */ jsxs16("div", { className: "flex gap-1.5", children: [
1036
+ /* @__PURE__ */ jsx28("dt", { className: "font-medium", children: item.label }),
1037
+ /* @__PURE__ */ jsx28("dd", { className: "font-mono text-foreground", children: item.value })
1038
+ ] }, item.label)) });
381
1039
  }
382
1040
 
383
1041
  // src/components/ReportSchemaError.tsx
384
- import { jsx as jsx21, jsxs as jsxs12 } from "react/jsx-runtime";
1042
+ import { jsx as jsx29, jsxs as jsxs17 } from "react/jsx-runtime";
385
1043
  function ReportSchemaError({ error }) {
386
- return /* @__PURE__ */ jsxs12("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
387
- /* @__PURE__ */ jsx21("p", { children: /* @__PURE__ */ jsx21("strong", { children: "Report could not be displayed." }) }),
388
- /* @__PURE__ */ jsx21("p", { children: error.message }),
389
- error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs12("p", { children: [
1044
+ return /* @__PURE__ */ jsxs17("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1045
+ /* @__PURE__ */ jsx29("p", { children: /* @__PURE__ */ jsx29("strong", { children: "Report could not be displayed." }) }),
1046
+ /* @__PURE__ */ jsx29("p", { children: error.message }),
1047
+ error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs17("p", { children: [
390
1048
  "The report bundle is newer than this version of ",
391
- /* @__PURE__ */ jsx21("code", { children: "executable-stories-react" }),
1049
+ /* @__PURE__ */ jsx29("code", { children: "executable-stories-react" }),
392
1050
  ". Upgrade the package, or regenerate the report with an older formatters CLI."
393
1051
  ] }) : null,
394
- error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs12("details", { children: [
395
- /* @__PURE__ */ jsxs12("summary", { children: [
1052
+ error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs17("details", { children: [
1053
+ /* @__PURE__ */ jsxs17("summary", { children: [
396
1054
  error.issues.length,
397
1055
  " validation issue",
398
1056
  error.issues.length === 1 ? "" : "s"
399
1057
  ] }),
400
- /* @__PURE__ */ jsx21("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1058
+ /* @__PURE__ */ jsx29("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
401
1059
  ] }) : null
402
1060
  ] });
403
1061
  }
404
1062
 
405
- // src/components/Report.tsx
406
- import { jsx as jsx22, jsxs as jsxs13 } from "react/jsx-runtime";
407
- function isResult(value) {
408
- return typeof value === "object" && value !== null && "ok" in value && typeof value.ok === "boolean";
1063
+ // src/components/ReportShell.tsx
1064
+ import { Fragment as Fragment10, jsx as jsx30, jsxs as jsxs18 } from "react/jsx-runtime";
1065
+ function ReportTitleBlock({ title }) {
1066
+ return /* @__PURE__ */ jsxs18(Fragment10, { children: [
1067
+ /* @__PURE__ */ jsx30("h1", { children: title ?? "Story Report" }),
1068
+ /* @__PURE__ */ jsx30(ReportSummary, {}),
1069
+ /* @__PURE__ */ jsx30(ReportMeta, {})
1070
+ ] });
1071
+ }
1072
+ function ReportErrorShell({
1073
+ error,
1074
+ className,
1075
+ title,
1076
+ dataTheme
1077
+ }) {
1078
+ return /* @__PURE__ */ jsx30("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ jsx30(ReportSchemaError, { error }) });
409
1079
  }
1080
+
1081
+ // src/components/Report.tsx
1082
+ import { jsx as jsx31, jsxs as jsxs19 } from "react/jsx-runtime";
410
1083
  function Report(props) {
411
1084
  const { report, customRenderers, renderers, className, title, dataTheme } = props;
412
- if (isResult(report)) {
413
- if (!report.ok) {
414
- return /* @__PURE__ */ jsx22(
415
- "main",
1085
+ const result = unwrapReport(report);
1086
+ if (!result.ok) {
1087
+ return /* @__PURE__ */ jsx31(ReportErrorShell, { error: result.error, className, title, dataTheme });
1088
+ }
1089
+ const data = result.data;
1090
+ const hasContent = data.features.length > 0;
1091
+ return /* @__PURE__ */ jsx31(ReportRoot, { report: data, customRenderers, renderers, children: /* @__PURE__ */ jsxs19("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: [
1092
+ /* @__PURE__ */ jsx31("header", { className: "es-report-header", children: /* @__PURE__ */ jsx31(ReportTitleBlock, { title }) }),
1093
+ hasContent ? /* @__PURE__ */ jsx31(ReportFeatureList, {}) : /* @__PURE__ */ jsx31(ReportEmpty, {})
1094
+ ] }) });
1095
+ }
1096
+
1097
+ // src/components/doc/MermaidView.tsx
1098
+ import { useEffect, useRef, useState as useState2 } from "react";
1099
+ import { jsx as jsx32, jsxs as jsxs20 } from "react/jsx-runtime";
1100
+ function prefersDark(el) {
1101
+ const themed = el?.closest("[data-theme]");
1102
+ if (themed?.dataset.theme === "dark") return true;
1103
+ if (themed?.dataset.theme === "light") return false;
1104
+ return typeof window !== "undefined" && !!window.matchMedia?.("(prefers-color-scheme: dark)").matches;
1105
+ }
1106
+ function MermaidView({ entry, load }) {
1107
+ const ref = useRef(null);
1108
+ const [svg, setSvg] = useState2(null);
1109
+ useEffect(() => {
1110
+ let cancelled = false;
1111
+ const draw = async () => {
1112
+ try {
1113
+ const mermaid = await load();
1114
+ mermaid.initialize({
1115
+ startOnLoad: false,
1116
+ securityLevel: "strict",
1117
+ theme: prefersDark(ref.current) ? "dark" : "default"
1118
+ });
1119
+ const id = `es-mermaid-${Math.random().toString(36).slice(2)}`;
1120
+ const { svg: rendered } = await mermaid.render(id, entry.code);
1121
+ if (!cancelled) setSvg(rendered);
1122
+ } catch {
1123
+ if (!cancelled) setSvg(null);
1124
+ }
1125
+ };
1126
+ void draw();
1127
+ const themed = ref.current?.closest("[data-theme]");
1128
+ const observer = themed ? new MutationObserver(() => void draw()) : void 0;
1129
+ observer?.observe(themed, { attributes: true, attributeFilter: ["data-theme"] });
1130
+ return () => {
1131
+ cancelled = true;
1132
+ observer?.disconnect();
1133
+ };
1134
+ }, [entry.code, load]);
1135
+ if (svg) {
1136
+ return /* @__PURE__ */ jsxs20("figure", { ref, className: "my-3", children: [
1137
+ entry.title ? /* @__PURE__ */ jsx32("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
1138
+ /* @__PURE__ */ jsx32(
1139
+ "div",
416
1140
  {
417
- className: ["es-report", className].filter(Boolean).join(" "),
418
- "aria-label": title ?? "Test report",
419
- "data-theme": dataTheme,
420
- children: /* @__PURE__ */ jsx22(ReportSchemaError, { error: report.error })
1141
+ role: "img",
1142
+ "aria-label": entry.title ?? "Mermaid diagram",
1143
+ className: "overflow-x-auto rounded-md border border-border bg-card p-3 [&_svg]:mx-auto [&_svg]:h-auto [&_svg]:max-w-full",
1144
+ dangerouslySetInnerHTML: { __html: svg }
421
1145
  }
422
- );
423
- }
424
- return /* @__PURE__ */ jsx22(
425
- ReportView,
426
- {
427
- report: report.data,
428
- customRenderers,
429
- renderers,
430
- className,
431
- title,
432
- dataTheme
433
- }
434
- );
1146
+ )
1147
+ ] });
435
1148
  }
436
- return /* @__PURE__ */ jsx22(
437
- ReportView,
1149
+ return /* @__PURE__ */ jsx32(MermaidSource, { entry, ref });
1150
+ }
1151
+
1152
+ // src/components/doc/MermaidDiagram.tsx
1153
+ import { jsx as jsx33 } from "react/jsx-runtime";
1154
+ var mermaidLoader;
1155
+ function loadMermaid() {
1156
+ mermaidLoader ??= import("mermaid").then((m) => m.default);
1157
+ return mermaidLoader;
1158
+ }
1159
+ function MermaidDiagram({ entry, load = loadMermaid }) {
1160
+ return /* @__PURE__ */ jsx33(MermaidView, { entry, load });
1161
+ }
1162
+
1163
+ // src/components/ScenarioExplorer.tsx
1164
+ import { useState as useState3 } from "react";
1165
+
1166
+ // src/components/ui/command.tsx
1167
+ import "react";
1168
+ import { Command as CommandPrimitive } from "cmdk";
1169
+ import { SearchIcon } from "lucide-react";
1170
+ import { jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
1171
+ function Command({
1172
+ className,
1173
+ ...props
1174
+ }) {
1175
+ return /* @__PURE__ */ jsx34(
1176
+ CommandPrimitive,
438
1177
  {
439
- report,
440
- customRenderers,
441
- renderers,
442
- className,
443
- title,
444
- dataTheme
1178
+ "data-slot": "command",
1179
+ className: cn(
1180
+ "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
1181
+ className
1182
+ ),
1183
+ ...props
445
1184
  }
446
1185
  );
447
1186
  }
448
- function ReportView({
449
- report,
450
- customRenderers,
451
- renderers,
1187
+ function CommandInput({
452
1188
  className,
453
- title,
454
- dataTheme
1189
+ ...props
455
1190
  }) {
456
- const hasContent = report.features.length > 0;
457
- return /* @__PURE__ */ jsx22(ReportRoot, { report, customRenderers, renderers, children: /* @__PURE__ */ jsxs13(
458
- "main",
1191
+ return /* @__PURE__ */ jsxs21(
1192
+ "div",
459
1193
  {
460
- className: ["es-report", className].filter(Boolean).join(" "),
461
- "aria-label": title ?? "Test report",
462
- "data-theme": dataTheme,
1194
+ "data-slot": "command-input-wrapper",
1195
+ className: "flex h-9 items-center gap-2 border-b px-3",
463
1196
  children: [
464
- /* @__PURE__ */ jsxs13("header", { className: "es-report-header", children: [
465
- /* @__PURE__ */ jsx22("h1", { children: title ?? "Story Report" }),
466
- /* @__PURE__ */ jsx22(ReportSummary, {})
467
- ] }),
468
- hasContent ? /* @__PURE__ */ jsx22(ReportFeatureList, {}) : /* @__PURE__ */ jsx22(ReportEmpty, {})
1197
+ /* @__PURE__ */ jsx34(SearchIcon, { className: "size-4 shrink-0 opacity-50" }),
1198
+ /* @__PURE__ */ jsx34(
1199
+ CommandPrimitive.Input,
1200
+ {
1201
+ "data-slot": "command-input",
1202
+ className: cn(
1203
+ "flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
1204
+ className
1205
+ ),
1206
+ ...props
1207
+ }
1208
+ )
469
1209
  ]
470
1210
  }
471
- ) });
1211
+ );
1212
+ }
1213
+ function CommandList({
1214
+ className,
1215
+ ...props
1216
+ }) {
1217
+ return /* @__PURE__ */ jsx34(
1218
+ CommandPrimitive.List,
1219
+ {
1220
+ "data-slot": "command-list",
1221
+ className: cn(
1222
+ "max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
1223
+ className
1224
+ ),
1225
+ ...props
1226
+ }
1227
+ );
1228
+ }
1229
+ function CommandEmpty({
1230
+ ...props
1231
+ }) {
1232
+ return /* @__PURE__ */ jsx34(
1233
+ CommandPrimitive.Empty,
1234
+ {
1235
+ "data-slot": "command-empty",
1236
+ className: "py-6 text-center text-sm",
1237
+ ...props
1238
+ }
1239
+ );
1240
+ }
1241
+ function CommandGroup({
1242
+ className,
1243
+ ...props
1244
+ }) {
1245
+ return /* @__PURE__ */ jsx34(
1246
+ CommandPrimitive.Group,
1247
+ {
1248
+ "data-slot": "command-group",
1249
+ className: cn(
1250
+ "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
1251
+ className
1252
+ ),
1253
+ ...props
1254
+ }
1255
+ );
1256
+ }
1257
+ function CommandItem({
1258
+ className,
1259
+ ...props
1260
+ }) {
1261
+ return /* @__PURE__ */ jsx34(
1262
+ CommandPrimitive.Item,
1263
+ {
1264
+ "data-slot": "command-item",
1265
+ className: cn(
1266
+ "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
1267
+ className
1268
+ ),
1269
+ ...props
1270
+ }
1271
+ );
472
1272
  }
473
1273
 
474
- // src/result.ts
475
- var ok = (data) => ({ ok: true, data });
476
- var err = (error) => ({ ok: false, error });
1274
+ // src/components/ScenarioExplorer.tsx
1275
+ import { jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
1276
+ var STATUS_FILTERS = [
1277
+ { value: "all", label: "All" },
1278
+ { value: "passed", label: "Passed" },
1279
+ { value: "failed", label: "Failed" },
1280
+ { value: "skipped", label: "Skipped" },
1281
+ { value: "pending", label: "Pending" }
1282
+ ];
1283
+ function ScenarioExplorer({ scenarios, summary }) {
1284
+ const [status, setStatus] = useState3("all");
1285
+ const visible = status === "all" ? scenarios : scenarios.filter((s) => s.status === status);
1286
+ return /* @__PURE__ */ jsxs22("div", { className: "font-sans text-foreground", children: [
1287
+ summary ? /* @__PURE__ */ jsxs22("p", { className: "mb-3 text-sm text-muted-foreground", children: [
1288
+ summary.total,
1289
+ " scenarios \xB7 ",
1290
+ /* @__PURE__ */ jsxs22("span", { className: "text-pass", children: [
1291
+ summary.passed,
1292
+ " passed"
1293
+ ] }),
1294
+ " \xB7",
1295
+ " ",
1296
+ /* @__PURE__ */ jsxs22("span", { className: "text-fail", children: [
1297
+ summary.failed,
1298
+ " failed"
1299
+ ] })
1300
+ ] }) : null,
1301
+ /* @__PURE__ */ jsx35("div", { className: "mb-2 flex flex-wrap gap-1.5", role: "group", "aria-label": "Filter by status", children: STATUS_FILTERS.map((f) => /* @__PURE__ */ jsx35(
1302
+ "button",
1303
+ {
1304
+ type: "button",
1305
+ "aria-pressed": status === f.value,
1306
+ onClick: () => setStatus(f.value),
1307
+ className: cn(
1308
+ "rounded-full border px-2.5 py-1 text-xs font-medium transition-colors",
1309
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
1310
+ status === f.value ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
1311
+ ),
1312
+ children: f.label
1313
+ },
1314
+ f.value
1315
+ )) }),
1316
+ /* @__PURE__ */ jsxs22(
1317
+ Command,
1318
+ {
1319
+ className: "rounded-lg border border-border bg-card",
1320
+ filter: (value, search) => value.toLowerCase().includes(search.toLowerCase()) ? 1 : 0,
1321
+ children: [
1322
+ /* @__PURE__ */ jsx35(CommandInput, { placeholder: "Search scenarios\u2026" }),
1323
+ /* @__PURE__ */ jsxs22(CommandList, { className: "max-h-none", children: [
1324
+ /* @__PURE__ */ jsx35(CommandEmpty, { children: "No scenarios match your filters." }),
1325
+ /* @__PURE__ */ jsx35(CommandGroup, { children: visible.map((s) => /* @__PURE__ */ jsx35(
1326
+ CommandItem,
1327
+ {
1328
+ value: `${s.title} ${s.feature} ${s.tags.join(" ")}`,
1329
+ asChild: true,
1330
+ className: "cursor-pointer",
1331
+ children: /* @__PURE__ */ jsxs22("a", { href: s.href, className: "flex items-center gap-3", children: [
1332
+ /* @__PURE__ */ jsx35(Badge, { variant: s.status, children: s.status }),
1333
+ /* @__PURE__ */ jsx35("span", { className: "min-w-0 flex-1 truncate font-medium", children: s.title }),
1334
+ s.tags.slice(0, 3).map((t) => /* @__PURE__ */ jsx35(Badge, { variant: "tag", className: "hidden sm:inline-flex", children: t }, t)),
1335
+ /* @__PURE__ */ jsx35("code", { className: "hidden font-mono text-xs text-muted-foreground md:inline", children: s.feature })
1336
+ ] })
1337
+ },
1338
+ s.id
1339
+ )) })
1340
+ ] })
1341
+ ]
1342
+ }
1343
+ )
1344
+ ] });
1345
+ }
477
1346
 
478
1347
  // src/schema/story-report.schema.ts
479
1348
  import { z } from "zod";
480
1349
 
481
- // ../executable-stories-formatters/schemas/story-report-v1.json
1350
+ // ../executable-stories-core/schemas/story-report-v1.json
482
1351
  var story_report_v1_default = {
483
1352
  $schema: "https://json-schema.org/draft/2020-12/schema",
484
1353
  $id: "https://executable-stories.dev/schemas/story-report-v1.schema.json",
@@ -541,25 +1410,61 @@ var story_report_v1_default = {
541
1410
  },
542
1411
  features: {
543
1412
  type: "array",
544
- items: { $ref: "#/$defs/Feature" },
1413
+ items: {
1414
+ $ref: "#/$defs/Feature"
1415
+ },
545
1416
  description: "Features grouped by sourceFile, sorted by title."
546
1417
  }
547
1418
  },
548
- required: ["schemaVersion", "runId", "startedAtMs", "finishedAtMs", "durationMs", "projectRoot", "summary", "features"],
1419
+ required: [
1420
+ "schemaVersion",
1421
+ "runId",
1422
+ "startedAtMs",
1423
+ "finishedAtMs",
1424
+ "durationMs",
1425
+ "projectRoot",
1426
+ "summary",
1427
+ "features"
1428
+ ],
549
1429
  additionalProperties: false
550
1430
  },
551
1431
  Summary: {
552
1432
  type: "object",
553
1433
  description: "Counts by status. Sums equal 'total'.",
554
1434
  properties: {
555
- total: { type: "integer", minimum: 0 },
556
- passed: { type: "integer", minimum: 0 },
557
- failed: { type: "integer", minimum: 0 },
558
- skipped: { type: "integer", minimum: 0 },
559
- pending: { type: "integer", minimum: 0 },
560
- durationMs: { type: "number", minimum: 0 }
1435
+ total: {
1436
+ type: "integer",
1437
+ minimum: 0
1438
+ },
1439
+ passed: {
1440
+ type: "integer",
1441
+ minimum: 0
1442
+ },
1443
+ failed: {
1444
+ type: "integer",
1445
+ minimum: 0
1446
+ },
1447
+ skipped: {
1448
+ type: "integer",
1449
+ minimum: 0
1450
+ },
1451
+ pending: {
1452
+ type: "integer",
1453
+ minimum: 0
1454
+ },
1455
+ durationMs: {
1456
+ type: "number",
1457
+ minimum: 0
1458
+ }
561
1459
  },
562
- required: ["total", "passed", "failed", "skipped", "pending", "durationMs"],
1460
+ required: [
1461
+ "total",
1462
+ "passed",
1463
+ "failed",
1464
+ "skipped",
1465
+ "pending",
1466
+ "durationMs"
1467
+ ],
563
1468
  additionalProperties: false
564
1469
  },
565
1470
  Feature: {
@@ -581,14 +1486,24 @@ var story_report_v1_default = {
581
1486
  minLength: 1,
582
1487
  description: "Source path, relative to projectRoot when possible."
583
1488
  },
584
- summary: { $ref: "#/$defs/Summary" },
1489
+ summary: {
1490
+ $ref: "#/$defs/Summary"
1491
+ },
585
1492
  scenarios: {
586
1493
  type: "array",
587
- items: { $ref: "#/$defs/Scenario" },
1494
+ items: {
1495
+ $ref: "#/$defs/Scenario"
1496
+ },
588
1497
  description: "Scenarios in this feature, in declaration order."
589
1498
  }
590
1499
  },
591
- required: ["id", "title", "sourceFile", "summary", "scenarios"],
1500
+ required: [
1501
+ "id",
1502
+ "title",
1503
+ "sourceFile",
1504
+ "summary",
1505
+ "scenarios"
1506
+ ],
592
1507
  additionalProperties: false
593
1508
  },
594
1509
  Scenario: {
@@ -600,346 +1515,777 @@ var story_report_v1_default = {
600
1515
  minLength: 1,
601
1516
  description: "Stable identifier: '<feature.id>--<slug-of-title>'. Suitable for use as a deep-link anchor."
602
1517
  },
603
- title: { type: "string", minLength: 1 },
604
- status: { $ref: "#/$defs/TestStatus" },
605
- durationMs: { type: "number", minimum: 0 },
1518
+ title: {
1519
+ type: "string",
1520
+ minLength: 1
1521
+ },
1522
+ status: {
1523
+ $ref: "#/$defs/TestStatus"
1524
+ },
1525
+ durationMs: {
1526
+ type: "number",
1527
+ minimum: 0
1528
+ },
606
1529
  tags: {
607
1530
  type: "array",
608
- items: { type: "string" },
1531
+ items: {
1532
+ type: "string"
1533
+ },
609
1534
  description: "Normalized tags, deduplicated, lowercased."
610
1535
  },
611
1536
  tickets: {
612
1537
  type: "array",
613
- items: { $ref: "#/$defs/Ticket" }
1538
+ items: {
1539
+ $ref: "#/$defs/Ticket"
1540
+ }
614
1541
  },
615
1542
  covers: {
616
1543
  type: "array",
617
- items: { type: "string" },
1544
+ items: {
1545
+ type: "string"
1546
+ },
618
1547
  description: "Product-code paths/globs this scenario exercises."
619
1548
  },
620
- sourceLine: { type: "integer", minimum: 1 },
621
- errorMessage: { type: "string" },
622
- errorStack: { type: "string" },
623
- retry: { type: "integer", minimum: 0 },
624
- retries: { type: "integer", minimum: 0 },
1549
+ sourceLine: {
1550
+ type: "integer",
1551
+ minimum: 1
1552
+ },
1553
+ errorMessage: {
1554
+ type: "string"
1555
+ },
1556
+ errorStack: {
1557
+ type: "string"
1558
+ },
1559
+ retry: {
1560
+ type: "integer",
1561
+ minimum: 0
1562
+ },
1563
+ retries: {
1564
+ type: "integer",
1565
+ minimum: 0
1566
+ },
625
1567
  docEntries: {
626
1568
  type: "array",
627
- items: { $ref: "#/$defs/DocEntry" },
1569
+ items: {
1570
+ $ref: "#/$defs/DocEntry"
1571
+ },
628
1572
  description: "Story-level doc entries (rendered before steps)."
629
1573
  },
630
1574
  steps: {
631
1575
  type: "array",
632
- items: { $ref: "#/$defs/Step" }
1576
+ items: {
1577
+ $ref: "#/$defs/Step"
1578
+ }
633
1579
  },
634
1580
  attachments: {
635
1581
  type: "array",
636
- items: { $ref: "#/$defs/Attachment" }
1582
+ items: {
1583
+ $ref: "#/$defs/Attachment"
1584
+ }
1585
+ },
1586
+ otelSpans: {
1587
+ type: "array",
1588
+ items: {
1589
+ $ref: "#/$defs/OtelSpan"
1590
+ }
637
1591
  }
638
1592
  },
639
- required: ["id", "title", "status", "durationMs", "tags", "retry", "retries", "docEntries", "steps", "attachments"],
1593
+ required: [
1594
+ "id",
1595
+ "title",
1596
+ "status",
1597
+ "durationMs",
1598
+ "tags",
1599
+ "retry",
1600
+ "retries",
1601
+ "docEntries",
1602
+ "steps",
1603
+ "attachments"
1604
+ ],
640
1605
  additionalProperties: false
641
1606
  },
642
1607
  Step: {
643
1608
  type: "object",
644
1609
  description: "A single BDD step.",
645
1610
  properties: {
646
- id: { type: "string", minLength: 1 },
647
- index: { type: "integer", minimum: 0 },
648
- keyword: { $ref: "#/$defs/StepKeyword" },
649
- text: { type: "string" },
650
- status: { $ref: "#/$defs/TestStatus" },
651
- durationMs: { type: "number", minimum: 0 },
652
- errorMessage: { type: "string" },
653
- mode: { $ref: "#/$defs/StepMode" },
1611
+ id: {
1612
+ type: "string",
1613
+ minLength: 1
1614
+ },
1615
+ index: {
1616
+ type: "integer",
1617
+ minimum: 0
1618
+ },
1619
+ keyword: {
1620
+ $ref: "#/$defs/StepKeyword"
1621
+ },
1622
+ text: {
1623
+ type: "string"
1624
+ },
1625
+ status: {
1626
+ $ref: "#/$defs/TestStatus"
1627
+ },
1628
+ durationMs: {
1629
+ type: "number",
1630
+ minimum: 0
1631
+ },
1632
+ errorMessage: {
1633
+ type: "string"
1634
+ },
1635
+ mode: {
1636
+ $ref: "#/$defs/StepMode"
1637
+ },
654
1638
  docEntries: {
655
1639
  type: "array",
656
- items: { $ref: "#/$defs/DocEntry" },
1640
+ items: {
1641
+ $ref: "#/$defs/DocEntry"
1642
+ },
657
1643
  description: "Doc entries attached to this step."
658
1644
  }
659
1645
  },
660
- required: ["id", "index", "keyword", "text", "status", "durationMs", "docEntries"],
1646
+ required: [
1647
+ "id",
1648
+ "index",
1649
+ "keyword",
1650
+ "text",
1651
+ "status",
1652
+ "durationMs",
1653
+ "docEntries"
1654
+ ],
661
1655
  additionalProperties: false
662
1656
  },
663
1657
  StepKeyword: {
664
1658
  type: "string",
665
- enum: ["Given", "When", "Then", "And", "But"]
1659
+ enum: [
1660
+ "Given",
1661
+ "When",
1662
+ "Then",
1663
+ "And",
1664
+ "But"
1665
+ ]
666
1666
  },
667
1667
  StepMode: {
668
1668
  type: "string",
669
- enum: ["normal", "skip", "only", "todo", "fails", "concurrent"]
1669
+ enum: [
1670
+ "normal",
1671
+ "skip",
1672
+ "only",
1673
+ "todo",
1674
+ "fails",
1675
+ "concurrent"
1676
+ ]
670
1677
  },
671
1678
  TestStatus: {
672
1679
  type: "string",
673
- enum: ["passed", "failed", "skipped", "pending"]
1680
+ enum: [
1681
+ "passed",
1682
+ "failed",
1683
+ "skipped",
1684
+ "pending"
1685
+ ]
674
1686
  },
675
1687
  Ticket: {
676
1688
  type: "object",
677
1689
  properties: {
678
- id: { type: "string", minLength: 1 },
679
- url: { type: "string" }
1690
+ id: {
1691
+ type: "string",
1692
+ minLength: 1
1693
+ },
1694
+ url: {
1695
+ type: "string"
1696
+ }
680
1697
  },
681
- required: ["id"],
1698
+ required: [
1699
+ "id"
1700
+ ],
682
1701
  additionalProperties: false
683
1702
  },
684
1703
  Attachment: {
685
1704
  type: "object",
686
1705
  properties: {
687
- name: { type: "string" },
688
- mediaType: { type: "string", minLength: 1 },
689
- body: { type: "string" },
1706
+ name: {
1707
+ type: "string"
1708
+ },
1709
+ mediaType: {
1710
+ type: "string",
1711
+ minLength: 1
1712
+ },
1713
+ body: {
1714
+ type: "string"
1715
+ },
690
1716
  contentEncoding: {
691
1717
  type: "string",
692
- enum: ["BASE64", "IDENTITY"]
1718
+ enum: [
1719
+ "BASE64",
1720
+ "IDENTITY"
1721
+ ]
693
1722
  }
694
1723
  },
695
- required: ["name", "mediaType", "body", "contentEncoding"],
1724
+ required: [
1725
+ "name",
1726
+ "mediaType",
1727
+ "body",
1728
+ "contentEncoding"
1729
+ ],
696
1730
  additionalProperties: false
697
1731
  },
698
1732
  CIInfo: {
699
1733
  type: "object",
700
1734
  properties: {
701
- name: { type: "string" },
702
- url: { type: "string" },
703
- buildNumber: { type: "string" },
704
- branch: { type: "string" },
705
- commitSha: { type: "string" },
706
- prNumber: { type: "string" }
1735
+ name: {
1736
+ type: "string"
1737
+ },
1738
+ url: {
1739
+ type: "string"
1740
+ },
1741
+ buildNumber: {
1742
+ type: "string"
1743
+ },
1744
+ branch: {
1745
+ type: "string"
1746
+ },
1747
+ commitSha: {
1748
+ type: "string"
1749
+ },
1750
+ prNumber: {
1751
+ type: "string"
1752
+ }
707
1753
  },
708
- required: ["name"],
1754
+ required: [
1755
+ "name"
1756
+ ],
709
1757
  additionalProperties: false
710
1758
  },
711
1759
  CoverageSummary: {
712
1760
  type: "object",
713
1761
  properties: {
714
- linesPct: { type: "number", minimum: 0, maximum: 100 },
715
- branchesPct: { type: "number", minimum: 0, maximum: 100 },
716
- functionsPct: { type: "number", minimum: 0, maximum: 100 },
717
- statementsPct: { type: "number", minimum: 0, maximum: 100 }
1762
+ linesPct: {
1763
+ type: "number",
1764
+ minimum: 0,
1765
+ maximum: 100
1766
+ },
1767
+ branchesPct: {
1768
+ type: "number",
1769
+ minimum: 0,
1770
+ maximum: 100
1771
+ },
1772
+ functionsPct: {
1773
+ type: "number",
1774
+ minimum: 0,
1775
+ maximum: 100
1776
+ },
1777
+ statementsPct: {
1778
+ type: "number",
1779
+ minimum: 0,
1780
+ maximum: 100
1781
+ }
718
1782
  },
719
1783
  additionalProperties: false
720
1784
  },
721
1785
  DocPhase: {
722
1786
  type: "string",
723
- enum: ["static", "runtime"]
1787
+ enum: [
1788
+ "static",
1789
+ "runtime"
1790
+ ]
724
1791
  },
725
1792
  DocEntry: {
726
1793
  oneOf: [
727
- { $ref: "#/$defs/DocNote" },
728
- { $ref: "#/$defs/DocTag" },
729
- { $ref: "#/$defs/DocKv" },
730
- { $ref: "#/$defs/DocCode" },
731
- { $ref: "#/$defs/DocTable" },
732
- { $ref: "#/$defs/DocLink" },
733
- { $ref: "#/$defs/DocSection" },
734
- { $ref: "#/$defs/DocMermaid" },
735
- { $ref: "#/$defs/DocScreenshot" },
736
- { $ref: "#/$defs/DocVideo" },
737
- { $ref: "#/$defs/DocHtml" },
738
- { $ref: "#/$defs/DocCustom" }
1794
+ {
1795
+ $ref: "#/$defs/DocNote"
1796
+ },
1797
+ {
1798
+ $ref: "#/$defs/DocTag"
1799
+ },
1800
+ {
1801
+ $ref: "#/$defs/DocKv"
1802
+ },
1803
+ {
1804
+ $ref: "#/$defs/DocCode"
1805
+ },
1806
+ {
1807
+ $ref: "#/$defs/DocTable"
1808
+ },
1809
+ {
1810
+ $ref: "#/$defs/DocLink"
1811
+ },
1812
+ {
1813
+ $ref: "#/$defs/DocSection"
1814
+ },
1815
+ {
1816
+ $ref: "#/$defs/DocMermaid"
1817
+ },
1818
+ {
1819
+ $ref: "#/$defs/DocScreenshot"
1820
+ },
1821
+ {
1822
+ $ref: "#/$defs/DocVideo"
1823
+ },
1824
+ {
1825
+ $ref: "#/$defs/DocHtml"
1826
+ },
1827
+ {
1828
+ $ref: "#/$defs/DocCustom"
1829
+ }
739
1830
  ]
740
1831
  },
741
1832
  DocNote: {
742
1833
  type: "object",
743
1834
  properties: {
744
- kind: { const: "note" },
745
- text: { type: "string" },
746
- phase: { $ref: "#/$defs/DocPhase" },
1835
+ kind: {
1836
+ const: "note"
1837
+ },
1838
+ text: {
1839
+ type: "string"
1840
+ },
1841
+ phase: {
1842
+ $ref: "#/$defs/DocPhase"
1843
+ },
747
1844
  children: {
748
1845
  type: "array",
749
- items: { $ref: "#/$defs/DocEntry" }
1846
+ items: {
1847
+ $ref: "#/$defs/DocEntry"
1848
+ }
750
1849
  }
751
1850
  },
752
- required: ["kind", "text", "phase"],
1851
+ required: [
1852
+ "kind",
1853
+ "text",
1854
+ "phase"
1855
+ ],
753
1856
  additionalProperties: false
754
1857
  },
755
1858
  DocTag: {
756
1859
  type: "object",
757
1860
  properties: {
758
- kind: { const: "tag" },
759
- names: { type: "array", items: { type: "string" } },
760
- phase: { $ref: "#/$defs/DocPhase" },
1861
+ kind: {
1862
+ const: "tag"
1863
+ },
1864
+ names: {
1865
+ type: "array",
1866
+ items: {
1867
+ type: "string"
1868
+ }
1869
+ },
1870
+ phase: {
1871
+ $ref: "#/$defs/DocPhase"
1872
+ },
761
1873
  children: {
762
1874
  type: "array",
763
- items: { $ref: "#/$defs/DocEntry" }
1875
+ items: {
1876
+ $ref: "#/$defs/DocEntry"
1877
+ }
764
1878
  }
765
1879
  },
766
- required: ["kind", "names", "phase"],
1880
+ required: [
1881
+ "kind",
1882
+ "names",
1883
+ "phase"
1884
+ ],
767
1885
  additionalProperties: false
768
1886
  },
769
1887
  DocKv: {
770
1888
  type: "object",
771
1889
  properties: {
772
- kind: { const: "kv" },
773
- label: { type: "string" },
1890
+ kind: {
1891
+ const: "kv"
1892
+ },
1893
+ label: {
1894
+ type: "string"
1895
+ },
774
1896
  value: {},
775
- phase: { $ref: "#/$defs/DocPhase" },
1897
+ phase: {
1898
+ $ref: "#/$defs/DocPhase"
1899
+ },
776
1900
  children: {
777
1901
  type: "array",
778
- items: { $ref: "#/$defs/DocEntry" }
1902
+ items: {
1903
+ $ref: "#/$defs/DocEntry"
1904
+ }
779
1905
  }
780
1906
  },
781
- required: ["kind", "label", "value", "phase"],
1907
+ required: [
1908
+ "kind",
1909
+ "label",
1910
+ "value",
1911
+ "phase"
1912
+ ],
782
1913
  additionalProperties: false
783
1914
  },
784
1915
  DocCode: {
785
1916
  type: "object",
786
1917
  properties: {
787
- kind: { const: "code" },
788
- label: { type: "string" },
789
- content: { type: "string" },
790
- lang: { type: "string" },
791
- phase: { $ref: "#/$defs/DocPhase" },
1918
+ kind: {
1919
+ const: "code"
1920
+ },
1921
+ label: {
1922
+ type: "string"
1923
+ },
1924
+ content: {
1925
+ type: "string"
1926
+ },
1927
+ lang: {
1928
+ type: "string"
1929
+ },
1930
+ phase: {
1931
+ $ref: "#/$defs/DocPhase"
1932
+ },
792
1933
  children: {
793
1934
  type: "array",
794
- items: { $ref: "#/$defs/DocEntry" }
1935
+ items: {
1936
+ $ref: "#/$defs/DocEntry"
1937
+ }
795
1938
  }
796
1939
  },
797
- required: ["kind", "label", "content", "phase"],
1940
+ required: [
1941
+ "kind",
1942
+ "label",
1943
+ "content",
1944
+ "phase"
1945
+ ],
798
1946
  additionalProperties: false
799
1947
  },
800
1948
  DocTable: {
801
1949
  type: "object",
802
1950
  properties: {
803
- kind: { const: "table" },
804
- label: { type: "string" },
805
- columns: { type: "array", items: { type: "string" } },
1951
+ kind: {
1952
+ const: "table"
1953
+ },
1954
+ label: {
1955
+ type: "string"
1956
+ },
1957
+ columns: {
1958
+ type: "array",
1959
+ items: {
1960
+ type: "string"
1961
+ }
1962
+ },
806
1963
  rows: {
807
1964
  type: "array",
808
1965
  items: {
809
1966
  type: "array",
810
- items: { type: "string" }
1967
+ items: {
1968
+ type: "string"
1969
+ }
811
1970
  }
812
1971
  },
813
- phase: { $ref: "#/$defs/DocPhase" },
1972
+ phase: {
1973
+ $ref: "#/$defs/DocPhase"
1974
+ },
814
1975
  children: {
815
1976
  type: "array",
816
- items: { $ref: "#/$defs/DocEntry" }
1977
+ items: {
1978
+ $ref: "#/$defs/DocEntry"
1979
+ }
817
1980
  }
818
1981
  },
819
- required: ["kind", "label", "columns", "rows", "phase"],
1982
+ required: [
1983
+ "kind",
1984
+ "label",
1985
+ "columns",
1986
+ "rows",
1987
+ "phase"
1988
+ ],
820
1989
  additionalProperties: false
821
1990
  },
822
1991
  DocLink: {
823
1992
  type: "object",
824
1993
  properties: {
825
- kind: { const: "link" },
826
- label: { type: "string" },
827
- url: { type: "string" },
828
- phase: { $ref: "#/$defs/DocPhase" },
1994
+ kind: {
1995
+ const: "link"
1996
+ },
1997
+ label: {
1998
+ type: "string"
1999
+ },
2000
+ url: {
2001
+ type: "string"
2002
+ },
2003
+ phase: {
2004
+ $ref: "#/$defs/DocPhase"
2005
+ },
829
2006
  children: {
830
2007
  type: "array",
831
- items: { $ref: "#/$defs/DocEntry" }
2008
+ items: {
2009
+ $ref: "#/$defs/DocEntry"
2010
+ }
832
2011
  }
833
2012
  },
834
- required: ["kind", "label", "url", "phase"],
2013
+ required: [
2014
+ "kind",
2015
+ "label",
2016
+ "url",
2017
+ "phase"
2018
+ ],
835
2019
  additionalProperties: false
836
2020
  },
837
2021
  DocSection: {
838
2022
  type: "object",
839
2023
  properties: {
840
- kind: { const: "section" },
841
- title: { type: "string" },
842
- markdown: { type: "string" },
843
- phase: { $ref: "#/$defs/DocPhase" },
2024
+ kind: {
2025
+ const: "section"
2026
+ },
2027
+ title: {
2028
+ type: "string"
2029
+ },
2030
+ markdown: {
2031
+ type: "string"
2032
+ },
2033
+ phase: {
2034
+ $ref: "#/$defs/DocPhase"
2035
+ },
844
2036
  children: {
845
2037
  type: "array",
846
- items: { $ref: "#/$defs/DocEntry" }
2038
+ items: {
2039
+ $ref: "#/$defs/DocEntry"
2040
+ }
847
2041
  }
848
2042
  },
849
- required: ["kind", "title", "markdown", "phase"],
2043
+ required: [
2044
+ "kind",
2045
+ "title",
2046
+ "markdown",
2047
+ "phase"
2048
+ ],
850
2049
  additionalProperties: false
851
2050
  },
852
2051
  DocMermaid: {
853
2052
  type: "object",
854
2053
  properties: {
855
- kind: { const: "mermaid" },
856
- code: { type: "string" },
857
- title: { type: "string" },
858
- phase: { $ref: "#/$defs/DocPhase" },
2054
+ kind: {
2055
+ const: "mermaid"
2056
+ },
2057
+ code: {
2058
+ type: "string"
2059
+ },
2060
+ title: {
2061
+ type: "string"
2062
+ },
2063
+ phase: {
2064
+ $ref: "#/$defs/DocPhase"
2065
+ },
859
2066
  children: {
860
2067
  type: "array",
861
- items: { $ref: "#/$defs/DocEntry" }
2068
+ items: {
2069
+ $ref: "#/$defs/DocEntry"
2070
+ }
862
2071
  }
863
2072
  },
864
- required: ["kind", "code", "phase"],
2073
+ required: [
2074
+ "kind",
2075
+ "code",
2076
+ "phase"
2077
+ ],
865
2078
  additionalProperties: false
866
2079
  },
867
2080
  DocScreenshot: {
868
2081
  type: "object",
869
2082
  properties: {
870
- kind: { const: "screenshot" },
871
- path: { type: "string" },
872
- alt: { type: "string" },
873
- phase: { $ref: "#/$defs/DocPhase" },
2083
+ kind: {
2084
+ const: "screenshot"
2085
+ },
2086
+ path: {
2087
+ type: "string"
2088
+ },
2089
+ alt: {
2090
+ type: "string"
2091
+ },
2092
+ phase: {
2093
+ $ref: "#/$defs/DocPhase"
2094
+ },
874
2095
  children: {
875
2096
  type: "array",
876
- items: { $ref: "#/$defs/DocEntry" }
2097
+ items: {
2098
+ $ref: "#/$defs/DocEntry"
2099
+ }
877
2100
  }
878
2101
  },
879
- required: ["kind", "path", "phase"],
2102
+ required: [
2103
+ "kind",
2104
+ "path",
2105
+ "phase"
2106
+ ],
880
2107
  additionalProperties: false
881
2108
  },
882
2109
  DocVideo: {
883
2110
  type: "object",
884
2111
  properties: {
885
- kind: { const: "video" },
886
- path: { type: "string" },
887
- caption: { type: "string" },
888
- poster: { type: "string" },
889
- phase: { $ref: "#/$defs/DocPhase" },
2112
+ kind: {
2113
+ const: "video"
2114
+ },
2115
+ path: {
2116
+ type: "string"
2117
+ },
2118
+ caption: {
2119
+ type: "string"
2120
+ },
2121
+ poster: {
2122
+ type: "string"
2123
+ },
2124
+ phase: {
2125
+ $ref: "#/$defs/DocPhase"
2126
+ },
890
2127
  children: {
891
2128
  type: "array",
892
- items: { $ref: "#/$defs/DocEntry" }
2129
+ items: {
2130
+ $ref: "#/$defs/DocEntry"
2131
+ }
893
2132
  }
894
2133
  },
895
- required: ["kind", "path", "phase"],
2134
+ required: [
2135
+ "kind",
2136
+ "path",
2137
+ "phase"
2138
+ ],
896
2139
  additionalProperties: false
897
2140
  },
898
2141
  DocHtml: {
899
2142
  type: "object",
900
2143
  properties: {
901
- kind: { const: "html" },
902
- path: { type: "string" },
903
- url: { type: "string" },
904
- content: { type: "string" },
905
- title: { type: "string" },
906
- height: { oneOf: [{ type: "number" }, { type: "string" }] },
907
- phase: { $ref: "#/$defs/DocPhase" },
2144
+ kind: {
2145
+ const: "html"
2146
+ },
2147
+ path: {
2148
+ type: "string"
2149
+ },
2150
+ url: {
2151
+ type: "string"
2152
+ },
2153
+ content: {
2154
+ type: "string"
2155
+ },
2156
+ title: {
2157
+ type: "string"
2158
+ },
2159
+ height: {
2160
+ oneOf: [
2161
+ {
2162
+ type: "number"
2163
+ },
2164
+ {
2165
+ type: "string"
2166
+ }
2167
+ ]
2168
+ },
2169
+ phase: {
2170
+ $ref: "#/$defs/DocPhase"
2171
+ },
908
2172
  children: {
909
2173
  type: "array",
910
- items: { $ref: "#/$defs/DocEntry" }
2174
+ items: {
2175
+ $ref: "#/$defs/DocEntry"
2176
+ }
911
2177
  }
912
2178
  },
913
- required: ["kind", "phase"],
914
- oneOf: [
915
- { required: ["path"] },
916
- { required: ["url"] },
917
- { required: ["content"] }
2179
+ required: [
2180
+ "kind",
2181
+ "phase"
918
2182
  ],
919
- additionalProperties: false
2183
+ additionalProperties: false,
2184
+ oneOf: [
2185
+ {
2186
+ required: [
2187
+ "path"
2188
+ ]
2189
+ },
2190
+ {
2191
+ required: [
2192
+ "url"
2193
+ ]
2194
+ },
2195
+ {
2196
+ required: [
2197
+ "content"
2198
+ ]
2199
+ }
2200
+ ]
920
2201
  },
921
2202
  DocCustom: {
922
2203
  type: "object",
923
2204
  properties: {
924
- kind: { const: "custom" },
925
- type: { type: "string", minLength: 1 },
2205
+ kind: {
2206
+ const: "custom"
2207
+ },
2208
+ type: {
2209
+ type: "string",
2210
+ minLength: 1
2211
+ },
926
2212
  data: {},
927
- phase: { $ref: "#/$defs/DocPhase" },
2213
+ phase: {
2214
+ $ref: "#/$defs/DocPhase"
2215
+ },
928
2216
  children: {
929
2217
  type: "array",
930
- items: { $ref: "#/$defs/DocEntry" }
2218
+ items: {
2219
+ $ref: "#/$defs/DocEntry"
2220
+ }
2221
+ }
2222
+ },
2223
+ required: [
2224
+ "kind",
2225
+ "type",
2226
+ "data",
2227
+ "phase"
2228
+ ],
2229
+ additionalProperties: false
2230
+ },
2231
+ OtelSpan: {
2232
+ type: "object",
2233
+ required: [
2234
+ "spanId",
2235
+ "name",
2236
+ "status"
2237
+ ],
2238
+ properties: {
2239
+ spanId: {
2240
+ type: "string"
2241
+ },
2242
+ parentSpanId: {
2243
+ type: "string"
2244
+ },
2245
+ name: {
2246
+ type: "string"
2247
+ },
2248
+ startTimeMs: {
2249
+ type: "number"
2250
+ },
2251
+ durationMs: {
2252
+ type: "number"
2253
+ },
2254
+ startTimeUnixNano: {
2255
+ type: "number"
2256
+ },
2257
+ endTimeUnixNano: {
2258
+ type: "number"
2259
+ },
2260
+ status: {
2261
+ enum: [
2262
+ "ok",
2263
+ "error",
2264
+ "unset"
2265
+ ]
2266
+ },
2267
+ statusMessage: {
2268
+ type: "string"
2269
+ },
2270
+ attributes: {
2271
+ type: "object"
931
2272
  }
932
2273
  },
933
- required: ["kind", "type", "data", "phase"],
934
2274
  additionalProperties: false
935
2275
  }
936
2276
  }
937
2277
  };
938
2278
 
2279
+ // src/schema/version.ts
2280
+ var STORY_REPORT_SCHEMA_MAJOR = 1;
2281
+
939
2282
  // src/schema/story-report.schema.ts
940
- var compiled = z.fromJSONSchema(story_report_v1_default);
2283
+ var schemaForZod = structuredClone(story_report_v1_default);
2284
+ if (schemaForZod.$defs?.DocHtml) {
2285
+ delete schemaForZod.$defs.DocHtml.oneOf;
2286
+ }
2287
+ var compiled = z.fromJSONSchema(schemaForZod);
941
2288
  var storyReportSchema = compiled;
942
- var STORY_REPORT_SCHEMA_MAJOR = 1;
943
2289
 
944
2290
  // src/schema/parse.ts
945
2291
  function parseStoryReport(input) {
@@ -977,6 +2323,7 @@ export {
977
2323
  DocCode,
978
2324
  DocCustom,
979
2325
  DocEntry,
2326
+ DocHtml,
980
2327
  DocKv,
981
2328
  DocLink,
982
2329
  DocMermaid,
@@ -985,11 +2332,16 @@ export {
985
2332
  DocSection,
986
2333
  DocTable,
987
2334
  DocTag,
2335
+ DocVideo,
2336
+ MermaidDiagram,
2337
+ MermaidSource,
988
2338
  Report,
2339
+ ReportAttachments,
989
2340
  ReportDocEntries,
990
2341
  ReportEmpty,
991
2342
  ReportFeature,
992
2343
  ReportFeatureList,
2344
+ ReportMeta,
993
2345
  ReportRoot,
994
2346
  ReportScenario,
995
2347
  ReportScenarioList,
@@ -998,7 +2350,9 @@ export {
998
2350
  ReportSteps,
999
2351
  ReportSummary,
1000
2352
  ReportSummaryView,
2353
+ ReportTrace,
1001
2354
  STORY_REPORT_SCHEMA_MAJOR,
2355
+ ScenarioExplorer,
1002
2356
  err,
1003
2357
  ok,
1004
2358
  parseStoryReport,