executable-stories-react 0.9.0 → 0.10.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 CHANGED
@@ -42,63 +42,6 @@ var import_react2 = require("react");
42
42
  var import_react = require("react");
43
43
  var ReportContext = (0, import_react.createContext)(null);
44
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
- // ../executable-stories-core/dist/utils/duration.js
79
- function formatDuration(ms) {
80
- if (ms < 1e3) {
81
- return `${Math.round(ms)} ms`;
82
- }
83
- if (ms < 6e4) {
84
- return `${(ms / 1e3).toFixed(2)} s`;
85
- }
86
- const minutes = Math.floor(ms / 6e4);
87
- const seconds = Math.floor(ms % 6e4 / 1e3);
88
- if (minutes < 60) {
89
- return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
90
- }
91
- const hours = Math.floor(minutes / 60);
92
- const remainingMinutes = minutes % 60;
93
- return `${hours}h ${remainingMinutes}m`;
94
- }
95
-
96
- // src/components/doc/DocNote.tsx
97
- var import_jsx_runtime2 = require("react/jsx-runtime");
98
- function DocNote({ entry }) {
99
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "my-2 text-sm text-muted-foreground", children: entry.text });
100
- }
101
-
102
45
  // src/components/ui/badge.tsx
103
46
  var React = require("react");
104
47
  var import_class_variance_authority = require("class-variance-authority");
@@ -157,14 +100,237 @@ function Badge({
157
100
  });
158
101
  }
159
102
 
160
- // src/components/doc/DocTag.tsx
103
+ // src/components/doc/NarrativeBlocks.tsx
104
+ var import_jsx_runtime = require("react/jsx-runtime");
105
+ var FILE_TREE_TYPE = "file-tree";
106
+ var DATA_MODEL_TYPE = "data-model";
107
+ function isChangeKind(value) {
108
+ return value === "added" || value === "modified" || value === "removed" || value === "renamed";
109
+ }
110
+ function isRecord(value) {
111
+ return typeof value === "object" && value !== null && !Array.isArray(value);
112
+ }
113
+ function str(value) {
114
+ return typeof value === "string" && value !== "" ? value : void 0;
115
+ }
116
+ function AuthoredMarker({ authored }) {
117
+ if (authored !== "agent") return null;
118
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { variant: "outline", className: "border-border text-[10px] text-muted-foreground", children: "AI-authored, not verified by a run" });
119
+ }
120
+ function BlockFrame({
121
+ title,
122
+ authored,
123
+ children
124
+ }) {
125
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("figure", { className: "my-3", children: [
126
+ title || authored === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("figcaption", { className: "mb-1.5 flex flex-wrap items-center gap-2", children: [
127
+ title ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-xs font-medium text-muted-foreground", children: title }) : null,
128
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AuthoredMarker, { authored })
129
+ ] }) : null,
130
+ children
131
+ ] });
132
+ }
133
+ function ChangeBadge({ change }) {
134
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { variant: "tag", className: "text-[10px]", children: change });
135
+ }
136
+ function parseFileTree(data) {
137
+ if (!isRecord(data) || !Array.isArray(data.files)) return void 0;
138
+ const files = [];
139
+ for (const raw of data.files) {
140
+ if (typeof raw === "string") {
141
+ if (raw) files.push({ path: raw });
142
+ continue;
143
+ }
144
+ if (!isRecord(raw)) continue;
145
+ const path = str(raw.path);
146
+ if (!path) continue;
147
+ files.push({
148
+ path,
149
+ change: isChangeKind(raw.change) ? raw.change : void 0,
150
+ note: str(raw.note)
151
+ });
152
+ }
153
+ if (files.length === 0) return void 0;
154
+ return { title: str(data.title), authored: str(data.authored), files };
155
+ }
156
+ function buildTree(files) {
157
+ const root = { name: "", children: /* @__PURE__ */ new Map() };
158
+ for (const file of files) {
159
+ const segments = file.path.split("/").filter(Boolean);
160
+ let node = root;
161
+ segments.forEach((segment, index) => {
162
+ let child = node.children.get(segment);
163
+ if (!child) {
164
+ child = { name: segment, children: /* @__PURE__ */ new Map() };
165
+ node.children.set(segment, child);
166
+ }
167
+ if (index === segments.length - 1) child.file = file;
168
+ node = child;
169
+ });
170
+ }
171
+ return root;
172
+ }
173
+ function sortedChildren(node) {
174
+ return [...node.children.values()].sort((a, b) => {
175
+ const aDir = a.children.size > 0;
176
+ const bDir = b.children.size > 0;
177
+ if (aDir !== bDir) return aDir ? -1 : 1;
178
+ return a.name.localeCompare(b.name);
179
+ });
180
+ }
181
+ function TreeBranch({ node, depth }) {
182
+ const children = sortedChildren(node);
183
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: cn("space-y-0.5", depth > 0 && "ml-3 border-l border-border/60 pl-3"), children: children.map((child) => {
184
+ const isDir = child.children.size > 0;
185
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
186
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-wrap items-baseline gap-2", children: [
187
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
188
+ "span",
189
+ {
190
+ className: cn(
191
+ "font-mono text-xs",
192
+ isDir ? "text-muted-foreground" : "text-foreground"
193
+ ),
194
+ children: isDir ? `${child.name}/` : child.name
195
+ }
196
+ ),
197
+ child.file?.change ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChangeBadge, { change: child.file.change }) : null,
198
+ child.file?.note ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-xs text-muted-foreground", children: child.file.note }) : null
199
+ ] }),
200
+ isDir ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TreeBranch, { node: child, depth: depth + 1 }) : null
201
+ ] }, child.name);
202
+ }) });
203
+ }
204
+ function FileTreeBlock({ entry }) {
205
+ const data = parseFileTree(entry.data);
206
+ if (!data) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UnreadableBlock, { entry });
207
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BlockFrame, { title: data.title ?? "Files changed", authored: data.authored, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { tabIndex: 0, className: "overflow-x-auto rounded-md border border-border bg-card p-3", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TreeBranch, { node: buildTree(data.files), depth: 0 }) }) });
208
+ }
209
+ function parseDataModel(data) {
210
+ if (!isRecord(data) || !Array.isArray(data.fields)) return void 0;
211
+ const fields = [];
212
+ for (const raw of data.fields) {
213
+ if (!isRecord(raw)) continue;
214
+ const name = str(raw.name);
215
+ if (!name) continue;
216
+ fields.push({
217
+ name,
218
+ type: str(raw.type),
219
+ note: str(raw.note),
220
+ change: isChangeKind(raw.change) ? raw.change : void 0
221
+ });
222
+ }
223
+ if (fields.length === 0) return void 0;
224
+ return {
225
+ title: str(data.title),
226
+ authored: str(data.authored),
227
+ name: str(data.name),
228
+ fields
229
+ };
230
+ }
231
+ function DataModelBlock({ entry }) {
232
+ const data = parseDataModel(entry.data);
233
+ if (!data) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UnreadableBlock, { entry });
234
+ const hasNotes = data.fields.some((f) => f.note);
235
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BlockFrame, { title: data.title ?? data.name ?? "Data model", authored: data.authored, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { tabIndex: 0, className: "overflow-x-auto rounded-md border border-border", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { className: "w-full border-collapse text-sm", children: [
236
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { className: "bg-muted/50", children: [
237
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: "Field" }),
238
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: "Type" }),
239
+ hasNotes ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: "Note" }) : null
240
+ ] }) }),
241
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: data.fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
242
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: "border-b border-border px-3 py-2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "flex flex-wrap items-center gap-2", children: [
243
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-mono text-xs text-foreground", children: field.name }),
244
+ field.change ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChangeBadge, { change: field.change }) : null
245
+ ] }) }),
246
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: "border-b border-border px-3 py-2 font-mono text-xs text-muted-foreground", children: field.type ?? "" }),
247
+ hasNotes ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: "border-b border-border px-3 py-2 text-foreground", children: field.note ?? "" }) : null
248
+ ] }, field.name)) })
249
+ ] }) }) });
250
+ }
251
+ function UnreadableBlock({ entry }) {
252
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "my-2", "data-type": entry.type, children: [
253
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: [
254
+ entry.type,
255
+ " (unrecognised shape)"
256
+ ] }),
257
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
258
+ "pre",
259
+ {
260
+ tabIndex: 0,
261
+ className: "overflow-x-auto rounded-md bg-muted p-3 font-mono text-xs text-foreground",
262
+ children: safeStringify(entry.data)
263
+ }
264
+ )
265
+ ] });
266
+ }
267
+ function safeStringify(value) {
268
+ try {
269
+ return JSON.stringify(value, null, 2);
270
+ } catch {
271
+ return String(value);
272
+ }
273
+ }
274
+ var narrativeBlockRenderers = {
275
+ [FILE_TREE_TYPE]: (entry) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileTreeBlock, { entry }),
276
+ [DATA_MODEL_TYPE]: (entry) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DataModelBlock, { entry })
277
+ };
278
+
279
+ // src/context/ReportRoot.tsx
280
+ var import_jsx_runtime2 = require("react/jsx-runtime");
281
+ var EMPTY_RENDERERS = {};
282
+ function ReportRoot({
283
+ report,
284
+ customRenderers,
285
+ renderers,
286
+ children
287
+ }) {
288
+ const value = (0, import_react2.useMemo)(
289
+ () => ({
290
+ report,
291
+ // Narrative blocks render everywhere the report does (static SSR, the
292
+ // island, Astro) without every host wiring them up. A host renderer for
293
+ // the same type still wins.
294
+ customRenderers: { ...narrativeBlockRenderers, ...customRenderers },
295
+ renderers: renderers ?? EMPTY_RENDERERS
296
+ }),
297
+ [report, customRenderers, renderers]
298
+ );
299
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ReportContext.Provider, { value, children });
300
+ }
301
+
302
+ // src/hooks/useReport.ts
303
+ var import_react3 = require("react");
304
+ function useReport() {
305
+ const ctx = (0, import_react3.useContext)(ReportContext);
306
+ if (!ctx) {
307
+ throw new Error(
308
+ "useReport must be used inside <ReportRoot> or <Report>. Wrap your tree with one of those."
309
+ );
310
+ }
311
+ return ctx.report;
312
+ }
313
+
314
+ // src/components/ReportScenario.tsx
315
+ var import_duration3 = require("executable-stories-core/utils/duration");
316
+
317
+ // src/components/ReportSteps.tsx
318
+ var import_duration = require("executable-stories-core/utils/duration");
319
+
320
+ // src/components/doc/DocNote.tsx
161
321
  var import_jsx_runtime3 = require("react/jsx-runtime");
