executable-stories-react 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,12 +2,12 @@
2
2
 
3
3
  // src/interactive/ReportInteractive.tsx
4
4
  import {
5
- useCallback as useCallback4,
5
+ useCallback as useCallback5,
6
6
  useDeferredValue,
7
7
  useId as useId2,
8
8
  useMemo as useMemo3,
9
- useRef as useRef3,
10
- useState as useState7
9
+ useRef as useRef5,
10
+ useState as useState8
11
11
  } from "react";
12
12
 
13
13
  // src/result.ts
@@ -26,63 +26,6 @@ import { useMemo } from "react";
26
26
  import { createContext } from "react";
27
27
  var ReportContext = createContext(null);
28
28
 
29
- // src/context/ReportRoot.tsx
30
- import { jsx } from "react/jsx-runtime";
31
- var EMPTY_CUSTOM = {};
32
- var EMPTY_RENDERERS = {};
33
- function ReportRoot({
34
- report,
35
- customRenderers,
36
- renderers,
37
- children
38
- }) {
39
- const value = useMemo(
40
- () => ({
41
- report,
42
- customRenderers: customRenderers ?? EMPTY_CUSTOM,
43
- renderers: renderers ?? EMPTY_RENDERERS
44
- }),
45
- [report, customRenderers, renderers]
46
- );
47
- return /* @__PURE__ */ jsx(ReportContext.Provider, { value, children });
48
- }
49
-
50
- // src/hooks/useReport.ts
51
- import { useContext } from "react";
52
- function useReport() {
53
- const ctx = useContext(ReportContext);
54
- if (!ctx) {
55
- throw new Error(
56
- "useReport must be used inside <ReportRoot> or <Report>. Wrap your tree with one of those."
57
- );
58
- }
59
- return ctx.report;
60
- }
61
-
62
- // ../executable-stories-core/dist/utils/duration.js
63
- function formatDuration(ms) {
64
- if (ms < 1e3) {
65
- return `${Math.round(ms)} ms`;
66
- }
67
- if (ms < 6e4) {
68
- return `${(ms / 1e3).toFixed(2)} s`;
69
- }
70
- const minutes = Math.floor(ms / 6e4);
71
- const seconds = Math.floor(ms % 6e4 / 1e3);
72
- if (minutes < 60) {
73
- return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
74
- }
75
- const hours = Math.floor(minutes / 60);
76
- const remainingMinutes = minutes % 60;
77
- return `${hours}h ${remainingMinutes}m`;
78
- }
79
-
80
- // src/components/doc/DocNote.tsx
81
- import { jsx as jsx2 } from "react/jsx-runtime";
82
- function DocNote({ entry }) {
83
- return /* @__PURE__ */ jsx2("p", { className: "my-2 text-sm text-muted-foreground", children: entry.text });
84
- }
85
-
86
29
  // src/components/ui/badge.tsx
87
30
  import "react";
88
31
  import { cva } from "class-variance-authority";
@@ -141,14 +84,249 @@ function Badge({
141
84
  });
142
85
  }
143
86
 
144
- // src/components/doc/DocTag.tsx
87
+ // src/components/doc/NarrativeBlocks.tsx
88
+ import { jsx, jsxs } from "react/jsx-runtime";
89
+ var FILE_TREE_TYPE = "file-tree";
90
+ var DATA_MODEL_TYPE = "data-model";
91
+ function isChangeKind(value) {
92
+ return value === "added" || value === "modified" || value === "removed" || value === "renamed";
93
+ }
94
+ function isRecord(value) {
95
+ return typeof value === "object" && value !== null && !Array.isArray(value);
96
+ }
97
+ function str(value) {
98
+ return typeof value === "string" && value !== "" ? value : void 0;
99
+ }
100
+ function AuthoredMarker({ authored }) {
101
+ if (authored !== "agent") return null;
102
+ return /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "border-border text-[10px] text-muted-foreground", children: "AI-authored, not verified by a run" });
103
+ }
104
+ function BlockFrame({
105
+ title,
106
+ authored,
107
+ children
108
+ }) {
109
+ return /* @__PURE__ */ jsxs("figure", { className: "my-3", children: [
110
+ title || authored === "agent" ? /* @__PURE__ */ jsxs("figcaption", { className: "mb-1.5 flex flex-wrap items-center gap-2", children: [
111
+ title ? /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: title }) : null,
112
+ /* @__PURE__ */ jsx(AuthoredMarker, { authored })
113
+ ] }) : null,
114
+ children
115
+ ] });
116
+ }
117
+ function ChangeBadge({ change }) {
118
+ return /* @__PURE__ */ jsx(Badge, { variant: "tag", className: "text-[10px]", children: change });
119
+ }
120
+ function parseFileTree(data) {
121
+ if (!isRecord(data) || !Array.isArray(data.files)) return void 0;
122
+ const files = [];
123
+ for (const raw of data.files) {
124
+ if (typeof raw === "string") {
125
+ if (raw) files.push({ path: raw });
126
+ continue;
127
+ }
128
+ if (!isRecord(raw)) continue;
129
+ const path = str(raw.path);
130
+ if (!path) continue;
131
+ files.push({
132
+ path,
133
+ change: isChangeKind(raw.change) ? raw.change : void 0,
134
+ note: str(raw.note)
135
+ });
136
+ }
137
+ if (files.length === 0) return void 0;
138
+ return { title: str(data.title), authored: str(data.authored), files };
139
+ }
140
+ function buildTree(files) {
141
+ const root = { name: "", children: /* @__PURE__ */ new Map() };
142
+ for (const file of files) {
143
+ const segments = file.path.split("/").filter(Boolean);
144
+ let node = root;
145
+ segments.forEach((segment, index) => {
146
+ let child = node.children.get(segment);
147
+ if (!child) {
148
+ child = { name: segment, children: /* @__PURE__ */ new Map() };
149
+ node.children.set(segment, child);
150
+ }
151
+ if (index === segments.length - 1) child.file = file;
152
+ node = child;
153
+ });
154
+ }
155
+ return root;
156
+ }
157
+ function sortedChildren(node) {
158
+ return [...node.children.values()].sort((a, b) => {
159
+ const aDir = a.children.size > 0;
160
+ const bDir = b.children.size > 0;
161
+ if (aDir !== bDir) return aDir ? -1 : 1;
162
+ return a.name.localeCompare(b.name);
163
+ });
164
+ }
165
+ function TreeBranch({ node, depth }) {
166
+ const children = sortedChildren(node);
167
+ return /* @__PURE__ */ jsx("ul", { className: cn("space-y-0.5", depth > 0 && "ml-3 border-l border-border/60 pl-3"), children: children.map((child) => {
168
+ const isDir = child.children.size > 0;
169
+ return /* @__PURE__ */ jsxs("li", { children: [
170
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-baseline gap-2", children: [
171
+ /* @__PURE__ */ jsx(
172
+ "span",
173
+ {
174
+ className: cn(
175
+ "font-mono text-xs",
176
+ isDir ? "text-muted-foreground" : "text-foreground"
177
+ ),
178
+ children: isDir ? `${child.name}/` : child.name
179
+ }
180
+ ),
181
+ child.file?.change ? /* @__PURE__ */ jsx(ChangeBadge, { change: child.file.change }) : null,
182
+ child.file?.note ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: child.file.note }) : null
183
+ ] }),
184
+ isDir ? /* @__PURE__ */ jsx(TreeBranch, { node: child, depth: depth + 1 }) : null
185
+ ] }, child.name);
186
+ }) });
187
+ }
188
+ function FileTreeBlock({ entry }) {
189
+ const data = parseFileTree(entry.data);
190
+ if (!data) return /* @__PURE__ */ jsx(UnreadableBlock, { entry });
191
+ return /* @__PURE__ */ jsx(BlockFrame, { title: data.title ?? "Files changed", authored: data.authored, children: /* @__PURE__ */ jsx("div", { tabIndex: 0, className: "overflow-x-auto rounded-md border border-border bg-card p-3", children: /* @__PURE__ */ jsx(TreeBranch, { node: buildTree(data.files), depth: 0 }) }) });
192
+ }
193
+ function parseDataModel(data) {
194
+ if (!isRecord(data) || !Array.isArray(data.fields)) return void 0;
195
+ const fields = [];
196
+ for (const raw of data.fields) {
197
+ if (!isRecord(raw)) continue;
198
+ const name = str(raw.name);
199
+ if (!name) continue;
200
+ fields.push({
201
+ name,
202
+ type: str(raw.type),
203
+ note: str(raw.note),
204
+ change: isChangeKind(raw.change) ? raw.change : void 0
205
+ });
206
+ }
207
+ if (fields.length === 0) return void 0;
208
+ return {
209
+ title: str(data.title),
210
+ authored: str(data.authored),
211
+ name: str(data.name),
212
+ fields
213
+ };
214
+ }
215
+ function DataModelBlock({ entry }) {
216
+ const data = parseDataModel(entry.data);
217
+ if (!data) return /* @__PURE__ */ jsx(UnreadableBlock, { entry });
218
+ const hasNotes = data.fields.some((f) => f.note);
219
+ return /* @__PURE__ */ jsx(BlockFrame, { title: data.title ?? data.name ?? "Data model", authored: data.authored, children: /* @__PURE__ */ jsx("div", { tabIndex: 0, className: "overflow-x-auto rounded-md border border-border", children: /* @__PURE__ */ jsxs("table", { className: "w-full border-collapse text-sm", children: [
220
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "bg-muted/50", children: [
221
+ /* @__PURE__ */ jsx("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: "Field" }),
222
+ /* @__PURE__ */ jsx("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: "Type" }),
223
+ hasNotes ? /* @__PURE__ */ jsx("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: "Note" }) : null
224
+ ] }) }),
225
+ /* @__PURE__ */ jsx("tbody", { children: data.fields.map((field) => /* @__PURE__ */ jsxs("tr", { children: [
226
+ /* @__PURE__ */ jsx("td", { className: "border-b border-border px-3 py-2", children: /* @__PURE__ */ jsxs("span", { className: "flex flex-wrap items-center gap-2", children: [
227
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-xs text-foreground", children: field.name }),
228
+ field.change ? /* @__PURE__ */ jsx(ChangeBadge, { change: field.change }) : null
229
+ ] }) }),
230
+ /* @__PURE__ */ jsx("td", { className: "border-b border-border px-3 py-2 font-mono text-xs text-muted-foreground", children: field.type ?? "" }),
231
+ hasNotes ? /* @__PURE__ */ jsx("td", { className: "border-b border-border px-3 py-2 text-foreground", children: field.note ?? "" }) : null
232
+ ] }, field.name)) })
233
+ ] }) }) });
234
+ }
235
+ function UnreadableBlock({ entry }) {
236
+ return /* @__PURE__ */ jsxs("div", { className: "my-2", "data-type": entry.type, children: [
237
+ /* @__PURE__ */ jsxs("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: [
238
+ entry.type,
239
+ " (unrecognised shape)"
240
+ ] }),
241
+ /* @__PURE__ */ jsx(
242
+ "pre",
243
+ {
244
+ tabIndex: 0,
245
+ className: "overflow-x-auto rounded-md bg-muted p-3 font-mono text-xs text-foreground",
246
+ children: safeStringify(entry.data)
247
+ }
248
+ )
249
+ ] });
250
+ }
251
+ function safeStringify(value) {
252
+ try {
253
+ return JSON.stringify(value, null, 2);
254
+ } catch {
255
+ return String(value);
256
+ }
257
+ }
258
+ var narrativeBlockRenderers = {
259
+ [FILE_TREE_TYPE]: (entry) => /* @__PURE__ */ jsx(FileTreeBlock, { entry }),
260
+ [DATA_MODEL_TYPE]: (entry) => /* @__PURE__ */ jsx(DataModelBlock, { entry })
261
+ };
262
+
263
+ // src/context/ReportRoot.tsx
264
+ import { jsx as jsx2 } from "react/jsx-runtime";
265
+ var EMPTY_RENDERERS = {};
266
+ function ReportRoot({
267
+ report,
268
+ customRenderers,
269
+ renderers,
270
+ children
271
+ }) {
272
+ const value = useMemo(
273
+ () => ({
274
+ report,
275
+ // Narrative blocks render everywhere the report does (static SSR, the
276
+ // island, Astro) without every host wiring them up. A host renderer for
277
+ // the same type still wins.
278
+ customRenderers: { ...narrativeBlockRenderers, ...customRenderers },
279
+ renderers: renderers ?? EMPTY_RENDERERS
280
+ }),
281
+ [report, customRenderers, renderers]
282
+ );
283
+ return /* @__PURE__ */ jsx2(ReportContext.Provider, { value, children });
284
+ }
285
+
286
+ // src/hooks/useReport.ts
287
+ import { useContext } from "react";
288
+ function useReport() {
289
+ const ctx = useContext(ReportContext);
290
+ if (!ctx) {
291
+ throw new Error(
292
+ "useReport must be used inside <ReportRoot> or <Report>. Wrap your tree with one of those."
293
+ );
294
+ }
295
+ return ctx.report;
296
+ }
297
+
298
+ // ../executable-stories-core/dist/utils/duration.js
299
+ function formatDuration(ms) {
300
+ if (ms < 1e3) {
301
+ return `${Math.round(ms)} ms`;
302
+ }
303
+ if (ms < 6e4) {
304
+ return `${(ms / 1e3).toFixed(2)} s`;
305
+ }
306
+ const minutes = Math.floor(ms / 6e4);
307
+ const seconds = Math.floor(ms % 6e4 / 1e3);
308
+ if (minutes < 60) {
309
+ return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
310
+ }
311
+ const hours = Math.floor(minutes / 60);
312
+ const remainingMinutes = minutes % 60;
313
+ return `${hours}h ${remainingMinutes}m`;
314
+ }
315
+
316
+ // src/components/doc/DocNote.tsx
145
317
  import { jsx as jsx3 } from "react/jsx-runtime";
318
+ function DocNote({ entry }) {
319
+ return /* @__PURE__ */ jsx3("p", { className: "my-2 text-sm text-muted-foreground", children: entry.text });
320
+ }
321
+
322
+ // src/components/doc/DocTag.tsx
323
+ import { jsx as jsx4 } from "react/jsx-runtime";
146
324
  function DocTag({ entry }) {
147
- return /* @__PURE__ */ jsx3("ul", { className: "my-2 flex flex-wrap gap-1.5", "aria-label": "Tags", children: entry.names.map((n) => /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsx3(Badge, { variant: "tag", children: n }) }, n)) });
325
+ return /* @__PURE__ */ jsx4("ul", { className: "my-2 flex flex-wrap gap-1.5", "aria-label": "Tags", children: entry.names.map((n) => /* @__PURE__ */ jsx4("li", { children: /* @__PURE__ */ jsx4(Badge, { variant: "tag", children: n }) }, n)) });
148
326
  }
149
327
 
150
328
  // src/components/doc/DocKv.tsx
151
- import { jsx as jsx4, jsxs } from "react/jsx-runtime";
329
+ import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
152
330
  function formatValue(value) {
153
331
  if (value === null) return "null";
154
332
  if (typeof value === "string") return value;
@@ -160,19 +338,19 @@ function formatValue(value) {
160
338
  }
161
339
  }
