executable-stories-react 0.1.16 → 0.2.0

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