executable-stories-react 0.1.16 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ssr.cjs ADDED
@@ -0,0 +1,1235 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/ssr-entry.ts
21
+ var ssr_entry_exports = {};
22
+ __export(ssr_entry_exports, {
23
+ renderReportToHtml: () => renderReportToHtml
24
+ });
25
+ module.exports = __toCommonJS(ssr_entry_exports);
26
+ var import_react7 = require("react");
27
+ var import_server = require("react-dom/server");
28
+
29
+ // src/result.ts
30
+ var ok = (data) => ({ ok: true, data });
31
+ function unwrapReport(value) {
32
+ if (typeof value === "object" && value !== null && "ok" in value && typeof value.ok === "boolean") {
33
+ return value;
34
+ }
35
+ return ok(value);
36
+ }
37
+
38
+ // src/context/ReportRoot.tsx
39
+ var import_react2 = require("react");
40
+
41
+ // src/context/ReportContext.ts
42
+ var import_react = require("react");
43
+ var ReportContext = (0, import_react.createContext)(null);
44
+
45
+ // src/context/ReportRoot.tsx
46
+ var import_jsx_runtime = require("react/jsx-runtime");
47
+ var EMPTY_CUSTOM = {};
48
+ var EMPTY_RENDERERS = {};
49
+ function ReportRoot({
50
+ report,
51
+ customRenderers,
52
+ renderers,
53
+ children
54
+ }) {
55
+ const value = (0, import_react2.useMemo)(
56
+ () => ({
57
+ report,
58
+ customRenderers: customRenderers ?? EMPTY_CUSTOM,
59
+ renderers: renderers ?? EMPTY_RENDERERS
60
+ }),
61
+ [report, customRenderers, renderers]
62
+ );
63
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReportContext.Provider, { value, children });
64
+ }
65
+
66
+ // src/hooks/useReport.ts
67
+ var import_react3 = require("react");
68
+ function useReport() {
69
+ const ctx = (0, import_react3.useContext)(ReportContext);
70
+ if (!ctx) {
71
+ throw new Error(
72
+ "useReport must be used inside <ReportRoot> or <Report>. Wrap your tree with one of those."
73
+ );
74
+ }
75
+ return ctx.report;
76
+ }
77
+
78
+ // src/components/ReportScenario.tsx
79
+ var import_duration3 = require("executable-stories-core/utils/duration");
80
+
81
+ // src/components/ReportSteps.tsx
82
+ var import_duration = require("executable-stories-core/utils/duration");
83
+
84
+ // src/components/doc/DocNote.tsx
85
+ var import_jsx_runtime2 = require("react/jsx-runtime");
86
+ function DocNote({ entry }) {
87
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "my-2 text-sm text-muted-foreground", children: entry.text });
88
+ }
89
+
90
+ // src/components/ui/badge.tsx
91
+ var React = require("react");
92
+ var import_class_variance_authority = require("class-variance-authority");
93
+ var import_use_render = require("@base-ui/react/use-render");
94
+
95
+ // src/lib/utils.ts
96
+ var import_clsx = require("clsx");
97
+ var import_tailwind_merge = require("tailwind-merge");
98
+ function cn(...inputs) {
99
+ return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
100
+ }
101
+
102
+ // src/components/ui/badge.tsx
103
+ var badgeVariants = (0, import_class_variance_authority.cva)(
104
+ "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",
105
+ {
106
+ variants: {
107
+ variant: {
108
+ default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
109
+ secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
110
+ 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",
111
+ outline: "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
112
+ ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
113
+ link: "text-primary underline-offset-4 [a&]:hover:underline",
114
+ passed: "border-pass-border bg-pass-bg text-pass font-mono uppercase",
115
+ failed: "border-fail-border bg-fail-bg text-fail font-mono uppercase",
116
+ skipped: "border-skip-border bg-skip-bg text-skip font-mono uppercase",
117
+ pending: "border-pend-border bg-pend-bg text-pend font-mono uppercase",
118
+ tag: "border-pass-border bg-pass-bg text-pass font-mono font-medium"
119
+ }
120
+ },
121
+ defaultVariants: {
122
+ variant: "default"
123
+ }
124
+ }
125
+ );
126
+ function Badge({
127
+ className,
128
+ variant = "default",
129
+ render,
130
+ ...props
131
+ }) {
132
+ return (0, import_use_render.useRender)({
133
+ defaultTagName: "span",
134
+ render,
135
+ props: {
136
+ "data-slot": "badge",
137
+ "data-variant": variant,
138
+ className: cn(badgeVariants({ variant }), className),
139
+ ...props
140
+ }
141
+ });
142
+ }
143
+
144
+ // src/components/doc/DocTag.tsx
145
+ var import_jsx_runtime3 = require("react/jsx-runtime");
146
+ function DocTag({ entry }) {
147
+ 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)) });
148
+ }
149
+
150
+ // src/components/doc/DocKv.tsx
151
+ var import_jsx_runtime4 = require("react/jsx-runtime");
152
+ function formatValue(value) {
153
+ if (value === null) return "null";
154
+ if (typeof value === "string") return value;
155
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
156
+ try {
157
+ return JSON.stringify(value);
158
+ } catch {
159
+ return String(value);
160
+ }
161
+ }
162
+ function DocKv({ entry }) {
163
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("dl", { className: "my-2 flex flex-wrap items-baseline gap-x-2 text-sm", children: [
164
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dt", { className: "font-semibold text-muted-foreground", children: entry.label }),
165
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dd", { className: "font-mono text-foreground", children: formatValue(entry.value) })
166
+ ] });
167
+ }
168
+
169
+ // src/hooks/useRenderers.ts
170
+ var import_react4 = require("react");
171
+ var EMPTY_CUSTOM2 = {};
172
+ var EMPTY_RENDERERS2 = {};
173
+ function useCustomRenderers() {
174
+ const ctx = (0, import_react4.useContext)(ReportContext);
175
+ return ctx?.customRenderers ?? EMPTY_CUSTOM2;
176
+ }
177
+ function useBuiltinRenderers() {
178
+ const ctx = (0, import_react4.useContext)(ReportContext);
179
+ return ctx?.renderers ?? EMPTY_RENDERERS2;
180
+ }
181
+
182
+ // src/components/doc/DocCode.tsx
183
+ var import_jsx_runtime5 = require("react/jsx-runtime");
184
+ function CodeFigure({ label, children }) {
185
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("figure", { className: "my-3", children: [
186
+ label ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: label }) : null,
187
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3", children })
188
+ ] });
189
+ }
190
+ function DocCode({ entry }) {
191
+ const renderers = useBuiltinRenderers();
192
+ if (renderers.code) {
193
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: renderers.code(entry) });
194
+ }
195
+ 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 }) });
196
+ }
197
+
198
+ // src/components/doc/DocTable.tsx
199
+ var import_jsx_runtime6 = require("react/jsx-runtime");
200
+ function DocTable({ entry }) {
201
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("figure", { className: "my-3", children: [
202
+ entry.label ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
203
+ /* @__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: [
204
+ /* @__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)) }) }),
205
+ /* @__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)) })
206
+ ] }) })
207
+ ] });
208
+ }
209
+
210
+ // src/components/doc/DocLink.tsx
211
+ var import_jsx_runtime7 = require("react/jsx-runtime");
212
+ function DocLink({ entry }) {
213
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
214
+ "a",
215
+ {
216
+ className: "text-sm font-medium text-primary underline underline-offset-2 hover:no-underline",
217
+ href: entry.url,
218
+ rel: "noreferrer noopener",
219
+ target: "_blank",
220
+ children: entry.label
221
+ }
222
+ );
223
+ }
224
+
225
+ // src/components/doc/DocSection.tsx
226
+ var import_marked = require("marked");
227
+
228
+ // src/lib/url.ts
229
+ function safeUrl(value) {
230
+ if (!value) return void 0;
231
+ const trimmed = value.trim();
232
+ const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(trimmed);
233
+ if (scheme && !/^https?$/i.test(scheme[1] ?? "")) return void 0;
234
+ return trimmed;
235
+ }
236
+ function safeImageUrl(value) {
237
+ if (!value) return void 0;
238
+ const trimmed = value.trim();
239
+ if (/^data:image\//i.test(trimmed)) return trimmed;
240
+ return safeUrl(trimmed);
241
+ }
242
+ function isLocalFsPath(value) {
243
+ return /^(?:[/\\]|[A-Za-z]:[/\\])/.test(value);
244
+ }
245
+
246
+ // src/components/doc/DocSection.tsx
247
+ var import_jsx_runtime8 = require("react/jsx-runtime");
248
+ function neutralizeUrl(value) {
249
+ return safeUrl(value) ?? "#";
250
+ }
251
+ function safeMarkdownHtml(markdown) {
252
+ const raw = import_marked.marked.parse(markdown, { async: false });
253
+ 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)}'`);
254
+ }
255
+ function DocSection({ entry }) {
256
+ const renderers = useBuiltinRenderers();
257
+ if (renderers.section) {
258
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_jsx_runtime8.Fragment, { children: renderers.section(entry) });
259
+ }
260
+ const html = safeMarkdownHtml(entry.markdown);
261
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("section", { className: "my-3 text-sm", "aria-label": entry.title, children: [
262
+ entry.title ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h4", { className: "mb-1 font-semibold text-foreground", children: entry.title }) : null,
263
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
264
+ "div",
265
+ {
266
+ className: "es-doc-prose text-muted-foreground",
267
+ dangerouslySetInnerHTML: { __html: html }
268
+ }
269
+ )
270
+ ] });
271
+ }
272
+
273
+ // src/components/doc/DocMermaid.tsx
274
+ var import_jsx_runtime9 = require("react/jsx-runtime");
275
+ function MermaidSource({ entry, ref }) {
276
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("figure", { ref, className: "my-3", "aria-label": entry.title ?? "Diagram", children: [
277
+ entry.title ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
278
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
279
+ "pre",
280
+ {
281
+ "data-mermaid": true,
282
+ tabIndex: 0,
283
+ className: "overflow-x-auto rounded-md bg-muted p-3 font-mono text-xs text-foreground",
284
+ children: entry.code
285
+ }
286
+ )
287
+ ] });
288
+ }
289
+ function DocMermaid({ entry }) {
290
+ const renderers = useBuiltinRenderers();
291
+ if (renderers.mermaid) {
292
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_jsx_runtime9.Fragment, { children: renderers.mermaid(entry) });
293
+ }
294
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MermaidSource, { entry });
295
+ }
296
+
297
+ // src/components/doc/DocScreenshot.tsx
298
+ var import_jsx_runtime10 = require("react/jsx-runtime");
299
+ function DocScreenshot({ entry }) {
300
+ const src = isLocalFsPath(entry.path) ? void 0 : safeImageUrl(entry.path);
301
+ if (!src) {
302
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("figure", { className: "my-3 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground", children: [
303
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "font-medium", children: "Screenshot unavailable" }),
304
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
305
+ entry.alt ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("figcaption", { className: "mt-1.5 text-xs", children: entry.alt }) : null
306
+ ] });
307
+ }
308
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("figure", { className: "my-3", children: [
309
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
310
+ "img",
311
+ {
312
+ src,
313
+ alt: entry.alt ?? "",
314
+ loading: "lazy",
315
+ className: "max-w-full rounded-md border border-border"
316
+ }
317
+ ),
318
+ entry.alt ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: entry.alt }) : null
319
+ ] });
320
+ }
321
+
322
+ // src/components/doc/DocVideo.tsx
323
+ var import_jsx_runtime11 = require("react/jsx-runtime");
324
+ function DocVideo({ entry }) {
325
+ if (isLocalFsPath(entry.path)) {
326
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("figure", { className: "my-3 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground", children: [
327
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "font-medium", children: "Video unavailable" }),
328
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
329
+ entry.caption ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("figcaption", { className: "mt-1.5 text-xs", children: entry.caption }) : null
330
+ ] });
331
+ }
332
+ const src = safeUrl(entry.path);
333
+ const poster = safeUrl(entry.poster);
334
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("figure", { className: "my-3", children: [
335
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
336
+ "video",
337
+ {
338
+ controls: true,
339
+ preload: "metadata",
340
+ ...src ? { src } : {},
341
+ ...poster ? { poster } : {},
342
+ className: "max-w-full rounded-md border border-border"
343
+ }
344
+ ),
345
+ entry.caption ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: entry.caption }) : null
346
+ ] });
347
+ }
348
+
349
+ // src/components/doc/DocHtml.tsx
350
+ var import_jsx_runtime12 = require("react/jsx-runtime");
351
+ function DocHtml({ entry }) {
352
+ const height = typeof entry.height === "number" ? `${entry.height}px` : entry.height ?? "400px";
353
+ const title = entry.title ?? "Embedded HTML";
354
+ const safeHref = safeUrl(entry.url ?? entry.path);
355
+ const frameProps = entry.content !== void 0 ? { srcDoc: entry.content } : { src: safeHref ?? "" };
356
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("figure", { className: "my-3 overflow-hidden rounded-md border border-border", children: [
357
+ /* @__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: [
358
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { children: entry.title ?? "HTML" }),
359
+ safeHref ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
360
+ "a",
361
+ {
362
+ href: safeHref,
363
+ target: "_blank",
364
+ rel: "noreferrer noopener",
365
+ "aria-label": "Open in new tab",
366
+ title: "Open in new tab",
367
+ className: "text-primary",
368
+ children: "\u2197"
369
+ }
370
+ ) : null
371
+ ] }),
372
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
373
+ "iframe",
374
+ {
375
+ ...frameProps,
376
+ sandbox: "allow-scripts",
377
+ loading: "lazy",
378
+ title,
379
+ className: "block w-full border-0",
380
+ style: { height }
381
+ }
382
+ )
383
+ ] });
384
+ }
385
+
386
+ // src/components/doc/DocCustom.tsx
387
+ var import_jsx_runtime13 = require("react/jsx-runtime");
388
+ function DocCustom({ entry }) {
389
+ const renderers = useCustomRenderers();
390
+ const renderer = renderers[entry.type];
391
+ if (renderer) {
392
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_jsx_runtime13.Fragment, { children: renderer(entry) });
393
+ }
394
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "my-2", "data-type": entry.type, children: [
395
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: entry.type }),
396
+ /* @__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) })
397
+ ] });
398
+ }
399
+ function safeStringify(value) {
400
+ try {
401
+ return JSON.stringify(value, null, 2);
402
+ } catch {
403
+ return String(value);
404
+ }
405
+ }
406
+
407
+ // src/components/doc/DocEntry.tsx
408
+ var import_jsx_runtime14 = require("react/jsx-runtime");
409
+ function DocEntry({ entry }) {
410
+ switch (entry.kind) {
411
+ case "note":
412
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocNote, { entry });
413
+ case "tag":
414
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocTag, { entry });
415
+ case "kv":
416
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocKv, { entry });
417
+ case "code":
418
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocCode, { entry });
419
+ case "table":
420
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocTable, { entry });
421
+ case "link":
422
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocLink, { entry });
423
+ case "section":
424
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocSection, { entry });
425
+ case "mermaid":
426
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocMermaid, { entry });
427
+ case "screenshot":
428
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocScreenshot, { entry });
429
+ case "video":
430
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocVideo, { entry });
431
+ case "html":
432
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocHtml, { entry });
433
+ case "custom":
434
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocCustom, { entry });
435
+ }
436
+ }
437
+
438
+ // src/components/ReportDocEntries.tsx
439
+ var import_jsx_runtime15 = require("react/jsx-runtime");
440
+ function ReportDocEntries({ entries }) {
441
+ if (entries.length === 0) return null;
442
+ 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)) });
443
+ }
444
+
445
+ // src/lib/step-params.tsx
446
+ var import_jsx_runtime16 = require("react/jsx-runtime");
447
+ var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.-])\d+(?:\.\d+)?(?![\w.-])/g;
448
+ function highlightStepParams(text) {
449
+ const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
450
+ if (matches.length === 0) return text;
451
+ const nodes = [];
452
+ let lastIndex = 0;
453
+ matches.forEach((match, i) => {
454
+ const start = match.index;
455
+ const end = start + match[0].length;
456
+ if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
457
+ nodes.push(
458
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "font-medium text-step-param italic", children: match[0] }, i)
459
+ );
460
+ lastIndex = end;
461
+ });
462
+ if (lastIndex < text.length) nodes.push(text.slice(lastIndex));
463
+ return nodes;
464
+ }
465
+
466
+ // src/components/ReportSteps.tsx
467
+ var import_jsx_runtime17 = require("react/jsx-runtime");
468
+ var STEP_GLYPH = {
469
+ passed: "\u2713",
470
+ failed: "\u2717",
471
+ skipped: "\u25CB",
472
+ pending: "\u25CB"
473
+ };
474
+ var STEP_GLYPH_COLOR = {
475
+ passed: "text-pass",
476
+ failed: "text-fail",
477
+ skipped: "text-skip",
478
+ pending: "text-pend"
479
+ };
480
+ function ReportSteps({ scenario }) {
481
+ if (scenario.steps.length === 0) return null;
482
+ 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)) });
483
+ }
484
+ function ReportStepItem({ step }) {
485
+ const kw = step.keyword.toLowerCase();
486
+ const continuation = kw === "and" || kw === "but";
487
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
488
+ "li",
489
+ {
490
+ id: step.id,
491
+ "data-status": step.status,
492
+ className: cn("py-1.5 text-[0.8125rem] leading-normal", continuation && "pl-5"),
493
+ children: [
494
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-baseline gap-2", children: [
495
+ /* @__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" }),
496
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
497
+ "span",
498
+ {
499
+ className: cn(
500
+ "min-w-[52px] shrink-0 font-mono text-xs font-semibold capitalize",
501
+ continuation ? "font-medium text-muted-foreground" : "text-keyword"
502
+ ),
503
+ children: step.keyword
504
+ }
505
+ ),
506
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "flex-1 text-foreground", children: highlightStepParams(step.text) }),
507
+ 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
508
+ ] }),
509
+ step.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
510
+ "pre",
511
+ {
512
+ role: "alert",
513
+ 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",
514
+ children: step.errorMessage
515
+ }
516
+ ) : null,
517
+ 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
518
+ ]
519
+ }
520
+ );
521
+ }
522
+
523
+ // src/components/ReportAttachments.tsx
524
+ var import_jsx_runtime18 = require("react/jsx-runtime");
525
+ function ReportAttachments({ attachments }) {
526
+ if (attachments.length === 0) return null;
527
+ return (
528
+ // role="group" (not a labelled <section>) so multiple scenarios' attachment
529
+ // blocks don't create duplicate "Attachments" region landmarks.
530
+ /* @__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) => {
531
+ const dataUri = att.contentEncoding === "BASE64" ? `data:${att.mediaType};base64,${att.body}` : `data:${att.mediaType},${encodeURIComponent(att.body)}`;
532
+ const isImage = att.mediaType.startsWith("image/");
533
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("figure", { className: "m-0", children: isImage ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
534
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
535
+ "img",
536
+ {
537
+ src: dataUri,
538
+ alt: att.name,
539
+ loading: "lazy",
540
+ className: "max-w-full rounded-md border border-border"
541
+ }
542
+ ),
543
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: att.name })
544
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
545
+ "a",
546
+ {
547
+ href: dataUri,
548
+ download: att.name,
549
+ className: "text-xs text-link underline underline-offset-2",
550
+ children: [
551
+ att.name,
552
+ " ",
553
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "text-muted-foreground", children: [
554
+ "(",
555
+ att.mediaType,
556
+ ")"
557
+ ] })
558
+ ]
559
+ }
560
+ ) }, `${att.name}-${i}`);
561
+ }) })
562
+ );
563
+ }
564
+
565
+ // src/components/ReportTrace.tsx
566
+ var import_duration2 = require("executable-stories-core/utils/duration");
567
+
568
+ // src/lib/trace.ts
569
+ var VALID = /* @__PURE__ */ new Set(["ok", "error", "unset"]);
570
+ var safeStatus = (s) => VALID.has(s) ? s : "unset";
571
+ function normalizeSpans(spans) {
572
+ const out = [];
573
+ for (const span of spans) {
574
+ if (!span || typeof span.spanId !== "string" || typeof span.name !== "string") continue;
575
+ let startTimeMs;
576
+ let durationMs;
577
+ if (span.startTimeMs != null && span.durationMs != null) {
578
+ startTimeMs = span.startTimeMs;
579
+ durationMs = span.durationMs;
580
+ } else if (span.startTimeUnixNano != null && span.endTimeUnixNano != null) {
581
+ startTimeMs = span.startTimeUnixNano / 1e6;
582
+ durationMs = (span.endTimeUnixNano - span.startTimeUnixNano) / 1e6;
583
+ } else {
584
+ continue;
585
+ }
586
+ durationMs = Math.max(0, durationMs);
587
+ if (!isFinite(startTimeMs) || !isFinite(durationMs)) continue;
588
+ out.push({
589
+ spanId: span.spanId,
590
+ parentSpanId: span.parentSpanId,
591
+ name: span.name,
592
+ startTimeMs,
593
+ durationMs,
594
+ status: safeStatus(span.status),
595
+ statusMessage: span.statusMessage
596
+ });
597
+ }
598
+ return out;
599
+ }
600
+ function toTraceRows(spans) {
601
+ const normalized = normalizeSpans(spans);
602
+ if (normalized.length === 0) return [];
603
+ const byId = /* @__PURE__ */ new Map();
604
+ for (const span of normalized) {
605
+ let key = span.spanId;
606
+ let suffix = 2;
607
+ while (byId.has(key)) key = `${span.spanId}__dup${suffix++}`;
608
+ byId.set(key, { span: { ...span, spanId: key }, children: [], depth: 0 });
609
+ }
610
+ const roots = [];
611
+ for (const node of byId.values()) {
612
+ const parent = node.span.parentSpanId ? byId.get(node.span.parentSpanId) : void 0;
613
+ if (parent && parent !== node) parent.children.push(node);
614
+ else roots.push(node);
615
+ }
616
+ for (const node of byId.values()) node.children.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
617
+ roots.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
618
+ const rows = [];
619
+ const seen = /* @__PURE__ */ new Set();
620
+ const walk = (node, depth) => {
621
+ if (seen.has(node.span.spanId)) return;
622
+ seen.add(node.span.spanId);
623
+ rows.push({ ...node.span, depth });
624
+ for (const child of node.children) walk(child, depth + 1);
625
+ };
626
+ for (const root of roots) walk(root, 0);
627
+ for (const node of byId.values()) {
628
+ if (!seen.has(node.span.spanId)) walk(node, 0);
629
+ }
630
+ return rows;
631
+ }
632
+ function traceWindow(rows) {
633
+ let min = Infinity;
634
+ let max = -Infinity;
635
+ for (const r of rows) {
636
+ min = Math.min(min, r.startTimeMs);
637
+ max = Math.max(max, r.startTimeMs + r.durationMs);
638
+ }
639
+ const total = max - min;
640
+ return { minStartMs: isFinite(min) ? min : 0, totalMs: total > 0 ? total : 1 };
641
+ }
642
+
643
+ // src/components/ReportTrace.tsx
644
+ var import_jsx_runtime19 = require("react/jsx-runtime");
645
+ var BAR_COLOR = {
646
+ ok: "bg-pass",
647
+ error: "bg-fail",
648
+ unset: "bg-skip"
649
+ };
650
+ function ReportTrace({ spans }) {
651
+ if (!spans || spans.length === 0) return null;
652
+ const rows = toTraceRows(spans);
653
+ if (rows.length === 0) return null;
654
+ const { minStartMs, totalMs } = traceWindow(rows);
655
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("details", { className: "mt-3 rounded-md border border-border bg-muted/30", children: [
656
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("summary", { className: "cursor-pointer px-3 py-2 text-xs font-medium text-muted-foreground", children: [
657
+ "Trace (",
658
+ rows.length,
659
+ " span",
660
+ rows.length === 1 ? "" : "s",
661
+ ", ",
662
+ (0, import_duration2.formatDuration)(totalMs),
663
+ ")"
664
+ ] }),
665
+ /* @__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) => {
666
+ const leftPct = (row.startTimeMs - minStartMs) / totalMs * 100;
667
+ const widthPct = Math.max(row.durationMs / totalMs * 100, 0.5);
668
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("li", { className: "flex items-center gap-2", children: [
669
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
670
+ "span",
671
+ {
672
+ className: "min-w-0 flex-1 truncate text-foreground",
673
+ style: { paddingLeft: `${row.depth * 12}px` },
674
+ title: row.statusMessage ? `${row.name} \u2014 ${row.statusMessage}` : row.name,
675
+ children: row.name
676
+ }
677
+ ),
678
+ /* @__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)(
679
+ "span",
680
+ {
681
+ className: cn("absolute top-0 h-full rounded-sm", BAR_COLOR[row.status]),
682
+ style: { left: `${leftPct}%`, width: `${widthPct}%` }
683
+ }
684
+ ) }),
685
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: (0, import_duration2.formatDuration)(row.durationMs) })
686
+ ] }, `${row.spanId}-${i}`);
687
+ }) })
688
+ ] });
689
+ }
690
+
691
+ // src/interactive/collapse-context.ts
692
+ var import_react5 = require("react");
693
+ var CollapseContext = (0, import_react5.createContext)(null);
694
+ var CollapseProvider = CollapseContext.Provider;
695
+ function useCollapse() {
696
+ return (0, import_react5.useContext)(CollapseContext);
697
+ }
698
+
699
+ // src/interactive/scenario-actions.tsx
700
+ var import_react6 = require("react");
701
+ var ScenarioActionsContext = (0, import_react6.createContext)(null);
702
+ var ScenarioActionsProvider = ScenarioActionsContext.Provider;
703
+ function useScenarioActions() {
704
+ return (0, import_react6.useContext)(ScenarioActionsContext);
705
+ }
706
+
707
+ // src/components/ui/card.tsx
708
+ var React2 = require("react");
709
+ var import_jsx_runtime20 = require("react/jsx-runtime");
710
+ function Card({ className, ...props }) {
711
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
712
+ "div",
713
+ {
714
+ "data-slot": "card",
715
+ className: cn(
716
+ "flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm",
717
+ className
718
+ ),
719
+ ...props
720
+ }
721
+ );
722
+ }
723
+ function CardHeader({ className, ...props }) {
724
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
725
+ "div",
726
+ {
727
+ "data-slot": "card-header",
728
+ className: cn(
729
+ "@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",
730
+ className
731
+ ),
732
+ ...props
733
+ }
734
+ );
735
+ }
736
+ function CardContent({ className, ...props }) {
737
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
738
+ "div",
739
+ {
740
+ "data-slot": "card-content",
741
+ className: cn("px-6", className),
742
+ ...props
743
+ }
744
+ );
745
+ }
746
+
747
+ // src/components/ReportScenario.tsx
748
+ var import_jsx_runtime21 = require("react/jsx-runtime");
749
+ var STATUS_LABEL = {
750
+ passed: "Passed",
751
+ failed: "Failed",
752
+ skipped: "Skipped",
753
+ pending: "Pending"
754
+ };
755
+ var STATUS_GLYPH = {
756
+ passed: "\u2713",
757
+ failed: "\u2717",
758
+ skipped: "\u25CB",
759
+ pending: "\u25CB"
760
+ };
761
+ var STATUS_GLYPH_COLOR = {
762
+ passed: "text-pass",
763
+ failed: "text-fail",
764
+ skipped: "text-skip",
765
+ pending: "text-pend"
766
+ };
767
+ var STATUS_BADGE = {
768
+ passed: "passed",
769
+ failed: "failed",
770
+ skipped: "skipped",
771
+ pending: "pending"
772
+ };
773
+ function ReportScenario({ scenario, hideTitle = false }) {
774
+ const titleId = `${scenario.id}-title`;
775
+ const bodyId = `${scenario.id}-body`;
776
+ const label = STATUS_LABEL[scenario.status];
777
+ const collapse = useCollapse();
778
+ const collapsible = collapse !== null && !hideTitle;
779
+ const collapsed = collapsible ? collapse.isCollapsed(scenario.id) : false;
780
+ const actions = useScenarioActions();
781
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
782
+ Card,
783
+ {
784
+ id: scenario.id,
785
+ "data-status": scenario.status,
786
+ ...hideTitle ? { "aria-label": `${scenario.title} \u2014 ${label}` } : { "aria-labelledby": titleId },
787
+ className: "gap-0 py-0",
788
+ children: [
789
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(CardHeader, { className: "px-4 py-3", children: [
790
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
791
+ hideTitle ? null : /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
792
+ collapsible ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
793
+ "button",
794
+ {
795
+ type: "button",
796
+ onClick: () => collapse.toggle(scenario.id),
797
+ "aria-expanded": !collapsed,
798
+ "aria-controls": bodyId,
799
+ "aria-label": `Toggle ${scenario.title}`,
800
+ className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
801
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
802
+ }
803
+ ) : null,
804
+ /* @__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] }),
805
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: scenario.title })
806
+ ] }),
807
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex shrink-0 items-center gap-2", children: [
808
+ 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,
809
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Badge, { variant: STATUS_BADGE[scenario.status], "aria-label": `Status: ${label}`, children: label }),
810
+ actions ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-0.5", children: [
811
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
812
+ "button",
813
+ {
814
+ type: "button",
815
+ onClick: () => actions.copyLink(scenario),
816
+ "aria-label": `Copy link to ${scenario.title}`,
817
+ title: "Copy permalink",
818
+ className: "cursor-pointer rounded px-1 py-0.5 font-mono text-[0.625rem] text-muted-foreground hover:bg-muted hover:text-foreground",
819
+ children: "link"
820
+ }
821
+ ),
822
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
823
+ "button",
824
+ {
825
+ type: "button",
826
+ onClick: () => actions.copyMarkdown(scenario),
827
+ "aria-label": `Copy ${scenario.title} as Markdown`,
828
+ title: "Copy as Markdown",
829
+ className: "cursor-pointer rounded px-1 py-0.5 font-mono text-[0.625rem] text-muted-foreground hover:bg-muted hover:text-foreground",
830
+ children: "md"
831
+ }
832
+ ),
833
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
834
+ "button",
835
+ {
836
+ type: "button",
837
+ onClick: () => actions.copyPrompt(scenario),
838
+ "aria-label": `Copy ${scenario.title} as an AI investigation prompt`,
839
+ title: "Copy as AI prompt",
840
+ className: "cursor-pointer rounded px-1 py-0.5 font-mono text-[0.625rem] text-muted-foreground hover:bg-muted hover:text-foreground",
841
+ children: "ai"
842
+ }
843
+ )
844
+ ] }) : null
845
+ ] })
846
+ ] }),
847
+ 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: [
848
+ 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}`)),
849
+ (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}`))
850
+ ] }) : null
851
+ ] }),
852
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
853
+ scenario.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
854
+ "pre",
855
+ {
856
+ role: "alert",
857
+ 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",
858
+ children: scenario.errorMessage
859
+ }
860
+ ) : null,
861
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ReportSteps, { scenario }),
862
+ 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,
863
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ReportAttachments, { attachments: scenario.attachments }),
864
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ReportTrace, { spans: scenario.otelSpans })
865
+ ] })
866
+ ]
867
+ }
868
+ );
869
+ }
870
+
871
+ // src/components/ReportScenarioList.tsx
872
+ var import_jsx_runtime22 = require("react/jsx-runtime");
873
+ function ReportScenarioList({ feature }) {
874
+ 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)) });
875
+ }
876
+
877
+ // src/components/ReportFeature.tsx
878
+ var import_jsx_runtime23 = require("react/jsx-runtime");
879
+ function ReportFeature({ feature }) {
880
+ const titleId = `${feature.id}-title`;
881
+ const bodyId = `${feature.id}-body`;
882
+ const s = feature.summary;
883
+ const skipped = s.skipped + s.pending;
884
+ const collapse = useCollapse();
885
+ const collapsed = collapse?.isCollapsed(feature.id) ?? false;
886
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
887
+ "section",
888
+ {
889
+ id: feature.id,
890
+ "data-slot": "feature",
891
+ "aria-labelledby": titleId,
892
+ className: "overflow-hidden rounded-xl border border-border bg-card shadow-sm",
893
+ children: [
894
+ /* @__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: [
895
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex min-w-0 items-center gap-2", children: [
896
+ collapse ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
897
+ "button",
898
+ {
899
+ type: "button",
900
+ onClick: () => collapse.toggle(feature.id),
901
+ "aria-expanded": !collapsed,
902
+ "aria-controls": bodyId,
903
+ "aria-label": `Toggle ${feature.title}`,
904
+ className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
905
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
906
+ }
907
+ ) : null,
908
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "min-w-0", children: [
909
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("h2", { id: titleId, className: "text-sm font-semibold tracking-tight text-foreground", children: feature.title }),
910
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "mt-0.5 truncate font-mono text-xs text-muted-foreground", children: feature.sourceFile })
911
+ ] })
912
+ ] }),
913
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
914
+ "div",
915
+ {
916
+ role: "img",
917
+ className: "flex shrink-0 items-center gap-2.5 font-mono text-xs font-medium",
918
+ "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
919
+ children: [
920
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { "aria-hidden": true, className: "text-pass", children: [
921
+ "\u2713",
922
+ s.passed
923
+ ] }),
924
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { "aria-hidden": true, className: "text-fail", children: [
925
+ "\u2717",
926
+ s.failed
927
+ ] }),
928
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { "aria-hidden": true, className: "text-skip", children: [
929
+ "\u25CB",
930
+ skipped
931
+ ] })
932
+ ]
933
+ }
934
+ )
935
+ ] }),
936
+ /* @__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 }) })
937
+ ]
938
+ }
939
+ );
940
+ }
941
+
942
+ // src/components/ReportFeatureList.tsx
943
+ var import_jsx_runtime24 = require("react/jsx-runtime");
944
+ function ReportFeatureList() {
945
+ const report = useReport();
946
+ 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)) });
947
+ }
948
+
949
+ // src/components/ui/empty.tsx
950
+ var import_class_variance_authority2 = require("class-variance-authority");
951
+ var import_jsx_runtime25 = require("react/jsx-runtime");
952
+ function Empty({ className, ...props }) {
953
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
954
+ "div",
955
+ {
956
+ "data-slot": "empty",
957
+ className: cn(
958
+ "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",
959
+ className
960
+ ),
961
+ ...props
962
+ }
963
+ );
964
+ }
965
+ function EmptyHeader({ className, ...props }) {
966
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
967
+ "div",
968
+ {
969
+ "data-slot": "empty-header",
970
+ className: cn(
971
+ "flex max-w-sm flex-col items-center gap-2 text-center",
972
+ className
973
+ ),
974
+ ...props
975
+ }
976
+ );
977
+ }
978
+ var emptyMediaVariants = (0, import_class_variance_authority2.cva)(
979
+ "mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
980
+ {
981
+ variants: {
982
+ variant: {
983
+ default: "bg-transparent",
984
+ icon: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-6"
985
+ }
986
+ },
987
+ defaultVariants: {
988
+ variant: "default"
989
+ }
990
+ }
991
+ );
992
+ function EmptyTitle({ className, ...props }) {
993
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
994
+ "div",
995
+ {
996
+ "data-slot": "empty-title",
997
+ className: cn("text-lg font-medium tracking-tight", className),
998
+ ...props
999
+ }
1000
+ );
1001
+ }
1002
+
1003
+ // src/components/ReportEmpty.tsx
1004
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1005
+ function ReportEmpty({ message }) {
1006
+ 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." }) }) });
1007
+ }
1008
+
1009
+ // src/components/ReportSummary.tsx
1010
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1011
+ function ReportSummary({ className }) {
1012
+ const report = useReport();
1013
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1014
+ ReportSummaryView,
1015
+ {
1016
+ summary: report.summary,
1017
+ ...className !== void 0 && { className },
1018
+ ariaLabel: "Run summary"
1019
+ }
1020
+ );
1021
+ }
1022
+ function ReportSummaryView({ summary, className, ariaLabel }) {
1023
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("p", { className: cn("text-sm text-muted-foreground", className), "aria-label": ariaLabel, children: [
1024
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { children: [
1025
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("strong", { className: "font-semibold text-foreground", children: summary.total }),
1026
+ " scenario",
1027
+ summary.total === 1 ? "" : "s"
1028
+ ] }),
1029
+ " \xB7 ",
1030
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { "data-status": "passed", className: "text-pass", children: [
1031
+ summary.passed,
1032
+ " passed"
1033
+ ] }),
1034
+ " \xB7 ",
1035
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { "data-status": "failed", className: "text-fail", children: [
1036
+ summary.failed,
1037
+ " failed"
1038
+ ] }),
1039
+ summary.skipped > 0 ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
1040
+ " \xB7 ",
1041
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { "data-status": "skipped", className: "text-skip", children: [
1042
+ summary.skipped,
1043
+ " skipped"
1044
+ ] })
1045
+ ] }) : null,
1046
+ summary.pending > 0 ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
1047
+ " \xB7 ",
1048
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { "data-status": "pending", className: "text-pend", children: [
1049
+ summary.pending,
1050
+ " pending"
1051
+ ] })
1052
+ ] }) : null
1053
+ ] });
1054
+ }
1055
+
1056
+ // src/components/ReportMeta.tsx
1057
+ var import_duration4 = require("executable-stories-core/utils/duration");
1058
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1059
+ function ReportMeta() {
1060
+ const report = useReport();
1061
+ const items = [];
1062
+ if (report.startedAtMs) {
1063
+ items.push({ label: "Started", value: new Date(report.startedAtMs).toLocaleString() });
1064
+ }
1065
+ if (report.durationMs) {
1066
+ items.push({ label: "Duration", value: (0, import_duration4.formatDuration)(report.durationMs) });
1067
+ }
1068
+ if (report.packageVersion) {
1069
+ items.push({ label: "Version", value: report.packageVersion });
1070
+ }
1071
+ if (report.gitSha) {
1072
+ items.push({ label: "Commit", value: report.gitSha.slice(0, 8) });
1073
+ }
1074
+ if (report.ci) {
1075
+ items.push({
1076
+ label: "CI",
1077
+ 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: [
1078
+ report.ci.name,
1079
+ report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
1080
+ ] }) : `${report.ci.name}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
1081
+ });
1082
+ }
1083
+ if (items.length === 0) return null;
1084
+ 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: [
1085
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("dt", { className: "font-medium", children: item.label }),
1086
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("dd", { className: "font-mono text-foreground", children: item.value })
1087
+ ] }, item.label)) });
1088
+ }
1089
+
1090
+ // src/components/ReportSchemaError.tsx
1091
+ var import_jsx_runtime29 = require("react/jsx-runtime");
1092
+ function ReportSchemaError({ error }) {
1093
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1094
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("strong", { children: "Report could not be displayed." }) }),
1095
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { children: error.message }),
1096
+ error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("p", { children: [
1097
+ "The report bundle is newer than this version of ",
1098
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("code", { children: "executable-stories-react" }),
1099
+ ". Upgrade the package, or regenerate the report with an older formatters CLI."
1100
+ ] }) : null,
1101
+ error.issues && error.issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("details", { children: [
1102
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("summary", { children: [
1103
+ error.issues.length,
1104
+ " validation issue",
1105
+ error.issues.length === 1 ? "" : "s"
1106
+ ] }),
1107
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1108
+ ] }) : null
1109
+ ] });
1110
+ }
1111
+
1112
+ // src/components/ReportShell.tsx
1113
+ var import_jsx_runtime30 = require("react/jsx-runtime");
1114
+ function ReportTitleBlock({ title }) {
1115
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
1116
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("h1", { children: title ?? "Story Report" }),
1117
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ReportSummary, {}),
1118
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ReportMeta, {})
1119
+ ] });
1120
+ }
1121
+ function ReportErrorShell({
1122
+ error,
1123
+ className,
1124
+ title,
1125
+ dataTheme
1126
+ }) {
1127
+ 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 }) });
1128
+ }
1129
+
1130
+ // src/components/Report.tsx
1131
+ var import_jsx_runtime31 = require("react/jsx-runtime");
1132
+ function Report(props) {
1133
+ const { report, customRenderers, renderers, className, title, dataTheme } = props;
1134
+ const result = unwrapReport(report);
1135
+ if (!result.ok) {
1136
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ReportErrorShell, { error: result.error, className, title, dataTheme });
1137
+ }
1138
+ const data = result.data;
1139
+ const hasContent = data.features.length > 0;
1140
+ 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: [
1141
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("header", { className: "es-report-header", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ReportTitleBlock, { title }) }),
1142
+ hasContent ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ReportFeatureList, {}) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ReportEmpty, {})
1143
+ ] }) });
1144
+ }
1145
+
1146
+ // src/ssr-entry.ts
1147
+ var ROOT_ID = "es-report-root";
1148
+ var DATA_ID = "es-report-data";
1149
+ function escapeJsonForScript(json) {
1150
+ return json.replace(/[<\u2028\u2029]/g, (c) => "\\u" + c.charCodeAt(0).toString(16).padStart(4, "0"));
1151
+ }
1152
+ var HLJS_URL = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/es/highlight.min.js";
1153
+ var HLJS_CSS_LIGHT = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css";
1154
+ var HLJS_CSS_DARK = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css";
1155
+ var MERMAID_URL = "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
1156
+ function cdnAssets(syntaxHighlighting, mermaid) {
1157
+ const headParts = [];
1158
+ const imports = [];
1159
+ const init = [];
1160
+ if (syntaxHighlighting) {
1161
+ headParts.push(`<link rel="stylesheet" href="${HLJS_CSS_LIGHT}">`);
1162
+ headParts.push(`<link rel="stylesheet" href="${HLJS_CSS_DARK}" media="(prefers-color-scheme: dark)">`);
1163
+ imports.push(`import hljs from "${HLJS_URL}";`);
1164
+ init.push("document.querySelectorAll('pre code').forEach((el) => hljs.highlightElement(el));");
1165
+ }
1166
+ if (mermaid) {
1167
+ imports.push(`import mermaid from "${MERMAID_URL}";`);
1168
+ init.push("mermaid.initialize({ startOnLoad: false, theme: 'neutral' });");
1169
+ init.push('await mermaid.run({ querySelector: "pre[data-mermaid]" });');
1170
+ }
1171
+ const body = imports.length ? `<script type="module">
1172
+ ${imports.join("\n")}
1173
+ ${init.join("\n")}
1174
+ </script>` : "";
1175
+ return { head: headParts.join("\n"), body };
1176
+ }
1177
+ var HTML_ESCAPES = {
1178
+ "&": "&amp;",
1179
+ "<": "&lt;",
1180
+ ">": "&gt;",
1181
+ '"': "&quot;",
1182
+ "'": "&#39;"
1183
+ };
1184
+ function escapeHtml(value) {
1185
+ return value.replace(/[&<>"']/g, (ch) => HTML_ESCAPES[ch] ?? ch);
1186
+ }
1187
+ function renderReportToHtml(report, options = {}) {
1188
+ const {
1189
+ title = "Story Report",
1190
+ css = "",
1191
+ theme = "light",
1192
+ headExtra = "",
1193
+ bodyExtra = "",
1194
+ lang = "en",
1195
+ syntaxHighlighting = true,
1196
+ mermaid = true,
1197
+ islandScript = ""
1198
+ } = options;
1199
+ const markup = (0, import_server.renderToStaticMarkup)(
1200
+ (0, import_react7.createElement)(Report, { report, title })
1201
+ );
1202
+ const cdn = cdnAssets(syntaxHighlighting, mermaid);
1203
+ const parsed = unwrapReport(report);
1204
+ const rawReport = parsed.ok ? parsed.data : void 0;
1205
+ const interactive = islandScript.length > 0 && rawReport !== void 0;
1206
+ const dataScript = interactive ? `<script type="application/json" id="${DATA_ID}">${escapeJsonForScript(JSON.stringify(rawReport))}</script>` : "";
1207
+ const islandTag = interactive ? `<script>${islandScript}</script>` : "";
1208
+ const cdnBody = interactive ? "" : cdn.body;
1209
+ const islandConfigAttrs = interactive ? ` data-es-syntax="${syntaxHighlighting ? "true" : "false"}" data-es-mermaid="${mermaid ? "true" : "false"}"` : "";
1210
+ const rootAttrs = interactive ? ` id="${ROOT_ID}" data-title="${escapeHtml(title)}"${islandConfigAttrs}` : "";
1211
+ return `<!DOCTYPE html>
1212
+ <html lang="${escapeHtml(lang)}" data-theme="${theme === "dark" ? "dark" : "light"}">
1213
+ <head>
1214
+ <meta charset="UTF-8">
1215
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1216
+ <meta name="color-scheme" content="light dark">
1217
+ <title>${escapeHtml(title)}</title>
1218
+ ${cdn.head}
1219
+ ${headExtra}
1220
+ <style>${css}</style>
1221
+ </head>
1222
+ <body>
1223
+ <div${rootAttrs} class="es-report-island font-sans text-foreground">${markup}</div>
1224
+ ${dataScript}
1225
+ ${bodyExtra}
1226
+ ${cdnBody}
1227
+ ${islandTag}
1228
+ </body>
1229
+ </html>`;
1230
+ }
1231
+ // Annotate the CommonJS export names for ESM import in node:
1232
+ 0 && (module.exports = {
1233
+ renderReportToHtml
1234
+ });
1235
+ //# sourceMappingURL=ssr.cjs.map