322
+ function DocNote({ entry }) {
323
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "my-2 text-sm text-muted-foreground", children: entry.text });
324
+ }
325
+
326
+ // src/components/doc/DocTag.tsx
327
+ var import_jsx_runtime4 = require("react/jsx-runtime");
162
328
  function DocTag({ entry }) {
163
- 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)) });
329
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "my-2 flex flex-wrap gap-1.5", "aria-label": "Tags", children: entry.names.map((n) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Badge, { variant: "tag", children: n }) }, n)) });
164
330
  }
165
331
 
166
332
  // src/components/doc/DocKv.tsx
167
- var import_jsx_runtime4 = require("react/jsx-runtime");
333
+ var import_jsx_runtime5 = require("react/jsx-runtime");
168
334
  function formatValue(value) {
169
335
  if (value === null) return "null";
170
336
  if (typeof value === "string") return value;
@@ -176,19 +342,19 @@ function formatValue(value) {
176
342
  }
177
343
  }
178
344
  function DocKv({ entry }) {
179
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("dl", { className: "my-2 flex flex-wrap items-baseline gap-x-2 text-sm", children: [
180
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dt", { className: "font-semibold text-muted-foreground", children: entry.label }),
181
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dd", { className: "font-mono text-foreground", children: formatValue(entry.value) })
345
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("dl", { className: "my-2 flex flex-wrap items-baseline gap-x-2 text-sm", children: [
346
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dt", { className: "font-semibold text-muted-foreground", children: entry.label }),
347
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dd", { className: "font-mono text-foreground", children: formatValue(entry.value) })
182
348
  ] });
183
349
  }
184
350
 
185
351
  // src/hooks/useRenderers.ts
186
352
  var import_react4 = require("react");
187
- var EMPTY_CUSTOM2 = {};
353
+ var EMPTY_CUSTOM = {};
188
354
  var EMPTY_RENDERERS2 = {};
189
355
  function useCustomRenderers() {
190
356
  const ctx = (0, import_react4.useContext)(ReportContext);
191
- return ctx?.customRenderers ?? EMPTY_CUSTOM2;
357
+ return ctx?.customRenderers ?? EMPTY_CUSTOM;
192
358
  }
193
359
  function useBuiltinRenderers() {
194
360
  const ctx = (0, import_react4.useContext)(ReportContext);
@@ -196,37 +362,37 @@ function useBuiltinRenderers() {
196
362
  }
197
363
 
198
364
  // src/components/doc/DocCode.tsx
199
- var import_jsx_runtime5 = require("react/jsx-runtime");
365
+ var import_jsx_runtime6 = require("react/jsx-runtime");
200
366
  function CodeFigure({ label, children }) {
201
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("figure", { className: "my-3", children: [
202
- label ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: label }) : null,
203
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3", children })
367
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("figure", { className: "my-3", children: [
368
+ label ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: label }) : null,
369
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3", children })
204
370
  ] });
205
371
  }
206
372
  function DocCode({ entry }) {
207
373
  const renderers = useBuiltinRenderers();
208
374
  if (renderers.code) {
209
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: renderers.code(entry) });
375
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: renderers.code(entry) });
210
376
  }
211
- 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 }) });
377
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CodeFigure, { label: entry.label, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
212
378
  }
213
379
 
214
380
  // src/components/doc/DocTable.tsx
215
- var import_jsx_runtime6 = require("react/jsx-runtime");
381
+ var import_jsx_runtime7 = require("react/jsx-runtime");
216
382
  function DocTable({ entry }) {
217
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("figure", { className: "my-3", children: [
218
- entry.label ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
219
- /* @__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: [
220
- /* @__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)) }) }),
221
- /* @__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)) })
383
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("figure", { className: "my-3", children: [
384
+ entry.label ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
385
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { tabIndex: 0, className: "overflow-x-auto rounded-md border border-border", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("table", { className: "w-full border-collapse text-sm", children: [
386
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tr", { className: "bg-muted/50", children: entry.columns.map((c) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: c }, c)) }) }),
387
+ /* @__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", { className: "border-b border-border px-3 py-2 text-foreground", children: cell }, j)) }, i)) })
222
388
  ] }) })
223
389
  ] });
224
390
  }
225
391
 
226
392
  // src/components/doc/DocLink.tsx
227
- var import_jsx_runtime7 = require("react/jsx-runtime");
393
+ var import_jsx_runtime8 = require("react/jsx-runtime");
228
394
  function DocLink({ entry }) {
229
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
395
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
230
396
  "a",
231
397
  {
232
398
  className: "text-sm font-medium text-primary underline underline-offset-2 hover:no-underline",
@@ -241,28 +407,13 @@ function DocLink({ entry }) {
241
407
  // src/components/doc/DocSection.tsx
242
408
  var import_marked = require("marked");
243
409
 
244
- // ../executable-stories-core/dist/utils/url.js
245
- function safeUrl(value) {
246
- if (!value) return void 0;
247
- const trimmed = value.trim();
248
- const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(trimmed);
249
- if (scheme && !/^https?$/i.test(scheme[1] ?? "")) return void 0;
250
- return trimmed;
251
- }
252
- function safeImageUrl(value) {
253
- if (!value) return void 0;
254
- const trimmed = value.trim();
255
- if (/^data:image\//i.test(trimmed)) return trimmed;
256
- return safeUrl(trimmed);
257
- }
258
- function isLocalFsPath(value) {
259
- return /^(?:[/\\]|[A-Za-z]:[/\\])/.test(value);
260
- }
410
+ // src/lib/url.ts
411
+ var import_url = require("executable-stories-core/utils/url");
261
412
 
262
413
  // src/components/doc/DocSection.tsx
263
- var import_jsx_runtime8 = require("react/jsx-runtime");
414
+ var import_jsx_runtime9 = require("react/jsx-runtime");
264
415
  function neutralizeUrl(value) {
265
- return safeUrl(value) ?? "#";
416
+ return (0, import_url.safeUrl)(value) ?? "#";
266
417
  }
267
418
  function safeMarkdownHtml(markdown) {
268
419
  const raw = import_marked.marked.parse(markdown, { async: false });
@@ -271,12 +422,12 @@ function safeMarkdownHtml(markdown) {
271
422
  function DocSection({ entry }) {
272
423
  const renderers = useBuiltinRenderers();
273
424
  if (renderers.section) {
274
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_jsx_runtime8.Fragment, { children: renderers.section(entry) });
425
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_jsx_runtime9.Fragment, { children: renderers.section(entry) });
275
426
  }
276
427
  const html = safeMarkdownHtml(entry.markdown);
277
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("section", { className: "my-3 text-sm", "aria-label": entry.title, children: [
278
- entry.title ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h4", { className: "mb-1 font-semibold text-foreground", children: entry.title }) : null,
279
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
428
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("section", { className: "my-3 text-sm", "aria-label": entry.title, children: [
429
+ entry.title ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h4", { className: "mb-1 font-semibold text-foreground", children: entry.title }) : null,
430
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
280
431
  "div",
281
432
  {
282
433
  className: "es-doc-prose prose prose-sm max-w-none",
@@ -287,11 +438,11 @@ function DocSection({ entry }) {
287
438
  }
288
439
 
289
440
  // src/components/doc/DocMermaid.tsx
290
- var import_jsx_runtime9 = require("react/jsx-runtime");
441
+ var import_jsx_runtime10 = require("react/jsx-runtime");
291
442
  function MermaidSource({ entry, ref }) {
292
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("figure", { ref, className: "my-3", "aria-label": entry.title ?? "Diagram", children: [
293
- entry.title ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
294
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
443
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("figure", { ref, className: "my-3", "aria-label": entry.title ?? "Diagram", children: [
444
+ entry.title ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
445
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
295
446
  "pre",
296
447
  {
297
448
  "data-mermaid": true,
@@ -305,24 +456,24 @@ function MermaidSource({ entry, ref }) {
305
456
  function DocMermaid({ entry }) {
306
457
  const renderers = useBuiltinRenderers();
307
458
  if (renderers.mermaid) {
308
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_jsx_runtime9.Fragment, { children: renderers.mermaid(entry) });
459
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_jsx_runtime10.Fragment, { children: renderers.mermaid(entry) });
309
460
  }
310
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MermaidSource, { entry });
461
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MermaidSource, { entry });
311
462
  }
312
463
 
313
464
  // src/components/doc/DocScreenshot.tsx
314
- var import_jsx_runtime10 = require("react/jsx-runtime");
465
+ var import_jsx_runtime11 = require("react/jsx-runtime");
315
466
  function DocScreenshot({ entry }) {
316
- const src = isLocalFsPath(entry.path) ? void 0 : safeImageUrl(entry.path);
467
+ const src = (0, import_url.isLocalFsPath)(entry.path) ? void 0 : (0, import_url.safeImageUrl)(entry.path);
317
468
  if (!src) {
318
- 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: [
319
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "font-medium", children: "Screenshot unavailable" }),
320
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
321
- entry.alt ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("figcaption", { className: "mt-1.5 text-xs", children: entry.alt }) : null
469
+ 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: [
470
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "font-medium", children: "Screenshot unavailable" }),
471
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
472
+ entry.alt ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("figcaption", { className: "mt-1.5 text-xs", children: entry.alt }) : null
322
473
  ] });
323
474
  }
324
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("figure", { className: "my-3", children: [
325
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
475
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("figure", { className: "my-3", children: [
476
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
326
477
  "img",
327
478
  {
328
479
  src,
@@ -331,24 +482,24 @@ function DocScreenshot({ entry }) {
331
482
  className: "max-w-full rounded-md border border-border"
332
483
  }
333
484
  ),
334
- entry.alt ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: entry.alt }) : null
485
+ entry.alt ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: entry.alt }) : null
335
486
  ] });
336
487
  }
337
488
 
338
489
  // src/components/doc/DocVideo.tsx
339
- var import_jsx_runtime11 = require("react/jsx-runtime");
490
+ var import_jsx_runtime12 = require("react/jsx-runtime");
340
491
  function DocVideo({ entry }) {
341
- if (isLocalFsPath(entry.path)) {
342
- 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: [
343
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "font-medium", children: "Video unavailable" }),
344
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
345
- entry.caption ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("figcaption", { className: "mt-1.5 text-xs", children: entry.caption }) : null
492
+ if ((0, import_url.isLocalFsPath)(entry.path)) {
493
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("figure", { className: "my-3 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground", children: [
494
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "font-medium", children: "Video unavailable" }),
495
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
496
+ entry.caption ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("figcaption", { className: "mt-1.5 text-xs", children: entry.caption }) : null
346
497
  ] });
347
498
  }
348
- const src = safeUrl(entry.path);
349
- const poster = safeUrl(entry.poster);
350
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("figure", { className: "my-3", children: [
351
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
499
+ const src = (0, import_url.safeUrl)(entry.path);
500
+ const poster = (0, import_url.safeUrl)(entry.poster);
501
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("figure", { className: "my-3", children: [
502
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
352
503
  "video",
353
504
  {
354
505
  controls: true,
@@ -358,21 +509,21 @@ function DocVideo({ entry }) {
358
509
  className: "max-w-full rounded-md border border-border"
359
510
  }
360
511
  ),
361
- entry.caption ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: entry.caption }) : null
512
+ entry.caption ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: entry.caption }) : null
362
513
  ] });
363
514
  }
364
515
 
365
516
  // src/components/doc/DocHtml.tsx
366
- var import_jsx_runtime12 = require("react/jsx-runtime");
517
+ var import_jsx_runtime13 = require("react/jsx-runtime");
367
518
  function DocHtml({ entry }) {
368
519
  const height = typeof entry.height === "number" ? `${entry.height}px` : entry.height ?? "400px";
369
520
  const title = entry.title ?? "Embedded HTML";
370
- const safeHref = safeUrl(entry.url ?? entry.path);
521
+ const safeHref = (0, import_url.safeUrl)(entry.url ?? entry.path);
371
522
  const frameProps = entry.content !== void 0 ? { srcDoc: entry.content } : { src: safeHref ?? "" };
372
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("figure", { className: "my-3 overflow-hidden rounded-md border border-border", children: [
373
- /* @__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: [
374
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { children: entry.title ?? "HTML" }),
375
- safeHref ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
523
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("figure", { className: "my-3 overflow-hidden rounded-md border border-border", children: [
524
+ /* @__PURE__ */ (0, import_jsx_runtime13.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: [
525
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: entry.title ?? "HTML" }),
526
+ safeHref ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
376
527
  "a",
377
528
  {
378
529
  href: safeHref,
@@ -385,7 +536,7 @@ function DocHtml({ entry }) {
385
536
  }
386
537
  ) : null
387
538
  ] }),
388
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
539
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
389
540
  "iframe",
390
541
  {
391
542
  ...frameProps,
@@ -400,7 +551,7 @@ function DocHtml({ entry }) {
400
551
  }
401
552
 
402
553
  // src/components/doc/DocState.tsx
403
- var import_jsx_runtime13 = require("react/jsx-runtime");
554
+ var import_jsx_runtime14 = require("react/jsx-runtime");
404
555
  function DocState({ entry }) {
405
556
  let json;
406
557
  try {
@@ -408,23 +559,23 @@ function DocState({ entry }) {
408
559
  } catch {
409
560
  json = String(entry.value);
410
561
  }
411
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(CodeFigure, { label: entry.label ?? "State", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("code", { className: "font-mono text-xs language-json", children: json }) });
562
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(CodeFigure, { label: entry.label ?? "State", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("code", { className: "font-mono text-xs language-json", children: json }) });
412
563
  }
413
564
 
414
565
  // src/components/doc/DocCustom.tsx
415
- var import_jsx_runtime14 = require("react/jsx-runtime");
566
+ var import_jsx_runtime15 = require("react/jsx-runtime");
416
567
  function DocCustom({ entry }) {
417
568
  const renderers = useCustomRenderers();
418
569
  const renderer = renderers[entry.type];
419
570
  if (renderer) {
420
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_jsx_runtime14.Fragment, { children: renderer(entry) });
571
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_jsx_runtime15.Fragment, { children: renderer(entry) });
421
572
  }
422
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "my-2", "data-type": entry.type, children: [
423
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: entry.type }),
424
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3 font-mono text-xs text-foreground", children: safeStringify(entry.data) })
573
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "my-2", "data-type": entry.type, children: [
574
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: entry.type }),
575
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3 font-mono text-xs text-foreground", children: safeStringify2(entry.data) })
425
576
  ] });
