executable-stories-react 0.1.16 → 0.2.1

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