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/index.cjs CHANGED
@@ -31,6 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  // src/index.ts
32
32
  var src_exports = {};
33
33
  __export(src_exports, {
34
+ DATA_MODEL_TYPE: () => DATA_MODEL_TYPE,
35
+ DataModelBlock: () => DataModelBlock,
34
36
  DocCode: () => DocCode,
35
37
  DocCustom: () => DocCustom,
36
38
  DocEntry: () => DocEntry,
@@ -44,6 +46,8 @@ __export(src_exports, {
44
46
  DocTable: () => DocTable,
45
47
  DocTag: () => DocTag,
46
48
  DocVideo: () => DocVideo,
49
+ FILE_TREE_TYPE: () => FILE_TREE_TYPE,
50
+ FileTreeBlock: () => FileTreeBlock,
47
51
  MermaidDiagram: () => MermaidDiagram,
48
52
  MermaidSource: () => MermaidSource,
49
53
  Report: () => Report,
@@ -73,7 +77,10 @@ __export(src_exports, {
73
77
  err: () => err,
74
78
  formatRelativeAge: () => formatRelativeAge,
75
79
  isReportStale: () => isReportStale,
80
+ narrativeBlockRenderers: () => narrativeBlockRenderers,
76
81
  ok: () => ok,
82
+ parseDataModel: () => parseDataModel,
83
+ parseFileTree: () => parseFileTree,
77
84
  parseStoryReport: () => parseStoryReport,
78
85
  prUrl: () => prUrl,
79
86
  reportLastRunMs: () => reportLastRunMs,
@@ -101,63 +108,6 @@ var import_react2 = require("react");
101
108
  var import_react = require("react");
102
109
  var ReportContext = (0, import_react.createContext)(null);
103
110
 
104
- // src/context/ReportRoot.tsx
105
- var import_jsx_runtime = require("react/jsx-runtime");
106
- var EMPTY_CUSTOM = {};
107
- var EMPTY_RENDERERS = {};
108
- function ReportRoot({
109
- report,
110
- customRenderers,
111
- renderers,
112
- children
113
- }) {
114
- const value = (0, import_react2.useMemo)(
115
- () => ({
116
- report,
117
- customRenderers: customRenderers ?? EMPTY_CUSTOM,
118
- renderers: renderers ?? EMPTY_RENDERERS
119
- }),
120
- [report, customRenderers, renderers]
121
- );
122
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReportContext.Provider, { value, children });
123
- }
124
-
125
- // src/hooks/useReport.ts
126
- var import_react3 = require("react");
127
- function useReport() {
128
- const ctx = (0, import_react3.useContext)(ReportContext);
129
- if (!ctx) {
130
- throw new Error(
131
- "useReport must be used inside <ReportRoot> or <Report>. Wrap your tree with one of those."
132
- );
133
- }
134
- return ctx.report;
135
- }
136
-
137
- // ../executable-stories-core/dist/utils/duration.js
138
- function formatDuration(ms) {
139
- if (ms < 1e3) {
140
- return `${Math.round(ms)} ms`;
141
- }
142
- if (ms < 6e4) {
143
- return `${(ms / 1e3).toFixed(2)} s`;
144
- }
145
- const minutes = Math.floor(ms / 6e4);
146
- const seconds = Math.floor(ms % 6e4 / 1e3);
147
- if (minutes < 60) {
148
- return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
149
- }
150
- const hours = Math.floor(minutes / 60);
151
- const remainingMinutes = minutes % 60;
152
- return `${hours}h ${remainingMinutes}m`;
153
- }
154
-
155
- // src/components/doc/DocNote.tsx
156
- var import_jsx_runtime2 = require("react/jsx-runtime");
157
- function DocNote({ entry }) {
158
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "my-2 text-sm text-muted-foreground", children: entry.text });
159
- }
160
-
161
111
  // src/components/ui/badge.tsx
162
112
  var React = require("react");
163
113
  var import_class_variance_authority = require("class-variance-authority");
@@ -216,14 +166,237 @@ function Badge({
216
166
  });
217
167
  }
218
168
 
219
- // src/components/doc/DocTag.tsx
169
+ // src/components/doc/NarrativeBlocks.tsx
170
+ var import_jsx_runtime = require("react/jsx-runtime");
171
+ var FILE_TREE_TYPE = "file-tree";
172
+ var DATA_MODEL_TYPE = "data-model";
173
+ function isChangeKind(value) {
174
+ return value === "added" || value === "modified" || value === "removed" || value === "renamed";
175
+ }
176
+ function isRecord(value) {
177
+ return typeof value === "object" && value !== null && !Array.isArray(value);
178
+ }
179
+ function str(value) {
180
+ return typeof value === "string" && value !== "" ? value : void 0;
181
+ }
182
+ function AuthoredMarker({ authored }) {
183
+ if (authored !== "agent") return null;
184
+ 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" });
185
+ }
186
+ function BlockFrame({
187
+ title,
188
+ authored,
189
+ children
190
+ }) {
191
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("figure", { className: "my-3", children: [
192
+ title || authored === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("figcaption", { className: "mb-1.5 flex flex-wrap items-center gap-2", children: [
193
+ title ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-xs font-medium text-muted-foreground", children: title }) : null,
194
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AuthoredMarker, { authored })
195
+ ] }) : null,
196
+ children
197
+ ] });
198
+ }
199
+ function ChangeBadge({ change }) {
200
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { variant: "tag", className: "text-[10px]", children: change });
201
+ }
202
+ function parseFileTree(data) {
203
+ if (!isRecord(data) || !Array.isArray(data.files)) return void 0;
204
+ const files = [];
205
+ for (const raw of data.files) {
206
+ if (typeof raw === "string") {
207
+ if (raw) files.push({ path: raw });
208
+ continue;
209
+ }
210
+ if (!isRecord(raw)) continue;
211
+ const path = str(raw.path);
212
+ if (!path) continue;
213
+ files.push({
214
+ path,
215
+ change: isChangeKind(raw.change) ? raw.change : void 0,
216
+ note: str(raw.note)
217
+ });
218
+ }
219
+ if (files.length === 0) return void 0;
220
+ return { title: str(data.title), authored: str(data.authored), files };
221
+ }
222
+ function buildTree(files) {
223
+ const root = { name: "", children: /* @__PURE__ */ new Map() };
224
+ for (const file of files) {
225
+ const segments = file.path.split("/").filter(Boolean);
226
+ let node = root;
227
+ segments.forEach((segment, index) => {
228
+ let child = node.children.get(segment);
229
+ if (!child) {
230
+ child = { name: segment, children: /* @__PURE__ */ new Map() };
231
+ node.children.set(segment, child);
232
+ }
233
+ if (index === segments.length - 1) child.file = file;
234
+ node = child;
235
+ });
236
+ }
237
+ return root;
238
+ }
239
+ function sortedChildren(node) {
240
+ return [...node.children.values()].sort((a, b) => {
241
+ const aDir = a.children.size > 0;
242
+ const bDir = b.children.size > 0;
243
+ if (aDir !== bDir) return aDir ? -1 : 1;
244
+ return a.name.localeCompare(b.name);
245
+ });
246
+ }
247
+ function TreeBranch({ node, depth }) {
248
+ const children = sortedChildren(node);
249
+ 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) => {
250
+ const isDir = child.children.size > 0;
251
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
252
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-wrap items-baseline gap-2", children: [
253
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
254
+ "span",
255
+ {
256
+ className: cn(
257
+ "font-mono text-xs",
258
+ isDir ? "text-muted-foreground" : "text-foreground"
259
+ ),
260
+ children: isDir ? `${child.name}/` : child.name
261
+ }
262
+ ),
263
+ child.file?.change ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChangeBadge, { change: child.file.change }) : null,
264
+ child.file?.note ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-xs text-muted-foreground", children: child.file.note }) : null
265
+ ] }),
266
+ isDir ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TreeBranch, { node: child, depth: depth + 1 }) : null
267
+ ] }, child.name);
268
+ }) });
269
+ }
270
+ function FileTreeBlock({ entry }) {
271
+ const data = parseFileTree(entry.data);
272
+ if (!data) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UnreadableBlock, { entry });
273
+ 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 }) }) });
274
+ }
275
+ function parseDataModel(data) {
276
+ if (!isRecord(data) || !Array.isArray(data.fields)) return void 0;
277
+ const fields = [];
278
+ for (const raw of data.fields) {
279
+ if (!isRecord(raw)) continue;
280
+ const name = str(raw.name);
281
+ if (!name) continue;
282
+ fields.push({
283
+ name,
284
+ type: str(raw.type),
285
+ note: str(raw.note),
286
+ change: isChangeKind(raw.change) ? raw.change : void 0
287
+ });
288
+ }
289
+ if (fields.length === 0) return void 0;
290
+ return {
291
+ title: str(data.title),
292
+ authored: str(data.authored),
293
+ name: str(data.name),
294
+ fields
295
+ };
296
+ }
297
+ function DataModelBlock({ entry }) {
298
+ const data = parseDataModel(entry.data);
299
+ if (!data) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UnreadableBlock, { entry });
300
+ const hasNotes = data.fields.some((f) => f.note);
301
+ 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: [
302
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { className: "bg-muted/50", children: [
303
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: "Field" }),
304
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: "Type" }),
305
+ 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
306
+ ] }) }),
307
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: data.fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
308
+ /* @__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: [
309
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-mono text-xs text-foreground", children: field.name }),
310
+ field.change ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChangeBadge, { change: field.change }) : null
311
+ ] }) }),
312
+ /* @__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 ?? "" }),
313
+ hasNotes ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: "border-b border-border px-3 py-2 text-foreground", children: field.note ?? "" }) : null
314
+ ] }, field.name)) })
315
+ ] }) }) });
316
+ }
317
+ function UnreadableBlock({ entry }) {
318
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "my-2", "data-type": entry.type, children: [
319
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: [
320
+ entry.type,
321
+ " (unrecognised shape)"
322
+ ] }),
323
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
324
+ "pre",
325
+ {
326
+ tabIndex: 0,
327
+ className: "overflow-x-auto rounded-md bg-muted p-3 font-mono text-xs text-foreground",
328
+ children: safeStringify(entry.data)
329
+ }
330
+ )
331
+ ] });
332
+ }
333
+ function safeStringify(value) {
334
+ try {
335
+ return JSON.stringify(value, null, 2);
336
+ } catch {
337
+ return String(value);
338
+ }
339
+ }
340
+ var narrativeBlockRenderers = {
341
+ [FILE_TREE_TYPE]: (entry) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileTreeBlock, { entry }),
342
+ [DATA_MODEL_TYPE]: (entry) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DataModelBlock, { entry })
343
+ };
344
+
345
+ // src/context/ReportRoot.tsx
346
+ var import_jsx_runtime2 = require("react/jsx-runtime");
347
+ var EMPTY_RENDERERS = {};
348
+ function ReportRoot({
349
+ report,
350
+ customRenderers,
351
+ renderers,
352
+ children
353
+ }) {
354
+ const value = (0, import_react2.useMemo)(
355
+ () => ({
356
+ report,
357
+ // Narrative blocks render everywhere the report does (static SSR, the
358
+ // island, Astro) without every host wiring them up. A host renderer for
359
+ // the same type still wins.
360
+ customRenderers: { ...narrativeBlockRenderers, ...customRenderers },
361
+ renderers: renderers ?? EMPTY_RENDERERS
362
+ }),
363
+ [report, customRenderers, renderers]
364
+ );
365
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ReportContext.Provider, { value, children });
366
+ }
367
+
368
+ // src/hooks/useReport.ts
369
+ var import_react3 = require("react");
370
+ function useReport() {
371
+ const ctx = (0, import_react3.useContext)(ReportContext);
372
+ if (!ctx) {
373
+ throw new Error(
374
+ "useReport must be used inside <ReportRoot> or <Report>. Wrap your tree with one of those."
375
+ );
376
+ }
377
+ return ctx.report;
378
+ }
379
+
380
+ // src/components/ReportScenario.tsx
381
+ var import_duration3 = require("executable-stories-core/utils/duration");
382
+
383
+ // src/components/ReportSteps.tsx
384
+ var import_duration = require("executable-stories-core/utils/duration");
385
+
386
+ // src/components/doc/DocNote.tsx
220
387
  var import_jsx_runtime3 = require("react/jsx-runtime");
388
+ function DocNote({ entry }) {
389
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "my-2 text-sm text-muted-foreground", children: entry.text });
390
+ }
391
+
392
+ // src/components/doc/DocTag.tsx
393
+ var import_jsx_runtime4 = require("react/jsx-runtime");
221
394
  function DocTag({ entry }) {
222
- 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)) });
395
+ 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)) });
223
396
  }
224
397
 
225
398
  // src/components/doc/DocKv.tsx
226
- var import_jsx_runtime4 = require("react/jsx-runtime");
399
+ var import_jsx_runtime5 = require("react/jsx-runtime");
227
400
  function formatValue(value) {
228
401
  if (value === null) return "null";
229
402
  if (typeof value === "string") return value;
@@ -235,19 +408,19 @@ function formatValue(value) {
235
408
  }
236
409
  }
237
410
  function DocKv({ entry }) {
238
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("dl", { className: "my-2 flex flex-wrap items-baseline gap-x-2 text-sm", children: [
239
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dt", { className: "font-semibold text-muted-foreground", children: entry.label }),
240
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dd", { className: "font-mono text-foreground", children: formatValue(entry.value) })
411
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("dl", { className: "my-2 flex flex-wrap items-baseline gap-x-2 text-sm", children: [
412
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dt", { className: "font-semibold text-muted-foreground", children: entry.label }),
413
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dd", { className: "font-mono text-foreground", children: formatValue(entry.value) })
241
414
  ] });
242
415
  }
243
416
 
244
417
  // src/hooks/useRenderers.ts
245
418
  var import_react4 = require("react");
246
- var EMPTY_CUSTOM2 = {};
419
+ var EMPTY_CUSTOM = {};
247
420
  var EMPTY_RENDERERS2 = {};
248
421
  function useCustomRenderers() {
249
422
  const ctx = (0, import_react4.useContext)(ReportContext);
250
- return ctx?.customRenderers ?? EMPTY_CUSTOM2;
423
+ return ctx?.customRenderers ?? EMPTY_CUSTOM;
251
424
  }
252
425
  function useBuiltinRenderers() {
253
426
  const ctx = (0, import_react4.useContext)(ReportContext);
@@ -255,37 +428,37 @@ function useBuiltinRenderers() {
255
428
  }
256
429
 
257
430
  // src/components/doc/DocCode.tsx
258
- var import_jsx_runtime5 = require("react/jsx-runtime");
431
+ var import_jsx_runtime6 = require("react/jsx-runtime");
259
432
  function CodeFigure({ label, children }) {
260
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("figure", { className: "my-3", children: [
261
- label ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: label }) : null,
262
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3", children })
433
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("figure", { className: "my-3", children: [
434
+ label ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: label }) : null,
435
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3", children })
263
436
  ] });
264
437
  }
265
438
  function DocCode({ entry }) {
266
439
  const renderers = useBuiltinRenderers();
267
440
  if (renderers.code) {
268
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: renderers.code(entry) });
441
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: renderers.code(entry) });
269
442
  }
270
- 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 }) });
443
+ 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 }) });
271
444
  }
272
445
 
273
446
  // src/components/doc/DocTable.tsx
274
- var import_jsx_runtime6 = require("react/jsx-runtime");
447
+ var import_jsx_runtime7 = require("react/jsx-runtime");
275
448
  function DocTable({ entry }) {
276
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("figure", { className: "my-3", children: [
277
- entry.label ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
278
- /* @__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: [
279
- /* @__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)) }) }),
280
- /* @__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)) })
449
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("figure", { className: "my-3", children: [
450
+ entry.label ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
451
+ /* @__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: [
452
+ /* @__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)) }) }),
453
+ /* @__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)) })
281
454
  ] }) })
282
455
  ] });
283
456
  }
284
457
 
285
458
  // src/components/doc/DocLink.tsx
286
- var import_jsx_runtime7 = require("react/jsx-runtime");
459
+ var import_jsx_runtime8 = require("react/jsx-runtime");
287
460
  function DocLink({ entry }) {
288
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
461
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
289
462
  "a",
290
463
  {
291
464
  className: "text-sm font-medium text-primary underline underline-offset-2 hover:no-underline",
@@ -300,28 +473,13 @@ function DocLink({ entry }) {
300
473
  // src/components/doc/DocSection.tsx
301
474
  var import_marked = require("marked");
302
475
 
303
- // ../executable-stories-core/dist/utils/url.js
304
- function safeUrl(value) {
305
- if (!value) return void 0;
306
- const trimmed = value.trim();
307
- const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(trimmed);
308
- if (scheme && !/^https?$/i.test(scheme[1] ?? "")) return void 0;
309
- return trimmed;
310
- }
311
- function safeImageUrl(value) {
312
- if (!value) return void 0;
313
- const trimmed = value.trim();
314
- if (/^data:image\//i.test(trimmed)) return trimmed;
315
- return safeUrl(trimmed);
316
- }
317
- function isLocalFsPath(value) {
318
- return /^(?:[/\\]|[A-Za-z]:[/\\])/.test(value);
319
- }
476
+ // src/lib/url.ts
477
+ var import_url = require("executable-stories-core/utils/url");
320
478
 
321
479
  // src/components/doc/DocSection.tsx
322
- var import_jsx_runtime8 = require("react/jsx-runtime");
480
+ var import_jsx_runtime9 = require("react/jsx-runtime");
323
481
  function neutralizeUrl(value) {
324
- return safeUrl(value) ?? "#";
482
+ return (0, import_url.safeUrl)(value) ?? "#";
325
483
  }
326
484
  function safeMarkdownHtml(markdown) {
327
485
  const raw = import_marked.marked.parse(markdown, { async: false });
@@ -330,12 +488,12 @@ function safeMarkdownHtml(markdown) {
330
488
  function DocSection({ entry }) {
331
489
  const renderers = useBuiltinRenderers();
332
490
  if (renderers.section) {
333
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_jsx_runtime8.Fragment, { children: renderers.section(entry) });
491
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_jsx_runtime9.Fragment, { children: renderers.section(entry) });
334
492
  }
335
493
  const html = safeMarkdownHtml(entry.markdown);
336
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("section", { className: "my-3 text-sm", "aria-label": entry.title, children: [
337
- entry.title ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h4", { className: "mb-1 font-semibold text-foreground", children: entry.title }) : null,
338
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
494
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("section", { className: "my-3 text-sm", "aria-label": entry.title, children: [
495
+ entry.title ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h4", { className: "mb-1 font-semibold text-foreground", children: entry.title }) : null,
496
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
339
497
  "div",
340
498
  {
341
499
  className: "es-doc-prose prose prose-sm max-w-none",
@@ -346,11 +504,11 @@ function DocSection({ entry }) {
346
504
  }
347
505
 
348
506
  // src/components/doc/DocMermaid.tsx
349
- var import_jsx_runtime9 = require("react/jsx-runtime");
507
+ var import_jsx_runtime10 = require("react/jsx-runtime");
350
508
  function MermaidSource({ entry, ref }) {
351
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("figure", { ref, className: "my-3", "aria-label": entry.title ?? "Diagram", children: [
352
- entry.title ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
353
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
509
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("figure", { ref, className: "my-3", "aria-label": entry.title ?? "Diagram", children: [
510
+ entry.title ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
511
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
354
512
  "pre",
355
513
  {
356
514
  "data-mermaid": true,
@@ -364,24 +522,24 @@ function MermaidSource({ entry, ref }) {
364
522
  function DocMermaid({ entry }) {
365
523
  const renderers = useBuiltinRenderers();
366
524
  if (renderers.mermaid) {
367
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_jsx_runtime9.Fragment, { children: renderers.mermaid(entry) });
525
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_jsx_runtime10.Fragment, { children: renderers.mermaid(entry) });
368
526
  }
369
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MermaidSource, { entry });
527
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MermaidSource, { entry });
370
528
  }
371
529
 
372
530
  // src/components/doc/DocScreenshot.tsx
373
- var import_jsx_runtime10 = require("react/jsx-runtime");
531
+ var import_jsx_runtime11 = require("react/jsx-runtime");
374
532
  function DocScreenshot({ entry }) {
375
- const src = isLocalFsPath(entry.path) ? void 0 : safeImageUrl(entry.path);
533
+ const src = (0, import_url.isLocalFsPath)(entry.path) ? void 0 : (0, import_url.safeImageUrl)(entry.path);
376
534
  if (!src) {
377
- 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: [
378
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "font-medium", children: "Screenshot unavailable" }),
379
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
380
- entry.alt ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("figcaption", { className: "mt-1.5 text-xs", children: entry.alt }) : null
535
+ 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: [
536
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "font-medium", children: "Screenshot unavailable" }),
537
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
538
+ entry.alt ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("figcaption", { className: "mt-1.5 text-xs", children: entry.alt }) : null
381
539
  ] });
382
540
  }
383
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("figure", { className: "my-3", children: [
384
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
541
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("figure", { className: "my-3", children: [
542
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
385
543
  "img",
386
544
  {
387
545
  src,
@@ -390,24 +548,24 @@ function DocScreenshot({ entry }) {
390
548
  className: "max-w-full rounded-md border border-border"
391
549
  }
392
550
  ),
393
- entry.alt ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: entry.alt }) : null
551
+ entry.alt ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: entry.alt }) : null
394
552
  ] });
395
553
  }
396
554
 
397
555
  // src/components/doc/DocVideo.tsx
398
- var import_jsx_runtime11 = require("react/jsx-runtime");
556
+ var import_jsx_runtime12 = require("react/jsx-runtime");
399
557
  function DocVideo({ entry }) {
400
- if (isLocalFsPath(entry.path)) {
401
- 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: [
402
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "font-medium", children: "Video unavailable" }),
403
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
404
- entry.caption ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("figcaption", { className: "mt-1.5 text-xs", children: entry.caption }) : null
558
+ if ((0, import_url.isLocalFsPath)(entry.path)) {
559
+ 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: [
560
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "font-medium", children: "Video unavailable" }),
561
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
562
+ entry.caption ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("figcaption", { className: "mt-1.5 text-xs", children: entry.caption }) : null
405
563
  ] });
406
564
  }
407
- const src = safeUrl(entry.path);
408
- const poster = safeUrl(entry.poster);
409
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("figure", { className: "my-3", children: [
410
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
565
+ const src = (0, import_url.safeUrl)(entry.path);
566
+ const poster = (0, import_url.safeUrl)(entry.poster);
567
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("figure", { className: "my-3", children: [
568
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
411
569
  "video",
412
570
  {
413
571
  controls: true,
@@ -417,21 +575,21 @@ function DocVideo({ entry }) {
417
575
  className: "max-w-full rounded-md border border-border"
418
576
  }
419
577
  ),
420
- entry.caption ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: entry.caption }) : null
578
+ entry.caption ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: entry.caption }) : null
421
579
  ] });
422
580
  }
423
581
 
424
582
  // src/components/doc/DocHtml.tsx
425
- var import_jsx_runtime12 = require("react/jsx-runtime");
583
+ var import_jsx_runtime13 = require("react/jsx-runtime");
426
584
  function DocHtml({ entry }) {
427
585
  const height = typeof entry.height === "number" ? `${entry.height}px` : entry.height ?? "400px";
428
586
  const title = entry.title ?? "Embedded HTML";
429
- const safeHref = safeUrl(entry.url ?? entry.path);
587
+ const safeHref = (0, import_url.safeUrl)(entry.url ?? entry.path);
430
588
  const frameProps = entry.content !== void 0 ? { srcDoc: entry.content } : { src: safeHref ?? "" };
431
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("figure", { className: "my-3 overflow-hidden rounded-md border border-border", children: [
432
- /* @__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: [
433
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { children: entry.title ?? "HTML" }),
434
- safeHref ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
589
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("figure", { className: "my-3 overflow-hidden rounded-md border border-border", children: [
590
+ /* @__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: [
591
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: entry.title ?? "HTML" }),
592
+ safeHref ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
435
593
  "a",
436
594
  {
437
595
  href: safeHref,
@@ -444,7 +602,7 @@ function DocHtml({ entry }) {
444
602
  }
445
603
  ) : null
446
604
  ] }),
447
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
605
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
448
606
  "iframe",
449
607
  {
450
608
  ...frameProps,
@@ -459,7 +617,7 @@ function DocHtml({ entry }) {
459
617
  }
460
618
 
461
619
  // src/components/doc/DocState.tsx
462
- var import_jsx_runtime13 = require("react/jsx-runtime");
620
+ var import_jsx_runtime14 = require("react/jsx-runtime");
463
621
  function DocState({ entry }) {
464
622
  let json;
465
623
  try {
@@ -467,23 +625,23 @@ function DocState({ entry }) {
467
625
  } catch {
468
626
  json = String(entry.value);
469
627
  }
470
- 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 }) });
628
+ 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 }) });
471
629
  }
472
630
 
473
631
  // src/components/doc/DocCustom.tsx
474
- var import_jsx_runtime14 = require("react/jsx-runtime");
632
+ var import_jsx_runtime15 = require("react/jsx-runtime");
475
633
  function DocCustom({ entry }) {
476
634
  const renderers = useCustomRenderers();
477
635
  const renderer = renderers[entry.type];
478
636
  if (renderer) {
479
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_jsx_runtime14.Fragment, { children: renderer(entry) });
637
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_jsx_runtime15.Fragment, { children: renderer(entry) });
480
638
  }
481
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "my-2", "data-type": entry.type, children: [
482
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: entry.type }),
483
- /* @__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) })
639
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "my-2", "data-type": entry.type, children: [
640
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: entry.type }),
641
+ /* @__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) })
484
642
  ] });
485
643
  }
486
- function safeStringify(value) {
644
+ function safeStringify2(value) {
487
645
  try {
488
646
  return JSON.stringify(value, null, 2);
489
647
  } catch {
@@ -492,47 +650,47 @@ function safeStringify(value) {
492
650
  }
493
651
 
494
652
  // src/components/doc/DocEntry.tsx
495
- var import_jsx_runtime15 = require("react/jsx-runtime");
653
+ var import_jsx_runtime16 = require("react/jsx-runtime");
496
654
  function DocEntry({ entry }) {
497
655
  switch (entry.kind) {
498
656
  case "note":
499
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocNote, { entry });
657
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocNote, { entry });
500
658
  case "tag":
501
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocTag, { entry });
659
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocTag, { entry });
502
660
  case "kv":
503
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocKv, { entry });
661
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocKv, { entry });
504
662
  case "code":
505
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocCode, { entry });
663
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocCode, { entry });
506
664
  case "table":
507
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocTable, { entry });
665
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocTable, { entry });
508
666
  case "link":
509
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocLink, { entry });
667
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocLink, { entry });
510
668
  case "section":
511
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocSection, { entry });
669
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocSection, { entry });
512
670
  case "mermaid":
513
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocMermaid, { entry });
671
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocMermaid, { entry });
514
672
  case "screenshot":
515
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocScreenshot, { entry });
673
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocScreenshot, { entry });
516
674
  case "video":
517
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocVideo, { entry });
675
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocVideo, { entry });
518
676
  case "html":
519
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocHtml, { entry });
677
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocHtml, { entry });
520
678
  case "state":
521
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocState, { entry });
679
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocState, { entry });
522
680
  case "custom":
523
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocCustom, { entry });
681
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DocCustom, { entry });
524
682
  }
525
683
  }
526
684
 
527
685
  // src/components/ReportDocEntries.tsx
528
- var import_jsx_runtime16 = require("react/jsx-runtime");
686
+ var import_jsx_runtime17 = require("react/jsx-runtime");
529
687
  function ReportDocEntries({ entries }) {
530
688
  if (entries.length === 0) return null;
531
- 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)) });
689
+ 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)) });
532
690
  }
533
691
 
534
692
  // src/lib/step-params.tsx
535
- var import_jsx_runtime17 = require("react/jsx-runtime");
693
+ var import_jsx_runtime18 = require("react/jsx-runtime");
536
694
  var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.-])\d+(?:\.\d+)?(?![\w.-])/g;
537
695
  function highlightStepParams(text) {
538
696
  const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
@@ -544,7 +702,7 @@ function highlightStepParams(text) {
544
702
  const end = start + match[0].length;
545
703
  if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
546
704
  nodes.push(
547
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "font-medium text-step-param italic", children: match[0] }, i)
705
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "font-medium text-step-param italic", children: match[0] }, i)
548
706
  );
549
707
  lastIndex = end;
550
708
  });
@@ -553,7 +711,7 @@ function highlightStepParams(text) {
553
711
  }
554
712
 
555
713
  // src/components/ReportSteps.tsx
556
- var import_jsx_runtime18 = require("react/jsx-runtime");
714
+ var import_jsx_runtime19 = require("react/jsx-runtime");
557
715
  var STEP_GLYPH = {
558
716
  passed: "\u2713",
559
717
  failed: "\u2717",
@@ -568,21 +726,21 @@ var STEP_GLYPH_COLOR = {
568
726
  };
569
727
  function ReportSteps({ scenario }) {
570
728
  if (scenario.steps.length === 0) return null;
571
- 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)) });
729
+ 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)) });
572
730
  }
573
731
  function ReportStepItem({ step }) {
574
732
  const kw = step.keyword.toLowerCase();
575
733
  const continuation = kw === "and" || kw === "but";
576
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
734
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
577
735
  "li",
578
736
  {
579
737
  id: step.id,
580
738
  "data-status": step.status,
581
739
  className: cn("py-1.5 text-[0.8125rem] leading-normal", continuation && "pl-5"),
582
740
  children: [
583
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-baseline gap-2", children: [
584
- /* @__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" }),
585
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
741
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-baseline gap-2", children: [
742
+ /* @__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" }),
743
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
586
744
  "span",
587
745
  {
588
746
  className: cn(
@@ -592,10 +750,10 @@ function ReportStepItem({ step }) {
592
750
  children: step.keyword
593
751
  }
594
752
  ),
595
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "flex-1 text-foreground", children: highlightStepParams(step.text) }),
596
- 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
753
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "flex-1 text-foreground", children: highlightStepParams(step.text) }),
754
+ 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
597
755
  ] }),
598
- step.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
756
+ step.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
599
757
  "pre",
600
758
  {
601
759
  role: "alert",
@@ -603,132 +761,17 @@ function ReportStepItem({ step }) {
603
761
  children: step.errorMessage
604
762
  }
605
763
  ) : null,
606
- 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
764
+ 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
607
765
  ]
608
766
  }
609
767
  );
610
768
  }
611
769
 
612
- // ../executable-stories-core/dist/state-diff.js
613
- var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
614
- function deepEqual(a, b) {
615
- if (Object.is(a, b)) return true;
616
- if (Array.isArray(a) && Array.isArray(b)) {
617
- return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
618
- }
619
- if (isPlainObject(a) && isPlainObject(b)) {
620
- const ak = Object.keys(a);
621
- const bk = Object.keys(b);
622
- return ak.length === bk.length && ak.every((k) => k in b && deepEqual(a[k], b[k]));
623
- }
624
- return false;
625
- }
626
- var joinKey = (path, key) => path === "" ? key : `${path}.${key}`;
627
- function walk(path, before, after, out) {
628
- if (Object.is(before, after)) return;
629
- if (Array.isArray(before) && Array.isArray(after)) {
630
- const shared = Math.min(before.length, after.length);
631
- for (let i = 0; i < shared; i++) walk(`${path}[${i}]`, before[i], after[i], out);
632
- for (let i = shared; i < after.length; i++) out.push({ path: `${path}[${i}]`, kind: "added", after: after[i] });
633
- for (let i = shared; i < before.length; i++) out.push({ path: `${path}[${i}]`, kind: "removed", before: before[i] });
634
- return;
635
- }
636
- if (isPlainObject(before) && isPlainObject(after)) {
637
- for (const key of Object.keys(after)) {
638
- const p = joinKey(path, key);
639
- if (key in before) walk(p, before[key], after[key], out);
640
- else out.push({ path: p, kind: "added", after: after[key] });
641
- }
642
- for (const key of Object.keys(before)) {
643
- if (!(key in after)) out.push({ path: joinKey(path, key), kind: "removed", before: before[key] });
644
- }
645
- return;
646
- }
647
- if (!deepEqual(before, after)) out.push({ path, kind: "changed", before, after });
648
- }
649
- function diffStateValues(before, after) {
650
- const out = [];
651
- walk("", before, after, out);
652
- return out;
653
- }
654
- function formatStateValue(value, maxLen = 60) {
655
- const s = value === void 0 ? "undefined" : JSON.stringify(value);
656
- return s.length > maxLen ? `${s.slice(0, maxLen - 1)}\u2026` : s;
657
- }
658
- function summarizeStateChanges(changes) {
659
- return changes.map((c) => {
660
- const at = c.path === "" ? "(root)" : c.path;
661
- switch (c.kind) {
662
- case "added":
663
- return `+ ${at}: ${formatStateValue(c.after)}`;
664
- case "removed":
665
- return `\u2212 ${at}`;
666
- case "changed":
667
- return `${at}: ${formatStateValue(c.before)} \u2192 ${formatStateValue(c.after)}`;
668
- }
669
- });
670
- }
671
-
672
- // ../executable-stories-core/dist/storyboard.js
673
- function findScreenshot(entries) {
674
- for (const entry of entries) {
675
- if (entry.kind === "screenshot") return entry;
676
- if (entry.children) {
677
- const nested = findScreenshot(entry.children);
678
- if (nested) return nested;
679
- }
680
- }
681
- return void 0;
682
- }
683
- function findStates(entries, out = []) {
684
- for (const entry of entries) {
685
- if (entry.kind === "state") out.push(entry);
686
- if (entry.children) findStates(entry.children, out);
687
- }
688
- return out;
689
- }
690
- var laneOf = (label) => label ?? "";
691
- function extractStoryboardFrames(scenario) {
692
- const frames = [];
693
- const laneOrder = [];
694
- const previous = /* @__PURE__ */ new Map();
695
- for (const step of scenario.steps) {
696
- const shot = findScreenshot(step.docEntries);
697
- const stateDocs = findStates(step.docEntries);
698
- if (!shot && stateDocs.length === 0) continue;
699
- const byLane = /* @__PURE__ */ new Map();
700
- for (const doc of stateDocs) {
701
- const lane = laneOf(doc.label);
702
- if (!byLane.has(lane) && !laneOrder.includes(lane)) laneOrder.push(lane);
703
- byLane.set(lane, doc);
704
- }
705
- const states = [];
706
- for (const lane of laneOrder) {
707
- const doc = byLane.get(lane);
708
- if (!doc) continue;
709
- const card = {
710
- ...doc.label !== void 0 && { label: doc.label },
711
- value: doc.value
712
- };
713
- if (previous.has(lane)) card.changes = diffStateValues(previous.get(lane), doc.value);
714
- previous.set(lane, doc.value);
715
- states.push(card);
716
- }
717
- frames.push({
718
- stepId: step.id,
719
- stepIndex: step.index,
720
- keyword: step.keyword,
721
- text: step.text,
722
- status: step.status,
723
- ...shot && { screenshot: { path: shot.path, ...shot.alt !== void 0 && { alt: shot.alt } } },
724
- states
725
- });
726
- }
727
- return frames;
728
- }
729
-
730
770
  // src/components/ReportStoryboard.tsx