426
577
  }
427
- function safeStringify(value) {
578
+ function safeStringify2(value) {
428
579
  try {
429
580
  return JSON.stringify(value, null, 2);
430
581
  } catch {
@@ -433,47 +584,47 @@ function safeStringify(value) {
433
584
  }
434
585
 
435
586
  // src/components/doc/DocEntry.tsx
436
- var import_jsx_runtime15 = require("react/jsx-runtime");
587
+ var import_jsx_runtime16 = require("react/jsx-runtime");
437
588
  function DocEntry({ entry }) {
438
589
  switch (entry.kind) {
439
590
  case "note":
440
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocNote, { entry });
591
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocNote, { entry });
441
592
  case "tag":
442
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocTag, { entry });
593
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocTag, { entry });
443
594
  case "kv":
444
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocKv, { entry });
595
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocKv, { entry });
445
596
  case "code":
446
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocCode, { entry });
597
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocCode, { entry });
447
598
  case "table":
448
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocTable, { entry });
599
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocTable, { entry });
449
600
  case "link":
450
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocLink, { entry });
601
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocLink, { entry });
451
602
  case "section":
452
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocSection, { entry });
603
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocSection, { entry });
453
604
  case "mermaid":
454
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocMermaid, { entry });
605
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocMermaid, { entry });
455
606
  case "screenshot":
456
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocScreenshot, { entry });
607
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocScreenshot, { entry });
457
608
  case "video":
458
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocVideo, { entry });
609
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocVideo, { entry });
459
610
  case "html":
460
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocHtml, { entry });
611
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocHtml, { entry });
461
612
  case "state":
462
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocState, { entry });
613
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocState, { entry });
463
614
  case "custom":
464
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocCustom, { entry });
615
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocCustom, { entry });
465
616
  }
466
617
  }
467
618
 
468
619
  // src/components/ReportDocEntries.tsx
469
- var import_jsx_runtime16 = require("react/jsx-runtime");
620
+ var import_jsx_runtime17 = require("react/jsx-runtime");
470
621
  function ReportDocEntries({ entries }) {
471
622
  if (entries.length === 0) return null;
472
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_jsx_runtime16.Fragment, { children: entries.map((entry, i) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocEntry, { entry }, i)) });
623
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_jsx_runtime17.Fragment, { children: entries.map((entry, i) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DocEntry, { entry }, i)) });
473
624
  }
474
625
 
475
626
  // src/lib/step-params.tsx
476
- var import_jsx_runtime17 = require("react/jsx-runtime");
627
+ var import_jsx_runtime18 = require("react/jsx-runtime");
477
628
  var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.-])\d+(?:\.\d+)?(?![\w.-])/g;
478
629
  function highlightStepParams(text) {
479
630
  const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
@@ -485,7 +636,7 @@ function highlightStepParams(text) {
485
636
  const end = start + match[0].length;
486
637
  if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
487
638
  nodes.push(
488
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "font-medium text-step-param italic", children: match[0] }, i)
639
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "font-medium text-step-param italic", children: match[0] }, i)
489
640
  );
490
641
  lastIndex = end;
491
642
  });
@@ -494,7 +645,7 @@ function highlightStepParams(text) {
494
645
  }
495
646
 
496
647
  // src/components/ReportSteps.tsx
497
- var import_jsx_runtime18 = require("react/jsx-runtime");
648
+ var import_jsx_runtime19 = require("react/jsx-runtime");
498
649
  var STEP_GLYPH = {
499
650
  passed: "\u2713",
500
651
  failed: "\u2717",
@@ -509,21 +660,21 @@ var STEP_GLYPH_COLOR = {
509
660
  };
510
661
  function ReportSteps({ scenario }) {
511
662
  if (scenario.steps.length === 0) return null;
512
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("ol", { "data-slot": "steps", className: "flex flex-col", children: scenario.steps.map((step) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ReportStepItem, { step }, step.id)) });
663
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("ol", { "data-slot": "steps", className: "flex flex-col", children: scenario.steps.map((step) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ReportStepItem, { step }, step.id)) });
513
664
  }
