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