731
- var import_jsx_runtime19 = require("react/jsx-runtime");
771
+ var import_storyboard = require("executable-stories-core/storyboard");
772
+ var import_state_diff = require("executable-stories-core/state-diff");
773
+ var import_url6 = require("executable-stories-core/utils/url");
774
+ var import_jsx_runtime20 = require("react/jsx-runtime");
732
775
  var FRAME_GLYPH = {
733
776
  passed: "\u2713",
734
777
  failed: "\u2717",
@@ -742,48 +785,48 @@ var FRAME_GLYPH_COLOR = {
742
785
  pending: "text-pend"
743
786
  };
744
787
  function ReportStoryboard({ scenario }) {
745
- const frames = extractStoryboardFrames(scenario);
788
+ const frames = (0, import_storyboard.extractStoryboardFrames)(scenario);
746
789
  if (frames.length < 2) return null;
747
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("section", { "data-slot": "storyboard", "aria-label": "Storyboard", className: "mb-3", children: [
748
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h4", { className: "mb-1.5 text-xs font-semibold tracking-wide text-muted-foreground uppercase", children: "Storyboard" }),
749
- /* @__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)) })
790
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("section", { "data-slot": "storyboard", "aria-label": "Storyboard", className: "mb-3", children: [
791
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("h4", { className: "mb-1.5 text-xs font-semibold tracking-wide text-muted-foreground uppercase", children: "Storyboard" }),
792
+ /* @__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)) })
750
793
  ] });
751
794
  }
752
795
  function StoryboardFrameItem({ frame }) {
753
796
  const label = `${frame.keyword} ${frame.text}`;
754
797
  const hasStates = frame.states.length > 0;
755
- const caption = /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("p", { className: "mt-1 flex items-baseline gap-1.5 text-[0.6875rem] leading-snug", children: [
756
- /* @__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" }),
757
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { className: "min-w-0", children: [
758
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "font-mono font-semibold text-keyword", children: frame.keyword }),
798
+ const caption = /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("p", { className: "mt-1 flex items-baseline gap-1.5 text-[0.6875rem] leading-snug", children: [
799
+ /* @__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" }),
800
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "min-w-0", children: [
801
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "font-mono font-semibold text-keyword", children: frame.keyword }),
759
802
  " ",
760
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-muted-foreground", children: frame.screenshot?.alt ?? frame.text })
803
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "text-muted-foreground", children: frame.screenshot?.alt ?? frame.text })
761
804
  ] })
762
805
  ] });
763
806
  return (
764
807
  // State cards need room for diff rows; screenshot-only frames keep the
765
808
  // original narrow film-cell width (and DOM) unchanged.
766
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("li", { className: cn("shrink-0", hasStates ? "w-64" : "w-40"), children: [
767
- frame.screenshot ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(FrameScreenshot, { frame, label }) : null,
768
- 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,
809
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("li", { className: cn("shrink-0", hasStates ? "w-64" : "w-40"), children: [
810
+ frame.screenshot ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(FrameScreenshot, { frame, label }) : null,
811
+ 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,
769
812
  frame.screenshot ? caption : (
770
813
  // No screenshot means no image anchor, so the caption carries the
771
814
  // jump-to-step link instead.
772
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("a", { href: `#${frame.stepId}`, "aria-label": `Jump to step: ${label}`, className: "block hover:underline", children: caption })
815
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("a", { href: `#${frame.stepId}`, "aria-label": `Jump to step: ${label}`, className: "block hover:underline", children: caption })
773
816
  )
774
817
  ] })
775
818
  );
776
819
  }
777
820
  function FrameScreenshot({ frame, label }) {
778
821
  const path = frame.screenshot?.path ?? "";
779
- const src = isLocalFsPath(path) ? void 0 : safeImageUrl(path);
780
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
822
+ const src = (0, import_url6.isLocalFsPath)(path) ? void 0 : (0, import_url6.safeImageUrl)(path);
823
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
781
824
  "a",
782
825
  {
783
826
  href: `#${frame.stepId}`,
784
827
  "aria-label": `Jump to step: ${label}`,
785
828
  className: "block overflow-hidden rounded-md border border-border transition-colors hover:border-ring",
786
- children: src ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
829
+ children: src ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
787
830
  "img",
788
831
  {
789
832
  src,
@@ -791,7 +834,7 @@ function FrameScreenshot({ frame, label }) {
791
834
  loading: "lazy",
792
835
  className: "h-24 w-full bg-muted object-cover object-top"
793
836
  }
794
- ) : /* @__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" })
837
+ ) : /* @__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" })
795
838
  }
796
839
  );
797
840
  }
@@ -810,30 +853,30 @@ function prettyJson(value) {
810
853
  function StateCardView({ card }) {
811
854
  const json = prettyJson(card.value);
812
855
  const firstCapture = card.changes === void 0;
813
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { "data-slot": "state-card", className: "rounded-md border border-border bg-card p-2", children: [
814
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("p", { className: "flex flex-wrap items-baseline gap-1.5", children: [
815
- /* @__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" }),
816
- firstCapture ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-[0.625rem] text-muted-foreground italic", children: "first capture" }) : null
856
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { "data-slot": "state-card", className: "rounded-md border border-border bg-card p-2", children: [
857
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("p", { className: "flex flex-wrap items-baseline gap-1.5", children: [
858
+ /* @__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" }),
859
+ firstCapture ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "text-[0.625rem] text-muted-foreground italic", children: "first capture" }) : null
817
860
  ] }),
818
- !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)(
861
+ !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)(
819
862
  "li",
820
863
  {
821
864
  className: cn(
822
865
  "rounded-sm px-1.5 py-0.5 font-mono text-[0.625rem] leading-snug break-all",
823
866
  CHANGE_TONE[change.kind]
824
867
  ),
825
- children: summarizeStateChanges([change])[0]
868
+ children: (0, import_state_diff.summarizeStateChanges)([change])[0]
826
869
  },
827
870
  i
828
- )) }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "mt-1.5 text-[0.625rem] text-muted-foreground", children: "No changes" })),
829
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
871
+ )) }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "mt-1.5 text-[0.625rem] text-muted-foreground", children: "No changes" })),
872
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
830
873
  "details",
831
874
  {
832
875
  open: firstCapture && json.length <= 240,
833
876
  className: cn(firstCapture ? "mt-1.5" : "mt-1"),
834
877
  children: [
835
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("summary", { className: "cursor-pointer text-[0.625rem] text-muted-foreground hover:text-foreground", children: "Snapshot" }),
836
- /* @__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 })
878
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("summary", { className: "cursor-pointer text-[0.625rem] text-muted-foreground hover:text-foreground", children: "Snapshot" }),
879
+ /* @__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 })
837
880
  ]
838
881
  }
839
882
  )
@@ -841,17 +884,17 @@ function StateCardView({ card }) {
841
884
  }
842
885
 
843
886
  // src/components/ReportAttachments.tsx
844
- var import_jsx_runtime20 = require("react/jsx-runtime");
887
+ var import_jsx_runtime21 = require("react/jsx-runtime");
845
888
  function ReportAttachments({ attachments }) {
846
889
  if (attachments.length === 0) return null;
847
890
  return (
848
891
  // role="group" (not a labelled <section>) so multiple scenarios' attachment
849
892
  // blocks don't create duplicate "Attachments" region landmarks.
850
- /* @__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) => {
893
+ /* @__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) => {
851
894
  const dataUri = att.contentEncoding === "BASE64" ? `data:${att.mediaType};base64,${att.body}` : `data:${att.mediaType},${encodeURIComponent(att.body)}`;
852
895
  const isImage = att.mediaType.startsWith("image/");
853
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("figure", { className: "m-0", children: isImage ? /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
854
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
896
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("figure", { className: "m-0", children: isImage ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
897
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
855
898
  "img",
856
899
  {
857
900
  src: dataUri,
@@ -860,8 +903,8 @@ function ReportAttachments({ attachments }) {
860
903
  className: "max-w-full rounded-md border border-border"
861
904
  }
862
905
  ),
863
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: att.name })
864
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
906
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: att.name })
907
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
865
908
  "a",
866
909
  {
867
910
  href: dataUri,
@@ -870,7 +913,7 @@ function ReportAttachments({ attachments }) {
870
913
  children: [
871
914
  att.name,
872
915
  " ",
873
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "text-muted-foreground", children: [
916
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "text-muted-foreground", children: [
874
917
  "(",
875
918
  att.mediaType,
876
919
  ")"
@@ -882,6 +925,9 @@ function ReportAttachments({ attachments }) {
882
925
  );
883
926
  }
884
927
 
928
+ // src/components/ReportTrace.tsx
929
+ var import_duration2 = require("executable-stories-core/utils/duration");
930
+
885
931
  // src/lib/trace.ts
886
932
  var VALID = /* @__PURE__ */ new Set(["ok", "error", "unset"]);
887
933
  var safeStatus = (s) => VALID.has(s) ? s : "unset";
@@ -934,15 +980,15 @@ function toTraceRows(spans) {
934
980
  roots.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
935
981
  const rows = [];
936
982
  const seen = /* @__PURE__ */ new Set();
937
- const walk2 = (node, depth) => {
983
+ const walk = (node, depth) => {
938
984
  if (seen.has(node.span.spanId)) return;
939
985
  seen.add(node.span.spanId);
940
986
  rows.push({ ...node.span, depth });
941
- for (const child of node.children) walk2(child, depth + 1);
987
+ for (const child of node.children) walk(child, depth + 1);
942
988
  };
943
- for (const root of roots) walk2(root, 0);
989
+ for (const root of roots) walk(root, 0);
944
990
  for (const node of byId.values()) {
945
- if (!seen.has(node.span.spanId)) walk2(node, 0);
991
+ if (!seen.has(node.span.spanId)) walk(node, 0);
946
992
  }
947
993
  return rows;
948
994
  }
@@ -958,7 +1004,7 @@ function traceWindow(rows) {
958
1004
  }
959
1005
 
960
1006
  // src/components/ReportTrace.tsx
961
- var import_jsx_runtime21 = require("react/jsx-runtime");
1007
+ var import_jsx_runtime22 = require("react/jsx-runtime");
962
1008
  var BAR_COLOR = {
963
1009
  ok: "bg-pass",
964
1010
  error: "bg-fail",
@@ -969,21 +1015,21 @@ function ReportTrace({ spans }) {
969
1015
  const rows = toTraceRows(spans);
970
1016
  if (rows.length === 0) return null;
971
1017
  const { minStartMs, totalMs } = traceWindow(rows);
972
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("details", { className: "mt-3 rounded-md border border-border bg-muted/30", children: [
973
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("summary", { className: "cursor-pointer px-3 py-2 text-xs font-medium text-muted-foreground", children: [
1018
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("details", { className: "mt-3 rounded-md border border-border bg-muted/30", children: [
1019
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("summary", { className: "cursor-pointer px-3 py-2 text-xs font-medium text-muted-foreground", children: [
974
1020
  "Trace (",
975
1021
  rows.length,
976
1022
  " span",
977
1023
  rows.length === 1 ? "" : "s",
978
1024
  ", ",
979
- formatDuration(totalMs),
1025
+ (0, import_duration2.formatDuration)(totalMs),
980
1026
  ")"
981
1027
  ] }),
982
- /* @__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) => {
1028
+ /* @__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) => {
983
1029
  const leftPct = (row.startTimeMs - minStartMs) / totalMs * 100;
984
1030
  const widthPct = Math.max(row.durationMs / totalMs * 100, 0.5);
985
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("li", { className: "flex items-center gap-2", children: [
986
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1031
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("li", { className: "flex items-center gap-2", children: [
1032
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
987
1033
  "span",
988
1034
  {
989
1035
  className: "min-w-0 flex-1 truncate text-foreground",
@@ -992,14 +1038,14 @@ function ReportTrace({ spans }) {
992
1038
  children: row.name
993
1039
  }
994
1040
  ),
995
- /* @__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)(
1041
+ /* @__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)(
996
1042
  "span",
997
1043
  {
998
1044
  className: cn("absolute top-0 h-full rounded-sm", BAR_COLOR[row.status]),
999
1045
  style: { left: `${leftPct}%`, width: `${widthPct}%` }
1000
1046
  }
1001
1047
  ) }),
1002
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: formatDuration(row.durationMs) })
1048
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: (0, import_duration2.formatDuration)(row.durationMs) })
1003
1049
  ] }, `${row.spanId}-${i}`);
1004
1050
  }) })
1005
1051
  ] });
@@ -1050,7 +1096,7 @@ function flakinessOf(entries) {
1050
1096
  }
1051
1097
 
1052
1098
  // src/components/ScenarioRunHistory.tsx
1053
- var import_jsx_runtime22 = require("react/jsx-runtime");
1099
+ var import_jsx_runtime23 = require("react/jsx-runtime");
1054
1100
  var MAX_DOTS = 10;
1055
1101
  var DOT_COLOR = {
1056
1102
  passed: "bg-pass",
@@ -1070,16 +1116,16 @@ function ScenarioRunHistory({ scenarioId }) {
1070
1116
  const recent = entries.slice(-MAX_DOTS);
1071
1117
  const flaky = flakinessOf(recent) === "flaky";
1072
1118
  const summary = flaky ? `${describeRunHistory(recent)} \xB7 Flaky` : describeRunHistory(recent);
1073
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
1074
- flaky ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Badge, { variant: "pending", "aria-label": "Flaky: status flips between recent runs", title: summary, children: "Flaky" }) : null,
1075
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1119
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
1120
+ flaky ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Badge, { variant: "pending", "aria-label": "Flaky: status flips between recent runs", title: summary, children: "Flaky" }) : null,
1121
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1076
1122
  "span",
1077
1123
  {
1078
1124
  role: "img",
1079
1125
  "aria-label": `Run history: ${summary}`,
1080
1126
  title: summary,
1081
1127
  className: "flex shrink-0 items-center gap-[3px]",
1082
- children: recent.map((e, i) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1128
+ children: recent.map((e, i) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1083
1129
  "span",
1084
1130
  {
1085
1131
  "aria-hidden": true,
@@ -1110,6 +1156,7 @@ function useCollapse() {
1110
1156
 
1111
1157
  // src/interactive/scenario-actions.tsx
1112
1158
  var import_react7 = require("react");
1159
+ var import_scenario_markdown = require("executable-stories-core/utils/scenario-markdown");
1113
1160
  var ScenarioActionsContext = (0, import_react7.createContext)(null);
1114
1161
  var ScenarioActionsProvider = ScenarioActionsContext.Provider;
1115
1162
  function useScenarioActions() {
@@ -1118,9 +1165,9 @@ function useScenarioActions() {
1118
1165
 
1119
1166
  // src/components/ui/card.tsx
1120
1167
  var React2 = require("react");
1121
- var import_jsx_runtime23 = require("react/jsx-runtime");
1168
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1122
1169
  function Card({ className, ...props }) {
1123
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1170
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1124
1171
  "div",
1125
1172
  {
1126
1173
  "data-slot": "card",
@@ -1133,7 +1180,7 @@ function Card({ className, ...props }) {
1133
1180
  );
1134
1181
  }
1135
1182
  function CardHeader({ className, ...props }) {
1136
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1183
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1137
1184
  "div",
1138
1185
  {
1139
1186
  "data-slot": "card-header",
@@ -1146,7 +1193,7 @@ function CardHeader({ className, ...props }) {
1146
1193
  );
1147
1194
  }
1148
1195
  function CardContent({ className, ...props }) {
1149
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1196
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1150
1197
  "div",
1151
1198
  {
1152
1199
  "data-slot": "card-content",
@@ -1160,13 +1207,13 @@ function CardContent({ className, ...props }) {
1160
1207
  var React3 = require("react");
1161
1208
  var import_menu = require("@base-ui/react/menu");
1162
1209
  var import_lucide_react = require("lucide-react");
1163
- var import_jsx_runtime24 = require("react/jsx-runtime");
1210
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1164
1211
  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";
1165
1212
  function DropdownMenu(props) {
1166
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_menu.Menu.Root, { "data-slot": "dropdown-menu", ...props });
1213
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_menu.Menu.Root, { "data-slot": "dropdown-menu", ...props });
1167
1214
  }
1168
1215
  function DropdownMenuTrigger(props) {
1169
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_menu.Menu.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
1216
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_menu.Menu.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
1170
1217
  }
1171
1218
  function DropdownMenuContent({
1172
1219
  className,
@@ -1174,14 +1221,14 @@ function DropdownMenuContent({
1174
1221
  sideOffset = 4,
1175
1222
  ...props
1176
1223
  }) {
1177
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_menu.Menu.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1224
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_menu.Menu.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1178
1225
  import_menu.Menu.Positioner,
1179
1226
  {
1180
1227
  "data-slot": "dropdown-menu-positioner",
1181
1228
  className: "z-50",
1182
1229
  align,
1183
1230
  sideOffset,
1184
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1231
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1185
1232
  import_menu.Menu.Popup,
1186
1233
  {
1187
1234
  "data-slot": "dropdown-menu-content",
@@ -1199,7 +1246,7 @@ function DropdownMenuItem({
1199
1246
  className,
1200
1247
  ...props
1201
1248
  }) {
1202
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1249
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1203
1250
  import_menu.Menu.Item,
1204
1251
  {
1205
1252
  "data-slot": "dropdown-menu-item",
@@ -1211,7 +1258,7 @@ function DropdownMenuItem({
1211
1258
 
1212
1259
  // src/components/ReportScenario.tsx
1213
1260
  var import_lucide_react2 = require("lucide-react");
1214
- var import_jsx_runtime25 = require("react/jsx-runtime");
1261
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1215
1262
  var STATUS_META = {
1216
1263
  passed: { label: "Passed", glyph: "\u2713", glyphColor: "text-pass", badgeVariant: "passed", showPill: false },
1217
1264
  failed: { label: "Failed", glyph: "\u2717", glyphColor: "text-fail", badgeVariant: "failed", showPill: true },
@@ -1227,7 +1274,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
1227
1274
  const collapsible = collapse !== null && !hideTitle;
1228
1275
  const collapsed = collapsible ? collapse.isCollapsed(scenario.id) : false;
1229
1276
  const actions = useScenarioActions();
1230
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
1277
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
1231
1278
  Card,
1232
1279
  {
1233
1280
  id: scenario.id,
@@ -1235,10 +1282,10 @@ function ReportScenario({ scenario, hideTitle = false }) {
1235
1282
  ...hideTitle ? { "aria-label": `${scenario.title}, ${label}` } : { "aria-labelledby": titleId },
1236
1283
  className: "gap-0 py-0",
1237
1284
  children: [
1238
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(CardHeader, { className: "px-4 py-3", children: [
1239
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
1240
- hideTitle ? null : /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1241
- collapsible ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1285
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(CardHeader, { className: "px-4 py-3", children: [
1286
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
1287
+ hideTitle ? null : /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1288
+ collapsible ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1242
1289
  "button",
1243
1290
  {
1244
1291
  type: "button",
@@ -1247,49 +1294,49 @@ function ReportScenario({ scenario, hideTitle = false }) {
1247
1294
  "aria-controls": bodyId,
1248
1295
  "aria-label": `Toggle ${scenario.title}`,
1249
1296
  className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
1250
- children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1297
+ children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1251
1298
  }
1252
1299
  ) : null,
1253
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { "aria-hidden": true, className: cn("text-sm leading-none", meta.glyphColor), children: meta.glyph }),
1254
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { children: scenario.title })
1300
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { "aria-hidden": true, className: cn("text-sm leading-none", meta.glyphColor), children: meta.glyph }),
1301
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { children: scenario.title })
1255
1302
  ] }),
1256
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex shrink-0 items-center gap-2", children: [
1257
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ScenarioRunHistory, { scenarioId: scenario.id }),
1258
- 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,
1259
- 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}` }),
1260
- actions ? /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(DropdownMenu, { children: [
1261
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1303
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex shrink-0 items-center gap-2", children: [
1304
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ScenarioRunHistory, { scenarioId: scenario.id }),
1305
+ 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,
1306
+ 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}` }),
1307
+ actions ? /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenu, { children: [
1308
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1262
1309
  DropdownMenuTrigger,
1263
1310
  {
1264
1311
  "aria-label": `Actions for ${scenario.title}`,
1265
1312
  className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
1266
- children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react2.MoreHorizontalIcon, { "aria-hidden": true })
1313
+ children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react2.MoreHorizontalIcon, { "aria-hidden": true })
1267
1314
  }
1268
1315
  ),
1269
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(DropdownMenuContent, { align: "end", children: [
1270
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
1271
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react2.LinkIcon, {}),
1316
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenuContent, { align: "end", children: [
1317
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
1318
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react2.LinkIcon, {}),
1272
1319
  " Copy link"
1273
1320
  ] }),
1274
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
1275
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react2.FileTextIcon, {}),
1321
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
1322
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react2.FileTextIcon, {}),
1276
1323
  " Copy as Markdown"
1277
1324
  ] }),
1278
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
1279
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react2.SparklesIcon, {}),
1325
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
1326
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react2.SparklesIcon, {}),
1280
1327
  " Explain with AI"
1281
1328
  ] })
1282
1329
  ] })
1283
1330
  ] }) : null
1284
1331
  ] })
1285
1332
  ] }),
1286
- 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: [
1287
- 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}`)),
1288
- (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}`))
1333
+ 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: [
1334
+ 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}`)),
1335
+ (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}`))
1289
1336
  ] }) : null
1290
1337
  ] }),
1291
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
1292
- scenario.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1338
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
1339
+ scenario.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1293
1340
  "pre",
1294
1341
  {
1295
1342
  role: "alert",
@@ -1297,11 +1344,11 @@ function ReportScenario({ scenario, hideTitle = false }) {
1297
1344
  children: scenario.errorMessage
1298
1345
  }
1299
1346
  ) : null,
1300
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ReportStoryboard, { scenario }),
1301
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ReportSteps, { scenario }),
1302
- 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,
1303
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ReportAttachments, { attachments: scenario.attachments }),
1304
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ReportTrace, { spans: scenario.otelSpans })
1347
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ReportStoryboard, { scenario }),
1348
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ReportSteps, { scenario }),
1349
+ 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,
1350
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ReportAttachments, { attachments: scenario.attachments }),
1351
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ReportTrace, { spans: scenario.otelSpans })
1305
1352
  ] })
1306
1353
  ]
1307
1354
  }
@@ -1309,13 +1356,13 @@ function ReportScenario({ scenario, hideTitle = false }) {
1309
1356
  }
1310
1357
 
1311
1358
  // src/components/ReportScenarioList.tsx
1312
- var import_jsx_runtime26 = require("react/jsx-runtime");
1359
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1313
1360
  function ReportScenarioList({ feature }) {
1314
- 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)) });
1361
+ 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)) });
1315
1362
  }
1316
1363
 
1317
1364
  // src/components/ReportFeature.tsx
1318
- var import_jsx_runtime27 = require("react/jsx-runtime");
1365
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1319
1366
  function ReportFeature({ feature }) {
1320
1367
  const titleId = `${feature.id}-title`;
1321
1368
  const bodyId = `${feature.id}-body`;
@@ -1326,7 +1373,7 @@ function ReportFeature({ feature }) {
1326
1373
  return (
1327
1374
  // Suite = a section heading + a divider, NOT another bordered card. Drops a
1328
1375
  // layer of nesting so the scenario cards below read as the primary unit.
1329
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
1376
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1330
1377
  "section",
1331
1378
  {
1332
1379
  id: feature.id,
@@ -1334,9 +1381,9 @@ function ReportFeature({ feature }) {
1334
1381
  "aria-labelledby": titleId,
1335
1382
  className: "flex flex-col gap-3",
1336
1383
  children: [
1337
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1338
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "min-w-0", children: [
1339
- /* @__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)(
1384
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1385
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "min-w-0", children: [
1386
+ /* @__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)(
1340
1387
  "button",
1341
1388
  {
1342
1389
  type: "button",
@@ -1345,31 +1392,31 @@ function ReportFeature({ feature }) {
1345
1392
  "aria-controls": bodyId,
1346
1393
  className: "flex w-full cursor-pointer items-center gap-2 text-left hover:text-foreground/80",
1347
1394
  children: [
1348
- /* @__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" }),
1349
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "truncate", children: feature.title })
1395
+ /* @__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" }),
1396
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "truncate", children: feature.title })
1350
1397
  ]
1351
1398
  }
1352
1399
  ) : feature.title }),
1353
- /* @__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 })
1400
+ /* @__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 })
1354
1401
  ] }),
1355
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
1402
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1356
1403
  "p",
1357
1404
  {
1358
1405
  className: "flex shrink-0 items-center gap-1.5 text-xs text-muted-foreground",
1359
1406
  "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
1360
1407
  children: [
1361
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { children: [
1408
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { children: [
1362
1409
  s.passed,
1363
1410
  " passed"
1364
1411
  ] }),
1365
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1366
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
1412
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1413
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
1367
1414
  s.failed,
1368
1415
  " failed"
1369
1416
  ] }),
1370
- skipped > 0 ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
1371
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1372
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { children: [
1417
+ skipped > 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
1418
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { "aria-hidden": true, children: "\xB7" }),
1419
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { children: [
1373
1420
  skipped,
1374
1421
  " skipped"
1375
1422
  ] })
@@ -1378,7 +1425,7 @@ function ReportFeature({ feature }) {
1378
1425
  }
1379
1426
  )
1380
1427
  ] }),
1381
- /* @__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 }) })
1428
+ /* @__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 }) })
1382
1429
  ]
1383
1430
  }
1384
1431
  )
@@ -1386,17 +1433,17 @@ function ReportFeature({ feature }) {
1386
1433
  }
1387
1434
 
1388
1435
  // src/components/ReportFeatureList.tsx
1389
- var import_jsx_runtime28 = require("react/jsx-runtime");
1436
+ var import_jsx_runtime29 = require("react/jsx-runtime");
1390
1437
  function ReportFeatureList() {
1391
1438
  const report = useReport();
1392
- 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)) });
1439
+ 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)) });
1393
1440
  }
1394
1441
 
1395
1442
  // src/components/ui/empty.tsx
1396
1443
  var import_class_variance_authority2 = require("class-variance-authority");
1397
- var import_jsx_runtime29 = require("react/jsx-runtime");
1444
+ var import_jsx_runtime30 = require("react/jsx-runtime");
1398
1445
  function Empty({ className, ...props }) {
1399
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1446
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1400
1447
  "div",
1401
1448
  {
1402
1449
  "data-slot": "empty",
@@ -1409,7 +1456,7 @@ function Empty({ className, ...props }) {
1409
1456
  );
1410
1457
  }
1411
1458
  function EmptyHeader({ className, ...props }) {
1412
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1459
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1413
1460
  "div",
1414
1461
  {
1415
1462
  "data-slot": "empty-header",
@@ -1436,7 +1483,7 @@ var emptyMediaVariants = (0, import_class_variance_authority2.cva)(
1436
1483
  }
1437
1484
  );
1438
1485
  function EmptyTitle({ className, ...props }) {
1439
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1486
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1440
1487
  "div",
1441
1488
  {
1442
1489
  "data-slot": "empty-title",
@@ -1447,16 +1494,16 @@ function EmptyTitle({ className, ...props }) {
1447
1494
  }
1448
1495
 
1449
1496
  // src/components/ReportEmpty.tsx
1450
- var import_jsx_runtime30 = require("react/jsx-runtime");
1497
+ var import_jsx_runtime31 = require("react/jsx-runtime");
1451
1498
  function ReportEmpty({ message }) {
1452
- 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." }) }) });
1499
+ 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." }) }) });
1453
1500
  }
1454
1501
 
1455
1502
  // src/components/ReportSummary.tsx
1456
- var import_jsx_runtime31 = require("react/jsx-runtime");
1503
+ var import_jsx_runtime32 = require("react/jsx-runtime");
1457
1504
  function ReportSummary({ className }) {
1458
1505
  const report = useReport();
1459
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1506
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1460
1507
  ReportSummaryView,
1461
1508
  {
1462
1509
  summary: report.summary,
@@ -1482,7 +1529,7 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1482
1529
  if (summary.pending > 0) {
1483
1530
  cards.push({ status: "pending", label: "Pending", value: summary.pending });
1484
1531
  }
1485
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1532
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1486
1533
  "dl",
1487
1534
  {
1488
1535
  className: cn(
@@ -1495,17 +1542,17 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1495
1542
  "aria-label": ariaLabel,
1496
1543
  children: cards.map((c) => {
1497
1544
  const tone = TONE[c.status];
1498
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
1545
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
1499
1546
  "div",
1500
1547
  {
1501
1548
  "data-status": c.status,
1502
1549
  className: "rounded-lg border bg-card px-4 py-3",
1503
1550
  children: [
1504
- /* @__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: [
1505
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
1551
+ /* @__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: [
1552
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
1506
1553
  c.label
1507
1554
  ] }),
1508
- /* @__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 })
1555
+ /* @__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 })
1509
1556
  ]
1510
1557
  },
1511
1558
  c.status
@@ -1515,6 +1562,9 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1515
1562
  );
1516
1563
  }
1517
1564
 
1565
+ // src/components/ReportMeta.tsx
1566
+ var import_duration4 = require("executable-stories-core/utils/duration");
1567
+
1518
1568
  // src/lib/provenance.ts
1519
1569
  function reportLastRunMs(report) {
1520
1570
  if (report.finishedAtMs && report.finishedAtMs > 0) return report.finishedAtMs;
@@ -1565,7 +1615,7 @@ function prUrl(ci) {
1565
1615
  }
1566
1616
 
1567
1617
  // src/components/ReportMeta.tsx
1568
- var import_jsx_runtime32 = require("react/jsx-runtime");
1618
+ var import_jsx_runtime33 = require("react/jsx-runtime");
1569
1619
  function ReportMeta() {
1570
1620
  const report = useReport();
1571
1621
  const items = [];
@@ -1574,7 +1624,7 @@ function ReportMeta() {
1574
1624
  items.push({ label: "Started", value: new Date(report.startedAtMs).toLocaleString() });
1575
1625
  }
1576
1626
  if (report.durationMs) {
1577
- items.push({ label: "Duration", value: formatDuration(report.durationMs) });
1627
+ items.push({ label: "Duration", value: (0, import_duration4.formatDuration)(report.durationMs) });
1578
1628
  }
1579
1629
  if (report.packageVersion) {
1580
1630
  items.push({ label: "Version", value: report.packageVersion });
@@ -1588,13 +1638,13 @@ function ReportMeta() {
1588
1638
  const short = sha.slice(0, 8);
1589
1639
  items.push({
1590
1640
  label: "Commit",
1591
- value: url ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1641
+ value: url ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1592
1642
  });
1593
1643
  }
1594
1644
  if (report.ci) {
1595
1645
  items.push({
1596
1646
  label: "CI",
1597
- value: report.ci.url ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1647
+ value: report.ci.url ? /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1598
1648
  ciDisplayName(report.ci.name),
1599
1649
  report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
1600
1650
  ] }) : `${ciDisplayName(report.ci.name)}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
@@ -1605,7 +1655,7 @@ function ReportMeta() {
1605
1655
  const label = `#${report.ci.prNumber}`;
1606
1656
  items.push({
1607
1657
  label: "PR",
1608
- value: url ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1658
+ value: url ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1609
1659
  });
1610
1660
  }
1611
1661
  if (items.length === 0) return null;
@@ -1613,48 +1663,48 @@ function ReportMeta() {
1613
1663
  // Run metadata is useful but secondary to "what failed" — tuck it into a
1614
1664
  // collapsed disclosure so it doesn't push the result down. Native <details>
1615
1665
  // so it works with no JS (the SSR/no-hydration report still toggles).
1616
- /* @__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: [
1617
- /* @__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: [
1618
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
1666
+ /* @__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: [
1667
+ /* @__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: [
1668
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
1619
1669
  "Run details"
1620
1670
  ] }),
1621
- /* @__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: [
1622
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("dt", { className: "font-medium", children: item.label }),
1623
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("dd", { className: "font-mono text-foreground", children: item.value })
1671
+ /* @__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: [
1672
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("dt", { className: "font-medium", children: item.label }),
1673
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("dd", { className: "font-mono text-foreground", children: item.value })
1624
1674
  ] }, item.label)) })
1625
1675
  ] })
1626
1676
  );
1627
1677
  }
1628
1678
 
1629
1679
  // src/components/ReportSchemaError.tsx
1630
- var import_jsx_runtime33 = require("react/jsx-runtime");
1680
+ var import_jsx_runtime34 = require("react/jsx-runtime");
1631
1681
  function ReportSchemaError({ error }) {
1632
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1633
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("strong", { children: "Report could not be displayed." }) }),
1634
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { children: error.message }),
1635
- error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("p", { children: [
1682
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1683
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("strong", { children: "Report could not be displayed." }) }),
1684
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { children: error.message }),
1685
+ error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("p", { children: [
1636
1686
  "The report bundle is newer than this version of ",
1637
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("code", { children: "executable-stories-react" }),
1687
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("code", { children: "executable-stories-react" }),
1638
1688
  ". Upgrade the package, or regenerate the report with an older formatters CLI."
1639
1689
  ] }) : null,
1640
- error.issues && error.issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("details", { children: [
1641
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("summary", { children: [
1690
+ error.issues && error.issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("details", { children: [
1691
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("summary", { children: [
1642
1692
  error.issues.length,
1643
1693
  " validation issue",
1644
1694
  error.issues.length === 1 ? "" : "s"
1645
1695
  ] }),
1646
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1696
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1647
1697
  ] }) : null
1648
1698
  ] });
1649
1699
  }
1650
1700
 
1651
1701
  // src/components/ReportShell.tsx
1652
- var import_jsx_runtime34 = require("react/jsx-runtime");
1702
+ var import_jsx_runtime35 = require("react/jsx-runtime");
1653
1703
  function ReportTitleBlock({ title }) {
1654
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
1655
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("h1", { children: title ?? "Story Report" }),
1656
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ReportSummary, {}),
1657
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ReportMeta, {})
1704
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
1705
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("h1", { children: title ?? "Story Report" }),
1706
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ReportSummary, {}),
1707
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ReportMeta, {})
1658
1708
  ] });
1659
1709
  }
1660
1710
  function ReportErrorShell({
@@ -1663,28 +1713,28 @@ function ReportErrorShell({
1663
1713
  title,
1664
1714
  dataTheme
1665
1715
  }) {
1666
- 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 }) });
1716
+ 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 }) });
1667
1717
  }
1668
1718
 
1669
1719
  // src/components/Report.tsx
1670
- var import_jsx_runtime35 = require("react/jsx-runtime");
1720
+ var import_jsx_runtime36 = require("react/jsx-runtime");
1671
1721
  function Report(props) {
1672
1722
  const { report, customRenderers, renderers, className, title, dataTheme } = props;
1673
1723
  const result = unwrapReport(report);
1674
1724
  if (!result.ok) {
1675
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ReportErrorShell, { error: result.error, className, title, dataTheme });
1725
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportErrorShell, { error: result.error, className, title, dataTheme });
1676
1726
  }
1677
1727
  const data = result.data;
1678
1728
  const hasContent = data.features.length > 0;
1679
- 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: [
1680
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("header", { className: "es-report-header", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ReportTitleBlock, { title }) }),
1681
- hasContent ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ReportFeatureList, {}) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ReportEmpty, {})
1729
+ 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: [
1730
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("header", { className: "es-report-header", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportTitleBlock, { title }) }),
1731
+ hasContent ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportFeatureList, {}) : /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportEmpty, {})
1682
1732
  ] }) });
1683
1733
  }
1684
1734
 
1685
1735
  // src/components/doc/MermaidView.tsx
1686
1736
  var import_react8 = require("react");
1687
- var import_jsx_runtime36 = require("react/jsx-runtime");
1737
+ var import_jsx_runtime37 = require("react/jsx-runtime");
1688
1738
  function prefersDark(el) {
1689
1739
  const themed = el?.closest("[data-theme]");
1690
1740
  if (themed?.dataset.theme === "dark") return true;
@@ -1738,9 +1788,9 @@ function MermaidView({ entry, load }) {
1738
1788
  };
1739
1789
  }, [entry.code, load]);
1740
1790
  if (svg) {
1741
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("figure", { ref, className: "my-3", children: [
1742
- entry.title ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
1743
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
1791
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("figure", { ref, className: "my-3", children: [
1792
+ entry.title ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
1793
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
1744
1794
  "div",
1745
1795
  {
1746
1796
  role: "img",
@@ -1751,8 +1801,8 @@ function MermaidView({ entry, load }) {
1751
1801
  )
1752
1802
  ] });
1753
1803
  }
1754
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
1755
- error ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
1804
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
1805
+ error ? /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
1756
1806
  "p",
1757
1807
  {
1758
1808
  role: "alert",
@@ -1764,19 +1814,19 @@ function MermaidView({ entry, load }) {
1764
1814
  ]
1765
1815
  }
1766
1816
  ) : null,
1767
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(MermaidSource, { entry, ref })
1817
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(MermaidSource, { entry, ref })
1768
1818
  ] });
1769
1819
  }
1770
1820
 
1771
1821
  // src/components/doc/MermaidDiagram.tsx
1772
- var import_jsx_runtime37 = require("react/jsx-runtime");
1822
+ var import_jsx_runtime38 = require("react/jsx-runtime");
1773
1823
  var mermaidLoader;
1774
1824
  function loadMermaid() {
1775
1825
  mermaidLoader ??= import("mermaid").then((m) => m.default);
1776
1826
  return mermaidLoader;
1777
1827
  }
1778
1828
  function MermaidDiagram({ entry, load = loadMermaid }) {
1779
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(MermaidView, { entry, load });
1829
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(MermaidView, { entry, load });
1780
1830
  }
1781
1831
 
1782
1832
  // src/components/ScenarioExplorer.tsx
@@ -1786,12 +1836,12 @@ var import_react9 = require("react");
1786
1836
  var React4 = require("react");
1787
1837
  var import_cmdk = require("cmdk");
1788
1838
  var import_lucide_react3 = require("lucide-react");
1789
- var import_jsx_runtime38 = require("react/jsx-runtime");
1839
+ var import_jsx_runtime39 = require("react/jsx-runtime");
1790
1840
  function Command({
1791
1841
  className,
1792
1842
  ...props
1793
1843
  }) {
1794
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
1844
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1795
1845
  import_cmdk.Command,
1796
1846
  {
1797
1847
  "data-slot": "command",
@@ -1807,14 +1857,14 @@ function CommandInput({
1807
1857
  className,
1808
1858
  ...props
1809
1859
  }) {
1810
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
1860
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
1811
1861
  "div",
1812
1862
  {
1813
1863
  "data-slot": "command-input-wrapper",
1814
1864
  className: "flex h-9 items-center gap-2 border-b px-3",
1815
1865
  children: [
1816
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react3.SearchIcon, { className: "size-4 shrink-0 opacity-50" }),
1817
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
1866
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_lucide_react3.SearchIcon, { className: "size-4 shrink-0 opacity-50" }),
1867
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1818
1868
  import_cmdk.Command.Input,
1819
1869
  {
1820
1870
  "data-slot": "command-input",
@@ -1833,7 +1883,7 @@ function CommandList({
1833
1883
  className,
1834
1884
  ...props
1835
1885
  }) {
1836
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
1886
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1837
1887
  import_cmdk.Command.List,
1838
1888
  {
1839
1889
  "data-slot": "command-list",
@@ -1848,7 +1898,7 @@ function CommandList({
1848
1898
  function CommandEmpty({
1849
1899
  ...props
1850
1900
  }) {
1851
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
1901
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1852
1902
  import_cmdk.Command.Empty,
1853
1903
  {
1854
1904
  "data-slot": "command-empty",
@@ -1861,7 +1911,7 @@ function CommandGroup({
1861
1911
  className,
1862
1912
  ...props
1863
1913
  }) {
1864
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
1914
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1865
1915
  import_cmdk.Command.Group,
1866
1916
  {
1867
1917
  "data-slot": "command-group",
@@ -1877,7 +1927,7 @@ function CommandItem({
1877
1927
  className,
1878
1928
  ...props
1879
1929
  }) {
1880
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
1930
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1881
1931
  import_cmdk.Command.Item,
1882
1932
  {
1883
1933
  "data-slot": "command-item",
@@ -1891,7 +1941,7 @@ function CommandItem({
1891
1941
  }
1892
1942
 
1893
1943
  // src/components/ScenarioExplorer.tsx
1894
- var import_jsx_runtime39 = require("react/jsx-runtime");
1944
+ var import_jsx_runtime40 = require("react/jsx-runtime");
1895
1945
  var STATUS_FILTERS = [
1896
1946
  { value: "all", label: "All" },
1897
1947
  { value: "passed", label: "Passed" },
@@ -1902,22 +1952,22 @@ var STATUS_FILTERS = [
1902
1952
  function ScenarioExplorer({ scenarios, summary }) {
1903
1953
  const [status, setStatus] = (0, import_react9.useState)("all");
1904
1954
  const visible = status === "all" ? scenarios : scenarios.filter((s) => s.status === status);
1905
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "font-sans text-foreground", children: [
1906
- summary ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("p", { className: "mb-3 text-sm text-muted-foreground", children: [
1955
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "font-sans text-foreground", children: [
1956
+ summary ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("p", { className: "mb-3 text-sm text-muted-foreground", children: [
1907
1957
  summary.total,
1908
1958
  " scenarios \xB7 ",
1909
- /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("span", { className: "text-pass", children: [
1959
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "text-pass", children: [
1910
1960
  summary.passed,
1911
1961
  " passed"
1912
1962
  ] }),
1913
1963
  " \xB7",
1914
1964
  " ",
1915
- /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("span", { className: "text-fail", children: [
1965
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "text-fail", children: [
1916
1966
  summary.failed,
1917
1967
  " failed"
1918
1968
  ] })
1919
1969
  ] }) : null,
1920
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "mb-2 flex flex-wrap gap-1.5", role: "group", "aria-label": "Filter by status", children: STATUS_FILTERS.map((f) => /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1970
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "mb-2 flex flex-wrap gap-1.5", role: "group", "aria-label": "Filter by status", children: STATUS_FILTERS.map((f) => /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
1921
1971
  "button",
1922
1972
  {
1923
1973
  type: "button",
@@ -1932,26 +1982,26 @@ function ScenarioExplorer({ scenarios, summary }) {
1932
1982
  },
1933
1983
  f.value
1934
1984
  )) }),
1935
- /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
1985
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
1936
1986
  Command,
1937
1987
  {
1938
1988
  className: "rounded-lg border border-border bg-card",
1939
1989
  filter: (value, search) => value.toLowerCase().includes(search.toLowerCase()) ? 1 : 0,
1940
1990
  children: [
1941
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(CommandInput, { placeholder: "Search scenarios\u2026" }),
1942
- /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(CommandList, { className: "max-h-none", children: [
1943
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(CommandEmpty, { children: "No scenarios match your filters." }),
1944
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(CommandGroup, { children: visible.map((s) => /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
1991
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(CommandInput, { placeholder: "Search scenarios\u2026" }),
1992
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(CommandList, { className: "max-h-none", children: [
1993
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(CommandEmpty, { children: "No scenarios match your filters." }),
1994
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(CommandGroup, { children: visible.map((s) => /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
1945
1995
  CommandItem,
1946
1996
  {
1947
1997
  value: `${s.title} ${s.feature} ${s.tags.join(" ")}`,
1948
1998
  asChild: true,
1949
1999
  className: "cursor-pointer",
1950
- children: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("a", { href: s.href, className: "flex items-center gap-3", children: [
1951
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(Badge, { variant: s.status, children: s.status }),
1952
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "min-w-0 flex-1 truncate font-medium", children: s.title }),
1953
- s.tags.slice(0, 3).map((t) => /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(Badge, { variant: "tag", className: "hidden sm:inline-flex", children: t }, t)),
1954
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("code", { className: "hidden font-mono text-xs text-muted-foreground md:inline", children: s.feature })
2000
+ children: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("a", { href: s.href, className: "flex items-center gap-3", children: [
2001
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Badge, { variant: s.status, children: s.status }),
2002
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "min-w-0 flex-1 truncate font-medium", children: s.title }),
2003
+ s.tags.slice(0, 3).map((t) => /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Badge, { variant: "tag", className: "hidden sm:inline-flex", children: t }, t)),
2004
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("code", { className: "hidden font-mono text-xs text-muted-foreground md:inline", children: s.feature })
1955
2005
  ] })
1956
2006
  },
1957
2007
  s.id
@@ -2974,6 +3024,8 @@ function parseStoryReport(input) {
2974
3024
  }
2975
3025
  // Annotate the CommonJS export names for ESM import in node:
2976
3026
  0 && (module.exports = {
3027
+ DATA_MODEL_TYPE,
3028
+ DataModelBlock,
2977
3029
  DocCode,
2978
3030
  DocCustom,
2979
3031
  DocEntry,
@@ -2987,6 +3039,8 @@ function parseStoryReport(input) {
2987
3039
  DocTable,
2988
3040
  DocTag,
2989
3041
  DocVideo,
3042
+ FILE_TREE_TYPE,
3043
+ FileTreeBlock,
2990
3044
  MermaidDiagram,
2991
3045
  MermaidSource,
2992
3046
  Report,
@@ -3016,7 +3070,10 @@ function parseStoryReport(input) {
3016
3070
  err,
3017
3071
  formatRelativeAge,
3018
3072
  isReportStale,
3073
+ narrativeBlockRenderers,
3019
3074
  ok,
3075
+ parseDataModel,
3076
+ parseFileTree,
3020
3077
  parseStoryReport,
3021
3078
  prUrl,
3022
3079
  reportLastRunMs,