514
665
  function ReportStepItem({ step }) {
515
666
  const kw = step.keyword.toLowerCase();
516
667
  const continuation = kw === "and" || kw === "but";
517
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
668
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
518
669
  "li",
519
670
  {
520
671
  id: step.id,
521
672
  "data-status": step.status,
522
673
  className: cn("py-1.5 text-[0.8125rem] leading-normal", continuation && "pl-5"),
523
674
  children: [
524
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-baseline gap-2", children: [
525
- /* @__PURE__ */ (0, import_jsx_runtime18.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" }),
526
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
675
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-baseline gap-2", children: [
676
+ /* @__PURE__ */ (0, import_jsx_runtime19.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" }),
677
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
527
678
  "span",
528
679
  {
529
680
  className: cn(
@@ -533,10 +684,10 @@ function ReportStepItem({ step }) {
533
684
  children: step.keyword
534
685
  }
535
686
  ),
536
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "flex-1 text-foreground", children: highlightStepParams(step.text) }),
537
- step.durationMs > 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "shrink-0 whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(step.durationMs) }) : null
687
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "flex-1 text-foreground", children: highlightStepParams(step.text) }),
688
+ step.durationMs > 0 ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "shrink-0 whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: (0, import_duration.formatDuration)(step.durationMs) }) : null
538
689
  ] }),
539
- step.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
690
+ step.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
540
691
  "pre",
541
692
  {
542
693
  role: "alert",
@@ -544,132 +695,17 @@ function ReportStepItem({ step }) {
544
695
  children: step.errorMessage
545
696
  }
546
697
  ) : null,
547
- step.docEntries.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { "data-es-docs": true, className: "mt-2 ml-6", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ReportDocEntries, { entries: step.docEntries }) }) : null
698
+ step.docEntries.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { "data-es-docs": true, className: "mt-2 ml-6", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ReportDocEntries, { entries: step.docEntries }) }) : null
548
699
  ]
549
700
  }
550
701
  );
551
702
  }
552
703
 
553
- // ../executable-stories-core/dist/state-diff.js
554
- var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
555
- function deepEqual(a, b) {
556
- if (Object.is(a, b)) return true;
557
- if (Array.isArray(a) && Array.isArray(b)) {
558
- return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
559
- }
560
- if (isPlainObject(a) && isPlainObject(b)) {
561
- const ak = Object.keys(a);
562
- const bk = Object.keys(b);
563
- return ak.length === bk.length && ak.every((k) => k in b && deepEqual(a[k], b[k]));
564
- }
565
- return false;
566
- }
567
- var joinKey = (path, key) => path === "" ? key : `${path}.${key}`;
568
- function walk(path, before, after, out) {
569
- if (Object.is(before, after)) return;
570
- if (Array.isArray(before) && Array.isArray(after)) {
571
- const shared = Math.min(before.length, after.length);
572
- for (let i = 0; i < shared; i++) walk(`${path}[${i}]`, before[i], after[i], out);
573
- for (let i = shared; i < after.length; i++) out.push({ path: `${path}[${i}]`, kind: "added", after: after[i] });
574
- for (let i = shared; i < before.length; i++) out.push({ path: `${path}[${i}]`, kind: "removed", before: before[i] });
575
- return;
576
- }
577
- if (isPlainObject(before) && isPlainObject(after)) {
578
- for (const key of Object.keys(after)) {
579
- const p = joinKey(path, key);
580
- if (key in before) walk(p, before[key], after[key], out);
581
- else out.push({ path: p, kind: "added", after: after[key] });
582
- }
583
- for (const key of Object.keys(before)) {
584
- if (!(key in after)) out.push({ path: joinKey(path, key), kind: "removed", before: before[key] });
585
- }
586
- return;
587
- }
588
- if (!deepEqual(before, after)) out.push({ path, kind: "changed", before, after });
589
- }
590
- function diffStateValues(before, after) {
591
- const out = [];
592
- walk("", before, after, out);
593
- return out;
594
- }
595
- function formatStateValue(value, maxLen = 60) {
596
- const s = value === void 0 ? "undefined" : JSON.stringify(value);
597
- return s.length > maxLen ? `${s.slice(0, maxLen - 1)}\u2026` : s;
598
- }
599
- function summarizeStateChanges(changes) {
600
- return changes.map((c) => {
601
- const at = c.path === "" ? "(root)" : c.path;
602
- switch (c.kind) {
603
- case "added":
604
- return `+ ${at}: ${formatStateValue(c.after)}`;
605
- case "removed":
606
- return `\u2212 ${at}`;
607
- case "changed":
608
- return `${at}: ${formatStateValue(c.before)} \u2192 ${formatStateValue(c.after)}`;
609
- }
610
- });
611
- }
612
-
613
- // ../executable-stories-core/dist/storyboard.js
614
- function findScreenshot(entries) {
615
- for (const entry of entries) {
616
- if (entry.kind === "screenshot") return entry;
617
- if (entry.children) {
618
- const nested = findScreenshot(entry.children);
619
- if (nested) return nested;
620
- }
621
- }
622
- return void 0;
623
- }
624
- function findStates(entries, out = []) {
625
- for (const entry of entries) {
626
- if (entry.kind === "state") out.push(entry);
627
- if (entry.children) findStates(entry.children, out);
628
- }
629
- return out;
630
- }
631
- var laneOf = (label) => label ?? "";
632
- function extractStoryboardFrames(scenario) {
633
- const frames = [];
634
- const laneOrder = [];
635
- const previous = /* @__PURE__ */ new Map();
636
- for (const step of scenario.steps) {
637
- const shot = findScreenshot(step.docEntries);
638
- const stateDocs = findStates(step.docEntries);
639
- if (!shot && stateDocs.length === 0) continue;
640
- const byLane = /* @__PURE__ */ new Map();
641
- for (const doc of stateDocs) {
642
- const lane = laneOf(doc.label);
643
- if (!byLane.has(lane) && !laneOrder.includes(lane)) laneOrder.push(lane);
644
- byLane.set(lane, doc);
645
- }
646
- const states = [];
647
- for (const lane of laneOrder) {
648
- const doc = byLane.get(lane);
649
- if (!doc) continue;
650
- const card = {
651
- ...doc.label !== void 0 && { label: doc.label },
652
- value: doc.value
653
- };
654
- if (previous.has(lane)) card.changes = diffStateValues(previous.get(lane), doc.value);
655
- previous.set(lane, doc.value);
656
- states.push(card);
657
- }
658
- frames.push({
659
- stepId: step.id,
660
- stepIndex: step.index,
661
- keyword: step.keyword,
662
- text: step.text,
663
- status: step.status,
664
- ...shot && { screenshot: { path: shot.path, ...shot.alt !== void 0 && { alt: shot.alt } } },
665
- states
666
- });
667
- }
668
- return frames;
669
- }
670
-
671
704
  // src/components/ReportStoryboard.tsx
672
- var import_jsx_runtime19 = require("react/jsx-runtime");
705
+ var import_storyboard = require("executable-stories-core/storyboard");
706
+ var import_state_diff = require("executable-stories-core/state-diff");
707
+ var import_url6 = require("executable-stories-core/utils/url");
708
+ var import_jsx_runtime20 = require("react/jsx-runtime");
673
709
  var FRAME_GLYPH = {
674
710
  passed: "\u2713",
675
711
  failed: "\u2717",
@@ -683,48 +719,48 @@ var FRAME_GLYPH_COLOR = {
683
719
  pending: "text-pend"
684
720
  };
685
721
  function ReportStoryboard({ scenario }) {
686
- const frames = extractStoryboardFrames(scenario);
722
+ const frames = (0, import_storyboard.extractStoryboardFrames)(scenario);
687
723
  if (frames.length < 2) return null;
688
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("section", { "data-slot": "storyboard", "aria-label": "Storyboard", className: "mb-3", children: [
689
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h4", { className: "mb-1.5 text-xs font-semibold tracking-wide text-muted-foreground uppercase", children: "Storyboard" }),
690
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("ol", { className: "flex items-start gap-3 overflow-x-auto pb-2", children: frames.map((frame) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(StoryboardFrameItem, { frame }, frame.stepId)) })
724
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("section", { "data-slot": "storyboard", "aria-label": "Storyboard", className: "mb-3", children: [
725
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("h4", { className: "mb-1.5 text-xs font-semibold tracking-wide text-muted-foreground uppercase", children: "Storyboard" }),
726
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("ol", { className: "flex items-start gap-3 overflow-x-auto pb-2", children: frames.map((frame) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(StoryboardFrameItem, { frame }, frame.stepId)) })
691
727
  ] });
692
728
  }
693
729
  function StoryboardFrameItem({ frame }) {
694
730
  const label = `${frame.keyword} ${frame.text}`;
695
731
  const hasStates = frame.states.length > 0;
696
- const caption = /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("p", { className: "mt-1 flex items-baseline gap-1.5 text-[0.6875rem] leading-snug", children: [
697
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { "aria-hidden": true, className: cn("shrink-0", FRAME_GLYPH_COLOR[frame.status]), children: FRAME_GLYPH[frame.status] ?? "\u25CB" }),
698
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { className: "min-w-0", children: [
699
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "font-mono font-semibold text-keyword", children: frame.keyword }),
732
+ const caption = /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("p", { className: "mt-1 flex items-baseline gap-1.5 text-[0.6875rem] leading-snug", children: [
733
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { "aria-hidden": true, className: cn("shrink-0", FRAME_GLYPH_COLOR[frame.status]), children: FRAME_GLYPH[frame.status] ?? "\u25CB" }),
734
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "min-w-0", children: [
735
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "font-mono font-semibold text-keyword", children: frame.keyword }),
700
736
  " ",
701
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-muted-foreground", children: frame.screenshot?.alt ?? frame.text })
737
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "text-muted-foreground", children: frame.screenshot?.alt ?? frame.text })
702
738
  ] })
703
739
  ] });