162
340
  function DocKv({ entry }) {
163
- return /* @__PURE__ */ jsxs("dl", { className: "my-2 flex flex-wrap items-baseline gap-x-2 text-sm", children: [
164
- /* @__PURE__ */ jsx4("dt", { className: "font-semibold text-muted-foreground", children: entry.label }),
165
- /* @__PURE__ */ jsx4("dd", { className: "font-mono text-foreground", children: formatValue(entry.value) })
341
+ return /* @__PURE__ */ jsxs2("dl", { className: "my-2 flex flex-wrap items-baseline gap-x-2 text-sm", children: [
342
+ /* @__PURE__ */ jsx5("dt", { className: "font-semibold text-muted-foreground", children: entry.label }),
343
+ /* @__PURE__ */ jsx5("dd", { className: "font-mono text-foreground", children: formatValue(entry.value) })
166
344
  ] });
167
345
  }
168
346
 
169
347
  // src/hooks/useRenderers.ts
170
348
  import { useContext as useContext2 } from "react";
171
- var EMPTY_CUSTOM2 = {};
349
+ var EMPTY_CUSTOM = {};
172
350
  var EMPTY_RENDERERS2 = {};
173
351
  function useCustomRenderers() {
174
352
  const ctx = useContext2(ReportContext);
175
- return ctx?.customRenderers ?? EMPTY_CUSTOM2;
353
+ return ctx?.customRenderers ?? EMPTY_CUSTOM;
176
354
  }
177
355
  function useBuiltinRenderers() {
178
356
  const ctx = useContext2(ReportContext);
@@ -180,37 +358,37 @@ function useBuiltinRenderers() {
180
358
  }
181
359
 
182
360
  // src/components/doc/DocCode.tsx
183
- import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
361
+ import { Fragment, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
184
362
  function CodeFigure({ label, children }) {
185
- return /* @__PURE__ */ jsxs2("figure", { className: "my-3", children: [
186
- label ? /* @__PURE__ */ jsx5("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: label }) : null,
187
- /* @__PURE__ */ jsx5("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3", children })
363
+ return /* @__PURE__ */ jsxs3("figure", { className: "my-3", children: [
364
+ label ? /* @__PURE__ */ jsx6("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: label }) : null,
365
+ /* @__PURE__ */ jsx6("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3", children })
188
366
  ] });
189
367
  }
190
368
  function DocCode({ entry }) {
191
369
  const renderers = useBuiltinRenderers();
192
370
  if (renderers.code) {
193
- return /* @__PURE__ */ jsx5(Fragment, { children: renderers.code(entry) });
371
+ return /* @__PURE__ */ jsx6(Fragment, { children: renderers.code(entry) });
194
372
  }
195
- return /* @__PURE__ */ jsx5(CodeFigure, { label: entry.label, children: /* @__PURE__ */ jsx5("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
373
+ return /* @__PURE__ */ jsx6(CodeFigure, { label: entry.label, children: /* @__PURE__ */ jsx6("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
196
374
  }
197
375
 
198
376
  // src/components/doc/DocTable.tsx
199
- import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
377
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
200
378
  function DocTable({ entry }) {
201
- return /* @__PURE__ */ jsxs3("figure", { className: "my-3", children: [
202
- entry.label ? /* @__PURE__ */ jsx6("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
203
- /* @__PURE__ */ jsx6("div", { tabIndex: 0, className: "overflow-x-auto rounded-md border border-border", children: /* @__PURE__ */ jsxs3("table", { className: "w-full border-collapse text-sm", children: [
204
- /* @__PURE__ */ jsx6("thead", { children: /* @__PURE__ */ jsx6("tr", { className: "bg-muted/50", children: entry.columns.map((c) => /* @__PURE__ */ jsx6("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: c }, c)) }) }),
205
- /* @__PURE__ */ jsx6("tbody", { children: entry.rows.map((row, i) => /* @__PURE__ */ jsx6("tr", { children: row.map((cell2, j) => /* @__PURE__ */ jsx6("td", { className: "border-b border-border px-3 py-2 text-foreground", children: cell2 }, j)) }, i)) })
379
+ return /* @__PURE__ */ jsxs4("figure", { className: "my-3", children: [
380
+ entry.label ? /* @__PURE__ */ jsx7("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
381
+ /* @__PURE__ */ jsx7("div", { tabIndex: 0, className: "overflow-x-auto rounded-md border border-border", children: /* @__PURE__ */ jsxs4("table", { className: "w-full border-collapse text-sm", children: [
382
+ /* @__PURE__ */ jsx7("thead", { children: /* @__PURE__ */ jsx7("tr", { className: "bg-muted/50", children: entry.columns.map((c) => /* @__PURE__ */ jsx7("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: c }, c)) }) }),
383
+ /* @__PURE__ */ jsx7("tbody", { children: entry.rows.map((row, i) => /* @__PURE__ */ jsx7("tr", { children: row.map((cell2, j) => /* @__PURE__ */ jsx7("td", { className: "border-b border-border px-3 py-2 text-foreground", children: cell2 }, j)) }, i)) })
206
384
  ] }) })
207
385
  ] });
208
386
  }
209
387
 
210
388
  // src/components/doc/DocLink.tsx
211
- import { jsx as jsx7 } from "react/jsx-runtime";
389
+ import { jsx as jsx8 } from "react/jsx-runtime";
212
390
  function DocLink({ entry }) {
213
- return /* @__PURE__ */ jsx7(
391
+ return /* @__PURE__ */ jsx8(
214
392
  "a",
215
393
  {
216
394
  className: "text-sm font-medium text-primary underline underline-offset-2 hover:no-underline",
@@ -244,7 +422,7 @@ function isLocalFsPath(value) {
244
422
  }
245
423
 
246
424
  // src/components/doc/DocSection.tsx
247
- import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
425
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
248
426
  function neutralizeUrl(value) {
249
427
  return safeUrl(value) ?? "#";
250
428
  }
@@ -255,12 +433,12 @@ function safeMarkdownHtml(markdown) {
255
433
  function DocSection({ entry }) {
256
434
  const renderers = useBuiltinRenderers();
257
435
  if (renderers.section) {
258
- return /* @__PURE__ */ jsx8(Fragment2, { children: renderers.section(entry) });
436
+ return /* @__PURE__ */ jsx9(Fragment2, { children: renderers.section(entry) });
259
437
  }
260
438
  const html = safeMarkdownHtml(entry.markdown);
261
- return /* @__PURE__ */ jsxs4("section", { className: "my-3 text-sm", "aria-label": entry.title, children: [
262
- entry.title ? /* @__PURE__ */ jsx8("h4", { className: "mb-1 font-semibold text-foreground", children: entry.title }) : null,
263
- /* @__PURE__ */ jsx8(
439
+ return /* @__PURE__ */ jsxs5("section", { className: "my-3 text-sm", "aria-label": entry.title, children: [
440
+ entry.title ? /* @__PURE__ */ jsx9("h4", { className: "mb-1 font-semibold text-foreground", children: entry.title }) : null,
441
+ /* @__PURE__ */ jsx9(
264
442
  "div",
265
443
  {
266
444
  className: "es-doc-prose prose prose-sm max-w-none",
@@ -271,11 +449,11 @@ function DocSection({ entry }) {
271
449
  }
272
450
 
273
451
  // src/components/doc/DocMermaid.tsx
274
- import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
452
+ import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
275
453
  function MermaidSource({ entry, ref }) {
276
- return /* @__PURE__ */ jsxs5("figure", { ref, className: "my-3", "aria-label": entry.title ?? "Diagram", children: [
277
- entry.title ? /* @__PURE__ */ jsx9("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
278
- /* @__PURE__ */ jsx9(
454
+ return /* @__PURE__ */ jsxs6("figure", { ref, className: "my-3", "aria-label": entry.title ?? "Diagram", children: [
455
+ entry.title ? /* @__PURE__ */ jsx10("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
456
+ /* @__PURE__ */ jsx10(
279
457
  "pre",
280
458
  {
281
459
  "data-mermaid": true,
@@ -289,24 +467,24 @@ function MermaidSource({ entry, ref }) {
289
467
  function DocMermaid({ entry }) {
290
468
  const renderers = useBuiltinRenderers();
291
469
  if (renderers.mermaid) {
292
- return /* @__PURE__ */ jsx9(Fragment3, { children: renderers.mermaid(entry) });
470
+ return /* @__PURE__ */ jsx10(Fragment3, { children: renderers.mermaid(entry) });
293
471
  }
294
- return /* @__PURE__ */ jsx9(MermaidSource, { entry });
472
+ return /* @__PURE__ */ jsx10(MermaidSource, { entry });
295
473
  }
296
474
 
297
475
  // src/components/doc/DocScreenshot.tsx
298
- import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
476
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
299
477
  function DocScreenshot({ entry }) {
300
478
  const src = isLocalFsPath(entry.path) ? void 0 : safeImageUrl(entry.path);
301
479
  if (!src) {
302
- return /* @__PURE__ */ jsxs6("figure", { className: "my-3 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground", children: [
303
- /* @__PURE__ */ jsx10("div", { className: "font-medium", children: "Screenshot unavailable" }),
304
- /* @__PURE__ */ jsx10("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
305
- entry.alt ? /* @__PURE__ */ jsx10("figcaption", { className: "mt-1.5 text-xs", children: entry.alt }) : null
480
+ return /* @__PURE__ */ jsxs7("figure", { className: "my-3 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground", children: [
481
+ /* @__PURE__ */ jsx11("div", { className: "font-medium", children: "Screenshot unavailable" }),
482
+ /* @__PURE__ */ jsx11("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
483
+ entry.alt ? /* @__PURE__ */ jsx11("figcaption", { className: "mt-1.5 text-xs", children: entry.alt }) : null
306
484
  ] });
307
485
  }
308
- return /* @__PURE__ */ jsxs6("figure", { className: "my-3", children: [
309
- /* @__PURE__ */ jsx10(
486
+ return /* @__PURE__ */ jsxs7("figure", { className: "my-3", children: [
487
+ /* @__PURE__ */ jsx11(
310
488
  "img",
311
489
  {
312
490
  src,
@@ -315,24 +493,24 @@ function DocScreenshot({ entry }) {
315
493
  className: "max-w-full rounded-md border border-border"
316
494
  }
317
495
  ),
318
- entry.alt ? /* @__PURE__ */ jsx10("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: entry.alt }) : null
496
+ entry.alt ? /* @__PURE__ */ jsx11("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: entry.alt }) : null
319
497
  ] });
320
498
  }
321
499
 
322
500
  // src/components/doc/DocVideo.tsx
323
- import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
501
+ import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
324
502
  function DocVideo({ entry }) {
325
503
  if (isLocalFsPath(entry.path)) {
326
- return /* @__PURE__ */ jsxs7("figure", { className: "my-3 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground", children: [
327
- /* @__PURE__ */ jsx11("div", { className: "font-medium", children: "Video unavailable" }),
328
- /* @__PURE__ */ jsx11("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
329
- entry.caption ? /* @__PURE__ */ jsx11("figcaption", { className: "mt-1.5 text-xs", children: entry.caption }) : null
504
+ return /* @__PURE__ */ jsxs8("figure", { className: "my-3 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground", children: [
505
+ /* @__PURE__ */ jsx12("div", { className: "font-medium", children: "Video unavailable" }),
506
+ /* @__PURE__ */ jsx12("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
507
+ entry.caption ? /* @__PURE__ */ jsx12("figcaption", { className: "mt-1.5 text-xs", children: entry.caption }) : null
330
508
  ] });
331
509
  }
332
510
  const src = safeUrl(entry.path);
333
511
  const poster = safeUrl(entry.poster);
334
- return /* @__PURE__ */ jsxs7("figure", { className: "my-3", children: [
335
- /* @__PURE__ */ jsx11(
512
+ return /* @__PURE__ */ jsxs8("figure", { className: "my-3", children: [
513
+ /* @__PURE__ */ jsx12(
336
514
  "video",
337
515
  {
338
516
  controls: true,
@@ -342,21 +520,21 @@ function DocVideo({ entry }) {
342
520
  className: "max-w-full rounded-md border border-border"
343
521
  }
344
522
  ),
345
- entry.caption ? /* @__PURE__ */ jsx11("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: entry.caption }) : null
523
+ entry.caption ? /* @__PURE__ */ jsx12("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: entry.caption }) : null
346
524
  ] });
347
525
  }
348
526
 
349
527
  // src/components/doc/DocHtml.tsx
350
- import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
528
+ import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
351
529
  function DocHtml({ entry }) {
352
530
  const height = typeof entry.height === "number" ? `${entry.height}px` : entry.height ?? "400px";
353
531
  const title = entry.title ?? "Embedded HTML";
354
532
  const safeHref = safeUrl(entry.url ?? entry.path);
355
533
  const frameProps = entry.content !== void 0 ? { srcDoc: entry.content } : { src: safeHref ?? "" };
356
- return /* @__PURE__ */ jsxs8("figure", { className: "my-3 overflow-hidden rounded-md border border-border", children: [
357
- /* @__PURE__ */ jsxs8("figcaption", { className: "flex items-center justify-between border-b border-border bg-muted/40 px-3 py-1.5 text-xs font-medium text-muted-foreground", children: [
358
- /* @__PURE__ */ jsx12("span", { children: entry.title ?? "HTML" }),
359
- safeHref ? /* @__PURE__ */ jsx12(
534
+ return /* @__PURE__ */ jsxs9("figure", { className: "my-3 overflow-hidden rounded-md border border-border", children: [
535
+ /* @__PURE__ */ jsxs9("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: [
536
+ /* @__PURE__ */ jsx13("span", { children: entry.title ?? "HTML" }),
537
+ safeHref ? /* @__PURE__ */ jsx13(
360
538
  "a",
361
539
  {
362
540
  href: safeHref,
@@ -369,7 +547,7 @@ function DocHtml({ entry }) {
369
547
  }
370
548
  ) : null
371
549
  ] }),
372
- /* @__PURE__ */ jsx12(
550
+ /* @__PURE__ */ jsx13(
373
551
  "iframe",
374
552
  {
375
553
  ...frameProps,
@@ -384,7 +562,7 @@ function DocHtml({ entry }) {
384
562
  }
385
563
 
386
564
  // src/components/doc/DocState.tsx
387
- import { jsx as jsx13 } from "react/jsx-runtime";
565
+ import { jsx as jsx14 } from "react/jsx-runtime";
388
566
  function DocState({ entry }) {
389
567
  let json;
390
568
  try {
@@ -392,23 +570,23 @@ function DocState({ entry }) {
392
570
  } catch {
393
571
  json = String(entry.value);
394
572
  }
395
- return /* @__PURE__ */ jsx13(CodeFigure, { label: entry.label ?? "State", children: /* @__PURE__ */ jsx13("code", { className: "font-mono text-xs language-json", children: json }) });
573
+ return /* @__PURE__ */ jsx14(CodeFigure, { label: entry.label ?? "State", children: /* @__PURE__ */ jsx14("code", { className: "font-mono text-xs language-json", children: json }) });
396
574
  }
397
575
 
398
576
  // src/components/doc/DocCustom.tsx
399
- import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
577
+ import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
400
578
  function DocCustom({ entry }) {
401
579
  const renderers = useCustomRenderers();
402
580
  const renderer = renderers[entry.type];
403
581
  if (renderer) {
404
- return /* @__PURE__ */ jsx14(Fragment4, { children: renderer(entry) });
582
+ return /* @__PURE__ */ jsx15(Fragment4, { children: renderer(entry) });
405
583
  }
406
- return /* @__PURE__ */ jsxs9("div", { className: "my-2", "data-type": entry.type, children: [
407
- /* @__PURE__ */ jsx14("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: entry.type }),
408
- /* @__PURE__ */ jsx14("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3 font-mono text-xs text-foreground", children: safeStringify(entry.data) })
584
+ return /* @__PURE__ */ jsxs10("div", { className: "my-2", "data-type": entry.type, children: [
585
+ /* @__PURE__ */ jsx15("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: entry.type }),
586
+ /* @__PURE__ */ jsx15("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3 font-mono text-xs text-foreground", children: safeStringify2(entry.data) })
409
587
  ] });
410
588
  }
411
- function safeStringify(value) {
589
+ function safeStringify2(value) {
412
590
  try {
413
591
  return JSON.stringify(value, null, 2);
414
592
  } catch {
@@ -417,47 +595,47 @@ function safeStringify(value) {
417
595
  }
418
596
 
419
597
  // src/components/doc/DocEntry.tsx
420
- import { jsx as jsx15 } from "react/jsx-runtime";
598
+ import { jsx as jsx16 } from "react/jsx-runtime";
421
599
  function DocEntry({ entry }) {
422
600
  switch (entry.kind) {
423
601
  case "note":
424
- return /* @__PURE__ */ jsx15(DocNote, { entry });
602
+ return /* @__PURE__ */ jsx16(DocNote, { entry });
425
603
  case "tag":
426
- return /* @__PURE__ */ jsx15(DocTag, { entry });
604
+ return /* @__PURE__ */ jsx16(DocTag, { entry });
427
605
  case "kv":
428
- return /* @__PURE__ */ jsx15(DocKv, { entry });
606
+ return /* @__PURE__ */ jsx16(DocKv, { entry });
429
607
  case "code":
430
- return /* @__PURE__ */ jsx15(DocCode, { entry });
608
+ return /* @__PURE__ */ jsx16(DocCode, { entry });
431
609
  case "table":
432
- return /* @__PURE__ */ jsx15(DocTable, { entry });
610
+ return /* @__PURE__ */ jsx16(DocTable, { entry });
433
611
  case "link":
434
- return /* @__PURE__ */ jsx15(DocLink, { entry });
612
+ return /* @__PURE__ */ jsx16(DocLink, { entry });
435
613
  case "section":
436
- return /* @__PURE__ */ jsx15(DocSection, { entry });
614
+ return /* @__PURE__ */ jsx16(DocSection, { entry });
437
615
  case "mermaid":
438
- return /* @__PURE__ */ jsx15(DocMermaid, { entry });
616
+ return /* @__PURE__ */ jsx16(DocMermaid, { entry });
439
617
  case "screenshot":
440
- return /* @__PURE__ */ jsx15(DocScreenshot, { entry });
618
+ return /* @__PURE__ */ jsx16(DocScreenshot, { entry });
441
619
  case "video":
442
- return /* @__PURE__ */ jsx15(DocVideo, { entry });
620
+ return /* @__PURE__ */ jsx16(DocVideo, { entry });
443
621
  case "html":
444
- return /* @__PURE__ */ jsx15(DocHtml, { entry });
622
+ return /* @__PURE__ */ jsx16(DocHtml, { entry });
445
623
  case "state":
446
- return /* @__PURE__ */ jsx15(DocState, { entry });
624
+ return /* @__PURE__ */ jsx16(DocState, { entry });
447
625
  case "custom":
448
- return /* @__PURE__ */ jsx15(DocCustom, { entry });
626
+ return /* @__PURE__ */ jsx16(DocCustom, { entry });
449
627
  }
450
628
  }
451
629
 
452
630
  // src/components/ReportDocEntries.tsx
453
- import { Fragment as Fragment5, jsx as jsx16 } from "react/jsx-runtime";
631
+ import { Fragment as Fragment5, jsx as jsx17 } from "react/jsx-runtime";
454
632
  function ReportDocEntries({ entries }) {
455
633
  if (entries.length === 0) return null;
456
- return /* @__PURE__ */ jsx16(Fragment5, { children: entries.map((entry, i) => /* @__PURE__ */ jsx16(DocEntry, { entry }, i)) });
634
+ return /* @__PURE__ */ jsx17(Fragment5, { children: entries.map((entry, i) => /* @__PURE__ */ jsx17(DocEntry, { entry }, i)) });
457
635
  }
458
636
 
459
637
  // src/lib/step-params.tsx
460
- import { jsx as jsx17 } from "react/jsx-runtime";
638
+ import { jsx as jsx18 } from "react/jsx-runtime";
461
639
  var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.-])\d+(?:\.\d+)?(?![\w.-])/g;
462
640
  function highlightStepParams(text) {
463
641
  const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
@@ -469,7 +647,7 @@ function highlightStepParams(text) {
469
647
  const end = start + match[0].length;
470
648
  if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
471
649
  nodes.push(
472
- /* @__PURE__ */ jsx17("span", { className: "font-medium text-step-param italic", children: match[0] }, i)
650
+ /* @__PURE__ */ jsx18("span", { className: "font-medium text-step-param italic", children: match[0] }, i)
473
651
  );
474
652
  lastIndex = end;
475
653
  });
@@ -478,7 +656,7 @@ function highlightStepParams(text) {
478
656
  }
479
657
 
480
658
  // src/components/ReportSteps.tsx
481
- import { jsx as jsx18, jsxs as jsxs10 } from "react/jsx-runtime";
659
+ import { jsx as jsx19, jsxs as jsxs11 } from "react/jsx-runtime";
482
660
  var STEP_GLYPH = {
483
661
  passed: "\u2713",
484
662
  failed: "\u2717",
@@ -493,21 +671,21 @@ var STEP_GLYPH_COLOR = {
493
671
  };
494
672
  function ReportSteps({ scenario }) {
495
673
  if (scenario.steps.length === 0) return null;
496
- return /* @__PURE__ */ jsx18("ol", { "data-slot": "steps", className: "flex flex-col", children: scenario.steps.map((step) => /* @__PURE__ */ jsx18(ReportStepItem, { step }, step.id)) });
674
+ return /* @__PURE__ */ jsx19("ol", { "data-slot": "steps", className: "flex flex-col", children: scenario.steps.map((step) => /* @__PURE__ */ jsx19(ReportStepItem, { step }, step.id)) });
497
675
  }
498
676
  function ReportStepItem({ step }) {
499
677
  const kw = step.keyword.toLowerCase();
500
678
  const continuation = kw === "and" || kw === "but";
501
- return /* @__PURE__ */ jsxs10(
679
+ return /* @__PURE__ */ jsxs11(
502
680
  "li",
503
681
  {
504
682
  id: step.id,
505
683
  "data-status": step.status,
506
684
  className: cn("py-1.5 text-[0.8125rem] leading-normal", continuation && "pl-5"),
507
685
  children: [
508
- /* @__PURE__ */ jsxs10("div", { className: "flex items-baseline gap-2", children: [
509
- /* @__PURE__ */ jsx18("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" }),
510
- /* @__PURE__ */ jsx18(
686
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-baseline gap-2", children: [
687
+ /* @__PURE__ */ jsx19("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" }),
688
+ /* @__PURE__ */ jsx19(
511
689
  "span",
512
690
  {
513
691
  className: cn(
@@ -517,10 +695,10 @@ function ReportStepItem({ step }) {
517
695
  children: step.keyword
518
696
  }
519
697
  ),
520
- /* @__PURE__ */ jsx18("span", { className: "flex-1 text-foreground", children: highlightStepParams(step.text) }),
521
- step.durationMs > 0 ? /* @__PURE__ */ jsx18("span", { className: "shrink-0 whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(step.durationMs) }) : null
698
+ /* @__PURE__ */ jsx19("span", { className: "flex-1 text-foreground", children: highlightStepParams(step.text) }),
699
+ step.durationMs > 0 ? /* @__PURE__ */ jsx19("span", { className: "shrink-0 whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(step.durationMs) }) : null
522
700
  ] }),
523
- step.errorMessage ? /* @__PURE__ */ jsx18(
701
+ step.errorMessage ? /* @__PURE__ */ jsx19(
524
702
  "pre",
525
703
  {
526
704
  role: "alert",
@@ -528,7 +706,7 @@ function ReportStepItem({ step }) {
528
706
  children: step.errorMessage
529
707
  }
530
708
  ) : null,
531
- step.docEntries.length > 0 ? /* @__PURE__ */ jsx18("div", { "data-es-docs": true, className: "mt-2 ml-6", children: /* @__PURE__ */ jsx18(ReportDocEntries, { entries: step.docEntries }) }) : null
709
+ step.docEntries.length > 0 ? /* @__PURE__ */ jsx19("div", { "data-es-docs": true, className: "mt-2 ml-6", children: /* @__PURE__ */ jsx19(ReportDocEntries, { entries: step.docEntries }) }) : null
532
710
  ]
533
711
  }
534
712
  );
@@ -653,7 +831,7 @@ function extractStoryboardFrames(scenario) {
653
831
  }
654
832
 
655
833
  // src/components/ReportStoryboard.tsx
656
- import { jsx as jsx19, jsxs as jsxs11 } from "react/jsx-runtime";
834
+ import { jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
657
835
  var FRAME_GLYPH = {
658
836
  passed: "\u2713",
659
837
  failed: "\u2717",
@@ -669,32 +847,32 @@ var FRAME_GLYPH_COLOR = {
669
847
  function ReportStoryboard({ scenario }) {
670
848
  const frames = extractStoryboardFrames(scenario);
671
849
  if (frames.length < 2) return null;
672
- return /* @__PURE__ */ jsxs11("section", { "data-slot": "storyboard", "aria-label": "Storyboard", className: "mb-3", children: [
673
- /* @__PURE__ */ jsx19("h4", { className: "mb-1.5 text-xs font-semibold tracking-wide text-muted-foreground uppercase", children: "Storyboard" }),
674
- /* @__PURE__ */ jsx19("ol", { className: "flex items-start gap-3 overflow-x-auto pb-2", children: frames.map((frame) => /* @__PURE__ */ jsx19(StoryboardFrameItem, { frame }, frame.stepId)) })
850
+ return /* @__PURE__ */ jsxs12("section", { "data-slot": "storyboard", "aria-label": "Storyboard", className: "mb-3", children: [
851
+ /* @__PURE__ */ jsx20("h4", { className: "mb-1.5 text-xs font-semibold tracking-wide text-muted-foreground uppercase", children: "Storyboard" }),
852
+ /* @__PURE__ */ jsx20("ol", { className: "flex items-start gap-3 overflow-x-auto pb-2", children: frames.map((frame) => /* @__PURE__ */ jsx20(StoryboardFrameItem, { frame }, frame.stepId)) })
675
853
  ] });
676
854
  }
677
855
  function StoryboardFrameItem({ frame }) {
678
856
  const label = `${frame.keyword} ${frame.text}`;
679
857
  const hasStates = frame.states.length > 0;
680
- const caption = /* @__PURE__ */ jsxs11("p", { className: "mt-1 flex items-baseline gap-1.5 text-[0.6875rem] leading-snug", children: [
681
- /* @__PURE__ */ jsx19("span", { "aria-hidden": true, className: cn("shrink-0", FRAME_GLYPH_COLOR[frame.status]), children: FRAME_GLYPH[frame.status] ?? "\u25CB" }),
682
- /* @__PURE__ */ jsxs11("span", { className: "min-w-0", children: [
683
- /* @__PURE__ */ jsx19("span", { className: "font-mono font-semibold text-keyword", children: frame.keyword }),
858
+ const caption = /* @__PURE__ */ jsxs12("p", { className: "mt-1 flex items-baseline gap-1.5 text-[0.6875rem] leading-snug", children: [
859
+ /* @__PURE__ */ jsx20("span", { "aria-hidden": true, className: cn("shrink-0", FRAME_GLYPH_COLOR[frame.status]), children: FRAME_GLYPH[frame.status] ?? "\u25CB" }),
860
+ /* @__PURE__ */ jsxs12("span", { className: "min-w-0", children: [
861
+ /* @__PURE__ */ jsx20("span", { className: "font-mono font-semibold text-keyword", children: frame.keyword }),
684
862
  " ",
685
- /* @__PURE__ */ jsx19("span", { className: "text-muted-foreground", children: frame.screenshot?.alt ?? frame.text })
863
+ /* @__PURE__ */ jsx20("span", { className: "text-muted-foreground", children: frame.screenshot?.alt ?? frame.text })
686
864
  ] })
687
865
  ] });
688
866
  return (
689
867
  // State cards need room for diff rows; screenshot-only frames keep the
690
868
  // original narrow film-cell width (and DOM) unchanged.
691
- /* @__PURE__ */ jsxs11("li", { className: cn("shrink-0", hasStates ? "w-64" : "w-40"), children: [
692
- frame.screenshot ? /* @__PURE__ */ jsx19(FrameScreenshot, { frame, label }) : null,
693
- hasStates ? /* @__PURE__ */ jsx19("div", { className: cn("flex flex-col gap-1.5", frame.screenshot && "mt-1.5"), children: frame.states.map((card, i) => /* @__PURE__ */ jsx19(StateCardView, { card }, card.label ?? i)) }) : null,
869
+ /* @__PURE__ */ jsxs12("li", { className: cn("shrink-0", hasStates ? "w-64" : "w-40"), children: [
870
+ frame.screenshot ? /* @__PURE__ */ jsx20(FrameScreenshot, { frame, label }) : null,
871
+ hasStates ? /* @__PURE__ */ jsx20("div", { className: cn("flex flex-col gap-1.5", frame.screenshot && "mt-1.5"), children: frame.states.map((card, i) => /* @__PURE__ */ jsx20(StateCardView, { card }, card.label ?? i)) }) : null,
694
872
  frame.screenshot ? caption : (
695
873
  // No screenshot means no image anchor, so the caption carries the
696
874
  // jump-to-step link instead.
697
- /* @__PURE__ */ jsx19("a", { href: `#${frame.stepId}`, "aria-label": `Jump to step: ${label}`, className: "block hover:underline", children: caption })
875
+ /* @__PURE__ */ jsx20("a", { href: `#${frame.stepId}`, "aria-label": `Jump to step: ${label}`, className: "block hover:underline", children: caption })
698
876
  )
699
877
  ] })
700
878
  );
@@ -702,13 +880,13 @@ function StoryboardFrameItem({ frame }) {
702
880
  function FrameScreenshot({ frame, label }) {
703
881
  const path = frame.screenshot?.path ?? "";
704
882
  const src = isLocalFsPath(path) ? void 0 : safeImageUrl(path);
705
- return /* @__PURE__ */ jsx19(
883
+ return /* @__PURE__ */ jsx20(
706
884
  "a",
707
885
  {
708
886
  href: `#${frame.stepId}`,
709
887
  "aria-label": `Jump to step: ${label}`,
710
888
  className: "block overflow-hidden rounded-md border border-border transition-colors hover:border-ring",
711
- children: src ? /* @__PURE__ */ jsx19(
889
+ children: src ? /* @__PURE__ */ jsx20(
712
890
  "img",
713
891
  {
714
892
  src,
@@ -716,7 +894,7 @@ function FrameScreenshot({ frame, label }) {
716
894
  loading: "lazy",
717
895
  className: "h-24 w-full bg-muted object-cover object-top"
718
896
  }
719
- ) : /* @__PURE__ */ jsx19("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" })
897
+ ) : /* @__PURE__ */ jsx20("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" })
720
898
  }
721
899
  );
722
900
  }
@@ -735,12 +913,12 @@ function prettyJson(value) {
735
913
  function StateCardView({ card }) {
736
914
  const json = prettyJson(card.value);
737
915
  const firstCapture = card.changes === void 0;
738
- return /* @__PURE__ */ jsxs11("div", { "data-slot": "state-card", className: "rounded-md border border-border bg-card p-2", children: [
739
- /* @__PURE__ */ jsxs11("p", { className: "flex flex-wrap items-baseline gap-1.5", children: [
740
- /* @__PURE__ */ jsx19("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" }),
741
- firstCapture ? /* @__PURE__ */ jsx19("span", { className: "text-[0.625rem] text-muted-foreground italic", children: "first capture" }) : null
916
+ return /* @__PURE__ */ jsxs12("div", { "data-slot": "state-card", className: "rounded-md border border-border bg-card p-2", children: [
917
+ /* @__PURE__ */ jsxs12("p", { className: "flex flex-wrap items-baseline gap-1.5", children: [
918
+ /* @__PURE__ */ jsx20("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" }),
919
+ firstCapture ? /* @__PURE__ */ jsx20("span", { className: "text-[0.625rem] text-muted-foreground italic", children: "first capture" }) : null
742
920
  ] }),
743
- !firstCapture && (card.changes && card.changes.length > 0 ? /* @__PURE__ */ jsx19("ul", { className: "mt-1.5 flex flex-col gap-0.5", children: card.changes.map((change, i) => /* @__PURE__ */ jsx19(
921
+ !firstCapture && (card.changes && card.changes.length > 0 ? /* @__PURE__ */ jsx20("ul", { className: "mt-1.5 flex flex-col gap-0.5", children: card.changes.map((change, i) => /* @__PURE__ */ jsx20(
744
922
  "li",
745
923
  {
746
924
  className: cn(
@@ -750,15 +928,15 @@ function StateCardView({ card }) {
750
928
  children: summarizeStateChanges([change])[0]
751
929
  },
752
930
  i
753
- )) }) : /* @__PURE__ */ jsx19("p", { className: "mt-1.5 text-[0.625rem] text-muted-foreground", children: "No changes" })),
754
- /* @__PURE__ */ jsxs11(
931
+ )) }) : /* @__PURE__ */ jsx20("p", { className: "mt-1.5 text-[0.625rem] text-muted-foreground", children: "No changes" })),
932
+ /* @__PURE__ */ jsxs12(
755
933
  "details",
756
934
  {
757
935
  open: firstCapture && json.length <= 240,
758
936
  className: cn(firstCapture ? "mt-1.5" : "mt-1"),
759
937
  children: [
760
- /* @__PURE__ */ jsx19("summary", { className: "cursor-pointer text-[0.625rem] text-muted-foreground hover:text-foreground", children: "Snapshot" }),
761
- /* @__PURE__ */ jsx19("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 })
938
+ /* @__PURE__ */ jsx20("summary", { className: "cursor-pointer text-[0.625rem] text-muted-foreground hover:text-foreground", children: "Snapshot" }),
939
+ /* @__PURE__ */ jsx20("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 })
762
940
  ]
763
941
  }
764
942
  )
@@ -766,17 +944,17 @@ function StateCardView({ card }) {
766
944
  }
767
945
 
768
946
  // src/components/ReportAttachments.tsx
769
- import { Fragment as Fragment6, jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
947
+ import { Fragment as Fragment6, jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
770
948
  function ReportAttachments({ attachments }) {
771
949
  if (attachments.length === 0) return null;
772
950
  return (
773
951
  // role="group" (not a labelled <section>) so multiple scenarios' attachment
774
952
  // blocks don't create duplicate "Attachments" region landmarks.
775
- /* @__PURE__ */ jsx20("div", { role: "group", "aria-label": "Attachments", className: "mt-3 flex flex-col gap-2", children: attachments.map((att, i) => {
953
+ /* @__PURE__ */ jsx21("div", { role: "group", "aria-label": "Attachments", className: "mt-3 flex flex-col gap-2", children: attachments.map((att, i) => {
776
954
  const dataUri = att.contentEncoding === "BASE64" ? `data:${att.mediaType};base64,${att.body}` : `data:${att.mediaType},${encodeURIComponent(att.body)}`;
777
955
  const isImage = att.mediaType.startsWith("image/");
778
- return /* @__PURE__ */ jsx20("figure", { className: "m-0", children: isImage ? /* @__PURE__ */ jsxs12(Fragment6, { children: [
779
- /* @__PURE__ */ jsx20(
956
+ return /* @__PURE__ */ jsx21("figure", { className: "m-0", children: isImage ? /* @__PURE__ */ jsxs13(Fragment6, { children: [
957
+ /* @__PURE__ */ jsx21(
780
958
  "img",
781
959
  {
782
960
  src: dataUri,
@@ -785,8 +963,8 @@ function ReportAttachments({ attachments }) {
785
963
  className: "max-w-full rounded-md border border-border"
786
964
  }
787
965
  ),
788
- /* @__PURE__ */ jsx20("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: att.name })
789
- ] }) : /* @__PURE__ */ jsxs12(
966
+ /* @__PURE__ */ jsx21("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: att.name })
967
+ ] }) : /* @__PURE__ */ jsxs13(
790
968
  "a",
791
969
  {
792
970
  href: dataUri,
@@ -795,7 +973,7 @@ function ReportAttachments({ attachments }) {
795
973
  children: [
796
974
  att.name,
797
975
  " ",
798
- /* @__PURE__ */ jsxs12("span", { className: "text-muted-foreground", children: [
976
+ /* @__PURE__ */ jsxs13("span", { className: "text-muted-foreground", children: [
799
977
  "(",
800
978
  att.mediaType,
801
979
  ")"
@@ -883,7 +1061,7 @@ function traceWindow(rows) {
883
1061
  }
884
1062
 
885
1063
  // src/components/ReportTrace.tsx
886
- import { jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
1064
+ import { jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
887
1065
  var BAR_COLOR = {
888
1066
  ok: "bg-pass",
889
1067
  error: "bg-fail",
@@ -894,8 +1072,8 @@ function ReportTrace({ spans }) {
894
1072
  const rows = toTraceRows(spans);
895
1073
  if (rows.length === 0) return null;
896
1074
  const { minStartMs, totalMs } = traceWindow(rows);
897
- return /* @__PURE__ */ jsxs13("details", { className: "mt-3 rounded-md border border-border bg-muted/30", children: [
898
- /* @__PURE__ */ jsxs13("summary", { className: "cursor-pointer px-3 py-2 text-xs font-medium text-muted-foreground", children: [
1075
+ return /* @__PURE__ */ jsxs14("details", { className: "mt-3 rounded-md border border-border bg-muted/30", children: [
1076
+ /* @__PURE__ */ jsxs14("summary", { className: "cursor-pointer px-3 py-2 text-xs font-medium text-muted-foreground", children: [
899
1077
  "Trace (",
900
1078
  rows.length,
901
1079
  " span",
@@ -904,11 +1082,11 @@ function ReportTrace({ spans }) {
904
1082
  formatDuration(totalMs),
905
1083
  ")"
906
1084
  ] }),
907
- /* @__PURE__ */ jsx21("ul", { className: "flex flex-col gap-0.5 px-3 pb-3 font-mono text-[0.6875rem]", children: rows.map((row, i) => {
1085
+ /* @__PURE__ */ jsx22("ul", { className: "flex flex-col gap-0.5 px-3 pb-3 font-mono text-[0.6875rem]", children: rows.map((row, i) => {
908
1086
  const leftPct = (row.startTimeMs - minStartMs) / totalMs * 100;
909
1087
  const widthPct = Math.max(row.durationMs / totalMs * 100, 0.5);
910
- return /* @__PURE__ */ jsxs13("li", { className: "flex items-center gap-2", children: [
911
- /* @__PURE__ */ jsx21(
1088
+ return /* @__PURE__ */ jsxs14("li", { className: "flex items-center gap-2", children: [
1089
+ /* @__PURE__ */ jsx22(
912
1090
  "span",
913
1091
  {
914
1092
  className: "min-w-0 flex-1 truncate text-foreground",
@@ -917,14 +1095,14 @@ function ReportTrace({ spans }) {
917
1095
  children: row.name
918
1096
  }
919
1097
  ),
920
- /* @__PURE__ */ jsx21("span", { className: "relative h-3 w-1/2 shrink-0 overflow-hidden rounded-sm bg-border/40", children: /* @__PURE__ */ jsx21(
1098
+ /* @__PURE__ */ jsx22("span", { className: "relative h-3 w-1/2 shrink-0 overflow-hidden rounded-sm bg-border/40", children: /* @__PURE__ */ jsx22(
921
1099
  "span",
922
1100
  {
923
1101
  className: cn("absolute top-0 h-full rounded-sm", BAR_COLOR[row.status]),
924
1102
  style: { left: `${leftPct}%`, width: `${widthPct}%` }
925
1103
  }
926
1104
  ) }),
927
- /* @__PURE__ */ jsx21("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: formatDuration(row.durationMs) })
1105
+ /* @__PURE__ */ jsx22("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: formatDuration(row.durationMs) })
928
1106
  ] }, `${row.spanId}-${i}`);
929
1107
  }) })
930
1108
  ] });
@@ -973,11 +1151,11 @@ function flakinessOf(entries) {
973
1151
  if (transitionScore > 0.2 || failureRate > 0.3) return "unstable";
974
1152
  return "stable";
975
1153
  }
976
- function diffSinceLastRun(history2, scenarios) {
1154
+ function diffSinceLastRun(history, scenarios) {
977
1155
  let hasPreviousRun = false;
978
1156
  const delta = { newlyFailing: [], newlyPassing: [], added: [] };
979
1157
  for (const scenario of scenarios) {
980
- const entries = history2[scenario.id];
1158
+ const entries = history[scenario.id];
981
1159
  if (!entries || entries.length === 0) continue;
982
1160
  const last = entries[entries.length - 1];
983
1161
  const prev = entries[entries.length - 2];
@@ -993,7 +1171,7 @@ function diffSinceLastRun(history2, scenarios) {
993
1171
  }
994
1172
 
995
1173
  // src/components/ScenarioRunHistory.tsx
996
- import { Fragment as Fragment7, jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
1174
+ import { Fragment as Fragment7, jsx as jsx23, jsxs as jsxs15 } from "react/jsx-runtime";
997
1175
  var MAX_DOTS = 10;
998
1176
  var DOT_COLOR = {
999
1177
  passed: "bg-pass",
@@ -1007,22 +1185,22 @@ function dotTitle(e) {
1007
1185
  return parts.join(" \xB7 ");
1008
1186
  }
1009
1187
  function ScenarioRunHistory({ scenarioId }) {
1010
- const history2 = useScenarioHistory();
1011
- const entries = history2?.[scenarioId];
1188
+ const history = useScenarioHistory();
1189
+ const entries = history?.[scenarioId];
1012
1190
  if (!entries || entries.length < 2) return null;
1013
1191
  const recent = entries.slice(-MAX_DOTS);
1014
1192
  const flaky = flakinessOf(recent) === "flaky";
1015
1193
  const summary = flaky ? `${describeRunHistory(recent)} \xB7 Flaky` : describeRunHistory(recent);
1016
- return /* @__PURE__ */ jsxs14(Fragment7, { children: [
1017
- flaky ? /* @__PURE__ */ jsx22(Badge, { variant: "pending", "aria-label": "Flaky: status flips between recent runs", title: summary, children: "Flaky" }) : null,
1018
- /* @__PURE__ */ jsx22(
1194
+ return /* @__PURE__ */ jsxs15(Fragment7, { children: [
1195
+ flaky ? /* @__PURE__ */ jsx23(Badge, { variant: "pending", "aria-label": "Flaky: status flips between recent runs", title: summary, children: "Flaky" }) : null,
1196
+ /* @__PURE__ */ jsx23(
1019
1197
  "span",
1020
1198
  {
1021
1199
  role: "img",
1022
1200
  "aria-label": `Run history: ${summary}`,
1023
1201
  title: summary,
1024
1202
  className: "flex shrink-0 items-center gap-[3px]",
1025
- children: recent.map((e, i) => /* @__PURE__ */ jsx22(
1203
+ children: recent.map((e, i) => /* @__PURE__ */ jsx23(
1026
1204
  "span",
1027
1205
  {
1028
1206
  "aria-hidden": true,
@@ -1251,9 +1429,9 @@ function scenarioPermalink(scenario) {
1251
1429
 
1252
1430
  // src/components/ui/card.tsx
1253
1431
  import "react";
1254
- import { jsx as jsx23 } from "react/jsx-runtime";
1432
+ import { jsx as jsx24 } from "react/jsx-runtime";
1255
1433
  function Card({ className, ...props }) {
1256
- return /* @__PURE__ */ jsx23(
1434
+ return /* @__PURE__ */ jsx24(
1257
1435
  "div",
1258
1436
  {
1259
1437
  "data-slot": "card",
@@ -1266,7 +1444,7 @@ function Card({ className, ...props }) {
1266
1444
  );
1267
1445
  }
1268
1446
  function CardHeader({ className, ...props }) {
1269
- return /* @__PURE__ */ jsx23(
1447
+ return /* @__PURE__ */ jsx24(
1270
1448
  "div",
1271
1449
  {
1272
1450
  "data-slot": "card-header",
@@ -1279,7 +1457,7 @@ function CardHeader({ className, ...props }) {
1279
1457
  );
1280
1458
  }
1281
1459
  function CardContent({ className, ...props }) {
1282
- return /* @__PURE__ */ jsx23(
1460
+ return /* @__PURE__ */ jsx24(
1283
1461
  "div",
1284
1462
  {
1285
1463
  "data-slot": "card-content",
@@ -1293,13 +1471,13 @@ function CardContent({ className, ...props }) {
1293
1471
  import "react";
1294
1472
  import { Menu as MenuPrimitive } from "@base-ui/react/menu";
1295
1473
  import { CheckIcon } from "lucide-react";
1296
- import { jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
1474
+ import { jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
1297
1475
  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";
1298
1476
  function DropdownMenu(props) {
1299
- return /* @__PURE__ */ jsx24(MenuPrimitive.Root, { "data-slot": "dropdown-menu", ...props });
1477
+ return /* @__PURE__ */ jsx25(MenuPrimitive.Root, { "data-slot": "dropdown-menu", ...props });
1300
1478
  }
1301
1479
  function DropdownMenuTrigger(props) {
1302
- return /* @__PURE__ */ jsx24(MenuPrimitive.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
1480
+ return /* @__PURE__ */ jsx25(MenuPrimitive.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
1303
1481
  }
1304
1482
  function DropdownMenuContent({
1305
1483
  className,
@@ -1307,14 +1485,14 @@ function DropdownMenuContent({
1307
1485
  sideOffset = 4,
1308
1486
  ...props
1309
1487
  }) {
1310
- return /* @__PURE__ */ jsx24(MenuPrimitive.Portal, { children: /* @__PURE__ */ jsx24(
1488
+ return /* @__PURE__ */ jsx25(MenuPrimitive.Portal, { children: /* @__PURE__ */ jsx25(
1311
1489
  MenuPrimitive.Positioner,
1312
1490
  {
1313
1491
  "data-slot": "dropdown-menu-positioner",
1314
1492
  className: "z-50",
1315
1493
  align,
1316
1494
  sideOffset,
1317
- children: /* @__PURE__ */ jsx24(
1495
+ children: /* @__PURE__ */ jsx25(
1318
1496
  MenuPrimitive.Popup,
1319
1497
  {
1320
1498
  "data-slot": "dropdown-menu-content",
@@ -1332,7 +1510,7 @@ function DropdownMenuItem({
1332
1510
  className,
1333
1511
  ...props
1334
1512
  }) {
1335
- return /* @__PURE__ */ jsx24(
1513
+ return /* @__PURE__ */ jsx25(
1336
1514
  MenuPrimitive.Item,
1337
1515
  {
1338
1516
  "data-slot": "dropdown-menu-item",
@@ -1344,7 +1522,7 @@ function DropdownMenuItem({
1344
1522
 
1345
1523
  // src/components/ReportScenario.tsx
1346
1524
  import { LinkIcon, FileTextIcon, SparklesIcon, MoreHorizontalIcon } from "lucide-react";
1347
- import { jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
1525
+ import { jsx as jsx26, jsxs as jsxs17 } from "react/jsx-runtime";
1348
1526
  var STATUS_META = {
1349
1527
  passed: { label: "Passed", glyph: "\u2713", glyphColor: "text-pass", badgeVariant: "passed", showPill: false },
1350
1528
  failed: { label: "Failed", glyph: "\u2717", glyphColor: "text-fail", badgeVariant: "failed", showPill: true },
@@ -1360,7 +1538,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
1360
1538
  const collapsible = collapse !== null && !hideTitle;
1361
1539
  const collapsed = collapsible ? collapse.isCollapsed(scenario.id) : false;
1362
1540
  const actions = useScenarioActions();
1363
- return /* @__PURE__ */ jsxs16(
1541
+ return /* @__PURE__ */ jsxs17(
1364
1542
  Card,
1365
1543
  {
1366
1544
  id: scenario.id,
@@ -1368,10 +1546,10 @@ function ReportScenario({ scenario, hideTitle = false }) {
1368
1546
  ...hideTitle ? { "aria-label": `${scenario.title}, ${label}` } : { "aria-labelledby": titleId },
1369
1547
  className: "gap-0 py-0",
1370
1548
  children: [
1371
- /* @__PURE__ */ jsxs16(CardHeader, { className: "px-4 py-3", children: [
1372
- /* @__PURE__ */ jsxs16("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
1373
- hideTitle ? null : /* @__PURE__ */ jsxs16("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1374
- collapsible ? /* @__PURE__ */ jsx25(
1549
+ /* @__PURE__ */ jsxs17(CardHeader, { className: "px-4 py-3", children: [
1550
+ /* @__PURE__ */ jsxs17("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
1551
+ hideTitle ? null : /* @__PURE__ */ jsxs17("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1552
+ collapsible ? /* @__PURE__ */ jsx26(
1375
1553
  "button",
1376
1554
  {
1377
1555
  type: "button",
@@ -1380,49 +1558,49 @@ function ReportScenario({ scenario, hideTitle = false }) {
1380
1558
  "aria-controls": bodyId,
1381
1559
  "aria-label": `Toggle ${scenario.title}`,
1382
1560
  className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
1383
- children: /* @__PURE__ */ jsx25("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1561
+ children: /* @__PURE__ */ jsx26("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
1384
1562
  }
1385
1563
  ) : null,
1386
- /* @__PURE__ */ jsx25("span", { "aria-hidden": true, className: cn("text-sm leading-none", meta.glyphColor), children: meta.glyph }),
1387
- /* @__PURE__ */ jsx25("span", { children: scenario.title })
1564
+ /* @__PURE__ */ jsx26("span", { "aria-hidden": true, className: cn("text-sm leading-none", meta.glyphColor), children: meta.glyph }),
1565
+ /* @__PURE__ */ jsx26("span", { children: scenario.title })
1388
1566
  ] }),
1389
- /* @__PURE__ */ jsxs16("div", { className: "flex shrink-0 items-center gap-2", children: [
1390
- /* @__PURE__ */ jsx25(ScenarioRunHistory, { scenarioId: scenario.id }),
1391
- scenario.durationMs > 0 ? /* @__PURE__ */ jsx25("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(scenario.durationMs) }) : null,
1392
- meta.showPill ? /* @__PURE__ */ jsx25(Badge, { variant: meta.badgeVariant, "aria-label": `Status: ${label}`, children: label }) : /* @__PURE__ */ jsx25("span", { className: "sr-only", children: `Status: ${label}` }),
1393
- actions ? /* @__PURE__ */ jsxs16(DropdownMenu, { children: [
1394
- /* @__PURE__ */ jsx25(
1567
+ /* @__PURE__ */ jsxs17("div", { className: "flex shrink-0 items-center gap-2", children: [
1568
+ /* @__PURE__ */ jsx26(ScenarioRunHistory, { scenarioId: scenario.id }),
1569
+ scenario.durationMs > 0 ? /* @__PURE__ */ jsx26("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(scenario.durationMs) }) : null,
1570
+ meta.showPill ? /* @__PURE__ */ jsx26(Badge, { variant: meta.badgeVariant, "aria-label": `Status: ${label}`, children: label }) : /* @__PURE__ */ jsx26("span", { className: "sr-only", children: `Status: ${label}` }),
1571
+ actions ? /* @__PURE__ */ jsxs17(DropdownMenu, { children: [
1572
+ /* @__PURE__ */ jsx26(
1395
1573
  DropdownMenuTrigger,
1396
1574
  {
1397
1575
  "aria-label": `Actions for ${scenario.title}`,
1398
1576
  className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
1399
- children: /* @__PURE__ */ jsx25(MoreHorizontalIcon, { "aria-hidden": true })
1577
+ children: /* @__PURE__ */ jsx26(MoreHorizontalIcon, { "aria-hidden": true })
1400
1578
  }
1401
1579
  ),
1402
- /* @__PURE__ */ jsxs16(DropdownMenuContent, { align: "end", children: [
1403
- /* @__PURE__ */ jsxs16(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
1404
- /* @__PURE__ */ jsx25(LinkIcon, {}),
1580
+ /* @__PURE__ */ jsxs17(DropdownMenuContent, { align: "end", children: [
1581
+ /* @__PURE__ */ jsxs17(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
1582
+ /* @__PURE__ */ jsx26(LinkIcon, {}),
1405
1583
  " Copy link"
1406
1584
  ] }),
1407
- /* @__PURE__ */ jsxs16(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
1408
- /* @__PURE__ */ jsx25(FileTextIcon, {}),
1585
+ /* @__PURE__ */ jsxs17(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
1586
+ /* @__PURE__ */ jsx26(FileTextIcon, {}),
1409
1587
  " Copy as Markdown"
1410
1588
  ] }),
1411
- /* @__PURE__ */ jsxs16(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
1412
- /* @__PURE__ */ jsx25(SparklesIcon, {}),
1589
+ /* @__PURE__ */ jsxs17(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
1590
+ /* @__PURE__ */ jsx26(SparklesIcon, {}),
1413
1591
  " Explain with AI"
1414
1592
  ] })
1415
1593
  ] })
1416
1594
  ] }) : null
1417
1595
  ] })
1418
1596
  ] }),
1419
- scenario.tags.length > 0 || (scenario.tickets?.length ?? 0) > 0 ? /* @__PURE__ */ jsxs16("ul", { "aria-label": "Tags and tickets", className: "flex flex-wrap gap-1.5", children: [
1420
- scenario.tags.map((t) => /* @__PURE__ */ jsx25("li", { children: /* @__PURE__ */ jsx25(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
1421
- (scenario.tickets ?? []).map((ticket) => /* @__PURE__ */ jsx25("li", { children: ticket.url ? /* @__PURE__ */ jsx25("a", { href: ticket.url, target: "_blank", rel: "noreferrer noopener", children: /* @__PURE__ */ jsx25(Badge, { variant: "outline", children: ticket.id }) }) : /* @__PURE__ */ jsx25(Badge, { variant: "outline", children: ticket.id }) }, `ticket-${ticket.id}`))
1597
+ scenario.tags.length > 0 || (scenario.tickets?.length ?? 0) > 0 ? /* @__PURE__ */ jsxs17("ul", { "aria-label": "Tags and tickets", className: "flex flex-wrap gap-1.5", children: [
1598
+ scenario.tags.map((t) => /* @__PURE__ */ jsx26("li", { children: /* @__PURE__ */ jsx26(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
1599
+ (scenario.tickets ?? []).map((ticket) => /* @__PURE__ */ jsx26("li", { children: ticket.url ? /* @__PURE__ */ jsx26("a", { href: ticket.url, target: "_blank", rel: "noreferrer noopener", children: /* @__PURE__ */ jsx26(Badge, { variant: "outline", children: ticket.id }) }) : /* @__PURE__ */ jsx26(Badge, { variant: "outline", children: ticket.id }) }, `ticket-${ticket.id}`))
1422
1600
  ] }) : null
1423
1601
  ] }),
1424
- /* @__PURE__ */ jsxs16(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
1425
- scenario.errorMessage ? /* @__PURE__ */ jsx25(
1602
+ /* @__PURE__ */ jsxs17(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
1603
+ scenario.errorMessage ? /* @__PURE__ */ jsx26(
1426
1604
  "pre",
1427
1605
  {
1428
1606
  role: "alert",
@@ -1430,11 +1608,11 @@ function ReportScenario({ scenario, hideTitle = false }) {
1430
1608
  children: scenario.errorMessage
1431
1609
  }
1432
1610
  ) : null,
1433
- /* @__PURE__ */ jsx25(ReportStoryboard, { scenario }),
1434
- /* @__PURE__ */ jsx25(ReportSteps, { scenario }),
1435
- scenario.docEntries.length > 0 ? /* @__PURE__ */ jsx25("div", { "data-es-docs": true, className: "mt-3", children: /* @__PURE__ */ jsx25(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
1436
- /* @__PURE__ */ jsx25(ReportAttachments, { attachments: scenario.attachments }),
1437
- /* @__PURE__ */ jsx25(ReportTrace, { spans: scenario.otelSpans })
1611
+ /* @__PURE__ */ jsx26(ReportStoryboard, { scenario }),
1612
+ /* @__PURE__ */ jsx26(ReportSteps, { scenario }),
1613
+ scenario.docEntries.length > 0 ? /* @__PURE__ */ jsx26("div", { "data-es-docs": true, className: "mt-3", children: /* @__PURE__ */ jsx26(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
1614
+ /* @__PURE__ */ jsx26(ReportAttachments, { attachments: scenario.attachments }),
1615
+ /* @__PURE__ */ jsx26(ReportTrace, { spans: scenario.otelSpans })
1438
1616
  ] })
1439
1617
  ]
1440
1618
  }
@@ -1442,13 +1620,13 @@ function ReportScenario({ scenario, hideTitle = false }) {
1442
1620
  }
1443
1621
 
1444
1622
  // src/components/ReportScenarioList.tsx
1445
- import { Fragment as Fragment8, jsx as jsx26 } from "react/jsx-runtime";
1623
+ import { Fragment as Fragment8, jsx as jsx27 } from "react/jsx-runtime";
1446
1624
  function ReportScenarioList({ feature }) {
1447
- return /* @__PURE__ */ jsx26(Fragment8, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ jsx26(ReportScenario, { scenario }, scenario.id)) });
1625
+ return /* @__PURE__ */ jsx27(Fragment8, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ jsx27(ReportScenario, { scenario }, scenario.id)) });
1448
1626
  }
1449
1627
 
1450
1628
  // src/components/ReportFeature.tsx
1451
- import { Fragment as Fragment9, jsx as jsx27, jsxs as jsxs17 } from "react/jsx-runtime";
1629
+ import { Fragment as Fragment9, jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
1452
1630
  function ReportFeature({ feature }) {
1453
1631
  const titleId = `${feature.id}-title`;
1454
1632
  const bodyId = `${feature.id}-body`;
@@ -1459,7 +1637,7 @@ function ReportFeature({ feature }) {
1459
1637
  return (
1460
1638
  // Suite = a section heading + a divider, NOT another bordered card. Drops a
1461
1639
  // layer of nesting so the scenario cards below read as the primary unit.
1462
- /* @__PURE__ */ jsxs17(
1640
+ /* @__PURE__ */ jsxs18(
1463
1641
  "section",
1464
1642
  {
1465
1643
  id: feature.id,
@@ -1467,9 +1645,9 @@ function ReportFeature({ feature }) {
1467
1645
  "aria-labelledby": titleId,
1468
1646
  className: "flex flex-col gap-3",
1469
1647
  children: [
1470
- /* @__PURE__ */ jsxs17("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1471
- /* @__PURE__ */ jsxs17("div", { className: "min-w-0", children: [
1472
- /* @__PURE__ */ jsx27("h2", { id: titleId, className: "text-base font-semibold tracking-tight text-foreground", children: collapse ? /* @__PURE__ */ jsxs17(
1648
+ /* @__PURE__ */ jsxs18("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1649
+ /* @__PURE__ */ jsxs18("div", { className: "min-w-0", children: [
1650
+ /* @__PURE__ */ jsx28("h2", { id: titleId, className: "text-base font-semibold tracking-tight text-foreground", children: collapse ? /* @__PURE__ */ jsxs18(
1473
1651
  "button",
1474
1652
  {
1475
1653
  type: "button",
@@ -1478,31 +1656,31 @@ function ReportFeature({ feature }) {
1478
1656
  "aria-controls": bodyId,
1479
1657
  className: "flex w-full cursor-pointer items-center gap-2 text-left hover:text-foreground/80",
1480
1658
  children: [
1481
- /* @__PURE__ */ jsx27("span", { "aria-hidden": true, className: cn("shrink-0 text-xs text-muted-foreground transition-transform", !collapsed && "rotate-90"), children: "\u25B8" }),
1482
- /* @__PURE__ */ jsx27("span", { className: "truncate", children: feature.title })
1659
+ /* @__PURE__ */ jsx28("span", { "aria-hidden": true, className: cn("shrink-0 text-xs text-muted-foreground transition-transform", !collapsed && "rotate-90"), children: "\u25B8" }),
1660
+ /* @__PURE__ */ jsx28("span", { className: "truncate", children: feature.title })
1483
1661
  ]
1484
1662
  }
1485
1663
  ) : feature.title }),
1486
- /* @__PURE__ */ jsx27("p", { className: cn("mt-0.5 truncate font-mono text-xs text-muted-foreground", collapse && "pl-5"), children: feature.sourceFile })
1664
+ /* @__PURE__ */ jsx28("p", { className: cn("mt-0.5 truncate font-mono text-xs text-muted-foreground", collapse && "pl-5"), children: feature.sourceFile })
1487
1665
  ] }),
1488
- /* @__PURE__ */ jsxs17(
1666
+ /* @__PURE__ */ jsxs18(
1489
1667
  "p",
1490
1668
  {
1491
1669
  className: "flex shrink-0 items-center gap-1.5 text-xs text-muted-foreground",
1492
1670
  "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
1493
1671
  children: [
1494
- /* @__PURE__ */ jsxs17("span", { children: [
1672
+ /* @__PURE__ */ jsxs18("span", { children: [
1495
1673
  s.passed,
1496
1674
  " passed"
1497
1675
  ] }),
1498
- /* @__PURE__ */ jsx27("span", { "aria-hidden": true, children: "\xB7" }),
1499
- /* @__PURE__ */ jsxs17("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
1676
+ /* @__PURE__ */ jsx28("span", { "aria-hidden": true, children: "\xB7" }),
1677
+ /* @__PURE__ */ jsxs18("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
1500
1678
  s.failed,
1501
1679
  " failed"
1502
1680
  ] }),
1503
- skipped > 0 ? /* @__PURE__ */ jsxs17(Fragment9, { children: [
1504
- /* @__PURE__ */ jsx27("span", { "aria-hidden": true, children: "\xB7" }),
1505
- /* @__PURE__ */ jsxs17("span", { children: [
1681
+ skipped > 0 ? /* @__PURE__ */ jsxs18(Fragment9, { children: [
1682
+ /* @__PURE__ */ jsx28("span", { "aria-hidden": true, children: "\xB7" }),
1683
+ /* @__PURE__ */ jsxs18("span", { children: [
1506
1684
  skipped,
1507
1685
  " skipped"
1508
1686
  ] })
@@ -1511,7 +1689,7 @@ function ReportFeature({ feature }) {
1511
1689
  }
1512
1690
  )
1513
1691
  ] }),
1514
- /* @__PURE__ */ jsx27("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-3", children: /* @__PURE__ */ jsx27(ReportScenarioList, { feature }) })
1692
+ /* @__PURE__ */ jsx28("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-3", children: /* @__PURE__ */ jsx28(ReportScenarioList, { feature }) })
1515
1693
  ]
1516
1694
  }
1517
1695
  )
@@ -1519,17 +1697,17 @@ function ReportFeature({ feature }) {
1519
1697
  }
1520
1698
 
1521
1699
  // src/components/ReportFeatureList.tsx
1522
- import { jsx as jsx28 } from "react/jsx-runtime";
1700
+ import { jsx as jsx29 } from "react/jsx-runtime";
1523
1701
  function ReportFeatureList() {
1524
1702
  const report = useReport();
1525
- return /* @__PURE__ */ jsx28("div", { className: "flex flex-col gap-8", children: report.features.map((feature) => /* @__PURE__ */ jsx28(ReportFeature, { feature }, feature.id)) });
1703
+ return /* @__PURE__ */ jsx29("div", { className: "flex flex-col gap-8", children: report.features.map((feature) => /* @__PURE__ */ jsx29(ReportFeature, { feature }, feature.id)) });
1526
1704
  }
1527
1705
 
1528
1706
  // src/components/ui/empty.tsx
1529
1707
  import { cva as cva2 } from "class-variance-authority";
1530
- import { jsx as jsx29 } from "react/jsx-runtime";
1708
+ import { jsx as jsx30 } from "react/jsx-runtime";
1531
1709
  function Empty({ className, ...props }) {
1532
- return /* @__PURE__ */ jsx29(
1710
+ return /* @__PURE__ */ jsx30(
1533
1711
  "div",
1534
1712
  {
1535
1713
  "data-slot": "empty",
@@ -1542,7 +1720,7 @@ function Empty({ className, ...props }) {
1542
1720
  );
1543
1721
  }
1544
1722
  function EmptyHeader({ className, ...props }) {
1545
- return /* @__PURE__ */ jsx29(
1723
+ return /* @__PURE__ */ jsx30(
1546
1724
  "div",
1547
1725
  {
1548
1726
  "data-slot": "empty-header",
@@ -1569,7 +1747,7 @@ var emptyMediaVariants = cva2(
1569
1747
  }
1570
1748
  );
1571
1749
  function EmptyTitle({ className, ...props }) {
1572
- return /* @__PURE__ */ jsx29(
1750
+ return /* @__PURE__ */ jsx30(
1573
1751
  "div",
1574
1752
  {
1575
1753
  "data-slot": "empty-title",
@@ -1580,16 +1758,16 @@ function EmptyTitle({ className, ...props }) {
1580
1758
  }
1581
1759
 
1582
1760
  // src/components/ReportEmpty.tsx
1583
- import { jsx as jsx30 } from "react/jsx-runtime";
1761
+ import { jsx as jsx31 } from "react/jsx-runtime";
1584
1762
  function ReportEmpty({ message }) {
1585
- return /* @__PURE__ */ jsx30(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ jsx30(EmptyHeader, { children: /* @__PURE__ */ jsx30(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1763
+ return /* @__PURE__ */ jsx31(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ jsx31(EmptyHeader, { children: /* @__PURE__ */ jsx31(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1586
1764
  }
1587
1765
 
1588
1766
  // src/components/ReportSummary.tsx
1589
- import { jsx as jsx31, jsxs as jsxs18 } from "react/jsx-runtime";
1767
+ import { jsx as jsx32, jsxs as jsxs19 } from "react/jsx-runtime";
1590
1768
  function ReportSummary({ className }) {
1591
1769
  const report = useReport();
1592
- return /* @__PURE__ */ jsx31(
1770
+ return /* @__PURE__ */ jsx32(
1593
1771
  ReportSummaryView,
1594
1772
  {
1595
1773
  summary: report.summary,
@@ -1615,7 +1793,7 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1615
1793
  if (summary.pending > 0) {
1616
1794
  cards.push({ status: "pending", label: "Pending", value: summary.pending });
1617
1795
  }
1618
- return /* @__PURE__ */ jsx31(
1796
+ return /* @__PURE__ */ jsx32(
1619
1797
  "dl",
1620
1798
  {
1621
1799
  className: cn(
@@ -1628,17 +1806,17 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1628
1806
  "aria-label": ariaLabel,
1629
1807
  children: cards.map((c) => {
1630
1808
  const tone = TONE[c.status];
1631
- return /* @__PURE__ */ jsxs18(
1809
+ return /* @__PURE__ */ jsxs19(
1632
1810
  "div",
1633
1811
  {
1634
1812
  "data-status": c.status,
1635
1813
  className: "rounded-lg border bg-card px-4 py-3",
1636
1814
  children: [
1637
- /* @__PURE__ */ jsxs18("dt", { className: "flex items-center gap-1.5 font-mono text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: [
1638
- /* @__PURE__ */ jsx31("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
1815
+ /* @__PURE__ */ jsxs19("dt", { className: "flex items-center gap-1.5 font-mono text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: [
1816
+ /* @__PURE__ */ jsx32("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
1639
1817
  c.label
1640
1818
  ] }),
1641
- /* @__PURE__ */ jsx31("dd", { className: cn("mt-2 text-3xl font-semibold leading-none tracking-tight tabular-nums", tone.num), children: c.value })
1819
+ /* @__PURE__ */ jsx32("dd", { className: cn("mt-2 text-3xl font-semibold leading-none tracking-tight tabular-nums", tone.num), children: c.value })
1642
1820
  ]
1643
1821
  },
1644
1822
  c.status
@@ -1698,7 +1876,7 @@ function prUrl(ci) {
1698
1876
  }
1699
1877
 
1700
1878
  // src/components/ReportMeta.tsx
1701
- import { jsx as jsx32, jsxs as jsxs19 } from "react/jsx-runtime";
1879
+ import { jsx as jsx33, jsxs as jsxs20 } from "react/jsx-runtime";
1702
1880
  function ReportMeta() {
1703
1881
  const report = useReport();
1704
1882
  const items = [];
@@ -1721,13 +1899,13 @@ function ReportMeta() {
1721
1899
  const short = sha.slice(0, 8);
1722
1900
  items.push({
1723
1901
  label: "Commit",
1724
- value: url ? /* @__PURE__ */ jsx32("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1902
+ value: url ? /* @__PURE__ */ jsx33("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1725
1903
  });
1726
1904
  }
1727
1905
  if (report.ci) {
1728
1906
  items.push({
1729
1907
  label: "CI",
1730
- value: report.ci.url ? /* @__PURE__ */ jsxs19("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1908
+ value: report.ci.url ? /* @__PURE__ */ jsxs20("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1731
1909
  ciDisplayName(report.ci.name),
1732
1910
  report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
1733
1911
  ] }) : `${ciDisplayName(report.ci.name)}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
@@ -1738,7 +1916,7 @@ function ReportMeta() {
1738
1916
  const label = `#${report.ci.prNumber}`;
1739
1917
  items.push({
1740
1918
  label: "PR",
1741
- value: url ? /* @__PURE__ */ jsx32("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1919
+ value: url ? /* @__PURE__ */ jsx33("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1742
1920
  });
1743
1921
  }
1744
1922
  if (items.length === 0) return null;
@@ -1746,50 +1924,50 @@ function ReportMeta() {
1746
1924
  // Run metadata is useful but secondary to "what failed" — tuck it into a
1747
1925
  // collapsed disclosure so it doesn't push the result down. Native <details>
1748
1926
  // so it works with no JS (the SSR/no-hydration report still toggles).
1749
- /* @__PURE__ */ jsxs19("details", { "data-es-meta-card": true, className: "es-run-details group rounded-lg border border-border bg-card text-xs", children: [
1750
- /* @__PURE__ */ jsxs19("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: [
1751
- /* @__PURE__ */ jsx32("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
1927
+ /* @__PURE__ */ jsxs20("details", { "data-es-meta-card": true, className: "es-run-details group rounded-lg border border-border bg-card text-xs", children: [
1928
+ /* @__PURE__ */ jsxs20("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: [
1929
+ /* @__PURE__ */ jsx33("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
1752
1930
  "Run details"
1753
1931
  ] }),
1754
- /* @__PURE__ */ jsx32("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__ */ jsxs19("div", { className: "flex items-baseline gap-1.5", children: [
1755
- /* @__PURE__ */ jsx32("dt", { className: "font-medium", children: item.label }),
1756
- /* @__PURE__ */ jsx32("dd", { className: "font-mono text-foreground", children: item.value })
1932
+ /* @__PURE__ */ jsx33("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__ */ jsxs20("div", { className: "flex items-baseline gap-1.5", children: [
1933
+ /* @__PURE__ */ jsx33("dt", { className: "font-medium", children: item.label }),
1934
+ /* @__PURE__ */ jsx33("dd", { className: "font-mono text-foreground", children: item.value })
1757
1935
  ] }, item.label)) })
1758
1936
  ] })
1759
1937
  );
1760
1938
  }
1761
1939
 
1762
1940
  // src/components/ReportSchemaError.tsx
1763
- import { jsx as jsx33, jsxs as jsxs20 } from "react/jsx-runtime";
1941
+ import { jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
1764
1942
  function ReportSchemaError({ error }) {
1765
- return /* @__PURE__ */ jsxs20("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1766
- /* @__PURE__ */ jsx33("p", { children: /* @__PURE__ */ jsx33("strong", { children: "Report could not be displayed." }) }),
1767
- /* @__PURE__ */ jsx33("p", { children: error.message }),
1768
- error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs20("p", { children: [
1943
+ return /* @__PURE__ */ jsxs21("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1944
+ /* @__PURE__ */ jsx34("p", { children: /* @__PURE__ */ jsx34("strong", { children: "Report could not be displayed." }) }),
1945
+ /* @__PURE__ */ jsx34("p", { children: error.message }),
1946
+ error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs21("p", { children: [
1769
1947
  "The report bundle is newer than this version of ",
1770
- /* @__PURE__ */ jsx33("code", { children: "executable-stories-react" }),
1948
+ /* @__PURE__ */ jsx34("code", { children: "executable-stories-react" }),
1771
1949
  ". Upgrade the package, or regenerate the report with an older formatters CLI."
1772
1950
  ] }) : null,
1773
- error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs20("details", { children: [
1774
- /* @__PURE__ */ jsxs20("summary", { children: [
1951
+ error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs21("details", { children: [
1952
+ /* @__PURE__ */ jsxs21("summary", { children: [
1775
1953
  error.issues.length,
1776
1954
  " validation issue",
1777
1955
  error.issues.length === 1 ? "" : "s"
1778
1956
  ] }),
1779
- /* @__PURE__ */ jsx33("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1957
+ /* @__PURE__ */ jsx34("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1780
1958
  ] }) : null
1781
1959
  ] });
1782
1960
  }
1783
1961
 
1784
1962
  // src/components/ReportShell.tsx
1785
- import { Fragment as Fragment10, jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
1963
+ import { Fragment as Fragment10, jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
1786
1964
  function ReportErrorShell({
1787
1965
  error,
1788
1966
  className,
1789
1967
  title,
1790
1968
  dataTheme
1791
1969
  }) {
1792
- return /* @__PURE__ */ jsx34("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ jsx34(ReportSchemaError, { error }) });
1970
+ return /* @__PURE__ */ jsx35("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ jsx35(ReportSchemaError, { error }) });
1793
1971
  }
1794
1972
 
1795
1973
  // src/interactive/ReportInteractive.tsx
@@ -1798,12 +1976,12 @@ import { Monitor, Moon, Sun } from "lucide-react";
1798
1976
  // src/components/ui/switch.tsx
1799
1977
  import "react";
1800
1978
  import { Switch as SwitchPrimitive } from "@base-ui/react/switch";
1801
- import { jsx as jsx35 } from "react/jsx-runtime";
1979
+ import { jsx as jsx36 } from "react/jsx-runtime";
1802
1980
  function Switch({
1803
1981
  className,
1804
1982
  ...props
1805
1983
  }) {
1806
- return /* @__PURE__ */ jsx35(
1984
+ return /* @__PURE__ */ jsx36(
1807
1985
  SwitchPrimitive.Root,
1808
1986
  {
1809
1987
  "data-slot": "switch",
@@ -1812,7 +1990,7 @@ function Switch({
1812
1990
  className
1813
1991
  ),
1814
1992
  ...props,
1815
- children: /* @__PURE__ */ jsx35(
1993
+ children: /* @__PURE__ */ jsx36(
1816
1994
  SwitchPrimitive.Thumb,
1817
1995
  {
1818
1996
  "data-slot": "switch-thumb",
@@ -1828,7 +2006,7 @@ import * as React5 from "react";
1828
2006
  import { ToggleGroup as ToggleGroupPrimitive } from "@base-ui/react/toggle-group";
1829
2007
  import { Toggle as TogglePrimitive } from "@base-ui/react/toggle";
1830
2008
  import { cva as cva3 } from "class-variance-authority";
1831
- import { jsx as jsx36 } from "react/jsx-runtime";
2009
+ import { jsx as jsx37 } from "react/jsx-runtime";
1832
2010
  var toggleItemVariants = cva3(
1833
2011
  "inline-flex items-center justify-center gap-1.5 rounded-md font-medium whitespace-nowrap text-muted-foreground transition-[color,background-color,box-shadow] outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 data-[pressed]:bg-background data-[pressed]:text-foreground data-[pressed]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
1834
2012
  {
@@ -1850,13 +2028,13 @@ function ToggleGroup({
1850
2028
  children,
1851
2029
  ...props
1852
2030
  }) {
1853
- return /* @__PURE__ */ jsx36(
2031
+ return /* @__PURE__ */ jsx37(
1854
2032
  ToggleGroupPrimitive,
1855
2033
  {
1856
2034
  "data-slot": "toggle-group",
1857
2035
  className: cn("flex w-fit items-center gap-0.5 rounded-lg bg-muted p-0.5", className),
1858
2036
  ...props,
1859
- children: /* @__PURE__ */ jsx36(ToggleGroupContext.Provider, { value: { size }, children })
2037
+ children: /* @__PURE__ */ jsx37(ToggleGroupContext.Provider, { value: { size }, children })
1860
2038
  }
1861
2039
  );
1862
2040
  }
@@ -1867,7 +2045,7 @@ function ToggleGroupItem({
1867
2045
  ...props
1868
2046
  }) {
1869
2047
  const context = React5.useContext(ToggleGroupContext);
1870
- return /* @__PURE__ */ jsx36(
2048
+ return /* @__PURE__ */ jsx37(
1871
2049
  TogglePrimitive,
1872
2050
  {
1873
2051
  "data-slot": "toggle-group-item",
@@ -1887,9 +2065,9 @@ import { Search } from "lucide-react";
1887
2065
 
1888
2066
  // src/components/ui/input.tsx
1889
2067
  import "react";
1890
- import { jsx as jsx37 } from "react/jsx-runtime";
2068
+ import { jsx as jsx38 } from "react/jsx-runtime";
1891
2069
  function Input({ className, type, ...props }) {
1892
- return /* @__PURE__ */ jsx37(
2070
+ return /* @__PURE__ */ jsx38(
1893
2071
  "input",
1894
2072
  {
1895
2073
  type,
@@ -1906,7 +2084,7 @@ function Input({ className, type, ...props }) {
1906
2084
  }
1907
2085
 
1908
2086
  // src/interactive/ReportSearch.tsx
1909
- import { jsx as jsx38, jsxs as jsxs22 } from "react/jsx-runtime";
2087
+ import { jsx as jsx39, jsxs as jsxs23 } from "react/jsx-runtime";
1910
2088
  var ReportSearch = forwardRef(
1911
2089
  function ReportSearch2(props, ref) {
1912
2090
  const {
@@ -1928,17 +2106,17 @@ var ReportSearch = forwardRef(
1928
2106
  e.preventDefault();
1929
2107
  }
1930
2108
  }
1931
- return /* @__PURE__ */ jsxs22("div", { className: cn("es-search flex flex-col gap-1", className), children: [
1932
- /* @__PURE__ */ jsx38("label", { htmlFor: inputId, className: "sr-only", children: "Search" }),
1933
- /* @__PURE__ */ jsxs22("div", { className: "relative w-full sm:w-72", children: [
1934
- /* @__PURE__ */ jsx38(
2109
+ return /* @__PURE__ */ jsxs23("div", { className: cn("es-search flex flex-col gap-1", className), children: [
2110
+ /* @__PURE__ */ jsx39("label", { htmlFor: inputId, className: "sr-only", children: "Search" }),
2111
+ /* @__PURE__ */ jsxs23("div", { className: "relative w-full sm:w-72", children: [
2112
+ /* @__PURE__ */ jsx39(
1935
2113
  Search,
1936
2114
  {
1937
2115
  "aria-hidden": true,
1938
2116
  className: "pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground"
1939
2117
  }
1940
2118
  ),
1941
- /* @__PURE__ */ jsx38(
2119
+ /* @__PURE__ */ jsx39(
1942
2120
  Input,
1943
2121
  {
1944
2122
  ref,
@@ -1955,7 +2133,7 @@ var ReportSearch = forwardRef(
1955
2133
  }
1956
2134
  )
1957
2135
  ] }),
1958
- showCounts ? /* @__PURE__ */ jsxs22(
2136
+ showCounts ? /* @__PURE__ */ jsxs23(
1959
2137
  "span",
1960
2138
  {
1961
2139
  className: "text-xs tabular-nums text-muted-foreground",
@@ -1975,6 +2153,54 @@ var ReportSearch = forwardRef(
1975
2153
  // src/interactive/ReportFailureBanner.tsx
1976
2154
  import { useCallback as useCallback2 } from "react";
1977
2155
 
2156
+ // src/lib/hash-state.ts
2157
+ var DEFAULT_URL_STATE = {
2158
+ query: "",
2159
+ status: "all",
2160
+ tags: [],
2161
+ detail: "full"
2162
+ };
2163
+ var STATUSES = ["all", "passed", "failed", "skipped", "pending"];
2164
+ function splitHash(hash) {
2165
+ const raw = hash.replace(/^#/, "");
2166
+ const split = raw.indexOf("?");
2167
+ if (split === -1) return { anchor: raw, params: new URLSearchParams() };
2168
+ return { anchor: raw.slice(0, split), params: new URLSearchParams(raw.slice(split + 1)) };
2169
+ }
2170
+ function decodeUrlState(params) {
2171
+ const status = params.get("status");
2172
+ const tags = params.get("tags");
2173
+ return {
2174
+ query: params.get("q") ?? DEFAULT_URL_STATE.query,
2175
+ status: STATUSES.includes(status) ? status : DEFAULT_URL_STATE.status,
2176
+ tags: tags ? tags.split(",").filter(Boolean) : [],
2177
+ detail: params.get("docs") === "0" ? "minimal" : "full"
2178
+ };
2179
+ }
2180
+ function encodeUrlState(state) {
2181
+ const params = new URLSearchParams();
2182
+ if (state.query) params.set("q", state.query);
2183
+ if (state.status !== "all") params.set("status", state.status);
2184
+ if (state.tags.length > 0) params.set("tags", state.tags.join(","));
2185
+ if (state.detail === "minimal") params.set("docs", "0");
2186
+ return params.toString();
2187
+ }
2188
+ function formatHash(anchor, params) {
2189
+ if (params) return `#${anchor}?${params}`;
2190
+ return anchor ? `#${anchor}` : "";
2191
+ }
2192
+ function writeHash(anchor, params) {
2193
+ if (typeof window === "undefined") return;
2194
+ const next = formatHash(anchor, params);
2195
+ if (next === window.location.hash) return;
2196
+ const url = next || window.location.href.split("#")[0];
2197
+ try {
2198
+ window.history.replaceState(window.history.state, "", url);
2199
+ } catch {
2200
+ window.location.hash = next.slice(1);
2201
+ }
2202
+ }
2203
+
1978
2204
  // src/lib/scroll.ts
1979
2205
  function prefersReducedMotion() {
1980
2206
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
@@ -1990,13 +2216,13 @@ function scrollToScenarioId(scenarioId, options = {}) {
1990
2216
  behavior: prefersReducedMotion() ? "auto" : "smooth",
1991
2217
  block: "start"
1992
2218
  });
1993
- if (options.updateHash !== false && typeof history !== "undefined") {
1994
- history.replaceState(null, "", `#${scenarioId}`);
2219
+ if (options.updateHash !== false) {
2220
+ writeHash(scenarioId, splitHash(window.location.hash).params.toString());
1995
2221
  }
1996
2222
  }
1997
2223
 
1998
2224
  // src/interactive/ReportFailureBanner.tsx
1999
- import { jsx as jsx39, jsxs as jsxs23 } from "react/jsx-runtime";
2225
+ import { jsx as jsx40, jsxs as jsxs24 } from "react/jsx-runtime";
2000
2226
  function ReportFailureBanner({ failures }) {
2001
2227
  const first = failures[0];
2002
2228
  const jumpToFirst = useCallback2(() => {
@@ -2004,7 +2230,7 @@ function ReportFailureBanner({ failures }) {
2004
2230
  scrollToScenarioId(first.scenarioId);
2005
2231
  }, [first]);
2006
2232
  if (failures.length === 0) return null;
2007
- return /* @__PURE__ */ jsxs23(
2233
+ return /* @__PURE__ */ jsxs24(
2008
2234
  "div",
2009
2235
  {
2010
2236
  className: "es-failure-banner",
@@ -2012,13 +2238,13 @@ function ReportFailureBanner({ failures }) {
2012
2238
  "aria-live": "polite",
2013
2239
  "aria-label": "Failure summary",
2014
2240
  children: [
2015
- /* @__PURE__ */ jsxs23("span", { className: "es-failure-banner-text", children: [
2016
- /* @__PURE__ */ jsx39("strong", { children: failures.length }),
2241
+ /* @__PURE__ */ jsxs24("span", { className: "es-failure-banner-text", children: [
2242
+ /* @__PURE__ */ jsx40("strong", { children: failures.length }),
2017
2243
  " ",
2018
2244
  "failure",
2019
2245
  failures.length === 1 ? "" : "s"
2020
2246
  ] }),
2021
- /* @__PURE__ */ jsx39(
2247
+ /* @__PURE__ */ jsx40(
2022
2248
  "button",
2023
2249
  {
2024
2250
  type: "button",
@@ -2035,7 +2261,7 @@ function ReportFailureBanner({ failures }) {
2035
2261
 
2036
2262
  // src/interactive/ReportFreshness.tsx
2037
2263
  import { useEffect, useState as useState2 } from "react";
2038
- import { Fragment as Fragment11, jsx as jsx40, jsxs as jsxs24 } from "react/jsx-runtime";
2264
+ import { Fragment as Fragment11, jsx as jsx41, jsxs as jsxs25 } from "react/jsx-runtime";
2039
2265
  function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
2040
2266
  const [now, setNow] = useState2(nowMs);
2041
2267
  useEffect(() => {
@@ -2043,7 +2269,7 @@ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
2043
2269
  }, [nowMs]);
2044
2270
  if (lastRunMs === void 0 || now === void 0) return null;
2045
2271
  const age = formatRelativeAge(lastRunMs, now);
2046
- const runLink = ciUrl ? /* @__PURE__ */ jsx40(
2272
+ const runLink = ciUrl ? /* @__PURE__ */ jsx41(
2047
2273
  "a",
2048
2274
  {
2049
2275
  href: ciUrl,
@@ -2054,9 +2280,9 @@ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
2054
2280
  }
2055
2281
  ) : null;
2056
2282
  if (isReportStale(lastRunMs, staleAfterDays, now)) {
2057
- return /* @__PURE__ */ jsxs24("div", { className: "es-stale-banner", role: "status", "aria-label": "Report freshness", children: [
2058
- /* @__PURE__ */ jsxs24("span", { children: [
2059
- /* @__PURE__ */ jsxs24("strong", { children: [
2283
+ return /* @__PURE__ */ jsxs25("div", { className: "es-stale-banner", role: "status", "aria-label": "Report freshness", children: [
2284
+ /* @__PURE__ */ jsxs25("span", { children: [
2285
+ /* @__PURE__ */ jsxs25("strong", { children: [
2060
2286
  "Last verified ",
2061
2287
  age,
2062
2288
  "."
@@ -2066,10 +2292,10 @@ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
2066
2292
  runLink
2067
2293
  ] });
2068
2294
  }
2069
- return /* @__PURE__ */ jsxs24("p", { className: "es-freshness", "aria-label": "Report freshness", children: [
2295
+ return /* @__PURE__ */ jsxs25("p", { className: "es-freshness", "aria-label": "Report freshness", children: [
2070
2296
  "Verified ",
2071
2297
  age,
2072
- runLink ? /* @__PURE__ */ jsxs24(Fragment11, { children: [
2298
+ runLink ? /* @__PURE__ */ jsxs25(Fragment11, { children: [
2073
2299
  " \xB7 ",
2074
2300
  runLink
2075
2301
  ] }) : null
@@ -2077,15 +2303,15 @@ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
2077
2303
  }
2078
2304
 
2079
2305
  // src/interactive/ReportLastRunDelta.tsx
2080
- import { Fragment as Fragment12, jsx as jsx41, jsxs as jsxs25 } from "react/jsx-runtime";
2306
+ import { Fragment as Fragment12, jsx as jsx42, jsxs as jsxs26 } from "react/jsx-runtime";
2081
2307
  var MAX_LINKS = 5;
2082
2308
  function ScenarioLinks({ scenarios, tone }) {
2083
- return /* @__PURE__ */ jsxs25(Fragment12, { children: [
2084
- scenarios.slice(0, MAX_LINKS).map((s, i) => /* @__PURE__ */ jsxs25("span", { children: [
2309
+ return /* @__PURE__ */ jsxs26(Fragment12, { children: [
2310
+ scenarios.slice(0, MAX_LINKS).map((s, i) => /* @__PURE__ */ jsxs26("span", { children: [
2085
2311
  i > 0 ? ", " : "",
2086
- /* @__PURE__ */ jsx41("a", { href: `#${s.id}`, className: cn("underline underline-offset-2 hover:no-underline", tone), children: s.title })
2312
+ /* @__PURE__ */ jsx42("a", { href: `#${s.id}`, className: cn("underline underline-offset-2 hover:no-underline", tone), children: s.title })
2087
2313
  ] }, s.id)),
2088
- scenarios.length > MAX_LINKS ? /* @__PURE__ */ jsxs25("span", { children: [
2314
+ scenarios.length > MAX_LINKS ? /* @__PURE__ */ jsxs26("span", { children: [
2089
2315
  " and ",
2090
2316
  scenarios.length - MAX_LINKS,
2091
2317
  " more"
@@ -2093,32 +2319,32 @@ function ScenarioLinks({ scenarios, tone }) {
2093
2319
  ] });
2094
2320
  }
2095
2321
  function ReportLastRunDelta({
2096
- history: history2,
2322
+ history,
2097
2323
  report
2098
2324
  }) {
2099
- if (!history2) return null;
2325
+ if (!history) return null;
2100
2326
  const scenarios = report.features.flatMap((f) => f.scenarios.map((s) => ({ id: s.id, title: s.title })));
2101
- const delta = diffSinceLastRun(history2, scenarios);
2327
+ const delta = diffSinceLastRun(history, scenarios);
2102
2328
  if (!delta) return null;
2103
2329
  const { newlyFailing, newlyPassing, added } = delta;
2104
2330
  if (newlyFailing.length === 0 && newlyPassing.length === 0 && added.length === 0) {
2105
- return /* @__PURE__ */ jsx41("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: "Since last run: no behavior changes." });
2331
+ return /* @__PURE__ */ jsx42("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: "Since last run: no behavior changes." });
2106
2332
  }
2107
- return /* @__PURE__ */ jsxs25("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: [
2108
- /* @__PURE__ */ jsx41("span", { className: "font-medium text-foreground", children: "Since last run:" }),
2109
- newlyFailing.length > 0 ? /* @__PURE__ */ jsxs25("span", { className: "ml-2 text-fail", children: [
2333
+ return /* @__PURE__ */ jsxs26("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: [
2334
+ /* @__PURE__ */ jsx42("span", { className: "font-medium text-foreground", children: "Since last run:" }),
2335
+ newlyFailing.length > 0 ? /* @__PURE__ */ jsxs26("span", { className: "ml-2 text-fail", children: [
2110
2336
  newlyFailing.length,
2111
2337
  " newly failing (",
2112
- /* @__PURE__ */ jsx41(ScenarioLinks, { scenarios: newlyFailing, tone: "text-fail" }),
2338
+ /* @__PURE__ */ jsx42(ScenarioLinks, { scenarios: newlyFailing, tone: "text-fail" }),
2113
2339
  ")"
2114
2340
  ] }) : null,
2115
- newlyPassing.length > 0 ? /* @__PURE__ */ jsxs25("span", { className: "ml-2 text-pass", children: [
2341
+ newlyPassing.length > 0 ? /* @__PURE__ */ jsxs26("span", { className: "ml-2 text-pass", children: [
2116
2342
  newlyPassing.length,
2117
2343
  " fixed (",
2118
- /* @__PURE__ */ jsx41(ScenarioLinks, { scenarios: newlyPassing, tone: "text-pass" }),
2344
+ /* @__PURE__ */ jsx42(ScenarioLinks, { scenarios: newlyPassing, tone: "text-pass" }),
2119
2345
  ")"
2120
2346
  ] }) : null,
2121
- added.length > 0 ? /* @__PURE__ */ jsxs25("span", { className: "ml-2", children: [
2347
+ added.length > 0 ? /* @__PURE__ */ jsxs26("span", { className: "ml-2", children: [
2122
2348
  added.length,
2123
2349
  " new"
2124
2350
  ] }) : null
@@ -2182,7 +2408,7 @@ function Button({
2182
2408
  }
2183
2409
 
2184
2410
  // src/interactive/ReportShortcutsHelp.tsx
2185
- import { jsx as jsx42, jsxs as jsxs26 } from "react/jsx-runtime";
2411
+ import { jsx as jsx43, jsxs as jsxs27 } from "react/jsx-runtime";
2186
2412
  var SHORTCUTS = [
2187
2413
  { keys: "/", description: "Focus search" },
2188
2414
  { keys: "f", description: "Jump to next failure" },
@@ -2194,9 +2420,9 @@ var SHORTCUTS = [
2194
2420
  ];
2195
2421
  function Keys({ keys }) {
2196
2422
  const parts = keys.split(" ");
2197
- return /* @__PURE__ */ jsx42("span", { className: "inline-flex items-center gap-1", children: parts.map((k, i) => /* @__PURE__ */ jsxs26("span", { className: "inline-flex items-center gap-1", children: [
2198
- i > 0 ? /* @__PURE__ */ jsx42("span", { className: "text-xs text-muted-foreground", children: "+" }) : null,
2199
- /* @__PURE__ */ jsx42("kbd", { className: "inline-flex h-6 min-w-6 items-center justify-center rounded border border-border bg-muted px-1.5 font-mono text-xs font-medium text-muted-foreground", children: k })
2423
+ return /* @__PURE__ */ jsx43("span", { className: "inline-flex items-center gap-1", children: parts.map((k, i) => /* @__PURE__ */ jsxs27("span", { className: "inline-flex items-center gap-1", children: [
2424
+ i > 0 ? /* @__PURE__ */ jsx43("span", { className: "text-xs text-muted-foreground", children: "+" }) : null,
2425
+ /* @__PURE__ */ jsx43("kbd", { className: "inline-flex h-6 min-w-6 items-center justify-center rounded border border-border bg-muted px-1.5 font-mono text-xs font-medium text-muted-foreground", children: k })
2200
2426
  ] }, k)) });
2201
2427
  }
2202
2428
  function ReportShortcutsHelp({ open, onClose }) {
@@ -2207,7 +2433,7 @@ function ReportShortcutsHelp({ open, onClose }) {
2207
2433
  if (open && !dialog.open) dialog.showModal();
2208
2434
  else if (!open && dialog.open) dialog.close();
2209
2435
  }, [open]);
2210
- return /* @__PURE__ */ jsxs26(
2436
+ return /* @__PURE__ */ jsxs27(
2211
2437
  "dialog",
2212
2438
  {
2213
2439
  ref: dialogRef,
@@ -2215,12 +2441,12 @@ function ReportShortcutsHelp({ open, onClose }) {
2215
2441
  "aria-label": "Keyboard shortcuts",
2216
2442
  onClose,
2217
2443
  children: [
2218
- /* @__PURE__ */ jsx42("h2", { className: "text-base font-semibold", children: "Keyboard shortcuts" }),
2219
- /* @__PURE__ */ jsx42("dl", { className: "mt-4 grid grid-cols-[auto_1fr] items-center gap-x-4 gap-y-2.5", children: SHORTCUTS.map((s) => /* @__PURE__ */ jsxs26("div", { className: "col-span-2 grid grid-cols-subgrid items-center", children: [
2220
- /* @__PURE__ */ jsx42("dt", { className: "justify-self-start", children: /* @__PURE__ */ jsx42(Keys, { keys: s.keys }) }),
2221
- /* @__PURE__ */ jsx42("dd", { className: "text-sm text-muted-foreground", children: s.description })
2444
+ /* @__PURE__ */ jsx43("h2", { className: "text-base font-semibold", children: "Keyboard shortcuts" }),
2445
+ /* @__PURE__ */ jsx43("dl", { className: "mt-4 grid grid-cols-[auto_1fr] items-center gap-x-4 gap-y-2.5", children: SHORTCUTS.map((s) => /* @__PURE__ */ jsxs27("div", { className: "col-span-2 grid grid-cols-subgrid items-center", children: [
2446
+ /* @__PURE__ */ jsx43("dt", { className: "justify-self-start", children: /* @__PURE__ */ jsx43(Keys, { keys: s.keys }) }),
2447
+ /* @__PURE__ */ jsx43("dd", { className: "text-sm text-muted-foreground", children: s.description })
2222
2448
  ] }, s.keys)) }),
2223
- /* @__PURE__ */ jsx42("form", { method: "dialog", className: "mt-6", children: /* @__PURE__ */ jsx42(Button, { type: "submit", variant: "outline", size: "sm", children: "Close" }) })
2449
+ /* @__PURE__ */ jsx43("form", { method: "dialog", className: "mt-6", children: /* @__PURE__ */ jsx43(Button, { type: "submit", variant: "outline", size: "sm", children: "Close" }) })
2224
2450
  ]
2225
2451
  }
2226
2452
  );
@@ -2282,14 +2508,16 @@ function useKeyboardShortcuts(handlers) {
2282
2508
  }
2283
2509
 
2284
2510
  // src/interactive/use-deep-link-scroll.ts
2285
- import { useEffect as useEffect4 } from "react";
2511
+ import { useEffect as useEffect4, useRef as useRef3 } from "react";
2286
2512
  function useDeepLinkScroll() {
2513
+ const lastAnchor = useRef3(null);
2287
2514
  useEffect4(() => {
2288
2515
  function scrollToHash() {
2289
- const hash = window.location.hash.replace(/^#/, "");
2290
- if (!hash) return;
2516
+ const { anchor } = splitHash(window.location.hash);
2517
+ if (!anchor || anchor === lastAnchor.current) return;
2518
+ lastAnchor.current = anchor;
2291
2519
  requestAnimationFrame(() => {
2292
- scrollToScenarioId(hash, { updateHash: false });
2520
+ scrollToScenarioId(anchor, { updateHash: false });
2293
2521
  });
2294
2522
  }
2295
2523
  scrollToHash();
@@ -2421,7 +2649,7 @@ function failuresFirst(report) {
2421
2649
 
2422
2650
  // src/interactive/ReportFilters.tsx
2423
2651
  import { useState as useState3 } from "react";
2424
- import { jsx as jsx43, jsxs as jsxs27 } from "react/jsx-runtime";
2652
+ import { jsx as jsx44, jsxs as jsxs28 } from "react/jsx-runtime";
2425
2653
  var TAG_LIMIT = 12;
2426
2654
  function ReportFilters({
2427
2655
  statuses,
@@ -2435,10 +2663,10 @@ function ReportFilters({
2435
2663
  if (statuses.length === 0 && tags.length === 0) return null;
2436
2664
  const tagsCollapsed = tags.length > TAG_LIMIT && !showAllTags;
2437
2665
  const visibleTags = tagsCollapsed ? tags.slice(0, TAG_LIMIT) : tags;
2438
- return /* @__PURE__ */ jsxs27("div", { className: "flex flex-col gap-2", children: [
2439
- statuses.length > 0 ? /* @__PURE__ */ jsx43("div", { role: "group", "aria-label": "Filter by status", className: "flex flex-wrap gap-1", children: statuses.map((opt) => {
2666
+ return /* @__PURE__ */ jsxs28("div", { className: "flex flex-col gap-2", children: [
2667
+ statuses.length > 0 ? /* @__PURE__ */ jsx44("div", { role: "group", "aria-label": "Filter by status", className: "flex flex-wrap gap-1", children: statuses.map((opt) => {
2440
2668
  const active = status === opt.key;
2441
- return /* @__PURE__ */ jsxs27(
2669
+ return /* @__PURE__ */ jsxs28(
2442
2670
  "button",
2443
2671
  {
2444
2672
  type: "button",
@@ -2450,23 +2678,23 @@ function ReportFilters({
2450
2678
  ),
2451
2679
  children: [
2452
2680
  opt.label,
2453
- /* @__PURE__ */ jsx43("span", { className: "ml-1 font-mono opacity-70", children: opt.count })
2681
+ /* @__PURE__ */ jsx44("span", { className: "ml-1 font-mono opacity-70", children: opt.count })
2454
2682
  ]
2455
2683
  },
2456
2684
  opt.key
2457
2685
  );
2458
2686
  }) }) : null,
2459
- tags.length > 0 ? /* @__PURE__ */ jsxs27("ul", { "aria-label": "Filter by tag", className: "flex flex-wrap items-center gap-1.5", children: [
2687
+ tags.length > 0 ? /* @__PURE__ */ jsxs28("ul", { "aria-label": "Filter by tag", className: "flex flex-wrap items-center gap-1.5", children: [
2460
2688
  visibleTags.map((tag) => {
2461
2689
  const active = activeTags.includes(tag);
2462
- return /* @__PURE__ */ jsx43("li", { children: /* @__PURE__ */ jsx43(
2690
+ return /* @__PURE__ */ jsx44("li", { children: /* @__PURE__ */ jsx44(
2463
2691
  "button",
2464
2692
  {
2465
2693
  type: "button",
2466
2694
  "aria-pressed": active,
2467
2695
  onClick: () => onToggleTag(tag),
2468
2696
  className: "cursor-pointer",
2469
- children: /* @__PURE__ */ jsx43(
2697
+ children: /* @__PURE__ */ jsx44(
2470
2698
  Badge,
2471
2699
  {
2472
2700
  variant: "tag",
@@ -2479,7 +2707,7 @@ function ReportFilters({
2479
2707
  }
2480
2708
  ) }, tag);
2481
2709
  }),
2482
- tags.length > TAG_LIMIT ? /* @__PURE__ */ jsx43("li", { children: /* @__PURE__ */ jsx43(
2710
+ tags.length > TAG_LIMIT ? /* @__PURE__ */ jsx44("li", { children: /* @__PURE__ */ jsx44(
2483
2711
  "button",
2484
2712
  {
2485
2713
  type: "button",
@@ -2495,7 +2723,7 @@ function ReportFilters({
2495
2723
 
2496
2724
  // src/interactive/ReportToc.tsx
2497
2725
  import { useEffect as useEffect5, useMemo as useMemo2, useState as useState4 } from "react";
2498
- import { jsx as jsx44, jsxs as jsxs28 } from "react/jsx-runtime";
2726
+ import { jsx as jsx45, jsxs as jsxs29 } from "react/jsx-runtime";
2499
2727
  var DOT = {
2500
2728
  passed: "bg-pass",
2501
2729
  failed: "bg-fail",
@@ -2524,11 +2752,11 @@ function TocContent({ onNavigate }) {
2524
2752
  return () => observer.disconnect();
2525
2753
  }, [scenarioIds]);
2526
2754
  if (report.features.length === 0) return null;
2527
- return /* @__PURE__ */ jsx44("ul", { className: "flex list-none flex-col gap-4 pl-0", children: report.features.map((feature) => /* @__PURE__ */ jsxs28("li", { children: [
2528
- /* @__PURE__ */ jsx44("p", { className: "mb-1.5 font-semibold leading-snug text-foreground", children: feature.title }),
2529
- /* @__PURE__ */ jsx44("ul", { className: "flex list-none flex-col gap-0.5 pl-0", children: feature.scenarios.map((scenario) => {
2755
+ return /* @__PURE__ */ jsx45("ul", { className: "flex list-none flex-col gap-4 pl-0", children: report.features.map((feature) => /* @__PURE__ */ jsxs29("li", { children: [
2756
+ /* @__PURE__ */ jsx45("p", { className: "mb-1.5 font-semibold leading-snug text-foreground", children: feature.title }),
2757
+ /* @__PURE__ */ jsx45("ul", { className: "flex list-none flex-col gap-0.5 pl-0", children: feature.scenarios.map((scenario) => {
2530
2758
  const active = activeId === scenario.id;
2531
- return /* @__PURE__ */ jsx44("li", { children: /* @__PURE__ */ jsxs28(
2759
+ return /* @__PURE__ */ jsx45("li", { children: /* @__PURE__ */ jsxs29(
2532
2760
  "a",
2533
2761
  {
2534
2762
  href: `#${scenario.id}`,
@@ -2543,14 +2771,14 @@ function TocContent({ onNavigate }) {
2543
2771
  active ? "border-primary font-medium text-foreground" : "border-border text-muted-foreground"
2544
2772
  ),
2545
2773
  children: [
2546
- /* @__PURE__ */ jsx44(
2774
+ /* @__PURE__ */ jsx45(
2547
2775
  "span",
2548
2776
  {
2549
2777
  "aria-hidden": true,
2550
2778
  className: cn("mt-1 size-1.5 shrink-0 rounded-full", DOT[scenario.status])
2551
2779
  }
2552
2780
  ),
2553
- /* @__PURE__ */ jsx44("span", { className: "break-words", children: scenario.title })
2781
+ /* @__PURE__ */ jsx45("span", { className: "break-words", children: scenario.title })
2554
2782
  ]
2555
2783
  }
2556
2784
  ) }, scenario.id);
@@ -2560,14 +2788,14 @@ function TocContent({ onNavigate }) {
2560
2788
  function ReportToc() {
2561
2789
  const report = useReport();
2562
2790
  if (report.features.length === 0) return null;
2563
- return /* @__PURE__ */ jsxs28(
2791
+ return /* @__PURE__ */ jsxs29(
2564
2792
  "nav",
2565
2793
  {
2566
2794
  "aria-label": "Table of contents",
2567
2795
  className: "sticky top-4 hidden h-fit w-64 shrink-0 self-start text-xs lg:block",
2568
2796
  children: [
2569
- /* @__PURE__ */ jsx44("p", { className: "mb-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Contents" }),
2570
- /* @__PURE__ */ jsx44(TocContent, {})
2797
+ /* @__PURE__ */ jsx45("p", { className: "mb-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Contents" }),
2798
+ /* @__PURE__ */ jsx45(TocContent, {})
2571
2799
  ]
2572
2800
  }
2573
2801
  );
@@ -2577,31 +2805,31 @@ function ReportToc() {
2577
2805
  import { useState as useState5 } from "react";
2578
2806
  import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
2579
2807
  import { ListIcon, XIcon } from "lucide-react";
2580
- import { jsx as jsx45, jsxs as jsxs29 } from "react/jsx-runtime";
2808
+ import { jsx as jsx46, jsxs as jsxs30 } from "react/jsx-runtime";
2581
2809
  function ReportTocDrawer() {
2582
2810
  const report = useReport();
2583
2811
  const [open, setOpen] = useState5(false);
2584
2812
  if (report.features.length === 0) return null;
2585
- return /* @__PURE__ */ jsxs29(DialogPrimitive.Root, { open, onOpenChange: setOpen, children: [
2586
- /* @__PURE__ */ jsxs29(DialogPrimitive.Trigger, { className: "flex cursor-pointer items-center gap-1 rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground lg:hidden [&_svg]:size-3.5", children: [
2587
- /* @__PURE__ */ jsx45(ListIcon, { "aria-hidden": true }),
2813
+ return /* @__PURE__ */ jsxs30(DialogPrimitive.Root, { open, onOpenChange: setOpen, children: [
2814
+ /* @__PURE__ */ jsxs30(DialogPrimitive.Trigger, { className: "flex cursor-pointer items-center gap-1 rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground lg:hidden [&_svg]:size-3.5", children: [
2815
+ /* @__PURE__ */ jsx46(ListIcon, { "aria-hidden": true }),
2588
2816
  " Contents"
2589
2817
  ] }),
2590
- /* @__PURE__ */ jsxs29(DialogPrimitive.Portal, { children: [
2591
- /* @__PURE__ */ jsx45(DialogPrimitive.Backdrop, { className: "fixed inset-0 z-50 bg-black/50 transition-opacity duration-200 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0" }),
2592
- /* @__PURE__ */ jsxs29(DialogPrimitive.Popup, { className: "fixed inset-y-0 left-0 z-50 flex w-72 max-w-[80vw] flex-col gap-3 overflow-y-auto border-r border-border bg-background p-4 font-sans text-xs shadow-lg outline-none transition-transform duration-200 data-[ending-style]:-translate-x-full data-[starting-style]:-translate-x-full", children: [
2593
- /* @__PURE__ */ jsxs29("div", { className: "flex items-center justify-between", children: [
2594
- /* @__PURE__ */ jsx45(DialogPrimitive.Title, { className: "text-xs font-semibold tracking-wide text-muted-foreground uppercase", children: "Contents" }),
2595
- /* @__PURE__ */ jsx45(
2818
+ /* @__PURE__ */ jsxs30(DialogPrimitive.Portal, { children: [
2819
+ /* @__PURE__ */ jsx46(DialogPrimitive.Backdrop, { className: "fixed inset-0 z-50 bg-black/50 transition-opacity duration-200 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0" }),
2820
+ /* @__PURE__ */ jsxs30(DialogPrimitive.Popup, { className: "fixed inset-y-0 left-0 z-50 flex w-72 max-w-[80vw] flex-col gap-3 overflow-y-auto border-r border-border bg-background p-4 font-sans text-xs shadow-lg outline-none transition-transform duration-200 data-[ending-style]:-translate-x-full data-[starting-style]:-translate-x-full", children: [
2821
+ /* @__PURE__ */ jsxs30("div", { className: "flex items-center justify-between", children: [
2822
+ /* @__PURE__ */ jsx46(DialogPrimitive.Title, { className: "text-xs font-semibold tracking-wide text-muted-foreground uppercase", children: "Contents" }),
2823
+ /* @__PURE__ */ jsx46(
2596
2824
  DialogPrimitive.Close,
2597
2825
  {
2598
2826
  "aria-label": "Close contents",
2599
2827
  className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
2600
- children: /* @__PURE__ */ jsx45(XIcon, { "aria-hidden": true })
2828
+ children: /* @__PURE__ */ jsx46(XIcon, { "aria-hidden": true })
2601
2829
  }
2602
2830
  )
2603
2831
  ] }),
2604
- /* @__PURE__ */ jsx45(TocContent, { onNavigate: () => setOpen(false) })
2832
+ /* @__PURE__ */ jsx46(TocContent, { onNavigate: () => setOpen(false) })
2605
2833
  ] })
2606
2834
  ] })
2607
2835
  ] });
@@ -2655,15 +2883,43 @@ function useTheme() {
2655
2883
  return { pref, setPref };
2656
2884
  }
2657
2885
 
2886
+ // src/interactive/use-url-state.ts
2887
+ import { useCallback as useCallback4, useEffect as useEffect7, useRef as useRef4, useState as useState7 } from "react";
2888
+ var WRITE_DELAY_MS = 250;
2889
+ function useUrlState() {
2890
+ const [state, setState] = useState7(DEFAULT_URL_STATE);
2891
+ const ready = useRef4(false);
2892
+ useEffect7(() => {
2893
+ const read = () => setState(decodeUrlState(splitHash(window.location.hash).params));
2894
+ read();
2895
+ ready.current = true;
2896
+ window.addEventListener("hashchange", read);
2897
+ return () => window.removeEventListener("hashchange", read);
2898
+ }, []);
2899
+ useEffect7(() => {
2900
+ if (!ready.current) return;
2901
+ const timer = setTimeout(
2902
+ () => writeHash(splitHash(window.location.hash).anchor, encodeUrlState(state)),
2903
+ WRITE_DELAY_MS
2904
+ );
2905
+ return () => clearTimeout(timer);
2906
+ }, [state]);
2907
+ const update = useCallback4(
2908
+ (patch) => setState((prev) => ({ ...prev, ...patch })),
2909
+ []
2910
+ );
2911
+ return [state, update];
2912
+ }
2913
+
2658
2914
  // src/interactive/ReportInteractive.tsx
2659
- import { Fragment as Fragment13, jsx as jsx46, jsxs as jsxs30 } from "react/jsx-runtime";
2915
+ import { Fragment as Fragment13, jsx as jsx47, jsxs as jsxs31 } from "react/jsx-runtime";
2660
2916
  function ReportInteractive(props) {
2661
2917
  const { report, className, title, dataTheme } = props;
2662
2918
  const result = unwrapReport(report);
2663
2919
  if (!result.ok) {
2664
- return /* @__PURE__ */ jsx46(ReportErrorShell, { error: result.error, className, title, dataTheme });
2920
+ return /* @__PURE__ */ jsx47(ReportErrorShell, { error: result.error, className, title, dataTheme });
2665
2921
  }
2666
- return /* @__PURE__ */ jsx46(ReportInteractiveView, { ...props, report: result.data });
2922
+ return /* @__PURE__ */ jsx47(ReportInteractiveView, { ...props, report: result.data });
2667
2923
  }
2668
2924
  var THEME_OPTIONS = [
2669
2925
  { value: "light", label: "Light", Icon: Sun },
@@ -2671,7 +2927,7 @@ var THEME_OPTIONS = [
2671
2927
  { value: "dark", label: "Dark", Icon: Moon }
2672
2928
  ];
2673
2929
  function ThemeSegment({ pref, onPref }) {
2674
- return /* @__PURE__ */ jsx46(
2930
+ return /* @__PURE__ */ jsx47(
2675
2931
  ToggleGroup,
2676
2932
  {
2677
2933
  size: "sm",
@@ -2681,7 +2937,7 @@ function ThemeSegment({ pref, onPref }) {
2681
2937
  if (next) onPref(next);
2682
2938
  },
2683
2939
  "aria-label": "Color theme",
2684
- children: THEME_OPTIONS.map(({ value, label, Icon }) => /* @__PURE__ */ jsx46(ToggleGroupItem, { value, "aria-label": label, title: label, className: "px-2", children: /* @__PURE__ */ jsx46(Icon, {}) }, value))
2940
+ children: THEME_OPTIONS.map(({ value, label, Icon }) => /* @__PURE__ */ jsx47(ToggleGroupItem, { value, "aria-label": label, title: label, className: "px-2", children: /* @__PURE__ */ jsx47(Icon, {}) }, value))
2685
2941
  }
2686
2942
  );
2687
2943
  }
@@ -2697,13 +2953,20 @@ function ReportInteractiveView({
2697
2953
  staleAfterDays = 7,
2698
2954
  scenarioHistory
2699
2955
  }) {
2700
- const [query, setQuery] = useState7("");
2701
- const [statusFilter, setStatusFilter] = useState7("all");
2702
- const [activeTags, setActiveTags] = useState7([]);
2703
- const [detail, setDetail] = useState7("full");
2704
- const [helpOpen, setHelpOpen] = useState7(false);
2956
+ const [urlState, setUrlState] = useUrlState();
2957
+ const { query, status: statusFilter, tags: activeTags, detail } = urlState;
2958
+ const setQuery = useCallback5((next) => setUrlState({ query: next }), [setUrlState]);
2959
+ const setStatusFilter = useCallback5(
2960
+ (next) => setUrlState({ status: next }),
2961
+ [setUrlState]
2962
+ );
2963
+ const setDetail = useCallback5(
2964
+ (next) => setUrlState({ detail: next }),
2965
+ [setUrlState]
2966
+ );
2967
+ const [helpOpen, setHelpOpen] = useState8(false);
2705
2968
  const { pref: themePref, setPref: setThemePref } = useTheme();
2706
- const searchRef = useRef3(null);
2969
+ const searchRef = useRef5(null);
2707
2970
  const expandId = useId2();
2708
2971
  const docsId = useId2();
2709
2972
  const deferredQuery = useDeferredValue(query);
@@ -2727,18 +2990,20 @@ function ReportInteractiveView({
2727
2990
  return opts;
2728
2991
  }, [report]);
2729
2992
  const tagOptions = useMemo3(() => allTags(report), [report]);
2730
- const toggleTag = useCallback4(
2731
- (tag) => setActiveTags((prev) => prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag]),
2732
- []
2993
+ const toggleTag = useCallback5(
2994
+ (tag) => setUrlState({
2995
+ tags: activeTags.includes(tag) ? activeTags.filter((t) => t !== tag) : [...activeTags, tag]
2996
+ }),
2997
+ [activeTags, setUrlState]
2733
2998
  );
2734
- const [toast, setToast] = useState7(null);
2735
- const toastTimer = useRef3(null);
2736
- const showToast = useCallback4((msg) => {
2999
+ const [toast, setToast] = useState8(null);
3000
+ const toastTimer = useRef5(null);
3001
+ const showToast = useCallback5((msg) => {
2737
3002
  setToast(msg);
2738
3003
  if (toastTimer.current) clearTimeout(toastTimer.current);
2739
3004
  toastTimer.current = setTimeout(() => setToast(null), 1800);
2740
3005
  }, []);
2741
- const copy = useCallback4(
3006
+ const copy = useCallback5(
2742
3007
  (text, msg) => {
2743
3008
  if (typeof navigator !== "undefined" && navigator.clipboard) {
2744
3009
  navigator.clipboard.writeText(text).then(() => showToast(msg), () => showToast("Copy failed"));
@@ -2754,15 +3019,15 @@ function ReportInteractiveView({
2754
3019
  }),
2755
3020
  [copy]
2756
3021
  );
2757
- const failureIndexRef = useRef3(0);
3022
+ const failureIndexRef = useRef5(0);
2758
3023
  const allCollapsibleIds = useMemo3(
2759
3024
  () => report.features.flatMap((f) => [f.id, ...f.scenarios.map((s) => s.id)]),
2760
3025
  [report]
2761
3026
  );
2762
- const collapse = useCollapseState(useCallback4(() => defaultCollapsedIds(report), [report]));
2763
- const collapseAll = useCallback4(() => collapse.collapseAll(allCollapsibleIds), [collapse, allCollapsibleIds]);
2764
- const [expandedAll, setExpandedAll] = useState7(() => report.summary.failed === 0);
2765
- const setExpanded = useCallback4(
3027
+ const collapse = useCollapseState(useCallback5(() => defaultCollapsedIds(report), [report]));
3028
+ const collapseAll = useCallback5(() => collapse.collapseAll(allCollapsibleIds), [collapse, allCollapsibleIds]);
3029
+ const [expandedAll, setExpandedAll] = useState8(() => report.summary.failed === 0);
3030
+ const setExpanded = useCallback5(
2766
3031
  (expanded) => {
2767
3032
  if (expanded) collapse.expandAll();
2768
3033
  else collapseAll();
@@ -2770,10 +3035,10 @@ function ReportInteractiveView({
2770
3035
  },
2771
3036
  [collapse, collapseAll]
2772
3037
  );
2773
- const focusSearch = useCallback4(() => {
3038
+ const focusSearch = useCallback5(() => {
2774
3039
  searchRef.current?.focus();
2775
3040
  }, []);
2776
- const stepFailure = useCallback4(
3041
+ const stepFailure = useCallback5(
2777
3042
  (direction) => {
2778
3043
  if (failures.length === 0) return;
2779
3044
  failureIndexRef.current = (failureIndexRef.current + direction + failures.length) % failures.length;
@@ -2782,10 +3047,10 @@ function ReportInteractiveView({
2782
3047
  },
2783
3048
  [failures]
2784
3049
  );
2785
- const toggleHelp = useCallback4(() => {
3050
+ const toggleHelp = useCallback5(() => {
2786
3051
  setHelpOpen((v) => !v);
2787
3052
  }, []);
2788
- const escape = useCallback4(() => {
3053
+ const escape = useCallback5(() => {
2789
3054
  if (helpOpen) setHelpOpen(false);
2790
3055
  else if (query !== "") setQuery("");
2791
3056
  }, [helpOpen, query]);
@@ -2803,13 +3068,13 @@ function ReportInteractiveView({
2803
3068
  const totalScenarios = report.summary.total;
2804
3069
  const matchedScenarios = filtered.summary.total;
2805
3070
  const showToc = !hideToc && totalScenarios > 3;
2806
- return /* @__PURE__ */ jsx46(CollapseProvider, { value: collapse.api, children: /* @__PURE__ */ jsx46(ScenarioActionsProvider, { value: scenarioActions, children: /* @__PURE__ */ jsx46(ScenarioHistoryProvider, { value: scenarioHistory ?? null, children: /* @__PURE__ */ jsx46(
3071
+ return /* @__PURE__ */ jsx47(CollapseProvider, { value: collapse.api, children: /* @__PURE__ */ jsx47(ScenarioActionsProvider, { value: scenarioActions, children: /* @__PURE__ */ jsx47(ScenarioHistoryProvider, { value: scenarioHistory ?? null, children: /* @__PURE__ */ jsx47(
2807
3072
  ReportRoot,
2808
3073
  {
2809
3074
  report: filtered,
2810
3075
  customRenderers,
2811
3076
  renderers,
2812
- children: /* @__PURE__ */ jsxs30(
3077
+ children: /* @__PURE__ */ jsxs31(
2813
3078
  "main",
2814
3079
  {
2815
3080
  className: cn("es-report", "es-report-interactive", className),
@@ -2818,8 +3083,8 @@ function ReportInteractiveView({
2818
3083
  "data-theme": dataTheme,
2819
3084
  "data-detail-level": detail,
2820
3085
  children: [
2821
- /* @__PURE__ */ jsxs30("header", { className: "es-report-header", children: [
2822
- /* @__PURE__ */ jsxs30(
3086
+ /* @__PURE__ */ jsxs31("header", { className: "es-report-header", children: [
3087
+ /* @__PURE__ */ jsxs31(
2823
3088
  "div",
2824
3089
  {
2825
3090
  "data-es-navbar": true,
@@ -2828,9 +3093,9 @@ function ReportInteractiveView({
2828
3093
  hideHeader ? "justify-end" : "justify-between"
2829
3094
  ),
2830
3095
  children: [
2831
- hideHeader ? null : /* @__PURE__ */ jsx46("h1", { className: "min-w-0", children: title ?? "Story Report" }),
2832
- /* @__PURE__ */ jsxs30("div", { className: "flex items-center gap-2", children: [
2833
- /* @__PURE__ */ jsx46(
3096
+ hideHeader ? null : /* @__PURE__ */ jsx47("h1", { className: "min-w-0", children: title ?? "Story Report" }),
3097
+ /* @__PURE__ */ jsxs31("div", { className: "flex items-center gap-2", children: [
3098
+ /* @__PURE__ */ jsx47(
2834
3099
  ReportSearch,
2835
3100
  {
2836
3101
  ref: searchRef,
@@ -2840,16 +3105,16 @@ function ReportInteractiveView({
2840
3105
  totalCount: totalScenarios
2841
3106
  }
2842
3107
  ),
2843
- /* @__PURE__ */ jsx46(ThemeSegment, { pref: themePref, onPref: setThemePref })
3108
+ /* @__PURE__ */ jsx47(ThemeSegment, { pref: themePref, onPref: setThemePref })
2844
3109
  ] })
2845
3110
  ]
2846
3111
  }
2847
3112
  ),
2848
- hideHeader ? null : /* @__PURE__ */ jsxs30(Fragment13, { children: [
2849
- /* @__PURE__ */ jsx46(ReportSummary, {}),
2850
- /* @__PURE__ */ jsx46(ReportMeta, {})
3113
+ hideHeader ? null : /* @__PURE__ */ jsxs31(Fragment13, { children: [
3114
+ /* @__PURE__ */ jsx47(ReportSummary, {}),
3115
+ /* @__PURE__ */ jsx47(ReportMeta, {})
2851
3116
  ] }),
2852
- /* @__PURE__ */ jsx46(
3117
+ /* @__PURE__ */ jsx47(
2853
3118
  ReportFreshness,
2854
3119
  {
2855
3120
  lastRunMs: reportLastRunMs(report),
@@ -2857,8 +3122,8 @@ function ReportInteractiveView({
2857
3122
  staleAfterDays
2858
3123
  }
2859
3124
  ),
2860
- /* @__PURE__ */ jsx46(ReportLastRunDelta, { history: scenarioHistory, report }),
2861
- /* @__PURE__ */ jsx46(
3125
+ /* @__PURE__ */ jsx47(ReportLastRunDelta, { history: scenarioHistory, report }),
3126
+ /* @__PURE__ */ jsx47(
2862
3127
  ReportFilters,
2863
3128
  {
2864
3129
  statuses: statusOptions,
@@ -2869,14 +3134,14 @@ function ReportInteractiveView({
2869
3134
  onToggleTag: toggleTag
2870
3135
  }
2871
3136
  ),
2872
- /* @__PURE__ */ jsxs30("div", { className: "flex flex-wrap items-center gap-x-6 gap-y-2 border-t border-border/60 pt-3", children: [
2873
- showToc ? /* @__PURE__ */ jsx46(ReportTocDrawer, {}) : null,
2874
- /* @__PURE__ */ jsxs30("div", { className: "flex items-center gap-2", children: [
2875
- /* @__PURE__ */ jsx46(Switch, { id: expandId, checked: expandedAll, onCheckedChange: setExpanded }),
2876
- /* @__PURE__ */ jsx46("label", { htmlFor: expandId, className: "cursor-pointer text-sm text-muted-foreground select-none", children: "Expand all" })
3137
+ /* @__PURE__ */ jsxs31("div", { className: "flex flex-wrap items-center gap-x-6 gap-y-2 border-t border-border/60 pt-3", children: [
3138
+ showToc ? /* @__PURE__ */ jsx47(ReportTocDrawer, {}) : null,
3139
+ /* @__PURE__ */ jsxs31("div", { className: "flex items-center gap-2", children: [
3140
+ /* @__PURE__ */ jsx47(Switch, { id: expandId, checked: expandedAll, onCheckedChange: setExpanded }),
3141
+ /* @__PURE__ */ jsx47("label", { htmlFor: expandId, className: "cursor-pointer text-sm text-muted-foreground select-none", children: "Expand all" })
2877
3142
  ] }),
2878
- /* @__PURE__ */ jsxs30("div", { className: "flex items-center gap-2", children: [
2879
- /* @__PURE__ */ jsx46(
3143
+ /* @__PURE__ */ jsxs31("div", { className: "flex items-center gap-2", children: [
3144
+ /* @__PURE__ */ jsx47(
2880
3145
  Switch,
2881
3146
  {
2882
3147
  id: docsId,
@@ -2884,16 +3149,16 @@ function ReportInteractiveView({
2884
3149
  onCheckedChange: (checked) => setDetail(checked ? "full" : "minimal")
2885
3150
  }
2886
3151
  ),
2887
- /* @__PURE__ */ jsx46("label", { htmlFor: docsId, className: "cursor-pointer text-sm text-muted-foreground select-none", children: "Show documentation" })
3152
+ /* @__PURE__ */ jsx47("label", { htmlFor: docsId, className: "cursor-pointer text-sm text-muted-foreground select-none", children: "Show documentation" })
2888
3153
  ] })
2889
3154
  ] })
2890
3155
  ] }),
2891
- /* @__PURE__ */ jsx46(ReportFailureBanner, { failures }),
2892
- hasContent ? showToc ? /* @__PURE__ */ jsxs30("div", { className: "flex gap-6", children: [
2893
- /* @__PURE__ */ jsx46(ReportToc, {}),
2894
- /* @__PURE__ */ jsx46("div", { className: "flex min-w-0 flex-1 flex-col gap-4", children: /* @__PURE__ */ jsx46(ReportFeatureList, {}) })
2895
- ] }) : /* @__PURE__ */ jsx46("div", { className: "flex flex-col gap-4", children: /* @__PURE__ */ jsx46(ReportFeatureList, {}) }) : /* @__PURE__ */ jsx46(ReportEmpty, { message: query ? "No scenarios match the search." : void 0 }),
2896
- /* @__PURE__ */ jsx46(
3156
+ /* @__PURE__ */ jsx47(ReportFailureBanner, { failures }),
3157
+ hasContent ? showToc ? /* @__PURE__ */ jsxs31("div", { className: "flex gap-6", children: [
3158
+ /* @__PURE__ */ jsx47(ReportToc, {}),
3159
+ /* @__PURE__ */ jsx47("div", { className: "flex min-w-0 flex-1 flex-col gap-4", children: /* @__PURE__ */ jsx47(ReportFeatureList, {}) })
3160
+ ] }) : /* @__PURE__ */ jsx47("div", { className: "flex flex-col gap-4", children: /* @__PURE__ */ jsx47(ReportFeatureList, {}) }) : /* @__PURE__ */ jsx47(ReportEmpty, { message: query ? "No scenarios match the search." : void 0 }),
3161
+ /* @__PURE__ */ jsx47(
2897
3162
  "button",
2898
3163
  {
2899
3164
  type: "button",
@@ -2904,8 +3169,8 @@ function ReportInteractiveView({
2904
3169
  children: "?"
2905
3170
  }
2906
3171
  ),
2907
- /* @__PURE__ */ jsx46(ReportShortcutsHelp, { open: helpOpen, onClose: () => setHelpOpen(false) }),
2908
- toast ? /* @__PURE__ */ jsx46(
3172
+ /* @__PURE__ */ jsx47(ReportShortcutsHelp, { open: helpOpen, onClose: () => setHelpOpen(false) }),
3173
+ toast ? /* @__PURE__ */ jsx47(
2909
3174
  "div",
2910
3175
  {
2911
3176
  role: "status",
@@ -2925,11 +3190,11 @@ function ReportInteractiveView({
2925
3190
  import { useMemo as useMemo4 } from "react";
2926
3191
 
2927
3192
  // src/interactive/island-renderers.tsx
2928
- import { createElement, useEffect as useEffect8, useState as useState9 } from "react";
3193
+ import { createElement, useEffect as useEffect9, useState as useState10 } from "react";
2929
3194
 
2930
3195
  // src/components/doc/MermaidView.tsx
2931
- import { useEffect as useEffect7, useRef as useRef4, useState as useState8 } from "react";
2932
- import { jsx as jsx47, jsxs as jsxs31 } from "react/jsx-runtime";
3196
+ import { useEffect as useEffect8, useRef as useRef6, useState as useState9 } from "react";
3197
+ import { Fragment as Fragment14, jsx as jsx48, jsxs as jsxs32 } from "react/jsx-runtime";
2933
3198
  function prefersDark(el) {
2934
3199
  const themed = el?.closest("[data-theme]");
2935
3200
  if (themed?.dataset.theme === "dark") return true;
@@ -2937,23 +3202,40 @@ function prefersDark(el) {
2937
3202
  return typeof window !== "undefined" && !!window.matchMedia?.("(prefers-color-scheme: dark)").matches;
2938
3203
  }
2939
3204
  function MermaidView({ entry, load }) {
2940
- const ref = useRef4(null);
2941
- const [svg, setSvg] = useState8(null);
2942
- useEffect7(() => {
3205
+ const ref = useRef6(null);
3206
+ const [svg, setSvg] = useState9(null);
3207
+ const [error, setError] = useState9(null);
3208
+ useEffect8(() => {
2943
3209
  let cancelled = false;
2944
3210
  const draw = async () => {
3211
+ let mermaid;
3212
+ try {
3213
+ mermaid = await load();
3214
+ } catch {
3215
+ if (!cancelled) {
3216
+ setSvg(null);
3217
+ setError(null);
3218
+ }
3219
+ return;
3220
+ }
2945
3221
  try {
2946
- const mermaid = await load();
2947
3222
  mermaid.initialize({
2948
3223
  startOnLoad: false,
2949
3224
  securityLevel: "strict",
2950
3225
  theme: prefersDark(ref.current) ? "dark" : "default"
2951
3226
  });
3227
+ await mermaid.parse(entry.code);
2952
3228
  const id = `es-mermaid-${Math.random().toString(36).slice(2)}`;
2953
3229
  const { svg: rendered } = await mermaid.render(id, entry.code);
2954
- if (!cancelled) setSvg(rendered);
2955
- } catch {
2956
- if (!cancelled) setSvg(null);
3230
+ if (!cancelled) {
3231
+ setSvg(rendered);
3232
+ setError(null);
3233
+ }
3234
+ } catch (err) {
3235
+ if (!cancelled) {
3236
+ setSvg(null);
3237
+ setError(err instanceof Error ? err.message : String(err));
3238
+ }
2957
3239
  }
2958
3240
  };
2959
3241
  void draw();
@@ -2966,9 +3248,9 @@ function MermaidView({ entry, load }) {
2966
3248
  };
2967
3249
  }, [entry.code, load]);
2968
3250
  if (svg) {
2969
- return /* @__PURE__ */ jsxs31("figure", { ref, className: "my-3", children: [
2970
- entry.title ? /* @__PURE__ */ jsx47("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
2971
- /* @__PURE__ */ jsx47(
3251
+ return /* @__PURE__ */ jsxs32("figure", { ref, className: "my-3", children: [
3252
+ entry.title ? /* @__PURE__ */ jsx48("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
3253
+ /* @__PURE__ */ jsx48(
2972
3254
  "div",
2973
3255
  {
2974
3256
  role: "img",
@@ -2979,11 +3261,25 @@ function MermaidView({ entry, load }) {
2979
3261
  )
2980
3262
  ] });
2981
3263
  }
2982
- return /* @__PURE__ */ jsx47(MermaidSource, { entry, ref });
3264
+ return /* @__PURE__ */ jsxs32(Fragment14, { children: [
3265
+ error ? /* @__PURE__ */ jsxs32(
3266
+ "p",
3267
+ {
3268
+ role: "alert",
3269
+ "data-es-mermaid-error": true,
3270
+ className: "mt-3 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive",
3271
+ children: [
3272
+ "Diagram failed to render: ",
3273
+ error
3274
+ ]
3275
+ }
3276
+ ) : null,
3277
+ /* @__PURE__ */ jsx48(MermaidSource, { entry, ref })
3278
+ ] });
2983
3279
  }
2984
3280
 
2985
3281
  // src/interactive/island-renderers.tsx
2986
- import { jsx as jsx48 } from "react/jsx-runtime";
3282
+ import { jsx as jsx49 } from "react/jsx-runtime";
2987
3283
  var HLJS_ESM = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/es/highlight.min.js";
2988
3284
  var MERMAID_ESM = "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
2989
3285
  function cachedCdnModule(url) {
@@ -3000,8 +3296,8 @@ function cachedCdnModule(url) {
3000
3296
  var loadMermaidFromCdn = cachedCdnModule(MERMAID_ESM);
3001
3297
  var loadHljs = cachedCdnModule(HLJS_ESM);
3002
3298
  function CdnHighlightedCode({ entry }) {
3003
- const [html, setHtml] = useState9(null);
3004
- useEffect8(() => {
3299
+ const [html, setHtml] = useState10(null);
3300
+ useEffect9(() => {
3005
3301
  let cancelled = false;
3006
3302
  void (async () => {
3007
3303
  try {
@@ -3017,13 +3313,13 @@ function CdnHighlightedCode({ entry }) {
3017
3313
  cancelled = true;
3018
3314
  };
3019
3315
  }, [entry.content, entry.lang]);
3020
- return /* @__PURE__ */ jsx48(CodeFigure, { label: entry.label, children: html !== null ? /* @__PURE__ */ jsx48(
3316
+ return /* @__PURE__ */ jsx49(CodeFigure, { label: entry.label, children: html !== null ? /* @__PURE__ */ jsx49(
3021
3317
  "code",
3022
3318
  {
3023
3319
  className: cn("hljs font-mono text-xs", entry.lang && `language-${entry.lang}`),
3024
3320
  dangerouslySetInnerHTML: { __html: html }
3025
3321
  }
3026
- ) : /* @__PURE__ */ jsx48("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
3322
+ ) : /* @__PURE__ */ jsx49("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
3027
3323
  }
3028
3324
  function buildIslandRenderers(opts) {
3029
3325
  const renderers = {};
@@ -3037,7 +3333,7 @@ function buildIslandRenderers(opts) {
3037
3333
  }
3038
3334
 
3039
3335
  // src/interactive/ReportInteractiveIsland.tsx
3040
- import { jsx as jsx49 } from "react/jsx-runtime";
3336
+ import { jsx as jsx50 } from "react/jsx-runtime";
3041
3337
  function ReportInteractiveIsland({
3042
3338
  report,
3043
3339
  title,
@@ -3051,7 +3347,7 @@ function ReportInteractiveIsland({
3051
3347
  () => buildIslandRenderers({ syntaxHighlighting, mermaid }),
3052
3348
  [syntaxHighlighting, mermaid]
3053
3349
  );
3054
- return /* @__PURE__ */ jsx49(
3350
+ return /* @__PURE__ */ jsx50(
3055
3351
  ReportInteractive,
3056
3352
  {
3057
3353
  report,