704
740
  return (
705
741
  // State cards need room for diff rows; screenshot-only frames keep the
706
742
  // original narrow film-cell width (and DOM) unchanged.
707
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("li", { className: cn("shrink-0", hasStates ? "w-64" : "w-40"), children: [
708
- frame.screenshot ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(FrameScreenshot, { frame, label }) : null,
709
- hasStates ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: cn("flex flex-col gap-1.5", frame.screenshot && "mt-1.5"), children: frame.states.map((card, i) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(StateCardView, { card }, card.label ?? i)) }) : null,
743
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("li", { className: cn("shrink-0", hasStates ? "w-64" : "w-40"), children: [
744
+ frame.screenshot ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(FrameScreenshot, { frame, label }) : null,
745
+ hasStates ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: cn("flex flex-col gap-1.5", frame.screenshot && "mt-1.5"), children: frame.states.map((card, i) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(StateCardView, { card }, card.label ?? i)) }) : null,
710
746
  frame.screenshot ? caption : (
711
747
  // No screenshot means no image anchor, so the caption carries the
712
748
  // jump-to-step link instead.
713
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("a", { href: `#${frame.stepId}`, "aria-label": `Jump to step: ${label}`, className: "block hover:underline", children: caption })
749
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("a", { href: `#${frame.stepId}`, "aria-label": `Jump to step: ${label}`, className: "block hover:underline", children: caption })
714
750
  )
715
751
  ] })
716
752
  );
717
753
  }
718
754
  function FrameScreenshot({ frame, label }) {
719
755
  const path = frame.screenshot?.path ?? "";
720
- const src = isLocalFsPath(path) ? void 0 : safeImageUrl(path);
721
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
756
+ const src = (0, import_url6.isLocalFsPath)(path) ? void 0 : (0, import_url6.safeImageUrl)(path);
757
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
722
758
  "a",
723
759
  {
724
760
  href: `#${frame.stepId}`,
725
761
  "aria-label": `Jump to step: ${label}`,
726
762
  className: "block overflow-hidden rounded-md border border-border transition-colors hover:border-ring",
727
- children: src ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
763
+ children: src ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
728
764
  "img",
729
765
  {
730
766
  src,
@@ -732,7 +768,7 @@ function FrameScreenshot({ frame, label }) {
732
768
  loading: "lazy",
733
769
  className: "h-24 w-full bg-muted object-cover object-top"
734
770
  }
735
- ) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "flex h-24 w-full items-center justify-center border-b border-dashed border-border bg-muted px-2 text-center text-[0.6875rem] text-muted-foreground", children: "Screenshot unavailable" })
771
+ ) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "flex h-24 w-full items-center justify-center border-b border-dashed border-border bg-muted px-2 text-center text-[0.6875rem] text-muted-foreground", children: "Screenshot unavailable" })
736
772
  }
737
773
  );
738
774
  }
@@ -751,30 +787,30 @@ function prettyJson(value) {
751
787
  function StateCardView({ card }) {
752
788
  const json = prettyJson(card.value);
753
789
  const firstCapture = card.changes === void 0;
754
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { "data-slot": "state-card", className: "rounded-md border border-border bg-card p-2", children: [
755
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("p", { className: "flex flex-wrap items-baseline gap-1.5", children: [
756
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "inline-flex rounded-full border border-border px-1.5 font-mono text-[0.625rem] font-medium text-muted-foreground", children: card.label ?? "State" }),
757
- firstCapture ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-[0.625rem] text-muted-foreground italic", children: "first capture" }) : null
790
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { "data-slot": "state-card", className: "rounded-md border border-border bg-card p-2", children: [
791
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("p", { className: "flex flex-wrap items-baseline gap-1.5", children: [
792
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "inline-flex rounded-full border border-border px-1.5 font-mono text-[0.625rem] font-medium text-muted-foreground", children: card.label ?? "State" }),
793
+ firstCapture ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "text-[0.625rem] text-muted-foreground italic", children: "first capture" }) : null
758
794
  ] }),
759
- !firstCapture && (card.changes && card.changes.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("ul", { className: "mt-1.5 flex flex-col gap-0.5", children: card.changes.map((change, i) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
795
+ !firstCapture && (card.changes && card.changes.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("ul", { className: "mt-1.5 flex flex-col gap-0.5", children: card.changes.map((change, i) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
760
796
  "li",
761
797
  {
762
798
  className: cn(
763
799
  "rounded-sm px-1.5 py-0.5 font-mono text-[0.625rem] leading-snug break-all",
764
800
  CHANGE_TONE[change.kind]
765
801
  ),
766
- children: summarizeStateChanges([change])[0]
802
+ children: (0, import_state_diff.summarizeStateChanges)([change])[0]
767
803
  },
768
804
  i
769
- )) }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "mt-1.5 text-[0.625rem] text-muted-foreground", children: "No changes" })),
770
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
805
+ )) }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "mt-1.5 text-[0.625rem] text-muted-foreground", children: "No changes" })),
806
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
771
807
  "details",
772
808
  {
773
809
  open: firstCapture && json.length <= 240,
774
810
  className: cn(firstCapture ? "mt-1.5" : "mt-1"),
775
811
  children: [
776
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("summary", { className: "cursor-pointer text-[0.625rem] text-muted-foreground hover:text-foreground", children: "Snapshot" }),
777
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("pre", { tabIndex: 0, className: "mt-1 max-h-48 overflow-auto rounded-sm bg-muted p-1.5 font-mono text-[0.625rem] leading-snug", children: json })
812
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("summary", { className: "cursor-pointer text-[0.625rem] text-muted-foreground hover:text-foreground", children: "Snapshot" }),
813
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("pre", { tabIndex: 0, className: "mt-1 max-h-48 overflow-auto rounded-sm bg-muted p-1.5 font-mono text-[0.625rem] leading-snug", children: json })
778
814
  ]
779
815
  }
780
816
  )
@@ -782,17 +818,17 @@ function StateCardView({ card }) {
782
818
  }
783
819
 
784
820
  // src/components/ReportAttachments.tsx
785
- var import_jsx_runtime20 = require("react/jsx-runtime");
821
+ var import_jsx_runtime21 = require("react/jsx-runtime");
786
822
  function ReportAttachments({ attachments }) {
787
823
  if (attachments.length === 0) return null;
788
824
  return (
789
825
  // role="group" (not a labelled <section>) so multiple scenarios' attachment
790
826
  // blocks don't create duplicate "Attachments" region landmarks.
791
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { role: "group", "aria-label": "Attachments", className: "mt-3 flex flex-col gap-2", children: attachments.map((att, i) => {
827
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { role: "group", "aria-label": "Attachments", className: "mt-3 flex flex-col gap-2", children: attachments.map((att, i) => {
792
828
  const dataUri = att.contentEncoding === "BASE64" ? `data:${att.mediaType};base64,${att.body}` : `data:${att.mediaType},${encodeURIComponent(att.body)}`;
793
829
  const isImage = att.mediaType.startsWith("image/");
794
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("figure", { className: "m-0", children: isImage ? /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
795
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
830
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("figure", { className: "m-0", children: isImage ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
831
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
796
832
  "img",
797
833
  {
798
834
  src: dataUri,
@@ -801,8 +837,8 @@ function ReportAttachments({ attachments }) {
801
837
  className: "max-w-full rounded-md border border-border"
802
838
  }
803
839
  ),
804
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: att.name })
805
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
840
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: att.name })
841
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
806
842
  "a",
807
843
  {
808
844
  href: dataUri,
@@ -811,7 +847,7 @@ function ReportAttachments({ attachments }) {
811
847
  children: [
812
848
  att.name,
813
849
  " ",
814
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "text-muted-foreground", children: [
850
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "text-muted-foreground", children: [
815
851
  "(",
816
852
  att.mediaType,
817
853
  ")"
@@ -823,6 +859,9 @@ function ReportAttachments({ attachments }) {
823
859
  );
824
860
  }
825
861
 
862
+ // src/components/ReportTrace.tsx
863
+ var import_duration2 = require("executable-stories-core/utils/duration");
864
+
826
865
  // src/lib/trace.ts
827
866
  var VALID = /* @__PURE__ */ new Set(["ok", "error", "unset"]);
828
867
  var safeStatus = (s) => VALID.has(s) ? s : "unset";
@@ -875,15 +914,15 @@ function toTraceRows(spans) {
875
914
  roots.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
876
915
  const rows = [];
877
916
  const seen = /* @__PURE__ */ new Set();
878
- const walk2 = (node, depth) => {
917
+ const walk = (node, depth) => {
879
918
  if (seen.has(node.span.spanId)) return;
880
919
  seen.add(node.span.spanId);
881
920
  rows.push({ ...node.span, depth });
882
- for (const child of node.children) walk2(child, depth + 1);
921
+ for (const child of node.children) walk(child, depth + 1);
883
922
  };
884
- for (const root of roots) walk2(root, 0);
923
+ for (const root of roots) walk(root, 0);
885
924
  for (const node of byId.values()) {
886
- if (!seen.has(node.span.spanId)) walk2(node, 0);
925
+ if (!seen.has(node.span.spanId)) walk(node, 0);
887
926
  }
888
927
  return rows;
889
928
  }
@@ -899,7 +938,7 @@ function traceWindow(rows) {
899
938
  }
900
939
 
901
940
  // src/components/ReportTrace.tsx
902
- var import_jsx_runtime21 = require("react/jsx-runtime");
941
+ var import_jsx_runtime22 = require("react/jsx-runtime");
903
942
  var BAR_COLOR = {
904
943
  ok: "bg-pass",
905
944
  error: "bg-fail",
@@ -910,21 +949,21 @@ function ReportTrace({ spans }) {
910
949
  const rows = toTraceRows(spans);
911
950
  if (rows.length === 0) return null;
912
951
  const { minStartMs, totalMs } = traceWindow(rows);
913
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("details", { className: "mt-3 rounded-md border border-border bg-muted/30", children: [
914
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("summary", { className: "cursor-pointer px-3 py-2 text-xs font-medium text-muted-foreground", children: [
952
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("details", { className: "mt-3 rounded-md border border-border bg-muted/30", children: [
953
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("summary", { className: "cursor-pointer px-3 py-2 text-xs font-medium text-muted-foreground", children: [
915
954
  "Trace (",
916
955
  rows.length,
917
956
  " span",
918
957
  rows.length === 1 ? "" : "s",
919
958
  ", ",
920
- formatDuration(totalMs),
959
+ (0, import_duration2.formatDuration)(totalMs),
921
960
  ")"
922
961
  ] }),
923
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("ul", { className: "flex flex-col gap-0.5 px-3 pb-3 font-mono text-[0.6875rem]", children: rows.map((row, i) => {
962
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("ul", { className: "flex flex-col gap-0.5 px-3 pb-3 font-mono text-[0.6875rem]", children: rows.map((row, i) => {
924
963
  const leftPct = (row.startTimeMs - minStartMs) / totalMs * 100;
925
964
  const widthPct = Math.max(row.durationMs / totalMs * 100, 0.5);
926
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("li", { className: "flex items-center gap-2", children: [
927
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
965
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("li", { className: "flex items-center gap-2", children: [
966
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
928
967
  "span",
929
968
  {
930
969
  className: "min-w-0 flex-1 truncate text-foreground",
@@ -933,14 +972,14 @@ function ReportTrace({ spans }) {
933
972
  children: row.name
934
973
  }
935
974
  ),
936
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "relative h-3 w-1/2 shrink-0 overflow-hidden rounded-sm bg-border/40", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
975
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "relative h-3 w-1/2 shrink-0 overflow-hidden rounded-sm bg-border/40", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
937
976
  "span",
938
977
  {
939
978
  className: cn("absolute top-0 h-full rounded-sm", BAR_COLOR[row.status]),
940
979
  style: { left: `${leftPct}%`, width: `${widthPct}%` }
941
980
  }
942
981
  ) }),
943
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: formatDuration(row.durationMs) })
982
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: (0, import_duration2.formatDuration)(row.durationMs) })
944
983
  ] }, `${row.spanId}-${i}`);
945
984
  }) })
946
985
  ] });
@@ -991,7 +1030,7 @@ function flakinessOf(entries) {
991
1030
  }
992
1031
 
993
1032
  // src/components/ScenarioRunHistory.tsx
994
- var import_jsx_runtime22 = require("react/jsx-runtime");
1033
+ var import_jsx_runtime23 = require("react/jsx-runtime");
995
1034
  var MAX_DOTS = 10;
996
1035
  var DOT_COLOR = {
997
1036
  passed: "bg-pass",
@@ -1011,16 +1050,16 @@ function ScenarioRunHistory({ scenarioId }) {
1011
1050
  const recent = entries.slice(-MAX_DOTS);
1012
1051
  const flaky = flakinessOf(recent) === "flaky";
1013
1052
  const summary = flaky ? `${describeRunHistory(recent)} \xB7 Flaky` : describeRunHistory(recent);
1014
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
1015
- flaky ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Badge, { variant: "pending", "aria-label": "Flaky: status flips between recent runs", title: summary, children: "Flaky" }) : null,
1016
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1053
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
1054
+ flaky ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Badge, { variant: "pending", "aria-label": "Flaky: status flips between recent runs", title: summary, children: "Flaky" }) : null,
1055
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1017
1056
  "span",
1018
1057
  {
1019
1058
  role: "img",
1020
1059
  "aria-label": `Run history: ${summary}`,
1021
1060
  title: summary,
1022
1061
  className: "flex shrink-0 items-center gap-[3px]",
1023
- children: recent.map((e, i) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1062
+ children: recent.map((e, i) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1024
1063
  "span",
1025
1064
  {
1026
1065
  "aria-hidden": true,
@@ -1051,6 +1090,7 @@ function useCollapse() {
1051
1090
 
1052
1091
  // src/interactive/scenario-actions.tsx
1053
1092
  var import_react7 = require("react");
1093
+ var import_scenario_markdown = require("executable-stories-core/utils/scenario-markdown");
1054
1094
  var ScenarioActionsContext = (0, import_react7.createContext)(null);
1055
1095
  var ScenarioActionsProvider = ScenarioActionsContext.Provider;
1056
1096
  function useScenarioActions() {
@@ -1059,9 +1099,9 @@ function useScenarioActions() {
1059
1099
 
1060
1100
  // src/components/ui/card.tsx
1061
1101
  var React2 = require("react");
1062
- var import_jsx_runtime23 = require("react/jsx-runtime");
1102
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1063
1103
  function Card({ className, ...props }) {
1064
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1104
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1065
1105
  "div",
1066
1106
  {
1067
1107
  "data-slot": "card",
@@ -1074,7 +1114,7 @@ function Card({ className, ...props }) {
1074
1114
  );
1075
1115
  }
1076
1116
  function CardHeader({ className, ...props }) {
1077
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1117
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1078
1118
  "div",
1079
1119
  {
1080
1120
  "data-slot": "card-header",
@@ -1087,7 +1127,7 @@ function CardHeader({ className, ...props }) {
1087
1127
  );
1088
1128
  }
1089
1129
  function CardContent({ className, ...props }) {
1090
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1130
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1091
1131
  "div",
1092
1132
  {
1093
1133
  "data-slot": "card-content",
@@ -1101,13 +1141,13 @@ function CardContent({ className, ...props }) {
1101
1141
  var React3 = require("react");
1102
1142
  var import_menu = require("@base-ui/react/menu");
1103
1143
  var import_lucide_react = require("lucide-react");
1104
- var import_jsx_runtime24 = require("react/jsx-runtime");
1144
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1105
1145
  var itemClasses = "relative flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 [&_svg:not([class*='text-'])]:text-muted-foreground";
1106
1146
  function DropdownMenu(props) {
1107
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_menu.Menu.Root, { "data-slot": "dropdown-menu", ...props });
1147
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_menu.Menu.Root, { "data-slot": "dropdown-menu", ...props });
1108
1148
  }
1109
1149
  function DropdownMenuTrigger(props) {
1110
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_menu.Menu.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
1150
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_menu.Menu.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
1111
1151
  }
1112
1152
  function DropdownMenuContent({
1113
1153
  className,
@@ -1115,14 +1155,14 @@ function DropdownMenuContent({
1115
1155
  sideOffset = 4,
1116
1156
  ...props
1117
1157
  }) {
1118
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_menu.Menu.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1158
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_menu.Menu.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1119
1159
  import_menu.Menu.Positioner,
1120
1160
  {
1121
1161
  "data-slot": "dropdown-menu-positioner",
1122
1162
  className: "z-50",
1123
1163
  align,
1124
1164
  sideOffset,
1125
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1165
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1126
1166
  import_menu.Menu.Popup,
1127
1167
  {
1128
1168
  "data-slot": "dropdown-menu-content",
@@ -1140,7 +1180,7 @@ function DropdownMenuItem({
1140
1180
  className,
1141
1181
  ...props
1142
1182
  }) {
1143
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1183
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1144
1184
  import_menu.Menu.Item,
1145
1185
  {
1146
1186
  "data-slot": "dropdown-menu-item",
@@ -1152,7 +1192,7 @@ function DropdownMenuItem({
1152
1192
 
1153
1193
  // src/components/ReportScenario.tsx
1154
1194
  var import_lucide_react2 = require("lucide-react");
1155
- var import_jsx_runtime25 = require("react/jsx-runtime");
1195
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1156
1196
  var STATUS_META = {
1157
1197
  passed: { label: "Passed", glyph: "\u2713", glyphColor: "text-pass", badgeVariant: "passed", showPill: false },
1158
1198
  failed: { label: "Failed", glyph: "\u2717", glyphColor: "text-fail", badgeVariant: "failed", showPill: true },
@@ -1168,7 +1208,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
1168
1208
  const collapsible = collapse !== null && !hideTitle;
1169
1209
  const collapsed = collapsible ? collapse.isCollapsed(scenario.id) : false;
1170
1210
  const actions = useScenarioActions();
1171
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
1211
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
1172
1212
  Card,
1173
1213
  {
1174
1214
  id: scenario.id,
@@ -1176,10 +1216,10 @@ function ReportScenario({ scenario, hideTitle = false }) {
1176
1216
  ...hideTitle ? { "aria-label": `${scenario.title}, ${label}` } : { "aria-labelledby": titleId },
1177
1217
  className: "gap-0 py-0",
1178
1218
  children: [
1179
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(CardHeader, { className: "px-4 py-3", children: [
1180
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
1181
- hideTitle ? null : /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1182
- collapsible ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1219
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(CardHeader, { className: "px-4 py-3", children: [
1220
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
1221
+ hideTitle ? null : /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1222
+ collapsible ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1183
1223
  "button",
1184
1224
  {
1185
1225
  type: "button",
@@ -1188,49 +1228,49 @@ function ReportScenario({ scenario, hideTitle = false }) {
1188
1228
  "aria-controls": bodyId,
1189
1229
  "aria-label": `Toggle ${scenario.title}`,
1190
1230
  className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
1191
- children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1231
+ children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1192
1232
  }
1193
1233
  ) : null,
1194
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { "aria-hidden": true, className: cn("text-sm leading-none", meta.glyphColor), children: meta.glyph }),
1195
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { children: scenario.title })
1234
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { "aria-hidden": true, className: cn("text-sm leading-none", meta.glyphColor), children: meta.glyph }),
1235
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { children: scenario.title })
1196
1236
  ] }),
1197
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex shrink-0 items-center gap-2", children: [
1198
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ScenarioRunHistory, { scenarioId: scenario.id }),
1199
- scenario.durationMs > 0 ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(scenario.durationMs) }) : null,
1200
- meta.showPill ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(Badge, { variant: meta.badgeVariant, "aria-label": `Status: ${label}`, children: label }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "sr-only", children: `Status: ${label}` }),
1201
- actions ? /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(DropdownMenu, { children: [
1202
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1237
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex shrink-0 items-center gap-2", children: [
1238
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ScenarioRunHistory, { scenarioId: scenario.id }),
1239
+ scenario.durationMs > 0 ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: (0, import_duration3.formatDuration)(scenario.durationMs) }) : null,
1240
+ meta.showPill ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Badge, { variant: meta.badgeVariant, "aria-label": `Status: ${label}`, children: label }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "sr-only", children: `Status: ${label}` }),
1241
+ actions ? /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenu, { children: [
1242
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1203
1243
  DropdownMenuTrigger,
1204
1244
  {
1205
1245
  "aria-label": `Actions for ${scenario.title}`,
1206
1246
  className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
1207
- children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react2.MoreHorizontalIcon, { "aria-hidden": true })
1247
+ children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react2.MoreHorizontalIcon, { "aria-hidden": true })
1208
1248
  }
1209
1249
  ),
1210
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(DropdownMenuContent, { align: "end", children: [
1211
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
1212
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react2.LinkIcon, {}),
1250
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenuContent, { align: "end", children: [
1251
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
1252
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react2.LinkIcon, {}),
1213
1253
  " Copy link"
1214
1254
  ] }),
1215
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
1216
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react2.FileTextIcon, {}),
1255
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
1256
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react2.FileTextIcon, {}),
1217
1257
  " Copy as Markdown"
1218
1258
  ] }),
1219
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
1220
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react2.SparklesIcon, {}),
1259
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
1260
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react2.SparklesIcon, {}),
1221
1261
  " Explain with AI"
1222
1262
  ] })
1223
1263
  ] })
1224
1264
  ] }) : null
1225
1265
  ] })
1226
1266
  ] }),
1227
- scenario.tags.length > 0 || (scenario.tickets?.length ?? 0) > 0 ? /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("ul", { "aria-label": "Tags and tickets", className: "flex flex-wrap gap-1.5", children: [
1228
- scenario.tags.map((t) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
1229
- (scenario.tickets ?? []).map((ticket) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("li", { children: ticket.url ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("a", { href: ticket.url, target: "_blank", rel: "noreferrer noopener", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(Badge, { variant: "outline", children: ticket.id }) }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(Badge, { variant: "outline", children: ticket.id }) }, `ticket-${ticket.id}`))
1267
+ scenario.tags.length > 0 || (scenario.tickets?.length ?? 0) > 0 ? /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("ul", { "aria-label": "Tags and tickets", className: "flex flex-wrap gap-1.5", children: [
1268
+ scenario.tags.map((t) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
1269
+ (scenario.tickets ?? []).map((ticket) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("li", { children: ticket.url ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("a", { href: ticket.url, target: "_blank", rel: "noreferrer noopener", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Badge, { variant: "outline", children: ticket.id }) }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Badge, { variant: "outline", children: ticket.id }) }, `ticket-${ticket.id}`))
1230
1270
  ] }) : null
1231
1271
  ] }),
1232
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
1233
- scenario.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1272
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
1273
+ scenario.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1234
1274
  "pre",
1235
1275
  {
1236
1276
  role: "alert",
@@ -1238,11 +1278,11 @@ function ReportScenario({ scenario, hideTitle = false }) {
1238
1278
  children: scenario.errorMessage
1239
1279
  }
1240
1280
  ) : null,
1241
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ReportStoryboard, { scenario }),
1242
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ReportSteps, { scenario }),
1243
- scenario.docEntries.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { "data-es-docs": true, className: "mt-3", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
1244
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ReportAttachments, { attachments: scenario.attachments }),
1245
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ReportTrace, { spans: scenario.otelSpans })
1281
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ReportStoryboard, { scenario }),
1282
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ReportSteps, { scenario }),
1283
+ scenario.docEntries.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { "data-es-docs": true, className: "mt-3", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
1284
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ReportAttachments, { attachments: scenario.attachments }),
1285
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ReportTrace, { spans: scenario.otelSpans })
1246
1286
  ] })
1247
1287
  ]
1248
1288
  }
@@ -1250,13 +1290,13 @@ function ReportScenario({ scenario, hideTitle = false }) {
1250
1290
  }
1251
1291
 
1252
1292
  // src/components/ReportScenarioList.tsx
1253
- var import_jsx_runtime26 = require("react/jsx-runtime");
1293
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1254
1294
  function ReportScenarioList({ feature }) {
1255
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_jsx_runtime26.Fragment, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ReportScenario, { scenario }, scenario.id)) });
1295
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_jsx_runtime27.Fragment, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ReportScenario, { scenario }, scenario.id)) });
1256
1296
  }
1257
1297
 
1258
1298
  // src/components/ReportFeature.tsx
1259
- var import_jsx_runtime27 = require("react/jsx-runtime");
1299
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1260
1300
  function ReportFeature({ feature }) {
1261
1301
  const titleId = `${feature.id}-title`;
1262
1302
  const bodyId = `${feature.id}-body`;
@@ -1267,7 +1307,7 @@ function ReportFeature({ feature }) {
1267
1307
  return (
1268
1308
  // Suite = a section heading + a divider, NOT another bordered card. Drops a
1269
1309
  // layer of nesting so the scenario cards below read as the primary unit.
1270
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
1310
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1271
1311
  "section",
1272
1312
  {
1273
1313
  id: feature.id,
@@ -1275,9 +1315,9 @@ function ReportFeature({ feature }) {
1275
1315
  "aria-labelledby": titleId,
1276
1316
  className: "flex flex-col gap-3",
1277
1317
  children: [
1278
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1279
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "min-w-0", children: [
1280
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("h2", { id: titleId, className: "text-base font-semibold tracking-tight text-foreground", children: collapse ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
1318
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1319
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "min-w-0", children: [
1320
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("h2", { id: titleId, className: "text-base font-semibold tracking-tight text-foreground", children: collapse ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1281
1321
  "button",
1282
1322
  {
1283
1323
  type: "button",
@@ -1286,31 +1326,31 @@ function ReportFeature({ feature }) {
1286
1326
  "aria-controls": bodyId,
1287
1327
  className: "flex w-full cursor-pointer items-center gap-2 text-left hover:text-foreground/80",
1288
1328
  children: [
1289
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { "aria-hidden": true, className: cn("shrink-0 text-xs text-muted-foreground transition-transform", !collapsed && "rotate-90"), children: "\u25B8" }),
1290
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "truncate", children: feature.title })
1329
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { "aria-hidden": true, className: cn("shrink-0 text-xs text-muted-foreground transition-transform", !collapsed && "rotate-90"), children: "\u25B8" }),
1330
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "truncate", children: feature.title })
1291
1331
  ]
1292
1332
  }
1293
1333
  ) : feature.title }),
1294
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { className: cn("mt-0.5 truncate font-mono text-xs text-muted-foreground", collapse && "pl-5"), children: feature.sourceFile })
1334
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("p", { className: cn("mt-0.5 truncate font-mono text-xs text-muted-foreground", collapse && "pl-5"), children: feature.sourceFile })
1295
1335
  ] }),
1296
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
1336
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1297
1337
  "p",
1298
1338
  {
1299
1339
  className: "flex shrink-0 items-center gap-1.5 text-xs text-muted-foreground",
1300
1340
  "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
1301
1341
  children: [
1302
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { children: [
1342
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { children: [
1303
1343
  s.passed,
1304
1344
  " passed"
1305
1345
  ] }),
1306
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1307
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
1346
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1347
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
1308
1348
  s.failed,
1309
1349
  " failed"
1310
1350
  ] }),
1311
- skipped > 0 ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
1312
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1313
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { children: [
1351
+ skipped > 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
1352
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1353
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { children: [
1314
1354
  skipped,
1315
1355
  " skipped"
1316
1356
  ] })
@@ -1319,7 +1359,7 @@ function ReportFeature({ feature }) {
1319
1359
  }
1320
1360
  )
1321
1361
  ] }),
1322
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-3", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ReportScenarioList, { feature }) })
1362
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-3", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ReportScenarioList, { feature }) })
1323
1363
  ]
1324
1364
  }
1325
1365
  )
@@ -1327,17 +1367,17 @@ function ReportFeature({ feature }) {
1327
1367
  }
1328
1368
 
1329
1369
  // src/components/ReportFeatureList.tsx
1330
- var import_jsx_runtime28 = require("react/jsx-runtime");
1370
+ var import_jsx_runtime29 = require("react/jsx-runtime");
1331
1371
  function ReportFeatureList() {
1332
1372
  const report = useReport();
1333
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "flex flex-col gap-8", children: report.features.map((feature) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ReportFeature, { feature }, feature.id)) });
1373
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "flex flex-col gap-8", children: report.features.map((feature) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(ReportFeature, { feature }, feature.id)) });
1334
1374
  }
1335
1375
 
1336
1376
  // src/components/ui/empty.tsx
1337
1377
  var import_class_variance_authority2 = require("class-variance-authority");
1338
- var import_jsx_runtime29 = require("react/jsx-runtime");
1378
+ var import_jsx_runtime30 = require("react/jsx-runtime");
1339
1379
  function Empty({ className, ...props }) {
1340
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1380
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1341
1381
  "div",
1342
1382
  {
1343
1383
  "data-slot": "empty",
@@ -1350,7 +1390,7 @@ function Empty({ className, ...props }) {
1350
1390
  );
1351
1391
  }
1352
1392
  function EmptyHeader({ className, ...props }) {
1353
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1393
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1354
1394
  "div",
1355
1395
  {
1356
1396
  "data-slot": "empty-header",
@@ -1377,7 +1417,7 @@ var emptyMediaVariants = (0, import_class_variance_authority2.cva)(
1377
1417
  }
1378
1418
  );
1379
1419
  function EmptyTitle({ className, ...props }) {
1380
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1420
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1381
1421
  "div",
1382
1422
  {
1383
1423
  "data-slot": "empty-title",
@@ -1388,16 +1428,16 @@ function EmptyTitle({ className, ...props }) {
1388
1428
  }
1389
1429
 
1390
1430
  // src/components/ReportEmpty.tsx
1391
- var import_jsx_runtime30 = require("react/jsx-runtime");
1431
+ var import_jsx_runtime31 = require("react/jsx-runtime");
1392
1432
  function ReportEmpty({ message }) {
1393
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(EmptyHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1433
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(EmptyHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1394
1434
  }
1395
1435
 
1396
1436
  // src/components/ReportSummary.tsx
1397
- var import_jsx_runtime31 = require("react/jsx-runtime");
1437
+ var import_jsx_runtime32 = require("react/jsx-runtime");
1398
1438
  function ReportSummary({ className }) {
1399
1439
  const report = useReport();
1400
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1440
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1401
1441
  ReportSummaryView,
1402
1442
  {
1403
1443
  summary: report.summary,
@@ -1423,7 +1463,7 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1423
1463
  if (summary.pending > 0) {
1424
1464
  cards.push({ status: "pending", label: "Pending", value: summary.pending });
1425
1465
  }
1426
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1466
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1427
1467
  "dl",
1428
1468
  {
1429
1469
  className: cn(
@@ -1436,17 +1476,17 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1436
1476
  "aria-label": ariaLabel,
1437
1477
  children: cards.map((c) => {
1438
1478
  const tone = TONE[c.status];
1439
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
1479
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
1440
1480
  "div",
1441
1481
  {
1442
1482
  "data-status": c.status,
1443
1483
  className: "rounded-lg border bg-card px-4 py-3",
1444
1484
  children: [
1445
- /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("dt", { className: "flex items-center gap-1.5 font-mono text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: [
1446
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
1485
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("dt", { className: "flex items-center gap-1.5 font-mono text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: [
1486
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
1447
1487
  c.label
1448
1488
  ] }),
1449
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("dd", { className: cn("mt-2 text-3xl font-semibold leading-none tracking-tight tabular-nums", tone.num), children: c.value })
1489
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("dd", { className: cn("mt-2 text-3xl font-semibold leading-none tracking-tight tabular-nums", tone.num), children: c.value })
1450
1490
  ]
1451
1491
  },
1452
1492
  c.status
@@ -1456,6 +1496,9 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1456
1496
  );
1457
1497
  }
1458
1498
 
1499
+ // src/components/ReportMeta.tsx
1500
+ var import_duration4 = require("executable-stories-core/utils/duration");
1501
+
1459
1502
  // src/lib/provenance.ts
1460
1503
  var CI_DISPLAY_NAMES = {
1461
1504
  github: "GitHub Actions",
@@ -1487,7 +1530,7 @@ function prUrl(ci) {
1487
1530
  }
1488
1531
 
1489
1532
  // src/components/ReportMeta.tsx
1490
- var import_jsx_runtime32 = require("react/jsx-runtime");
1533
+ var import_jsx_runtime33 = require("react/jsx-runtime");
1491
1534
  function ReportMeta() {
1492
1535
  const report = useReport();
1493
1536
  const items = [];
@@ -1496,7 +1539,7 @@ function ReportMeta() {
1496
1539
  items.push({ label: "Started", value: new Date(report.startedAtMs).toLocaleString() });
1497
1540
  }
1498
1541
  if (report.durationMs) {
1499
- items.push({ label: "Duration", value: formatDuration(report.durationMs) });
1542
+ items.push({ label: "Duration", value: (0, import_duration4.formatDuration)(report.durationMs) });
1500
1543
  }
1501
1544
  if (report.packageVersion) {
1502
1545
  items.push({ label: "Version", value: report.packageVersion });
@@ -1510,13 +1553,13 @@ function ReportMeta() {
1510
1553
  const short = sha.slice(0, 8);
1511
1554
  items.push({
1512
1555
  label: "Commit",
1513
- value: url ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1556
+ value: url ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1514
1557
  });
1515
1558
  }
1516
1559
  if (report.ci) {
1517
1560
  items.push({
1518
1561
  label: "CI",
1519
- value: report.ci.url ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1562
+ value: report.ci.url ? /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1520
1563
  ciDisplayName(report.ci.name),
1521
1564
  report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
1522
1565
  ] }) : `${ciDisplayName(report.ci.name)}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
@@ -1527,7 +1570,7 @@ function ReportMeta() {
1527
1570
  const label = `#${report.ci.prNumber}`;
1528
1571
  items.push({
1529
1572
  label: "PR",
1530
- value: url ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1573
+ value: url ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1531
1574
  });
1532
1575
  }
1533
1576
  if (items.length === 0) return null;
@@ -1535,48 +1578,48 @@ function ReportMeta() {
1535
1578
  // Run metadata is useful but secondary to "what failed" — tuck it into a
1536
1579
  // collapsed disclosure so it doesn't push the result down. Native <details>
1537
1580
  // so it works with no JS (the SSR/no-hydration report still toggles).
1538
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("details", { "data-es-meta-card": true, className: "es-run-details group rounded-lg border border-border bg-card text-xs", children: [
1539
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("summary", { className: "flex cursor-pointer list-none select-none items-center gap-1.5 px-4 py-2 font-medium text-muted-foreground [&::-webkit-details-marker]:hidden", children: [
1540
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
1581
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("details", { "data-es-meta-card": true, className: "es-run-details group rounded-lg border border-border bg-card text-xs", children: [
1582
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("summary", { className: "flex cursor-pointer list-none select-none items-center gap-1.5 px-4 py-2 font-medium text-muted-foreground [&::-webkit-details-marker]:hidden", children: [
1583
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
1541
1584
  "Run details"
1542
1585
  ] }),
1543
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("dl", { className: "flex flex-wrap gap-x-5 gap-y-2 border-t border-border px-4 py-3 text-muted-foreground", "aria-label": "Run metadata", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-baseline gap-1.5", children: [
1544
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("dt", { className: "font-medium", children: item.label }),
1545
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("dd", { className: "font-mono text-foreground", children: item.value })
1586
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("dl", { className: "flex flex-wrap gap-x-5 gap-y-2 border-t border-border px-4 py-3 text-muted-foreground", "aria-label": "Run metadata", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "flex items-baseline gap-1.5", children: [
1587
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("dt", { className: "font-medium", children: item.label }),
1588
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("dd", { className: "font-mono text-foreground", children: item.value })
1546
1589
  ] }, item.label)) })
1547
1590
  ] })
1548
1591
  );
1549
1592
  }
1550
1593
 
1551
1594
  // src/components/ReportSchemaError.tsx
1552
- var import_jsx_runtime33 = require("react/jsx-runtime");
1595
+ var import_jsx_runtime34 = require("react/jsx-runtime");
1553
1596
  function ReportSchemaError({ error }) {
1554
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1555
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("strong", { children: "Report could not be displayed." }) }),
1556
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { children: error.message }),
1557
- error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("p", { children: [
1597
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1598
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("strong", { children: "Report could not be displayed." }) }),
1599
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { children: error.message }),
1600
+ error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("p", { children: [
1558
1601
  "The report bundle is newer than this version of ",
1559
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("code", { children: "executable-stories-react" }),
1602
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("code", { children: "executable-stories-react" }),
1560
1603
  ". Upgrade the package, or regenerate the report with an older formatters CLI."
1561
1604
  ] }) : null,
1562
- error.issues && error.issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("details", { children: [
1563
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("summary", { children: [
1605
+ error.issues && error.issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("details", { children: [
1606
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("summary", { children: [
1564
1607
  error.issues.length,
1565
1608
  " validation issue",
1566
1609
  error.issues.length === 1 ? "" : "s"
1567
1610
  ] }),
1568
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1611
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1569
1612
  ] }) : null
1570
1613
  ] });
1571
1614
  }
1572
1615
 
1573
1616
  // src/components/ReportShell.tsx
1574
- var import_jsx_runtime34 = require("react/jsx-runtime");
1617
+ var import_jsx_runtime35 = require("react/jsx-runtime");
1575
1618
  function ReportTitleBlock({ title }) {
1576
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
1577
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("h1", { children: title ?? "Story Report" }),
1578
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ReportSummary, {}),
1579
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ReportMeta, {})
1619
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
1620
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("h1", { children: title ?? "Story Report" }),
1621
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ReportSummary, {}),
1622
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ReportMeta, {})
1580
1623
  ] });
1581
1624
  }
1582
1625
  function ReportErrorShell({
@@ -1585,22 +1628,22 @@ function ReportErrorShell({
1585
1628
  title,
1586
1629
  dataTheme
1587
1630
  }) {
1588
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ReportSchemaError, { error }) });
1631
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ReportSchemaError, { error }) });
1589
1632
  }
1590
1633
 
1591
1634
  // src/components/Report.tsx
1592
- var import_jsx_runtime35 = require("react/jsx-runtime");
1635
+ var import_jsx_runtime36 = require("react/jsx-runtime");
1593
1636
  function Report(props) {
1594
1637
  const { report, customRenderers, renderers, className, title, dataTheme } = props;
1595
1638
  const result = unwrapReport(report);
1596
1639
  if (!result.ok) {
1597
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ReportErrorShell, { error: result.error, className, title, dataTheme });
1640
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportErrorShell, { error: result.error, className, title, dataTheme });
1598
1641
  }
1599
1642
  const data = result.data;
1600
1643
  const hasContent = data.features.length > 0;
1601
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ReportRoot, { report: data, customRenderers, renderers, children: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: [
1602
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("header", { className: "es-report-header", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ReportTitleBlock, { title }) }),
1603
- hasContent ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ReportFeatureList, {}) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ReportEmpty, {})
1644
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportRoot, { report: data, customRenderers, renderers, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: [
1645
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("header", { className: "es-report-header", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportTitleBlock, { title }) }),
1646
+ hasContent ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportFeatureList, {}) : /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportEmpty, {})
1604
1647
  ] }) });
1605
1648
  }
1606
1649