executable-stories-react 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +414 -464
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -2
- package/dist/index.d.ts +14 -2
- package/dist/index.js +313 -361
- package/dist/index.js.map +1 -1
- package/dist/interactive.cjs +1779 -1340
- package/dist/interactive.cjs.map +1 -1
- package/dist/interactive.d.cts +32 -32
- package/dist/interactive.d.ts +32 -32
- package/dist/interactive.js +1694 -1246
- package/dist/interactive.js.map +1 -1
- package/dist/parse.cjs +34 -147
- package/dist/parse.cjs.map +1 -1
- package/dist/parse.js +34 -147
- package/dist/parse.js.map +1 -1
- package/dist/report-island.global.js +64 -54
- package/dist/ssr.cjs +331 -260
- package/dist/ssr.cjs.map +1 -1
- package/dist/ssr.js +250 -176
- package/dist/ssr.js.map +1 -1
- package/dist/tailwind.css +2 -2
- package/package.json +24 -23
package/dist/interactive.js
CHANGED
|
@@ -1,45 +1,146 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/interactive/ReportInteractive.tsx
|
|
4
|
+
import { Monitor, Moon, Sun } from "lucide-react";
|
|
5
|
+
import { useTools } from "webmcpable/react";
|
|
4
6
|
import {
|
|
5
7
|
useCallback as useCallback5,
|
|
6
8
|
useDeferredValue,
|
|
7
9
|
useId as useId2,
|
|
8
10
|
useMemo as useMemo3,
|
|
9
11
|
useRef as useRef5,
|
|
10
|
-
useState as
|
|
12
|
+
useState as useState9
|
|
11
13
|
} from "react";
|
|
12
14
|
|
|
13
|
-
// src/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
// src/components/ui/empty.tsx
|
|
16
|
+
import { cva } from "class-variance-authority";
|
|
17
|
+
|
|
18
|
+
// src/lib/utils.ts
|
|
19
|
+
import { clsx } from "clsx";
|
|
20
|
+
import { twMerge } from "tailwind-merge";
|
|
21
|
+
function cn(...inputs) {
|
|
22
|
+
return twMerge(clsx(inputs));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/components/ui/empty.tsx
|
|
26
|
+
import { jsx } from "react/jsx-runtime";
|
|
27
|
+
function Empty({ className, ...props }) {
|
|
28
|
+
return /* @__PURE__ */ jsx(
|
|
29
|
+
"div",
|
|
30
|
+
{
|
|
31
|
+
"data-slot": "empty",
|
|
32
|
+
className: cn(
|
|
33
|
+
"flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12",
|
|
34
|
+
className
|
|
35
|
+
),
|
|
36
|
+
...props
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
function EmptyHeader({ className, ...props }) {
|
|
41
|
+
return /* @__PURE__ */ jsx(
|
|
42
|
+
"div",
|
|
43
|
+
{
|
|
44
|
+
"data-slot": "empty-header",
|
|
45
|
+
className: cn(
|
|
46
|
+
"flex max-w-sm flex-col items-center gap-2 text-center",
|
|
47
|
+
className
|
|
48
|
+
),
|
|
49
|
+
...props
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
var emptyMediaVariants = cva(
|
|
54
|
+
"mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
55
|
+
{
|
|
56
|
+
variants: {
|
|
57
|
+
variant: {
|
|
58
|
+
default: "bg-transparent",
|
|
59
|
+
icon: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-6"
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
defaultVariants: {
|
|
63
|
+
variant: "default"
|
|
64
|
+
}
|
|
18
65
|
}
|
|
19
|
-
|
|
66
|
+
);
|
|
67
|
+
function EmptyTitle({ className, ...props }) {
|
|
68
|
+
return /* @__PURE__ */ jsx(
|
|
69
|
+
"div",
|
|
70
|
+
{
|
|
71
|
+
"data-slot": "empty-title",
|
|
72
|
+
className: cn("text-lg font-medium tracking-tight", className),
|
|
73
|
+
...props
|
|
74
|
+
}
|
|
75
|
+
);
|
|
20
76
|
}
|
|
21
77
|
|
|
22
|
-
// src/
|
|
23
|
-
import {
|
|
78
|
+
// src/components/ReportEmpty.tsx
|
|
79
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
80
|
+
function ReportEmpty({ message }) {
|
|
81
|
+
return /* @__PURE__ */ jsx2(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ jsx2(EmptyHeader, { children: /* @__PURE__ */ jsx2(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/hooks/useReport.ts
|
|
85
|
+
import { useContext } from "react";
|
|
24
86
|
|
|
25
87
|
// src/context/ReportContext.ts
|
|
26
88
|
import { createContext } from "react";
|
|
27
89
|
var ReportContext = createContext(null);
|
|
28
90
|
|
|
29
|
-
// src/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
91
|
+
// src/hooks/useReport.ts
|
|
92
|
+
function useReport() {
|
|
93
|
+
const ctx = useContext(ReportContext);
|
|
94
|
+
if (!ctx) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
"useReport must be used inside <ReportRoot> or <Report>. Wrap your tree with one of those."
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return ctx.report;
|
|
100
|
+
}
|
|
33
101
|
|
|
34
|
-
// src/
|
|
35
|
-
import {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
102
|
+
// src/components/ReportScenario.tsx
|
|
103
|
+
import { formatDuration as formatDuration3 } from "executable-stories-core/utils/duration";
|
|
104
|
+
|
|
105
|
+
// src/lib/step-params.tsx
|
|
106
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
107
|
+
var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.-])\d+(?:\.\d+)?(?![\w.-])/g;
|
|
108
|
+
function highlightStepParams(text) {
|
|
109
|
+
const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
|
|
110
|
+
if (matches.length === 0) return text;
|
|
111
|
+
const nodes = [];
|
|
112
|
+
let lastIndex = 0;
|
|
113
|
+
matches.forEach((match, i) => {
|
|
114
|
+
const start = match.index;
|
|
115
|
+
const end = start + match[0].length;
|
|
116
|
+
if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
|
|
117
|
+
nodes.push(
|
|
118
|
+
/* @__PURE__ */ jsx3("span", { className: "font-medium text-step-param italic", children: match[0] }, i)
|
|
119
|
+
);
|
|
120
|
+
lastIndex = end;
|
|
121
|
+
});
|
|
122
|
+
if (lastIndex < text.length) nodes.push(text.slice(lastIndex));
|
|
123
|
+
return nodes;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/components/ReportSteps.tsx
|
|
127
|
+
import {
|
|
128
|
+
assertionState,
|
|
129
|
+
assertiveSteps
|
|
130
|
+
} from "executable-stories-core/utils/assertive-steps";
|
|
131
|
+
import { formatDuration } from "executable-stories-core/utils/duration";
|
|
132
|
+
|
|
133
|
+
// src/components/doc/DocNote.tsx
|
|
134
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
135
|
+
function DocNote({ entry }) {
|
|
136
|
+
return /* @__PURE__ */ jsx4("p", { className: "my-2 text-sm text-muted-foreground", children: entry.text });
|
|
39
137
|
}
|
|
40
138
|
|
|
41
139
|
// src/components/ui/badge.tsx
|
|
42
|
-
|
|
140
|
+
import "react";
|
|
141
|
+
import { cva as cva2 } from "class-variance-authority";
|
|
142
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
143
|
+
var badgeVariants = cva2(
|
|
43
144
|
"inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3",
|
|
44
145
|
{
|
|
45
146
|
variants: {
|
|
@@ -84,342 +185,119 @@ function Badge({
|
|
|
84
185
|
});
|
|
85
186
|
}
|
|
86
187
|
|
|
87
|
-
// src/components/doc/
|
|
88
|
-
import { jsx
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
function isChangeKind(value) {
|
|
92
|
-
return value === "added" || value === "modified" || value === "removed" || value === "renamed";
|
|
188
|
+
// src/components/doc/DocTag.tsx
|
|
189
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
190
|
+
function DocTag({ entry }) {
|
|
191
|
+
return /* @__PURE__ */ jsx5("ul", { className: "my-2 flex flex-wrap gap-1.5", "aria-label": "Tags", children: entry.names.map((n) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsx5(Badge, { variant: "tag", children: n }) }, n)) });
|
|
93
192
|
}
|
|
94
|
-
|
|
95
|
-
|
|
193
|
+
|
|
194
|
+
// src/components/doc/DocKv.tsx
|
|
195
|
+
import { jsx as jsx6, jsxs } from "react/jsx-runtime";
|
|
196
|
+
function formatValue(value) {
|
|
197
|
+
if (value === null) return "null";
|
|
198
|
+
if (typeof value === "string") return value;
|
|
199
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
200
|
+
try {
|
|
201
|
+
return JSON.stringify(value);
|
|
202
|
+
} catch {
|
|
203
|
+
return String(value);
|
|
204
|
+
}
|
|
96
205
|
}
|
|
97
|
-
function
|
|
98
|
-
return
|
|
206
|
+
function DocKv({ entry }) {
|
|
207
|
+
return /* @__PURE__ */ jsxs("dl", { className: "my-2 flex flex-wrap items-baseline gap-x-2 text-sm", children: [
|
|
208
|
+
/* @__PURE__ */ jsx6("dt", { className: "font-semibold text-muted-foreground", children: entry.label }),
|
|
209
|
+
/* @__PURE__ */ jsx6("dd", { className: "font-mono text-foreground", children: formatValue(entry.value) })
|
|
210
|
+
] });
|
|
99
211
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
212
|
+
|
|
213
|
+
// src/hooks/useRenderers.ts
|
|
214
|
+
import { useContext as useContext2 } from "react";
|
|
215
|
+
var EMPTY_CUSTOM = {};
|
|
216
|
+
var EMPTY_RENDERERS = {};
|
|
217
|
+
function useCustomRenderers() {
|
|
218
|
+
const ctx = useContext2(ReportContext);
|
|
219
|
+
return ctx?.customRenderers ?? EMPTY_CUSTOM;
|
|
103
220
|
}
|
|
104
|
-
function
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
children
|
|
221
|
+
function useBuiltinRenderers() {
|
|
222
|
+
const ctx = useContext2(ReportContext);
|
|
223
|
+
return ctx?.renderers ?? EMPTY_RENDERERS;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// src/components/doc/DocCode.tsx
|
|
227
|
+
import { Fragment, jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
228
|
+
function CodeFigure({ label, children }) {
|
|
229
|
+
return /* @__PURE__ */ jsxs2("figure", { className: "my-3", children: [
|
|
230
|
+
label ? /* @__PURE__ */ jsx7("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: label }) : null,
|
|
231
|
+
/* @__PURE__ */ jsx7("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3", children })
|
|
115
232
|
] });
|
|
116
233
|
}
|
|
117
|
-
function
|
|
118
|
-
|
|
234
|
+
function DocCode({ entry }) {
|
|
235
|
+
const renderers = useBuiltinRenderers();
|
|
236
|
+
if (renderers.code) {
|
|
237
|
+
return /* @__PURE__ */ jsx7(Fragment, { children: renderers.code(entry) });
|
|
238
|
+
}
|
|
239
|
+
return /* @__PURE__ */ jsx7(CodeFigure, { label: entry.label, children: /* @__PURE__ */ jsx7("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
|
|
119
240
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
241
|
+
|
|
242
|
+
// src/components/doc/DocTable.tsx
|
|
243
|
+
import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
244
|
+
function DocTable({ entry }) {
|
|
245
|
+
return /* @__PURE__ */ jsxs3("figure", { className: "my-3", children: [
|
|
246
|
+
entry.label ? /* @__PURE__ */ jsx8("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
|
|
247
|
+
/* @__PURE__ */ jsx8("div", { tabIndex: 0, className: "overflow-x-auto rounded-md border border-border", children: /* @__PURE__ */ jsxs3("table", { className: "w-full border-collapse text-sm", children: [
|
|
248
|
+
/* @__PURE__ */ jsx8("thead", { children: /* @__PURE__ */ jsx8("tr", { className: "bg-muted/50", children: entry.columns.map((c) => /* @__PURE__ */ jsx8("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: c }, c)) }) }),
|
|
249
|
+
/* @__PURE__ */ jsx8("tbody", { children: entry.rows.map((row, i) => /* @__PURE__ */ jsx8("tr", { children: row.map((cell, j) => /* @__PURE__ */ jsx8("td", { className: "border-b border-border px-3 py-2 text-foreground", children: cell }, j)) }, i)) })
|
|
250
|
+
] }) })
|
|
251
|
+
] });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/components/doc/DocLink.tsx
|
|
255
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
256
|
+
function DocLink({ entry }) {
|
|
257
|
+
return /* @__PURE__ */ jsx9(
|
|
258
|
+
"a",
|
|
259
|
+
{
|
|
260
|
+
className: "text-sm font-medium text-primary underline underline-offset-2 hover:no-underline",
|
|
261
|
+
href: entry.url,
|
|
262
|
+
rel: "noreferrer noopener",
|
|
263
|
+
target: "_blank",
|
|
264
|
+
children: entry.label
|
|
127
265
|
}
|
|
128
|
-
|
|
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 };
|
|
266
|
+
);
|
|
139
267
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (index === segments.length - 1) child.file = file;
|
|
152
|
-
node = child;
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
return root;
|
|
268
|
+
|
|
269
|
+
// src/lib/markdown.ts
|
|
270
|
+
import { marked } from "marked";
|
|
271
|
+
|
|
272
|
+
// src/lib/url.ts
|
|
273
|
+
import { safeUrl, safeImageUrl, isLocalFsPath } from "executable-stories-core/utils/url";
|
|
274
|
+
|
|
275
|
+
// src/lib/markdown.ts
|
|
276
|
+
function safeMarkdownHtml(markdown) {
|
|
277
|
+
const raw = marked.parse(markdown, { async: false });
|
|
278
|
+
return raw.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<\/?(?:iframe|object|embed|form|base)\b[^>]*>/gi, "").replace(/\son[a-z]+\s*=\s*"[^"]*"/gi, "").replace(/\son[a-z]+\s*=\s*'[^']*'/gi, "").replace(/\son[a-z]+\s*=\s*[^\s>]+/gi, "").replace(/(href|src)\s*=\s*"([^"]*)"/gi, (_m, attr, val) => `${attr}="${neutralizeUrl(val)}"`).replace(/(href|src)\s*=\s*'([^']*)'/gi, (_m, attr, val) => `${attr}='${neutralizeUrl(val)}'`);
|
|
156
279
|
}
|
|
157
|
-
function
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
// src/components/ReportScenario.tsx
|
|
299
|
-
import { formatDuration as formatDuration3 } from "executable-stories-core/utils/duration";
|
|
300
|
-
|
|
301
|
-
// src/components/ReportSteps.tsx
|
|
302
|
-
import { formatDuration } from "executable-stories-core/utils/duration";
|
|
303
|
-
|
|
304
|
-
// src/components/doc/DocNote.tsx
|
|
305
|
-
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
306
|
-
function DocNote({ entry }) {
|
|
307
|
-
return /* @__PURE__ */ jsx3("p", { className: "my-2 text-sm text-muted-foreground", children: entry.text });
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// src/components/doc/DocTag.tsx
|
|
311
|
-
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
312
|
-
function DocTag({ entry }) {
|
|
313
|
-
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)) });
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// src/components/doc/DocKv.tsx
|
|
317
|
-
import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
318
|
-
function formatValue(value) {
|
|
319
|
-
if (value === null) return "null";
|
|
320
|
-
if (typeof value === "string") return value;
|
|
321
|
-
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
322
|
-
try {
|
|
323
|
-
return JSON.stringify(value);
|
|
324
|
-
} catch {
|
|
325
|
-
return String(value);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
function DocKv({ entry }) {
|
|
329
|
-
return /* @__PURE__ */ jsxs2("dl", { className: "my-2 flex flex-wrap items-baseline gap-x-2 text-sm", children: [
|
|
330
|
-
/* @__PURE__ */ jsx5("dt", { className: "font-semibold text-muted-foreground", children: entry.label }),
|
|
331
|
-
/* @__PURE__ */ jsx5("dd", { className: "font-mono text-foreground", children: formatValue(entry.value) })
|
|
332
|
-
] });
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// src/hooks/useRenderers.ts
|
|
336
|
-
import { useContext as useContext2 } from "react";
|
|
337
|
-
var EMPTY_CUSTOM = {};
|
|
338
|
-
var EMPTY_RENDERERS2 = {};
|
|
339
|
-
function useCustomRenderers() {
|
|
340
|
-
const ctx = useContext2(ReportContext);
|
|
341
|
-
return ctx?.customRenderers ?? EMPTY_CUSTOM;
|
|
342
|
-
}
|
|
343
|
-
function useBuiltinRenderers() {
|
|
344
|
-
const ctx = useContext2(ReportContext);
|
|
345
|
-
return ctx?.renderers ?? EMPTY_RENDERERS2;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// src/components/doc/DocCode.tsx
|
|
349
|
-
import { Fragment, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
350
|
-
function CodeFigure({ label, children }) {
|
|
351
|
-
return /* @__PURE__ */ jsxs3("figure", { className: "my-3", children: [
|
|
352
|
-
label ? /* @__PURE__ */ jsx6("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: label }) : null,
|
|
353
|
-
/* @__PURE__ */ jsx6("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3", children })
|
|
354
|
-
] });
|
|
355
|
-
}
|
|
356
|
-
function DocCode({ entry }) {
|
|
357
|
-
const renderers = useBuiltinRenderers();
|
|
358
|
-
if (renderers.code) {
|
|
359
|
-
return /* @__PURE__ */ jsx6(Fragment, { children: renderers.code(entry) });
|
|
360
|
-
}
|
|
361
|
-
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 }) });
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// src/components/doc/DocTable.tsx
|
|
365
|
-
import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
366
|
-
function DocTable({ entry }) {
|
|
367
|
-
return /* @__PURE__ */ jsxs4("figure", { className: "my-3", children: [
|
|
368
|
-
entry.label ? /* @__PURE__ */ jsx7("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
|
|
369
|
-
/* @__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: [
|
|
370
|
-
/* @__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)) }) }),
|
|
371
|
-
/* @__PURE__ */ jsx7("tbody", { children: entry.rows.map((row, i) => /* @__PURE__ */ jsx7("tr", { children: row.map((cell, j) => /* @__PURE__ */ jsx7("td", { className: "border-b border-border px-3 py-2 text-foreground", children: cell }, j)) }, i)) })
|
|
372
|
-
] }) })
|
|
373
|
-
] });
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// src/components/doc/DocLink.tsx
|
|
377
|
-
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
378
|
-
function DocLink({ entry }) {
|
|
379
|
-
return /* @__PURE__ */ jsx8(
|
|
380
|
-
"a",
|
|
381
|
-
{
|
|
382
|
-
className: "text-sm font-medium text-primary underline underline-offset-2 hover:no-underline",
|
|
383
|
-
href: entry.url,
|
|
384
|
-
rel: "noreferrer noopener",
|
|
385
|
-
target: "_blank",
|
|
386
|
-
children: entry.label
|
|
387
|
-
}
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// src/lib/markdown.ts
|
|
392
|
-
import { marked } from "marked";
|
|
393
|
-
|
|
394
|
-
// src/lib/url.ts
|
|
395
|
-
import { safeUrl, safeImageUrl, isLocalFsPath } from "executable-stories-core/utils/url";
|
|
396
|
-
|
|
397
|
-
// src/lib/markdown.ts
|
|
398
|
-
function safeMarkdownHtml(markdown) {
|
|
399
|
-
const raw = marked.parse(markdown, { async: false });
|
|
400
|
-
return raw.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<\/?(?:iframe|object|embed|form|base)\b[^>]*>/gi, "").replace(/\son[a-z]+\s*=\s*"[^"]*"/gi, "").replace(/\son[a-z]+\s*=\s*'[^']*'/gi, "").replace(/\son[a-z]+\s*=\s*[^\s>]+/gi, "").replace(/(href|src)\s*=\s*"([^"]*)"/gi, (_m, attr, val) => `${attr}="${neutralizeUrl(val)}"`).replace(/(href|src)\s*=\s*'([^']*)'/gi, (_m, attr, val) => `${attr}='${neutralizeUrl(val)}'`);
|
|
401
|
-
}
|
|
402
|
-
function dedent(text) {
|
|
403
|
-
const lines = text.replace(/\t/g, " ").split("\n");
|
|
404
|
-
const indents = lines.filter((line) => line.trim().length > 0).map((line) => line.length - line.trimStart().length);
|
|
405
|
-
const shortest = indents.length > 0 ? Math.min(...indents) : 0;
|
|
406
|
-
return lines.map((line) => line.slice(shortest)).join("\n").trim();
|
|
280
|
+
function dedent(text) {
|
|
281
|
+
const lines = text.replace(/\t/g, " ").split("\n");
|
|
282
|
+
const indents = lines.filter((line) => line.trim().length > 0).map((line) => line.length - line.trimStart().length);
|
|
283
|
+
const shortest = indents.length > 0 ? Math.min(...indents) : 0;
|
|
284
|
+
return lines.map((line) => line.slice(shortest)).join("\n").trim();
|
|
407
285
|
}
|
|
408
286
|
function neutralizeUrl(value) {
|
|
409
287
|
return safeUrl(value) ?? "#";
|
|
410
288
|
}
|
|
411
289
|
|
|
412
290
|
// src/components/doc/DocSection.tsx
|
|
413
|
-
import { Fragment as Fragment2, jsx as
|
|
291
|
+
import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
414
292
|
function DocSection({ entry }) {
|
|
415
293
|
const renderers = useBuiltinRenderers();
|
|
416
294
|
if (renderers.section) {
|
|
417
|
-
return /* @__PURE__ */
|
|
295
|
+
return /* @__PURE__ */ jsx10(Fragment2, { children: renderers.section(entry) });
|
|
418
296
|
}
|
|
419
297
|
const html = safeMarkdownHtml(entry.markdown);
|
|
420
|
-
return /* @__PURE__ */
|
|
421
|
-
entry.title ? /* @__PURE__ */
|
|
422
|
-
/* @__PURE__ */
|
|
298
|
+
return /* @__PURE__ */ jsxs4("section", { className: "my-3 text-sm", "aria-label": entry.title, children: [
|
|
299
|
+
entry.title ? /* @__PURE__ */ jsx10("h4", { className: "mb-1 font-semibold text-foreground", children: entry.title }) : null,
|
|
300
|
+
/* @__PURE__ */ jsx10(
|
|
423
301
|
"div",
|
|
424
302
|
{
|
|
425
303
|
className: "es-doc-prose prose prose-sm max-w-none",
|
|
@@ -430,11 +308,11 @@ function DocSection({ entry }) {
|
|
|
430
308
|
}
|
|
431
309
|
|
|
432
310
|
// src/components/doc/DocMermaid.tsx
|
|
433
|
-
import { Fragment as Fragment3, jsx as
|
|
311
|
+
import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
434
312
|
function MermaidSource({ entry, ref }) {
|
|
435
|
-
return /* @__PURE__ */
|
|
436
|
-
entry.title ? /* @__PURE__ */
|
|
437
|
-
/* @__PURE__ */
|
|
313
|
+
return /* @__PURE__ */ jsxs5("figure", { ref, className: "my-3", "aria-label": entry.title ?? "Diagram", children: [
|
|
314
|
+
entry.title ? /* @__PURE__ */ jsx11("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
|
|
315
|
+
/* @__PURE__ */ jsx11(
|
|
438
316
|
"pre",
|
|
439
317
|
{
|
|
440
318
|
"data-mermaid": true,
|
|
@@ -448,24 +326,24 @@ function MermaidSource({ entry, ref }) {
|
|
|
448
326
|
function DocMermaid({ entry }) {
|
|
449
327
|
const renderers = useBuiltinRenderers();
|
|
450
328
|
if (renderers.mermaid) {
|
|
451
|
-
return /* @__PURE__ */
|
|
329
|
+
return /* @__PURE__ */ jsx11(Fragment3, { children: renderers.mermaid(entry) });
|
|
452
330
|
}
|
|
453
|
-
return /* @__PURE__ */
|
|
331
|
+
return /* @__PURE__ */ jsx11(MermaidSource, { entry });
|
|
454
332
|
}
|
|
455
333
|
|
|
456
334
|
// src/components/doc/DocScreenshot.tsx
|
|
457
|
-
import { jsx as
|
|
335
|
+
import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
458
336
|
function DocScreenshot({ entry }) {
|
|
459
337
|
const src = isLocalFsPath(entry.path) ? void 0 : safeImageUrl(entry.path);
|
|
460
338
|
if (!src) {
|
|
461
|
-
return /* @__PURE__ */
|
|
462
|
-
/* @__PURE__ */
|
|
463
|
-
/* @__PURE__ */
|
|
464
|
-
entry.alt ? /* @__PURE__ */
|
|
339
|
+
return /* @__PURE__ */ jsxs6("figure", { className: "my-3 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground", children: [
|
|
340
|
+
/* @__PURE__ */ jsx12("div", { className: "font-medium", children: "Screenshot unavailable" }),
|
|
341
|
+
/* @__PURE__ */ jsx12("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
|
|
342
|
+
entry.alt ? /* @__PURE__ */ jsx12("figcaption", { className: "mt-1.5 text-xs", children: entry.alt }) : null
|
|
465
343
|
] });
|
|
466
344
|
}
|
|
467
|
-
return /* @__PURE__ */
|
|
468
|
-
/* @__PURE__ */
|
|
345
|
+
return /* @__PURE__ */ jsxs6("figure", { className: "my-3", children: [
|
|
346
|
+
/* @__PURE__ */ jsx12(
|
|
469
347
|
"img",
|
|
470
348
|
{
|
|
471
349
|
src,
|
|
@@ -474,24 +352,24 @@ function DocScreenshot({ entry }) {
|
|
|
474
352
|
className: "max-w-full rounded-md border border-border"
|
|
475
353
|
}
|
|
476
354
|
),
|
|
477
|
-
entry.alt ? /* @__PURE__ */
|
|
355
|
+
entry.alt ? /* @__PURE__ */ jsx12("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: entry.alt }) : null
|
|
478
356
|
] });
|
|
479
357
|
}
|
|
480
358
|
|
|
481
359
|
// src/components/doc/DocVideo.tsx
|
|
482
|
-
import { jsx as
|
|
360
|
+
import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
483
361
|
function DocVideo({ entry }) {
|
|
484
362
|
if (isLocalFsPath(entry.path)) {
|
|
485
|
-
return /* @__PURE__ */
|
|
486
|
-
/* @__PURE__ */
|
|
487
|
-
/* @__PURE__ */
|
|
488
|
-
entry.caption ? /* @__PURE__ */
|
|
363
|
+
return /* @__PURE__ */ jsxs7("figure", { className: "my-3 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground", children: [
|
|
364
|
+
/* @__PURE__ */ jsx13("div", { className: "font-medium", children: "Video unavailable" }),
|
|
365
|
+
/* @__PURE__ */ jsx13("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
|
|
366
|
+
entry.caption ? /* @__PURE__ */ jsx13("figcaption", { className: "mt-1.5 text-xs", children: entry.caption }) : null
|
|
489
367
|
] });
|
|
490
368
|
}
|
|
491
369
|
const src = safeUrl(entry.path);
|
|
492
370
|
const poster = safeUrl(entry.poster);
|
|
493
|
-
return /* @__PURE__ */
|
|
494
|
-
/* @__PURE__ */
|
|
371
|
+
return /* @__PURE__ */ jsxs7("figure", { className: "my-3", children: [
|
|
372
|
+
/* @__PURE__ */ jsx13(
|
|
495
373
|
"video",
|
|
496
374
|
{
|
|
497
375
|
controls: true,
|
|
@@ -501,21 +379,21 @@ function DocVideo({ entry }) {
|
|
|
501
379
|
className: "max-w-full rounded-md border border-border"
|
|
502
380
|
}
|
|
503
381
|
),
|
|
504
|
-
entry.caption ? /* @__PURE__ */
|
|
382
|
+
entry.caption ? /* @__PURE__ */ jsx13("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: entry.caption }) : null
|
|
505
383
|
] });
|
|
506
384
|
}
|
|
507
385
|
|
|
508
386
|
// src/components/doc/DocHtml.tsx
|
|
509
|
-
import { jsx as
|
|
387
|
+
import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
510
388
|
function DocHtml({ entry }) {
|
|
511
389
|
const height = typeof entry.height === "number" ? `${entry.height}px` : entry.height ?? "400px";
|
|
512
390
|
const title = entry.title ?? "Embedded HTML";
|
|
513
391
|
const safeHref = safeUrl(entry.url ?? entry.path);
|
|
514
392
|
const frameProps = entry.content !== void 0 ? { srcDoc: entry.content } : { src: safeHref ?? "" };
|
|
515
|
-
return /* @__PURE__ */
|
|
516
|
-
/* @__PURE__ */
|
|
517
|
-
/* @__PURE__ */
|
|
518
|
-
safeHref ? /* @__PURE__ */
|
|
393
|
+
return /* @__PURE__ */ jsxs8("figure", { className: "my-3 overflow-hidden rounded-md border border-border", children: [
|
|
394
|
+
/* @__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: [
|
|
395
|
+
/* @__PURE__ */ jsx14("span", { children: entry.title ?? "HTML" }),
|
|
396
|
+
safeHref ? /* @__PURE__ */ jsx14(
|
|
519
397
|
"a",
|
|
520
398
|
{
|
|
521
399
|
href: safeHref,
|
|
@@ -528,7 +406,7 @@ function DocHtml({ entry }) {
|
|
|
528
406
|
}
|
|
529
407
|
) : null
|
|
530
408
|
] }),
|
|
531
|
-
/* @__PURE__ */
|
|
409
|
+
/* @__PURE__ */ jsx14(
|
|
532
410
|
"iframe",
|
|
533
411
|
{
|
|
534
412
|
...frameProps,
|
|
@@ -543,7 +421,7 @@ function DocHtml({ entry }) {
|
|
|
543
421
|
}
|
|
544
422
|
|
|
545
423
|
// src/components/doc/DocState.tsx
|
|
546
|
-
import { jsx as
|
|
424
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
547
425
|
function DocState({ entry }) {
|
|
548
426
|
let json;
|
|
549
427
|
try {
|
|
@@ -551,23 +429,23 @@ function DocState({ entry }) {
|
|
|
551
429
|
} catch {
|
|
552
430
|
json = String(entry.value);
|
|
553
431
|
}
|
|
554
|
-
return /* @__PURE__ */
|
|
432
|
+
return /* @__PURE__ */ jsx15(CodeFigure, { label: entry.label ?? "State", children: /* @__PURE__ */ jsx15("code", { className: "font-mono text-xs language-json", children: json }) });
|
|
555
433
|
}
|
|
556
434
|
|
|
557
435
|
// src/components/doc/DocCustom.tsx
|
|
558
|
-
import { Fragment as Fragment4, jsx as
|
|
436
|
+
import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
559
437
|
function DocCustom({ entry }) {
|
|
560
438
|
const renderers = useCustomRenderers();
|
|
561
439
|
const renderer = renderers[entry.type];
|
|
562
440
|
if (renderer) {
|
|
563
|
-
return /* @__PURE__ */
|
|
441
|
+
return /* @__PURE__ */ jsx16(Fragment4, { children: renderer(entry) });
|
|
564
442
|
}
|
|
565
|
-
return /* @__PURE__ */
|
|
566
|
-
/* @__PURE__ */
|
|
567
|
-
/* @__PURE__ */
|
|
443
|
+
return /* @__PURE__ */ jsxs9("div", { className: "my-2", "data-type": entry.type, children: [
|
|
444
|
+
/* @__PURE__ */ jsx16("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: entry.type }),
|
|
445
|
+
/* @__PURE__ */ jsx16("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3 font-mono text-xs text-foreground", children: safeStringify(entry.data) })
|
|
568
446
|
] });
|
|
569
447
|
}
|
|
570
|
-
function
|
|
448
|
+
function safeStringify(value) {
|
|
571
449
|
try {
|
|
572
450
|
return JSON.stringify(value, null, 2);
|
|
573
451
|
} catch {
|
|
@@ -576,68 +454,47 @@ function safeStringify2(value) {
|
|
|
576
454
|
}
|
|
577
455
|
|
|
578
456
|
// src/components/doc/DocEntry.tsx
|
|
579
|
-
import { jsx as
|
|
457
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
580
458
|
function DocEntry({ entry }) {
|
|
581
459
|
switch (entry.kind) {
|
|
582
460
|
case "note":
|
|
583
|
-
return /* @__PURE__ */
|
|
461
|
+
return /* @__PURE__ */ jsx17(DocNote, { entry });
|
|
584
462
|
case "tag":
|
|
585
|
-
return /* @__PURE__ */
|
|
463
|
+
return /* @__PURE__ */ jsx17(DocTag, { entry });
|
|
586
464
|
case "kv":
|
|
587
|
-
return /* @__PURE__ */
|
|
465
|
+
return /* @__PURE__ */ jsx17(DocKv, { entry });
|
|
588
466
|
case "code":
|
|
589
|
-
return /* @__PURE__ */
|
|
467
|
+
return /* @__PURE__ */ jsx17(DocCode, { entry });
|
|
590
468
|
case "table":
|
|
591
|
-
return /* @__PURE__ */
|
|
469
|
+
return /* @__PURE__ */ jsx17(DocTable, { entry });
|
|
592
470
|
case "link":
|
|
593
|
-
return /* @__PURE__ */
|
|
471
|
+
return /* @__PURE__ */ jsx17(DocLink, { entry });
|
|
594
472
|
case "section":
|
|
595
|
-
return /* @__PURE__ */
|
|
473
|
+
return /* @__PURE__ */ jsx17(DocSection, { entry });
|
|
596
474
|
case "mermaid":
|
|
597
|
-
return /* @__PURE__ */
|
|
475
|
+
return /* @__PURE__ */ jsx17(DocMermaid, { entry });
|
|
598
476
|
case "screenshot":
|
|
599
|
-
return /* @__PURE__ */
|
|
477
|
+
return /* @__PURE__ */ jsx17(DocScreenshot, { entry });
|
|
600
478
|
case "video":
|
|
601
|
-
return /* @__PURE__ */
|
|
479
|
+
return /* @__PURE__ */ jsx17(DocVideo, { entry });
|
|
602
480
|
case "html":
|
|
603
|
-
return /* @__PURE__ */
|
|
481
|
+
return /* @__PURE__ */ jsx17(DocHtml, { entry });
|
|
604
482
|
case "state":
|
|
605
|
-
return /* @__PURE__ */
|
|
483
|
+
return /* @__PURE__ */ jsx17(DocState, { entry });
|
|
606
484
|
case "custom":
|
|
607
|
-
return /* @__PURE__ */
|
|
485
|
+
return /* @__PURE__ */ jsx17(DocCustom, { entry });
|
|
608
486
|
}
|
|
609
487
|
}
|
|
610
488
|
|
|
611
489
|
// src/components/ReportDocEntries.tsx
|
|
612
|
-
import { Fragment as Fragment5, jsx as
|
|
490
|
+
import { Fragment as Fragment5, jsx as jsx18 } from "react/jsx-runtime";
|
|
613
491
|
function ReportDocEntries({ entries }) {
|
|
614
492
|
if (entries.length === 0) return null;
|
|
615
|
-
return /* @__PURE__ */
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
// src/lib/step-params.tsx
|
|
619
|
-
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
620
|
-
var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.-])\d+(?:\.\d+)?(?![\w.-])/g;
|
|
621
|
-
function highlightStepParams(text) {
|
|
622
|
-
const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
|
|
623
|
-
if (matches.length === 0) return text;
|
|
624
|
-
const nodes = [];
|
|
625
|
-
let lastIndex = 0;
|
|
626
|
-
matches.forEach((match, i) => {
|
|
627
|
-
const start = match.index;
|
|
628
|
-
const end = start + match[0].length;
|
|
629
|
-
if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
|
|
630
|
-
nodes.push(
|
|
631
|
-
/* @__PURE__ */ jsx18("span", { className: "font-medium text-step-param italic", children: match[0] }, i)
|
|
632
|
-
);
|
|
633
|
-
lastIndex = end;
|
|
634
|
-
});
|
|
635
|
-
if (lastIndex < text.length) nodes.push(text.slice(lastIndex));
|
|
636
|
-
return nodes;
|
|
493
|
+
return /* @__PURE__ */ jsx18(Fragment5, { children: entries.map((entry, i) => /* @__PURE__ */ jsx18(DocEntry, { entry }, i)) });
|
|
637
494
|
}
|
|
638
495
|
|
|
639
496
|
// src/components/ReportSteps.tsx
|
|
640
|
-
import { jsx as jsx19, jsxs as
|
|
497
|
+
import { jsx as jsx19, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
641
498
|
var STEP_GLYPH = {
|
|
642
499
|
passed: "\u2713",
|
|
643
500
|
failed: "\u2717",
|
|
@@ -652,20 +509,46 @@ var STEP_GLYPH_COLOR = {
|
|
|
652
509
|
};
|
|
653
510
|
function ReportSteps({ scenario }) {
|
|
654
511
|
if (scenario.steps.length === 0) return null;
|
|
655
|
-
|
|
512
|
+
const unasserted = new Set(
|
|
513
|
+
assertionState(scenario.steps) === "unasserted" ? assertiveSteps(scenario.steps).map((step) => step.id) : []
|
|
514
|
+
);
|
|
515
|
+
return /* @__PURE__ */ jsx19("ol", { "data-slot": "steps", className: "flex flex-col", children: scenario.steps.map((step) => /* @__PURE__ */ jsx19(
|
|
516
|
+
ReportStepItem,
|
|
517
|
+
{
|
|
518
|
+
step,
|
|
519
|
+
unasserted: unasserted.has(step.id)
|
|
520
|
+
},
|
|
521
|
+
step.id
|
|
522
|
+
)) });
|
|
656
523
|
}
|
|
657
|
-
function ReportStepItem({
|
|
524
|
+
function ReportStepItem({
|
|
525
|
+
step,
|
|
526
|
+
unasserted = false
|
|
527
|
+
}) {
|
|
658
528
|
const kw = step.keyword.toLowerCase();
|
|
659
529
|
const continuation = kw === "and" || kw === "but";
|
|
660
|
-
return /* @__PURE__ */
|
|
530
|
+
return /* @__PURE__ */ jsxs10(
|
|
661
531
|
"li",
|
|
662
532
|
{
|
|
663
533
|
id: step.id,
|
|
664
534
|
"data-status": step.status,
|
|
665
|
-
className: cn(
|
|
535
|
+
className: cn(
|
|
536
|
+
"py-1.5 text-[0.8125rem] leading-normal",
|
|
537
|
+
continuation && "pl-5"
|
|
538
|
+
),
|
|
666
539
|
children: [
|
|
667
|
-
/* @__PURE__ */
|
|
668
|
-
/* @__PURE__ */ jsx19(
|
|
540
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-baseline gap-2", children: [
|
|
541
|
+
/* @__PURE__ */ jsx19(
|
|
542
|
+
"span",
|
|
543
|
+
{
|
|
544
|
+
"aria-hidden": true,
|
|
545
|
+
className: cn(
|
|
546
|
+
"w-4 shrink-0 text-center text-xs",
|
|
547
|
+
STEP_GLYPH_COLOR[step.status]
|
|
548
|
+
),
|
|
549
|
+
children: STEP_GLYPH[step.status] ?? "\u25CB"
|
|
550
|
+
}
|
|
551
|
+
),
|
|
669
552
|
/* @__PURE__ */ jsx19(
|
|
670
553
|
"span",
|
|
671
554
|
{
|
|
@@ -677,6 +560,7 @@ function ReportStepItem({ step }) {
|
|
|
677
560
|
}
|
|
678
561
|
),
|
|
679
562
|
/* @__PURE__ */ jsx19("span", { className: "flex-1 text-foreground", children: highlightStepParams(step.text) }),
|
|
563
|
+
unasserted ? /* @__PURE__ */ jsx19("span", { className: "shrink-0 rounded-full border border-pend-border bg-pend-bg px-2 py-0.5 text-[0.6875rem] font-medium text-pend", children: "No assertion" }) : null,
|
|
680
564
|
step.durationMs > 0 ? /* @__PURE__ */ jsx19("span", { className: "shrink-0 whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(step.durationMs) }) : null
|
|
681
565
|
] }),
|
|
682
566
|
step.errorMessage ? /* @__PURE__ */ jsx19(
|
|
@@ -697,7 +581,7 @@ function ReportStepItem({ step }) {
|
|
|
697
581
|
import { extractStoryboardFrames } from "executable-stories-core/storyboard";
|
|
698
582
|
import { summarizeStateChanges } from "executable-stories-core/state-diff";
|
|
699
583
|
import { isLocalFsPath as isLocalFsPath2, safeImageUrl as safeImageUrl2 } from "executable-stories-core/utils/url";
|
|
700
|
-
import { jsx as jsx20, jsxs as
|
|
584
|
+
import { jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
701
585
|
var FRAME_GLYPH = {
|
|
702
586
|
passed: "\u2713",
|
|
703
587
|
failed: "\u2717",
|
|
@@ -713,7 +597,7 @@ var FRAME_GLYPH_COLOR = {
|
|
|
713
597
|
function ReportStoryboard({ scenario }) {
|
|
714
598
|
const frames = extractStoryboardFrames(scenario);
|
|
715
599
|
if (frames.length < 2) return null;
|
|
716
|
-
return /* @__PURE__ */
|
|
600
|
+
return /* @__PURE__ */ jsxs11("section", { "data-slot": "storyboard", "aria-label": "Storyboard", className: "mb-3", children: [
|
|
717
601
|
/* @__PURE__ */ jsx20("h4", { className: "mb-1.5 text-xs font-semibold tracking-wide text-muted-foreground uppercase", children: "Storyboard" }),
|
|
718
602
|
/* @__PURE__ */ jsx20("ol", { className: "flex items-start gap-3 overflow-x-auto pb-2", children: frames.map((frame) => /* @__PURE__ */ jsx20(StoryboardFrameItem, { frame }, frame.stepId)) })
|
|
719
603
|
] });
|
|
@@ -721,9 +605,9 @@ function ReportStoryboard({ scenario }) {
|
|
|
721
605
|
function StoryboardFrameItem({ frame }) {
|
|
722
606
|
const label = `${frame.keyword} ${frame.text}`;
|
|
723
607
|
const hasStates = frame.states.length > 0;
|
|
724
|
-
const caption = /* @__PURE__ */
|
|
608
|
+
const caption = /* @__PURE__ */ jsxs11("p", { className: "mt-1 flex items-baseline gap-1.5 text-[0.6875rem] leading-snug", children: [
|
|
725
609
|
/* @__PURE__ */ jsx20("span", { "aria-hidden": true, className: cn("shrink-0", FRAME_GLYPH_COLOR[frame.status]), children: FRAME_GLYPH[frame.status] ?? "\u25CB" }),
|
|
726
|
-
/* @__PURE__ */
|
|
610
|
+
/* @__PURE__ */ jsxs11("span", { className: "min-w-0", children: [
|
|
727
611
|
/* @__PURE__ */ jsx20("span", { className: "font-mono font-semibold text-keyword", children: frame.keyword }),
|
|
728
612
|
" ",
|
|
729
613
|
/* @__PURE__ */ jsx20("span", { className: "text-muted-foreground", children: frame.screenshot?.alt ?? frame.text })
|
|
@@ -732,7 +616,7 @@ function StoryboardFrameItem({ frame }) {
|
|
|
732
616
|
return (
|
|
733
617
|
// State cards need room for diff rows; screenshot-only frames keep the
|
|
734
618
|
// original narrow film-cell width (and DOM) unchanged.
|
|
735
|
-
/* @__PURE__ */
|
|
619
|
+
/* @__PURE__ */ jsxs11("li", { className: cn("shrink-0", hasStates ? "w-64" : "w-40"), children: [
|
|
736
620
|
frame.screenshot ? /* @__PURE__ */ jsx20(FrameScreenshot, { frame, label }) : null,
|
|
737
621
|
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,
|
|
738
622
|
frame.screenshot ? caption : (
|
|
@@ -779,8 +663,8 @@ function prettyJson(value) {
|
|
|
779
663
|
function StateCardView({ card }) {
|
|
780
664
|
const json = prettyJson(card.value);
|
|
781
665
|
const firstCapture = card.changes === void 0;
|
|
782
|
-
return /* @__PURE__ */
|
|
783
|
-
/* @__PURE__ */
|
|
666
|
+
return /* @__PURE__ */ jsxs11("div", { "data-slot": "state-card", className: "rounded-md border border-border bg-card p-2", children: [
|
|
667
|
+
/* @__PURE__ */ jsxs11("p", { className: "flex flex-wrap items-baseline gap-1.5", children: [
|
|
784
668
|
/* @__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" }),
|
|
785
669
|
firstCapture ? /* @__PURE__ */ jsx20("span", { className: "text-[0.625rem] text-muted-foreground italic", children: "first capture" }) : null
|
|
786
670
|
] }),
|
|
@@ -795,7 +679,7 @@ function StateCardView({ card }) {
|
|
|
795
679
|
},
|
|
796
680
|
i
|
|
797
681
|
)) }) : /* @__PURE__ */ jsx20("p", { className: "mt-1.5 text-[0.625rem] text-muted-foreground", children: "No changes" })),
|
|
798
|
-
/* @__PURE__ */
|
|
682
|
+
/* @__PURE__ */ jsxs11(
|
|
799
683
|
"details",
|
|
800
684
|
{
|
|
801
685
|
open: firstCapture && json.length <= 240,
|
|
@@ -810,7 +694,7 @@ function StateCardView({ card }) {
|
|
|
810
694
|
}
|
|
811
695
|
|
|
812
696
|
// src/components/ReportAttachments.tsx
|
|
813
|
-
import { Fragment as Fragment6, jsx as jsx21, jsxs as
|
|
697
|
+
import { Fragment as Fragment6, jsx as jsx21, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
814
698
|
function ReportAttachments({ attachments }) {
|
|
815
699
|
if (attachments.length === 0) return null;
|
|
816
700
|
return (
|
|
@@ -819,7 +703,7 @@ function ReportAttachments({ attachments }) {
|
|
|
819
703
|
/* @__PURE__ */ jsx21("div", { role: "group", "aria-label": "Attachments", className: "mt-3 flex flex-col gap-2", children: attachments.map((att, i) => {
|
|
820
704
|
const dataUri = att.contentEncoding === "BASE64" ? `data:${att.mediaType};base64,${att.body}` : `data:${att.mediaType},${encodeURIComponent(att.body)}`;
|
|
821
705
|
const isImage = att.mediaType.startsWith("image/");
|
|
822
|
-
return /* @__PURE__ */ jsx21("figure", { className: "m-0", children: isImage ? /* @__PURE__ */
|
|
706
|
+
return /* @__PURE__ */ jsx21("figure", { className: "m-0", children: isImage ? /* @__PURE__ */ jsxs12(Fragment6, { children: [
|
|
823
707
|
/* @__PURE__ */ jsx21(
|
|
824
708
|
"img",
|
|
825
709
|
{
|
|
@@ -830,7 +714,7 @@ function ReportAttachments({ attachments }) {
|
|
|
830
714
|
}
|
|
831
715
|
),
|
|
832
716
|
/* @__PURE__ */ jsx21("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: att.name })
|
|
833
|
-
] }) : /* @__PURE__ */
|
|
717
|
+
] }) : /* @__PURE__ */ jsxs12(
|
|
834
718
|
"a",
|
|
835
719
|
{
|
|
836
720
|
href: dataUri,
|
|
@@ -839,7 +723,7 @@ function ReportAttachments({ attachments }) {
|
|
|
839
723
|
children: [
|
|
840
724
|
att.name,
|
|
841
725
|
" ",
|
|
842
|
-
/* @__PURE__ */
|
|
726
|
+
/* @__PURE__ */ jsxs12("span", { className: "text-muted-foreground", children: [
|
|
843
727
|
"(",
|
|
844
728
|
att.mediaType,
|
|
845
729
|
")"
|
|
@@ -930,7 +814,7 @@ function traceWindow(rows) {
|
|
|
930
814
|
}
|
|
931
815
|
|
|
932
816
|
// src/components/ReportTrace.tsx
|
|
933
|
-
import { jsx as jsx22, jsxs as
|
|
817
|
+
import { jsx as jsx22, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
934
818
|
var BAR_COLOR = {
|
|
935
819
|
ok: "bg-pass",
|
|
936
820
|
error: "bg-fail",
|
|
@@ -941,8 +825,8 @@ function ReportTrace({ spans }) {
|
|
|
941
825
|
const rows = toTraceRows(spans);
|
|
942
826
|
if (rows.length === 0) return null;
|
|
943
827
|
const { minStartMs, totalMs } = traceWindow(rows);
|
|
944
|
-
return /* @__PURE__ */
|
|
945
|
-
/* @__PURE__ */
|
|
828
|
+
return /* @__PURE__ */ jsxs13("details", { className: "mt-3 rounded-md border border-border bg-muted/30", children: [
|
|
829
|
+
/* @__PURE__ */ jsxs13("summary", { className: "cursor-pointer px-3 py-2 text-xs font-medium text-muted-foreground", children: [
|
|
946
830
|
"Trace (",
|
|
947
831
|
rows.length,
|
|
948
832
|
" span",
|
|
@@ -954,7 +838,7 @@ function ReportTrace({ spans }) {
|
|
|
954
838
|
/* @__PURE__ */ jsx22("ul", { className: "flex flex-col gap-0.5 px-3 pb-3 font-mono text-[0.6875rem]", children: rows.map((row, i) => {
|
|
955
839
|
const leftPct = (row.startTimeMs - minStartMs) / totalMs * 100;
|
|
956
840
|
const widthPct = Math.max(row.durationMs / totalMs * 100, 0.5);
|
|
957
|
-
return /* @__PURE__ */
|
|
841
|
+
return /* @__PURE__ */ jsxs13("li", { className: "flex items-center gap-2", children: [
|
|
958
842
|
/* @__PURE__ */ jsx22(
|
|
959
843
|
"span",
|
|
960
844
|
{
|
|
@@ -1040,7 +924,7 @@ function diffSinceLastRun(history, scenarios) {
|
|
|
1040
924
|
}
|
|
1041
925
|
|
|
1042
926
|
// src/components/ScenarioRunHistory.tsx
|
|
1043
|
-
import { Fragment as Fragment7, jsx as jsx23, jsxs as
|
|
927
|
+
import { Fragment as Fragment7, jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1044
928
|
var MAX_DOTS = 10;
|
|
1045
929
|
var DOT_COLOR = {
|
|
1046
930
|
passed: "bg-pass",
|
|
@@ -1060,7 +944,7 @@ function ScenarioRunHistory({ scenarioId }) {
|
|
|
1060
944
|
const recent = entries.slice(-MAX_DOTS);
|
|
1061
945
|
const flaky = flakinessOf(recent) === "flaky";
|
|
1062
946
|
const summary = flaky ? `${describeRunHistory(recent)} \xB7 Flaky` : describeRunHistory(recent);
|
|
1063
|
-
return /* @__PURE__ */
|
|
947
|
+
return /* @__PURE__ */ jsxs14(Fragment7, { children: [
|
|
1064
948
|
flaky ? /* @__PURE__ */ jsx23(Badge, { variant: "pending", "aria-label": "Flaky: status flips between recent runs", title: summary, children: "Flaky" }) : null,
|
|
1065
949
|
/* @__PURE__ */ jsx23(
|
|
1066
950
|
"span",
|
|
@@ -1090,24 +974,103 @@ function ScenarioRunHistory({ scenarioId }) {
|
|
|
1090
974
|
] });
|
|
1091
975
|
}
|
|
1092
976
|
|
|
1093
|
-
// src/
|
|
1094
|
-
import {
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
function
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
function readPersisted() {
|
|
1102
|
-
if (typeof localStorage === "undefined") return null;
|
|
1103
|
-
try {
|
|
1104
|
-
const raw = localStorage.getItem(STORAGE_KEY);
|
|
1105
|
-
return raw ? new Set(JSON.parse(raw)) : null;
|
|
1106
|
-
} catch {
|
|
1107
|
-
return null;
|
|
1108
|
-
}
|
|
977
|
+
// src/components/ScenarioStaleness.tsx
|
|
978
|
+
import { useEffect, useState } from "react";
|
|
979
|
+
|
|
980
|
+
// src/lib/provenance.ts
|
|
981
|
+
function reportLastRunMs(report) {
|
|
982
|
+
if (report.finishedAtMs && report.finishedAtMs > 0) return report.finishedAtMs;
|
|
983
|
+
if (report.startedAtMs && report.startedAtMs > 0) return report.startedAtMs;
|
|
984
|
+
return void 0;
|
|
1109
985
|
}
|
|
1110
|
-
function
|
|
986
|
+
function formatRelativeAge(thenMs, nowMs) {
|
|
987
|
+
const deltaMs = Math.max(0, nowMs - thenMs);
|
|
988
|
+
const minutes = Math.floor(deltaMs / 6e4);
|
|
989
|
+
if (minutes < 1) return "just now";
|
|
990
|
+
if (minutes < 60) return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
|
|
991
|
+
const hours = Math.floor(minutes / 60);
|
|
992
|
+
if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
|
|
993
|
+
const days = Math.floor(hours / 24);
|
|
994
|
+
return `${days} day${days === 1 ? "" : "s"} ago`;
|
|
995
|
+
}
|
|
996
|
+
function isReportStale(lastRunMs, staleAfterDays, nowMs) {
|
|
997
|
+
if (lastRunMs === void 0 || staleAfterDays <= 0) return false;
|
|
998
|
+
return nowMs - lastRunMs >= staleAfterDays * 864e5;
|
|
999
|
+
}
|
|
1000
|
+
var CI_DISPLAY_NAMES = {
|
|
1001
|
+
github: "GitHub Actions",
|
|
1002
|
+
gitlab: "GitLab CI",
|
|
1003
|
+
circleci: "CircleCI",
|
|
1004
|
+
jenkins: "Jenkins",
|
|
1005
|
+
azure: "Azure DevOps",
|
|
1006
|
+
buildkite: "Buildkite",
|
|
1007
|
+
travis: "Travis CI",
|
|
1008
|
+
ci: "CI"
|
|
1009
|
+
};
|
|
1010
|
+
function ciDisplayName(name) {
|
|
1011
|
+
return CI_DISPLAY_NAMES[name] ?? name;
|
|
1012
|
+
}
|
|
1013
|
+
function commitUrl(ci, sha) {
|
|
1014
|
+
const commitSha = ci?.commitSha ?? sha;
|
|
1015
|
+
if (!ci?.url || !commitSha) return void 0;
|
|
1016
|
+
const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
|
|
1017
|
+
if (github) return `${github[1]}/commit/${commitSha}`;
|
|
1018
|
+
const gitlab = ci.url.match(/^(https?:\/\/.+)\/-\/pipelines\//);
|
|
1019
|
+
if (gitlab) return `${gitlab[1]}/-/commit/${commitSha}`;
|
|
1020
|
+
return void 0;
|
|
1021
|
+
}
|
|
1022
|
+
function prUrl(ci) {
|
|
1023
|
+
if (!ci?.url || !ci.prNumber) return void 0;
|
|
1024
|
+
const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
|
|
1025
|
+
if (github) return `${github[1]}/pull/${ci.prNumber}`;
|
|
1026
|
+
return void 0;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// src/interactive/scenario-freshness-context.tsx
|
|
1030
|
+
import { createContext as createContext3, useContext as useContext4 } from "react";
|
|
1031
|
+
var ScenarioFreshnessContext = createContext3(null);
|
|
1032
|
+
var ScenarioFreshnessProvider = ScenarioFreshnessContext.Provider;
|
|
1033
|
+
function useScenarioFreshness() {
|
|
1034
|
+
return useContext4(ScenarioFreshnessContext);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// src/components/ScenarioStaleness.tsx
|
|
1038
|
+
import { jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1039
|
+
function ScenarioStaleness({ scenario }) {
|
|
1040
|
+
const freshness = useScenarioFreshness();
|
|
1041
|
+
const fixedNow = freshness?.nowMs;
|
|
1042
|
+
const [now, setNow] = useState(fixedNow);
|
|
1043
|
+
useEffect(() => {
|
|
1044
|
+
if (fixedNow === void 0) setNow(Date.now());
|
|
1045
|
+
}, [fixedNow]);
|
|
1046
|
+
if (!freshness || now === void 0) return null;
|
|
1047
|
+
const lastRunAtMs = scenario.lastRunAtMs;
|
|
1048
|
+
if (!lastRunAtMs || lastRunAtMs <= 0) return null;
|
|
1049
|
+
if (!isReportStale(lastRunAtMs, freshness.staleAfterDays, now)) return null;
|
|
1050
|
+
return /* @__PURE__ */ jsxs15(Badge, { variant: "outline", "aria-label": "Scenario freshness", children: [
|
|
1051
|
+
"Last ran ",
|
|
1052
|
+
formatRelativeAge(lastRunAtMs, now)
|
|
1053
|
+
] });
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// src/interactive/collapse-context.ts
|
|
1057
|
+
import { createContext as createContext4, useCallback, useContext as useContext5, useState as useState2 } from "react";
|
|
1058
|
+
var CollapseContext = createContext4(null);
|
|
1059
|
+
var CollapseProvider = CollapseContext.Provider;
|
|
1060
|
+
function useCollapse() {
|
|
1061
|
+
return useContext5(CollapseContext);
|
|
1062
|
+
}
|
|
1063
|
+
var STORAGE_KEY = "es-collapsed-ids";
|
|
1064
|
+
function readPersisted() {
|
|
1065
|
+
if (typeof localStorage === "undefined") return null;
|
|
1066
|
+
try {
|
|
1067
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
1068
|
+
return raw ? new Set(JSON.parse(raw)) : null;
|
|
1069
|
+
} catch {
|
|
1070
|
+
return null;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
function persist(ids) {
|
|
1111
1074
|
if (typeof localStorage === "undefined") return;
|
|
1112
1075
|
try {
|
|
1113
1076
|
localStorage.setItem(STORAGE_KEY, JSON.stringify([...ids]));
|
|
@@ -1115,7 +1078,7 @@ function persist(ids) {
|
|
|
1115
1078
|
}
|
|
1116
1079
|
}
|
|
1117
1080
|
function useCollapseState(computeDefault) {
|
|
1118
|
-
const [collapsed, setCollapsed] =
|
|
1081
|
+
const [collapsed, setCollapsed] = useState2(
|
|
1119
1082
|
() => readPersisted() ?? computeDefault?.() ?? /* @__PURE__ */ new Set()
|
|
1120
1083
|
);
|
|
1121
1084
|
const isCollapsed = useCallback((id) => collapsed.has(id), [collapsed]);
|
|
@@ -1146,12 +1109,12 @@ function useCollapseState(computeDefault) {
|
|
|
1146
1109
|
}
|
|
1147
1110
|
|
|
1148
1111
|
// src/interactive/scenario-actions.tsx
|
|
1149
|
-
import { createContext as
|
|
1112
|
+
import { createContext as createContext5, useContext as useContext6 } from "react";
|
|
1150
1113
|
import { scenarioToMarkdown as coreScenarioToMarkdown } from "executable-stories-core/utils/scenario-markdown";
|
|
1151
|
-
var ScenarioActionsContext =
|
|
1114
|
+
var ScenarioActionsContext = createContext5(null);
|
|
1152
1115
|
var ScenarioActionsProvider = ScenarioActionsContext.Provider;
|
|
1153
1116
|
function useScenarioActions() {
|
|
1154
|
-
return
|
|
1117
|
+
return useContext6(ScenarioActionsContext);
|
|
1155
1118
|
}
|
|
1156
1119
|
function scenarioToMarkdown(scenario) {
|
|
1157
1120
|
return coreScenarioToMarkdown(scenario, { variant: "compact" });
|
|
@@ -1319,6 +1282,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
|
|
|
1319
1282
|
] }),
|
|
1320
1283
|
/* @__PURE__ */ jsxs17("div", { className: "flex shrink-0 items-center gap-2", children: [
|
|
1321
1284
|
/* @__PURE__ */ jsx26(ScenarioRunHistory, { scenarioId: scenario.id }),
|
|
1285
|
+
/* @__PURE__ */ jsx26(ScenarioStaleness, { scenario }),
|
|
1322
1286
|
scenario.durationMs > 0 ? /* @__PURE__ */ jsx26("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration3(scenario.durationMs) }) : null,
|
|
1323
1287
|
meta.showPill ? /* @__PURE__ */ jsx26(Badge, { variant: meta.badgeVariant, "aria-label": `Status: ${label}`, children: label }) : /* @__PURE__ */ jsx26("span", { className: "sr-only", children: `Status: ${label}` }),
|
|
1324
1288
|
actions ? /* @__PURE__ */ jsxs17(DropdownMenu, { children: [
|
|
@@ -1490,71 +1454,74 @@ function ReportFeatureList() {
|
|
|
1490
1454
|
return /* @__PURE__ */ jsx30("div", { className: "flex flex-col gap-8", children: report.features.map((feature) => /* @__PURE__ */ jsx30(ReportFeature, { feature }, feature.id)) });
|
|
1491
1455
|
}
|
|
1492
1456
|
|
|
1493
|
-
// src/components/
|
|
1494
|
-
import {
|
|
1495
|
-
import { jsx as jsx31 } from "react/jsx-runtime";
|
|
1496
|
-
function
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
"flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12",
|
|
1503
|
-
className
|
|
1504
|
-
),
|
|
1505
|
-
...props
|
|
1506
|
-
}
|
|
1507
|
-
);
|
|
1508
|
-
}
|
|
1509
|
-
function EmptyHeader({ className, ...props }) {
|
|
1510
|
-
return /* @__PURE__ */ jsx31(
|
|
1511
|
-
"div",
|
|
1512
|
-
{
|
|
1513
|
-
"data-slot": "empty-header",
|
|
1514
|
-
className: cn(
|
|
1515
|
-
"flex max-w-sm flex-col items-center gap-2 text-center",
|
|
1516
|
-
className
|
|
1517
|
-
),
|
|
1518
|
-
...props
|
|
1519
|
-
}
|
|
1520
|
-
);
|
|
1521
|
-
}
|
|
1522
|
-
var emptyMediaVariants = cva2(
|
|
1523
|
-
"mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
1524
|
-
{
|
|
1525
|
-
variants: {
|
|
1526
|
-
variant: {
|
|
1527
|
-
default: "bg-transparent",
|
|
1528
|
-
icon: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-6"
|
|
1529
|
-
}
|
|
1530
|
-
},
|
|
1531
|
-
defaultVariants: {
|
|
1532
|
-
variant: "default"
|
|
1533
|
-
}
|
|
1457
|
+
// src/components/ReportMeta.tsx
|
|
1458
|
+
import { formatDuration as formatDuration4 } from "executable-stories-core/utils/duration";
|
|
1459
|
+
import { jsx as jsx31, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
1460
|
+
function ReportMeta() {
|
|
1461
|
+
const report = useReport();
|
|
1462
|
+
const items = [];
|
|
1463
|
+
const linkClass = "text-primary underline underline-offset-2";
|
|
1464
|
+
if (report.startedAtMs) {
|
|
1465
|
+
items.push({ label: "Started", value: new Date(report.startedAtMs).toLocaleString() });
|
|
1534
1466
|
}
|
|
1535
|
-
)
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
{
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1467
|
+
if (report.durationMs) {
|
|
1468
|
+
items.push({ label: "Duration", value: formatDuration4(report.durationMs) });
|
|
1469
|
+
}
|
|
1470
|
+
if (report.packageVersion) {
|
|
1471
|
+
items.push({ label: "Version", value: report.packageVersion });
|
|
1472
|
+
}
|
|
1473
|
+
if (report.ci?.branch) {
|
|
1474
|
+
items.push({ label: "Branch", value: report.ci.branch });
|
|
1475
|
+
}
|
|
1476
|
+
const sha = report.ci?.commitSha ?? report.gitSha;
|
|
1477
|
+
if (sha) {
|
|
1478
|
+
const url = commitUrl(report.ci, report.gitSha);
|
|
1479
|
+
const short = sha.slice(0, 8);
|
|
1480
|
+
items.push({
|
|
1481
|
+
label: "Commit",
|
|
1482
|
+
value: url ? /* @__PURE__ */ jsx31("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
1485
|
+
if (report.ci) {
|
|
1486
|
+
items.push({
|
|
1487
|
+
label: "CI",
|
|
1488
|
+
value: report.ci.url ? /* @__PURE__ */ jsxs20("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
|
|
1489
|
+
ciDisplayName(report.ci.name),
|
|
1490
|
+
report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
|
|
1491
|
+
] }) : `${ciDisplayName(report.ci.name)}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
if (report.ci?.prNumber) {
|
|
1495
|
+
const url = prUrl(report.ci);
|
|
1496
|
+
const label = `#${report.ci.prNumber}`;
|
|
1497
|
+
items.push({
|
|
1498
|
+
label: "PR",
|
|
1499
|
+
value: url ? /* @__PURE__ */ jsx31("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
if (items.length === 0) return null;
|
|
1503
|
+
return (
|
|
1504
|
+
// Run metadata is useful but secondary to "what failed" — tuck it into a
|
|
1505
|
+
// collapsed disclosure so it doesn't push the result down. Native <details>
|
|
1506
|
+
// so it works with no JS (the SSR/no-hydration report still toggles).
|
|
1507
|
+
/* @__PURE__ */ jsxs20("details", { "data-es-meta-card": true, className: "es-run-details group rounded-lg border border-border bg-card text-xs", children: [
|
|
1508
|
+
/* @__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: [
|
|
1509
|
+
/* @__PURE__ */ jsx31("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
|
|
1510
|
+
"Run details"
|
|
1511
|
+
] }),
|
|
1512
|
+
/* @__PURE__ */ jsx31("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: [
|
|
1513
|
+
/* @__PURE__ */ jsx31("dt", { className: "font-medium", children: item.label }),
|
|
1514
|
+
/* @__PURE__ */ jsx31("dd", { className: "font-mono text-foreground", children: item.value })
|
|
1515
|
+
] }, item.label)) })
|
|
1516
|
+
] })
|
|
1544
1517
|
);
|
|
1545
1518
|
}
|
|
1546
1519
|
|
|
1547
|
-
// src/components/ReportEmpty.tsx
|
|
1548
|
-
import { jsx as jsx32 } from "react/jsx-runtime";
|
|
1549
|
-
function ReportEmpty({ message }) {
|
|
1550
|
-
return /* @__PURE__ */ jsx32(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ jsx32(EmptyHeader, { children: /* @__PURE__ */ jsx32(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
1520
|
// src/components/ReportSummary.tsx
|
|
1554
|
-
import { jsx as
|
|
1521
|
+
import { jsx as jsx32, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
1555
1522
|
function ReportSummary({ className }) {
|
|
1556
1523
|
const report = useReport();
|
|
1557
|
-
return /* @__PURE__ */
|
|
1524
|
+
return /* @__PURE__ */ jsx32(
|
|
1558
1525
|
ReportSummaryView,
|
|
1559
1526
|
{
|
|
1560
1527
|
summary: report.summary,
|
|
@@ -1580,7 +1547,7 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
|
|
|
1580
1547
|
if (summary.pending > 0) {
|
|
1581
1548
|
cards.push({ status: "pending", label: "Pending", value: summary.pending });
|
|
1582
1549
|
}
|
|
1583
|
-
return /* @__PURE__ */
|
|
1550
|
+
return /* @__PURE__ */ jsx32(
|
|
1584
1551
|
"dl",
|
|
1585
1552
|
{
|
|
1586
1553
|
className: cn(
|
|
@@ -1593,17 +1560,17 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
|
|
|
1593
1560
|
"aria-label": ariaLabel,
|
|
1594
1561
|
children: cards.map((c) => {
|
|
1595
1562
|
const tone = TONE[c.status];
|
|
1596
|
-
return /* @__PURE__ */
|
|
1563
|
+
return /* @__PURE__ */ jsxs21(
|
|
1597
1564
|
"div",
|
|
1598
1565
|
{
|
|
1599
1566
|
"data-status": c.status,
|
|
1600
1567
|
className: "rounded-lg border bg-card px-4 py-3",
|
|
1601
1568
|
children: [
|
|
1602
|
-
/* @__PURE__ */
|
|
1603
|
-
/* @__PURE__ */
|
|
1569
|
+
/* @__PURE__ */ jsxs21("dt", { className: "flex items-center gap-1.5 font-mono text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: [
|
|
1570
|
+
/* @__PURE__ */ jsx32("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
|
|
1604
1571
|
c.label
|
|
1605
1572
|
] }),
|
|
1606
|
-
/* @__PURE__ */
|
|
1573
|
+
/* @__PURE__ */ jsx32("dd", { className: cn("mt-2 text-3xl font-semibold leading-none tracking-tight tabular-nums", tone.num), children: c.value })
|
|
1607
1574
|
]
|
|
1608
1575
|
},
|
|
1609
1576
|
c.status
|
|
@@ -1613,165 +1580,48 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
|
|
|
1613
1580
|
);
|
|
1614
1581
|
}
|
|
1615
1582
|
|
|
1616
|
-
// src/components/
|
|
1617
|
-
import {
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
if (lastRunMs === void 0 || staleAfterDays <= 0) return false;
|
|
1637
|
-
return nowMs - lastRunMs >= staleAfterDays * 864e5;
|
|
1638
|
-
}
|
|
1639
|
-
var CI_DISPLAY_NAMES = {
|
|
1640
|
-
github: "GitHub Actions",
|
|
1641
|
-
gitlab: "GitLab CI",
|
|
1642
|
-
circleci: "CircleCI",
|
|
1643
|
-
jenkins: "Jenkins",
|
|
1644
|
-
azure: "Azure DevOps",
|
|
1645
|
-
buildkite: "Buildkite",
|
|
1646
|
-
travis: "Travis CI",
|
|
1647
|
-
ci: "CI"
|
|
1648
|
-
};
|
|
1649
|
-
function ciDisplayName(name) {
|
|
1650
|
-
return CI_DISPLAY_NAMES[name] ?? name;
|
|
1651
|
-
}
|
|
1652
|
-
function commitUrl(ci, sha) {
|
|
1653
|
-
const commitSha = ci?.commitSha ?? sha;
|
|
1654
|
-
if (!ci?.url || !commitSha) return void 0;
|
|
1655
|
-
const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
|
|
1656
|
-
if (github) return `${github[1]}/commit/${commitSha}`;
|
|
1657
|
-
const gitlab = ci.url.match(/^(https?:\/\/.+)\/-\/pipelines\//);
|
|
1658
|
-
if (gitlab) return `${gitlab[1]}/-/commit/${commitSha}`;
|
|
1659
|
-
return void 0;
|
|
1583
|
+
// src/components/ReportSchemaError.tsx
|
|
1584
|
+
import { jsx as jsx33, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
1585
|
+
function ReportSchemaError({ error }) {
|
|
1586
|
+
return /* @__PURE__ */ jsxs22("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
|
|
1587
|
+
/* @__PURE__ */ jsx33("p", { children: /* @__PURE__ */ jsx33("strong", { children: "Report could not be displayed." }) }),
|
|
1588
|
+
/* @__PURE__ */ jsx33("p", { children: error.message }),
|
|
1589
|
+
error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs22("p", { children: [
|
|
1590
|
+
"The report bundle is newer than this version of ",
|
|
1591
|
+
/* @__PURE__ */ jsx33("code", { children: "executable-stories-react" }),
|
|
1592
|
+
". Upgrade the package, or regenerate the report with an older formatters CLI."
|
|
1593
|
+
] }) : null,
|
|
1594
|
+
error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs22("details", { children: [
|
|
1595
|
+
/* @__PURE__ */ jsxs22("summary", { children: [
|
|
1596
|
+
error.issues.length,
|
|
1597
|
+
" validation issue",
|
|
1598
|
+
error.issues.length === 1 ? "" : "s"
|
|
1599
|
+
] }),
|
|
1600
|
+
/* @__PURE__ */ jsx33("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
|
|
1601
|
+
] }) : null
|
|
1602
|
+
] });
|
|
1660
1603
|
}
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1604
|
+
|
|
1605
|
+
// src/components/ReportShell.tsx
|
|
1606
|
+
import { Fragment as Fragment10, jsx as jsx34, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
1607
|
+
function ReportErrorShell({
|
|
1608
|
+
error,
|
|
1609
|
+
className,
|
|
1610
|
+
title,
|
|
1611
|
+
dataTheme
|
|
1612
|
+
}) {
|
|
1613
|
+
return /* @__PURE__ */ jsx34("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ jsx34(ReportSchemaError, { error }) });
|
|
1666
1614
|
}
|
|
1667
1615
|
|
|
1668
|
-
// src/components/ReportMeta.tsx
|
|
1669
|
-
import { jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
1670
|
-
function ReportMeta() {
|
|
1671
|
-
const report = useReport();
|
|
1672
|
-
const items = [];
|
|
1673
|
-
const linkClass = "text-primary underline underline-offset-2";
|
|
1674
|
-
if (report.startedAtMs) {
|
|
1675
|
-
items.push({ label: "Started", value: new Date(report.startedAtMs).toLocaleString() });
|
|
1676
|
-
}
|
|
1677
|
-
if (report.durationMs) {
|
|
1678
|
-
items.push({ label: "Duration", value: formatDuration4(report.durationMs) });
|
|
1679
|
-
}
|
|
1680
|
-
if (report.packageVersion) {
|
|
1681
|
-
items.push({ label: "Version", value: report.packageVersion });
|
|
1682
|
-
}
|
|
1683
|
-
if (report.ci?.branch) {
|
|
1684
|
-
items.push({ label: "Branch", value: report.ci.branch });
|
|
1685
|
-
}
|
|
1686
|
-
const sha = report.ci?.commitSha ?? report.gitSha;
|
|
1687
|
-
if (sha) {
|
|
1688
|
-
const url = commitUrl(report.ci, report.gitSha);
|
|
1689
|
-
const short = sha.slice(0, 8);
|
|
1690
|
-
items.push({
|
|
1691
|
-
label: "Commit",
|
|
1692
|
-
value: url ? /* @__PURE__ */ jsx34("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
|
|
1693
|
-
});
|
|
1694
|
-
}
|
|
1695
|
-
if (report.ci) {
|
|
1696
|
-
items.push({
|
|
1697
|
-
label: "CI",
|
|
1698
|
-
value: report.ci.url ? /* @__PURE__ */ jsxs21("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
|
|
1699
|
-
ciDisplayName(report.ci.name),
|
|
1700
|
-
report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
|
|
1701
|
-
] }) : `${ciDisplayName(report.ci.name)}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
|
|
1702
|
-
});
|
|
1703
|
-
}
|
|
1704
|
-
if (report.ci?.prNumber) {
|
|
1705
|
-
const url = prUrl(report.ci);
|
|
1706
|
-
const label = `#${report.ci.prNumber}`;
|
|
1707
|
-
items.push({
|
|
1708
|
-
label: "PR",
|
|
1709
|
-
value: url ? /* @__PURE__ */ jsx34("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
|
|
1710
|
-
});
|
|
1711
|
-
}
|
|
1712
|
-
if (items.length === 0) return null;
|
|
1713
|
-
return (
|
|
1714
|
-
// Run metadata is useful but secondary to "what failed" — tuck it into a
|
|
1715
|
-
// collapsed disclosure so it doesn't push the result down. Native <details>
|
|
1716
|
-
// so it works with no JS (the SSR/no-hydration report still toggles).
|
|
1717
|
-
/* @__PURE__ */ jsxs21("details", { "data-es-meta-card": true, className: "es-run-details group rounded-lg border border-border bg-card text-xs", children: [
|
|
1718
|
-
/* @__PURE__ */ jsxs21("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: [
|
|
1719
|
-
/* @__PURE__ */ jsx34("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
|
|
1720
|
-
"Run details"
|
|
1721
|
-
] }),
|
|
1722
|
-
/* @__PURE__ */ jsx34("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__ */ jsxs21("div", { className: "flex items-baseline gap-1.5", children: [
|
|
1723
|
-
/* @__PURE__ */ jsx34("dt", { className: "font-medium", children: item.label }),
|
|
1724
|
-
/* @__PURE__ */ jsx34("dd", { className: "font-mono text-foreground", children: item.value })
|
|
1725
|
-
] }, item.label)) })
|
|
1726
|
-
] })
|
|
1727
|
-
);
|
|
1728
|
-
}
|
|
1729
|
-
|
|
1730
|
-
// src/components/ReportSchemaError.tsx
|
|
1731
|
-
import { jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
1732
|
-
function ReportSchemaError({ error }) {
|
|
1733
|
-
return /* @__PURE__ */ jsxs22("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
|
|
1734
|
-
/* @__PURE__ */ jsx35("p", { children: /* @__PURE__ */ jsx35("strong", { children: "Report could not be displayed." }) }),
|
|
1735
|
-
/* @__PURE__ */ jsx35("p", { children: error.message }),
|
|
1736
|
-
error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs22("p", { children: [
|
|
1737
|
-
"The report bundle is newer than this version of ",
|
|
1738
|
-
/* @__PURE__ */ jsx35("code", { children: "executable-stories-react" }),
|
|
1739
|
-
". Upgrade the package, or regenerate the report with an older formatters CLI."
|
|
1740
|
-
] }) : null,
|
|
1741
|
-
error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs22("details", { children: [
|
|
1742
|
-
/* @__PURE__ */ jsxs22("summary", { children: [
|
|
1743
|
-
error.issues.length,
|
|
1744
|
-
" validation issue",
|
|
1745
|
-
error.issues.length === 1 ? "" : "s"
|
|
1746
|
-
] }),
|
|
1747
|
-
/* @__PURE__ */ jsx35("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
|
|
1748
|
-
] }) : null
|
|
1749
|
-
] });
|
|
1750
|
-
}
|
|
1751
|
-
|
|
1752
|
-
// src/components/ReportShell.tsx
|
|
1753
|
-
import { Fragment as Fragment10, jsx as jsx36, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
1754
|
-
function ReportErrorShell({
|
|
1755
|
-
error,
|
|
1756
|
-
className,
|
|
1757
|
-
title,
|
|
1758
|
-
dataTheme
|
|
1759
|
-
}) {
|
|
1760
|
-
return /* @__PURE__ */ jsx36("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ jsx36(ReportSchemaError, { error }) });
|
|
1761
|
-
}
|
|
1762
|
-
|
|
1763
|
-
// src/interactive/ReportInteractive.tsx
|
|
1764
|
-
import { Monitor, Moon, Sun } from "lucide-react";
|
|
1765
|
-
|
|
1766
1616
|
// src/components/ui/switch.tsx
|
|
1767
1617
|
import "react";
|
|
1768
1618
|
import { Switch as SwitchPrimitive } from "@base-ui/react/switch";
|
|
1769
|
-
import { jsx as
|
|
1619
|
+
import { jsx as jsx35 } from "react/jsx-runtime";
|
|
1770
1620
|
function Switch({
|
|
1771
1621
|
className,
|
|
1772
1622
|
...props
|
|
1773
1623
|
}) {
|
|
1774
|
-
return /* @__PURE__ */
|
|
1624
|
+
return /* @__PURE__ */ jsx35(
|
|
1775
1625
|
SwitchPrimitive.Root,
|
|
1776
1626
|
{
|
|
1777
1627
|
"data-slot": "switch",
|
|
@@ -1780,7 +1630,7 @@ function Switch({
|
|
|
1780
1630
|
className
|
|
1781
1631
|
),
|
|
1782
1632
|
...props,
|
|
1783
|
-
children: /* @__PURE__ */
|
|
1633
|
+
children: /* @__PURE__ */ jsx35(
|
|
1784
1634
|
SwitchPrimitive.Thumb,
|
|
1785
1635
|
{
|
|
1786
1636
|
"data-slot": "switch-thumb",
|
|
@@ -1796,7 +1646,7 @@ import * as React5 from "react";
|
|
|
1796
1646
|
import { ToggleGroup as ToggleGroupPrimitive } from "@base-ui/react/toggle-group";
|
|
1797
1647
|
import { Toggle as TogglePrimitive } from "@base-ui/react/toggle";
|
|
1798
1648
|
import { cva as cva3 } from "class-variance-authority";
|
|
1799
|
-
import { jsx as
|
|
1649
|
+
import { jsx as jsx36 } from "react/jsx-runtime";
|
|
1800
1650
|
var toggleItemVariants = cva3(
|
|
1801
1651
|
"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",
|
|
1802
1652
|
{
|
|
@@ -1818,13 +1668,13 @@ function ToggleGroup({
|
|
|
1818
1668
|
children,
|
|
1819
1669
|
...props
|
|
1820
1670
|
}) {
|
|
1821
|
-
return /* @__PURE__ */
|
|
1671
|
+
return /* @__PURE__ */ jsx36(
|
|
1822
1672
|
ToggleGroupPrimitive,
|
|
1823
1673
|
{
|
|
1824
1674
|
"data-slot": "toggle-group",
|
|
1825
1675
|
className: cn("flex w-fit items-center gap-0.5 rounded-lg bg-muted p-0.5", className),
|
|
1826
1676
|
...props,
|
|
1827
|
-
children: /* @__PURE__ */
|
|
1677
|
+
children: /* @__PURE__ */ jsx36(ToggleGroupContext.Provider, { value: { size }, children })
|
|
1828
1678
|
}
|
|
1829
1679
|
);
|
|
1830
1680
|
}
|
|
@@ -1835,7 +1685,7 @@ function ToggleGroupItem({
|
|
|
1835
1685
|
...props
|
|
1836
1686
|
}) {
|
|
1837
1687
|
const context = React5.useContext(ToggleGroupContext);
|
|
1838
|
-
return /* @__PURE__ */
|
|
1688
|
+
return /* @__PURE__ */ jsx36(
|
|
1839
1689
|
TogglePrimitive,
|
|
1840
1690
|
{
|
|
1841
1691
|
"data-slot": "toggle-group-item",
|
|
@@ -1846,474 +1696,283 @@ function ToggleGroupItem({
|
|
|
1846
1696
|
);
|
|
1847
1697
|
}
|
|
1848
1698
|
|
|
1849
|
-
// src/
|
|
1850
|
-
import {
|
|
1851
|
-
forwardRef,
|
|
1852
|
-
useId
|
|
1853
|
-
} from "react";
|
|
1854
|
-
import { Search } from "lucide-react";
|
|
1699
|
+
// src/context/ReportRoot.tsx
|
|
1700
|
+
import { useMemo } from "react";
|
|
1855
1701
|
|
|
1856
|
-
// src/components/
|
|
1857
|
-
import "react";
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
{
|
|
1863
|
-
type,
|
|
1864
|
-
"data-slot": "input",
|
|
1865
|
-
className: cn(
|
|
1866
|
-
"h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30",
|
|
1867
|
-
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
|
|
1868
|
-
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
|
1869
|
-
className
|
|
1870
|
-
),
|
|
1871
|
-
...props
|
|
1872
|
-
}
|
|
1873
|
-
);
|
|
1702
|
+
// src/components/doc/NarrativeBlocks.tsx
|
|
1703
|
+
import { jsx as jsx37, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
1704
|
+
var FILE_TREE_TYPE = "file-tree";
|
|
1705
|
+
var DATA_MODEL_TYPE = "data-model";
|
|
1706
|
+
function isChangeKind(value) {
|
|
1707
|
+
return value === "added" || value === "modified" || value === "removed" || value === "renamed";
|
|
1874
1708
|
}
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
import { jsx as jsx40, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
1878
|
-
var ReportSearch = forwardRef(
|
|
1879
|
-
function ReportSearch2(props, ref) {
|
|
1880
|
-
const {
|
|
1881
|
-
value,
|
|
1882
|
-
onChange,
|
|
1883
|
-
matchedCount,
|
|
1884
|
-
totalCount,
|
|
1885
|
-
placeholder = "Search scenarios or tags\u2026",
|
|
1886
|
-
className
|
|
1887
|
-
} = props;
|
|
1888
|
-
const inputId = useId();
|
|
1889
|
-
const showCounts = value.trim().length > 0 && typeof matchedCount === "number" && typeof totalCount === "number";
|
|
1890
|
-
function handleChange(e) {
|
|
1891
|
-
onChange(e.target.value);
|
|
1892
|
-
}
|
|
1893
|
-
function handleKeyDown(e) {
|
|
1894
|
-
if (e.key === "Escape" && value !== "") {
|
|
1895
|
-
onChange("");
|
|
1896
|
-
e.preventDefault();
|
|
1897
|
-
}
|
|
1898
|
-
}
|
|
1899
|
-
return /* @__PURE__ */ jsxs24("div", { className: cn("es-search flex flex-col gap-1", className), children: [
|
|
1900
|
-
/* @__PURE__ */ jsx40("label", { htmlFor: inputId, className: "sr-only", children: "Search" }),
|
|
1901
|
-
/* @__PURE__ */ jsxs24("div", { className: "relative w-full sm:w-72", children: [
|
|
1902
|
-
/* @__PURE__ */ jsx40(
|
|
1903
|
-
Search,
|
|
1904
|
-
{
|
|
1905
|
-
"aria-hidden": true,
|
|
1906
|
-
className: "pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground"
|
|
1907
|
-
}
|
|
1908
|
-
),
|
|
1909
|
-
/* @__PURE__ */ jsx40(
|
|
1910
|
-
Input,
|
|
1911
|
-
{
|
|
1912
|
-
ref,
|
|
1913
|
-
id: inputId,
|
|
1914
|
-
type: "search",
|
|
1915
|
-
value,
|
|
1916
|
-
onChange: handleChange,
|
|
1917
|
-
onKeyDown: handleKeyDown,
|
|
1918
|
-
placeholder,
|
|
1919
|
-
autoComplete: "off",
|
|
1920
|
-
spellCheck: false,
|
|
1921
|
-
"aria-keyshortcuts": "/",
|
|
1922
|
-
className: "pl-8"
|
|
1923
|
-
}
|
|
1924
|
-
)
|
|
1925
|
-
] }),
|
|
1926
|
-
showCounts ? /* @__PURE__ */ jsxs24(
|
|
1927
|
-
"span",
|
|
1928
|
-
{
|
|
1929
|
-
className: "text-xs tabular-nums text-muted-foreground",
|
|
1930
|
-
"aria-live": "polite",
|
|
1931
|
-
children: [
|
|
1932
|
-
matchedCount,
|
|
1933
|
-
" of ",
|
|
1934
|
-
totalCount,
|
|
1935
|
-
" scenarios"
|
|
1936
|
-
]
|
|
1937
|
-
}
|
|
1938
|
-
) : null
|
|
1939
|
-
] });
|
|
1940
|
-
}
|
|
1941
|
-
);
|
|
1942
|
-
|
|
1943
|
-
// src/interactive/ReportFailureBanner.tsx
|
|
1944
|
-
import { useCallback as useCallback2 } from "react";
|
|
1945
|
-
|
|
1946
|
-
// src/lib/hash-state.ts
|
|
1947
|
-
var DEFAULT_URL_STATE = {
|
|
1948
|
-
query: "",
|
|
1949
|
-
status: "all",
|
|
1950
|
-
tags: [],
|
|
1951
|
-
detail: "full"
|
|
1952
|
-
};
|
|
1953
|
-
var STATUSES = ["all", "passed", "failed", "skipped", "pending"];
|
|
1954
|
-
function splitHash(hash) {
|
|
1955
|
-
const raw = hash.replace(/^#/, "");
|
|
1956
|
-
const split = raw.indexOf("?");
|
|
1957
|
-
if (split === -1) return { anchor: raw, params: new URLSearchParams() };
|
|
1958
|
-
return { anchor: raw.slice(0, split), params: new URLSearchParams(raw.slice(split + 1)) };
|
|
1709
|
+
function isRecord(value) {
|
|
1710
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1959
1711
|
}
|
|
1960
|
-
function
|
|
1961
|
-
|
|
1962
|
-
const tags = params.get("tags");
|
|
1963
|
-
return {
|
|
1964
|
-
query: params.get("q") ?? DEFAULT_URL_STATE.query,
|
|
1965
|
-
status: STATUSES.includes(status) ? status : DEFAULT_URL_STATE.status,
|
|
1966
|
-
tags: tags ? tags.split(",").filter(Boolean) : [],
|
|
1967
|
-
detail: params.get("docs") === "0" ? "minimal" : "full"
|
|
1968
|
-
};
|
|
1712
|
+
function str(value) {
|
|
1713
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
1969
1714
|
}
|
|
1970
|
-
function
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
if (state.status !== "all") params.set("status", state.status);
|
|
1974
|
-
if (state.tags.length > 0) params.set("tags", state.tags.join(","));
|
|
1975
|
-
if (state.detail === "minimal") params.set("docs", "0");
|
|
1976
|
-
return params.toString();
|
|
1715
|
+
function AuthoredMarker({ authored }) {
|
|
1716
|
+
if (authored !== "agent") return null;
|
|
1717
|
+
return /* @__PURE__ */ jsx37(Badge, { variant: "outline", className: "border-border text-[10px] text-muted-foreground", children: "AI-authored, not verified by a run" });
|
|
1977
1718
|
}
|
|
1978
|
-
function
|
|
1979
|
-
|
|
1980
|
-
|
|
1719
|
+
function BlockFrame({
|
|
1720
|
+
title,
|
|
1721
|
+
authored,
|
|
1722
|
+
children
|
|
1723
|
+
}) {
|
|
1724
|
+
return /* @__PURE__ */ jsxs24("figure", { className: "my-3", children: [
|
|
1725
|
+
title || authored === "agent" ? /* @__PURE__ */ jsxs24("figcaption", { className: "mb-1.5 flex flex-wrap items-center gap-2", children: [
|
|
1726
|
+
title ? /* @__PURE__ */ jsx37("span", { className: "text-xs font-medium text-muted-foreground", children: title }) : null,
|
|
1727
|
+
/* @__PURE__ */ jsx37(AuthoredMarker, { authored })
|
|
1728
|
+
] }) : null,
|
|
1729
|
+
children
|
|
1730
|
+
] });
|
|
1981
1731
|
}
|
|
1982
|
-
function
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1732
|
+
function ChangeBadge({ change }) {
|
|
1733
|
+
return /* @__PURE__ */ jsx37(Badge, { variant: "tag", className: "text-[10px]", children: change });
|
|
1734
|
+
}
|
|
1735
|
+
function parseFileTree(data) {
|
|
1736
|
+
if (!isRecord(data) || !Array.isArray(data.files)) return void 0;
|
|
1737
|
+
const files = [];
|
|
1738
|
+
for (const raw of data.files) {
|
|
1739
|
+
if (typeof raw === "string") {
|
|
1740
|
+
if (raw) files.push({ path: raw });
|
|
1741
|
+
continue;
|
|
1742
|
+
}
|
|
1743
|
+
if (!isRecord(raw)) continue;
|
|
1744
|
+
const path = str(raw.path);
|
|
1745
|
+
if (!path) continue;
|
|
1746
|
+
files.push({
|
|
1747
|
+
path,
|
|
1748
|
+
change: isChangeKind(raw.change) ? raw.change : void 0,
|
|
1749
|
+
note: str(raw.note)
|
|
1750
|
+
});
|
|
1991
1751
|
}
|
|
1752
|
+
if (files.length === 0) return void 0;
|
|
1753
|
+
return { title: str(data.title), authored: str(data.authored), files };
|
|
1992
1754
|
}
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1755
|
+
function buildTree(files) {
|
|
1756
|
+
const root = { name: "", children: /* @__PURE__ */ new Map() };
|
|
1757
|
+
for (const file of files) {
|
|
1758
|
+
const segments = file.path.split("/").filter(Boolean);
|
|
1759
|
+
let node = root;
|
|
1760
|
+
segments.forEach((segment, index) => {
|
|
1761
|
+
let child = node.children.get(segment);
|
|
1762
|
+
if (!child) {
|
|
1763
|
+
child = { name: segment, children: /* @__PURE__ */ new Map() };
|
|
1764
|
+
node.children.set(segment, child);
|
|
1765
|
+
}
|
|
1766
|
+
if (index === segments.length - 1) child.file = file;
|
|
1767
|
+
node = child;
|
|
1768
|
+
});
|
|
1998
1769
|
}
|
|
1999
|
-
return
|
|
1770
|
+
return root;
|
|
2000
1771
|
}
|
|
2001
|
-
function
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
block: "start"
|
|
1772
|
+
function sortedChildren(node) {
|
|
1773
|
+
return [...node.children.values()].sort((a, b) => {
|
|
1774
|
+
const aDir = a.children.size > 0;
|
|
1775
|
+
const bDir = b.children.size > 0;
|
|
1776
|
+
if (aDir !== bDir) return aDir ? -1 : 1;
|
|
1777
|
+
return a.name.localeCompare(b.name);
|
|
2008
1778
|
});
|
|
2009
|
-
if (options.updateHash !== false) {
|
|
2010
|
-
writeHash(scenarioId, splitHash(window.location.hash).params.toString());
|
|
2011
|
-
}
|
|
2012
1779
|
}
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
}, [first]);
|
|
2022
|
-
if (failures.length === 0) return null;
|
|
2023
|
-
return /* @__PURE__ */ jsxs25(
|
|
2024
|
-
"div",
|
|
2025
|
-
{
|
|
2026
|
-
className: "es-failure-banner",
|
|
2027
|
-
role: "status",
|
|
2028
|
-
"aria-live": "polite",
|
|
2029
|
-
"aria-label": "Failure summary",
|
|
2030
|
-
children: [
|
|
2031
|
-
/* @__PURE__ */ jsxs25("span", { className: "es-failure-banner-text", children: [
|
|
2032
|
-
/* @__PURE__ */ jsx41("strong", { children: failures.length }),
|
|
2033
|
-
" ",
|
|
2034
|
-
"failure",
|
|
2035
|
-
failures.length === 1 ? "" : "s"
|
|
2036
|
-
] }),
|
|
2037
|
-
/* @__PURE__ */ jsx41(
|
|
2038
|
-
"button",
|
|
1780
|
+
function TreeBranch({ node, depth }) {
|
|
1781
|
+
const children = sortedChildren(node);
|
|
1782
|
+
return /* @__PURE__ */ jsx37("ul", { className: cn("space-y-0.5", depth > 0 && "ml-3 border-l border-border/60 pl-3"), children: children.map((child) => {
|
|
1783
|
+
const isDir = child.children.size > 0;
|
|
1784
|
+
return /* @__PURE__ */ jsxs24("li", { children: [
|
|
1785
|
+
/* @__PURE__ */ jsxs24("div", { className: "flex flex-wrap items-baseline gap-2", children: [
|
|
1786
|
+
/* @__PURE__ */ jsx37(
|
|
1787
|
+
"span",
|
|
2039
1788
|
{
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
children:
|
|
1789
|
+
className: cn(
|
|
1790
|
+
"font-mono text-xs",
|
|
1791
|
+
isDir ? "text-muted-foreground" : "text-foreground"
|
|
1792
|
+
),
|
|
1793
|
+
children: isDir ? `${child.name}/` : child.name
|
|
2045
1794
|
}
|
|
2046
|
-
)
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
);
|
|
2050
|
-
}
|
|
2051
|
-
|
|
2052
|
-
// src/interactive/ReportFreshness.tsx
|
|
2053
|
-
import { useEffect, useState as useState2 } from "react";
|
|
2054
|
-
import { Fragment as Fragment11, jsx as jsx42, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
2055
|
-
function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
|
|
2056
|
-
const [now, setNow] = useState2(nowMs);
|
|
2057
|
-
useEffect(() => {
|
|
2058
|
-
if (nowMs === void 0) setNow(Date.now());
|
|
2059
|
-
}, [nowMs]);
|
|
2060
|
-
if (lastRunMs === void 0 || now === void 0) return null;
|
|
2061
|
-
const age = formatRelativeAge(lastRunMs, now);
|
|
2062
|
-
const runLink = ciUrl ? /* @__PURE__ */ jsx42(
|
|
2063
|
-
"a",
|
|
2064
|
-
{
|
|
2065
|
-
href: ciUrl,
|
|
2066
|
-
target: "_blank",
|
|
2067
|
-
rel: "noreferrer noopener",
|
|
2068
|
-
className: "underline underline-offset-2",
|
|
2069
|
-
children: "View CI run"
|
|
2070
|
-
}
|
|
2071
|
-
) : null;
|
|
2072
|
-
if (isReportStale(lastRunMs, staleAfterDays, now)) {
|
|
2073
|
-
return /* @__PURE__ */ jsxs26("div", { className: "es-stale-banner", role: "status", "aria-label": "Report freshness", children: [
|
|
2074
|
-
/* @__PURE__ */ jsxs26("span", { children: [
|
|
2075
|
-
/* @__PURE__ */ jsxs26("strong", { children: [
|
|
2076
|
-
"Last verified ",
|
|
2077
|
-
age,
|
|
2078
|
-
"."
|
|
2079
|
-
] }),
|
|
2080
|
-
" This report may no longer match the current behavior. Look for a newer run."
|
|
1795
|
+
),
|
|
1796
|
+
child.file?.change ? /* @__PURE__ */ jsx37(ChangeBadge, { change: child.file.change }) : null,
|
|
1797
|
+
child.file?.note ? /* @__PURE__ */ jsx37("span", { className: "text-xs text-muted-foreground", children: child.file.note }) : null
|
|
2081
1798
|
] }),
|
|
2082
|
-
|
|
2083
|
-
] });
|
|
1799
|
+
isDir ? /* @__PURE__ */ jsx37(TreeBranch, { node: child, depth: depth + 1 }) : null
|
|
1800
|
+
] }, child.name);
|
|
1801
|
+
}) });
|
|
1802
|
+
}
|
|
1803
|
+
function FileTreeBlock({ entry }) {
|
|
1804
|
+
const data = parseFileTree(entry.data);
|
|
1805
|
+
if (!data) return /* @__PURE__ */ jsx37(UnreadableBlock, { entry });
|
|
1806
|
+
return /* @__PURE__ */ jsx37(BlockFrame, { title: data.title ?? "Files changed", authored: data.authored, children: /* @__PURE__ */ jsx37("div", { tabIndex: 0, className: "overflow-x-auto rounded-md border border-border bg-card p-3", children: /* @__PURE__ */ jsx37(TreeBranch, { node: buildTree(data.files), depth: 0 }) }) });
|
|
1807
|
+
}
|
|
1808
|
+
function parseDataModel(data) {
|
|
1809
|
+
if (!isRecord(data) || !Array.isArray(data.fields)) return void 0;
|
|
1810
|
+
const fields = [];
|
|
1811
|
+
for (const raw of data.fields) {
|
|
1812
|
+
if (!isRecord(raw)) continue;
|
|
1813
|
+
const name = str(raw.name);
|
|
1814
|
+
if (!name) continue;
|
|
1815
|
+
fields.push({
|
|
1816
|
+
name,
|
|
1817
|
+
type: str(raw.type),
|
|
1818
|
+
note: str(raw.note),
|
|
1819
|
+
change: isChangeKind(raw.change) ? raw.change : void 0
|
|
1820
|
+
});
|
|
2084
1821
|
}
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
] });
|
|
1822
|
+
if (fields.length === 0) return void 0;
|
|
1823
|
+
return {
|
|
1824
|
+
title: str(data.title),
|
|
1825
|
+
authored: str(data.authored),
|
|
1826
|
+
name: str(data.name),
|
|
1827
|
+
fields
|
|
1828
|
+
};
|
|
2093
1829
|
}
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
/* @__PURE__ */
|
|
2103
|
-
] }
|
|
2104
|
-
|
|
2105
|
-
"
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
1830
|
+
function DataModelBlock({ entry }) {
|
|
1831
|
+
const data = parseDataModel(entry.data);
|
|
1832
|
+
if (!data) return /* @__PURE__ */ jsx37(UnreadableBlock, { entry });
|
|
1833
|
+
const hasNotes = data.fields.some((f) => f.note);
|
|
1834
|
+
return /* @__PURE__ */ jsx37(BlockFrame, { title: data.title ?? data.name ?? "Data model", authored: data.authored, children: /* @__PURE__ */ jsx37("div", { tabIndex: 0, className: "overflow-x-auto rounded-md border border-border", children: /* @__PURE__ */ jsxs24("table", { className: "w-full border-collapse text-sm", children: [
|
|
1835
|
+
/* @__PURE__ */ jsx37("thead", { children: /* @__PURE__ */ jsxs24("tr", { className: "bg-muted/50", children: [
|
|
1836
|
+
/* @__PURE__ */ jsx37("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: "Field" }),
|
|
1837
|
+
/* @__PURE__ */ jsx37("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: "Type" }),
|
|
1838
|
+
hasNotes ? /* @__PURE__ */ jsx37("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: "Note" }) : null
|
|
1839
|
+
] }) }),
|
|
1840
|
+
/* @__PURE__ */ jsx37("tbody", { children: data.fields.map((field) => /* @__PURE__ */ jsxs24("tr", { children: [
|
|
1841
|
+
/* @__PURE__ */ jsx37("td", { className: "border-b border-border px-3 py-2", children: /* @__PURE__ */ jsxs24("span", { className: "flex flex-wrap items-center gap-2", children: [
|
|
1842
|
+
/* @__PURE__ */ jsx37("span", { className: "font-mono text-xs text-foreground", children: field.name }),
|
|
1843
|
+
field.change ? /* @__PURE__ */ jsx37(ChangeBadge, { change: field.change }) : null
|
|
1844
|
+
] }) }),
|
|
1845
|
+
/* @__PURE__ */ jsx37("td", { className: "border-b border-border px-3 py-2 font-mono text-xs text-muted-foreground", children: field.type ?? "" }),
|
|
1846
|
+
hasNotes ? /* @__PURE__ */ jsx37("td", { className: "border-b border-border px-3 py-2 text-foreground", children: field.note ?? "" }) : null
|
|
1847
|
+
] }, field.name)) })
|
|
1848
|
+
] }) }) });
|
|
1849
|
+
}
|
|
1850
|
+
function UnreadableBlock({ entry }) {
|
|
1851
|
+
return /* @__PURE__ */ jsxs24("div", { className: "my-2", "data-type": entry.type, children: [
|
|
1852
|
+
/* @__PURE__ */ jsxs24("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: [
|
|
1853
|
+
entry.type,
|
|
1854
|
+
" (unrecognised shape)"
|
|
1855
|
+
] }),
|
|
1856
|
+
/* @__PURE__ */ jsx37(
|
|
1857
|
+
"pre",
|
|
1858
|
+
{
|
|
1859
|
+
tabIndex: 0,
|
|
1860
|
+
className: "overflow-x-auto rounded-md bg-muted p-3 font-mono text-xs text-foreground",
|
|
1861
|
+
children: safeStringify2(entry.data)
|
|
1862
|
+
}
|
|
1863
|
+
)
|
|
2109
1864
|
] });
|
|
2110
1865
|
}
|
|
2111
|
-
function
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
}
|
|
2115
|
-
|
|
2116
|
-
const scenarios = report.features.flatMap((f) => f.scenarios.map((s) => ({ id: s.id, title: s.title })));
|
|
2117
|
-
const delta = diffSinceLastRun(history, scenarios);
|
|
2118
|
-
if (!delta) return null;
|
|
2119
|
-
const { newlyFailing, newlyPassing, added } = delta;
|
|
2120
|
-
if (newlyFailing.length === 0 && newlyPassing.length === 0 && added.length === 0) {
|
|
2121
|
-
return /* @__PURE__ */ jsx43("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: "Since last run: no behavior changes." });
|
|
1866
|
+
function safeStringify2(value) {
|
|
1867
|
+
try {
|
|
1868
|
+
return JSON.stringify(value, null, 2);
|
|
1869
|
+
} catch {
|
|
1870
|
+
return String(value);
|
|
2122
1871
|
}
|
|
2123
|
-
return /* @__PURE__ */ jsxs27("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: [
|
|
2124
|
-
/* @__PURE__ */ jsx43("span", { className: "font-medium text-foreground", children: "Since last run:" }),
|
|
2125
|
-
newlyFailing.length > 0 ? /* @__PURE__ */ jsxs27("span", { className: "ml-2 text-fail", children: [
|
|
2126
|
-
newlyFailing.length,
|
|
2127
|
-
" newly failing (",
|
|
2128
|
-
/* @__PURE__ */ jsx43(ScenarioLinks, { scenarios: newlyFailing, tone: "text-fail" }),
|
|
2129
|
-
")"
|
|
2130
|
-
] }) : null,
|
|
2131
|
-
newlyPassing.length > 0 ? /* @__PURE__ */ jsxs27("span", { className: "ml-2 text-pass", children: [
|
|
2132
|
-
newlyPassing.length,
|
|
2133
|
-
" fixed (",
|
|
2134
|
-
/* @__PURE__ */ jsx43(ScenarioLinks, { scenarios: newlyPassing, tone: "text-pass" }),
|
|
2135
|
-
")"
|
|
2136
|
-
] }) : null,
|
|
2137
|
-
added.length > 0 ? /* @__PURE__ */ jsxs27("span", { className: "ml-2", children: [
|
|
2138
|
-
added.length,
|
|
2139
|
-
" new"
|
|
2140
|
-
] }) : null
|
|
2141
|
-
] });
|
|
2142
1872
|
}
|
|
1873
|
+
var narrativeBlockRenderers = {
|
|
1874
|
+
[FILE_TREE_TYPE]: (entry) => /* @__PURE__ */ jsx37(FileTreeBlock, { entry }),
|
|
1875
|
+
[DATA_MODEL_TYPE]: (entry) => /* @__PURE__ */ jsx37(DataModelBlock, { entry })
|
|
1876
|
+
};
|
|
2143
1877
|
|
|
2144
|
-
// src/
|
|
2145
|
-
import {
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,background-color,border-color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
2153
|
-
{
|
|
2154
|
-
variants: {
|
|
2155
|
-
variant: {
|
|
2156
|
-
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
2157
|
-
destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
|
|
2158
|
-
outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
|
2159
|
-
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
2160
|
-
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
2161
|
-
link: "text-primary underline-offset-4 hover:underline"
|
|
2162
|
-
},
|
|
2163
|
-
size: {
|
|
2164
|
-
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
2165
|
-
xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
2166
|
-
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
|
|
2167
|
-
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
2168
|
-
icon: "size-9",
|
|
2169
|
-
"icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
|
|
2170
|
-
"icon-sm": "size-8",
|
|
2171
|
-
"icon-lg": "size-10"
|
|
2172
|
-
}
|
|
2173
|
-
},
|
|
2174
|
-
defaultVariants: {
|
|
2175
|
-
variant: "default",
|
|
2176
|
-
size: "default"
|
|
2177
|
-
}
|
|
2178
|
-
}
|
|
2179
|
-
);
|
|
2180
|
-
function Button({
|
|
2181
|
-
className,
|
|
2182
|
-
variant = "default",
|
|
2183
|
-
size = "default",
|
|
2184
|
-
render,
|
|
2185
|
-
...props
|
|
1878
|
+
// src/context/ReportRoot.tsx
|
|
1879
|
+
import { jsx as jsx38 } from "react/jsx-runtime";
|
|
1880
|
+
var EMPTY_RENDERERS2 = {};
|
|
1881
|
+
function ReportRoot({
|
|
1882
|
+
report,
|
|
1883
|
+
customRenderers,
|
|
1884
|
+
renderers,
|
|
1885
|
+
children
|
|
2186
1886
|
}) {
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
1887
|
+
const value = useMemo(
|
|
1888
|
+
() => ({
|
|
1889
|
+
report,
|
|
1890
|
+
// Narrative blocks render everywhere the report does (static SSR, the
|
|
1891
|
+
// island, Astro) without every host wiring them up. A host renderer for
|
|
1892
|
+
// the same type still wins.
|
|
1893
|
+
customRenderers: { ...narrativeBlockRenderers, ...customRenderers },
|
|
1894
|
+
renderers: renderers ?? EMPTY_RENDERERS2
|
|
1895
|
+
}),
|
|
1896
|
+
[report, customRenderers, renderers]
|
|
1897
|
+
);
|
|
1898
|
+
return /* @__PURE__ */ jsx38(ReportContext.Provider, { value, children });
|
|
2198
1899
|
}
|
|
2199
1900
|
|
|
2200
|
-
// src/
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
return /* @__PURE__ */ jsx44("span", { className: "inline-flex items-center gap-1", children: parts.map((k, i) => /* @__PURE__ */ jsxs28("span", { className: "inline-flex items-center gap-1", children: [
|
|
2214
|
-
i > 0 ? /* @__PURE__ */ jsx44("span", { className: "text-xs text-muted-foreground", children: "+" }) : null,
|
|
2215
|
-
/* @__PURE__ */ jsx44("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 })
|
|
2216
|
-
] }, k)) });
|
|
1901
|
+
// src/lib/hash-state.ts
|
|
1902
|
+
var DEFAULT_URL_STATE = {
|
|
1903
|
+
query: "",
|
|
1904
|
+
status: "all",
|
|
1905
|
+
tags: [],
|
|
1906
|
+
detail: "full"
|
|
1907
|
+
};
|
|
1908
|
+
var STATUSES = ["all", "passed", "failed", "skipped", "pending"];
|
|
1909
|
+
function splitHash(hash) {
|
|
1910
|
+
const raw = hash.replace(/^#/, "");
|
|
1911
|
+
const split = raw.indexOf("?");
|
|
1912
|
+
if (split === -1) return { anchor: raw, params: new URLSearchParams() };
|
|
1913
|
+
return { anchor: raw.slice(0, split), params: new URLSearchParams(raw.slice(split + 1)) };
|
|
2217
1914
|
}
|
|
2218
|
-
function
|
|
2219
|
-
const
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
);
|
|
1915
|
+
function decodeUrlState(params) {
|
|
1916
|
+
const status = params.get("status");
|
|
1917
|
+
const tags = params.get("tags");
|
|
1918
|
+
return {
|
|
1919
|
+
query: params.get("q") ?? DEFAULT_URL_STATE.query,
|
|
1920
|
+
status: STATUSES.includes(status) ? status : DEFAULT_URL_STATE.status,
|
|
1921
|
+
tags: tags ? tags.split(",").filter(Boolean) : [],
|
|
1922
|
+
detail: params.get("docs") === "0" ? "minimal" : "full"
|
|
1923
|
+
};
|
|
1924
|
+
}
|
|
1925
|
+
function encodeUrlState(state) {
|
|
1926
|
+
const params = new URLSearchParams();
|
|
1927
|
+
if (state.query) params.set("q", state.query);
|
|
1928
|
+
if (state.status !== "all") params.set("status", state.status);
|
|
1929
|
+
if (state.tags.length > 0) params.set("tags", state.tags.join(","));
|
|
1930
|
+
if (state.detail === "minimal") params.set("docs", "0");
|
|
1931
|
+
return params.toString();
|
|
1932
|
+
}
|
|
1933
|
+
function formatHash(anchor, params) {
|
|
1934
|
+
if (params) return `#${anchor}?${params}`;
|
|
1935
|
+
return anchor ? `#${anchor}` : "";
|
|
1936
|
+
}
|
|
1937
|
+
function writeHash(anchor, params) {
|
|
1938
|
+
if (typeof window === "undefined") return;
|
|
1939
|
+
const next = formatHash(anchor, params);
|
|
1940
|
+
if (next === window.location.hash) return;
|
|
1941
|
+
const url = next || window.location.href.split("#")[0];
|
|
1942
|
+
try {
|
|
1943
|
+
window.history.replaceState(window.history.state, "", url);
|
|
1944
|
+
} catch {
|
|
1945
|
+
window.location.hash = next.slice(1);
|
|
1946
|
+
}
|
|
2243
1947
|
}
|
|
2244
1948
|
|
|
2245
|
-
// src/
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
if (target.isContentEditable) return true;
|
|
2252
|
-
return false;
|
|
1949
|
+
// src/lib/scroll.ts
|
|
1950
|
+
function prefersReducedMotion() {
|
|
1951
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
1952
|
+
return false;
|
|
1953
|
+
}
|
|
1954
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
2253
1955
|
}
|
|
2254
|
-
function
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
|
2266
|
-
switch (e.key) {
|
|
2267
|
-
case "/":
|
|
2268
|
-
h.onFocusSearch?.();
|
|
2269
|
-
e.preventDefault();
|
|
2270
|
-
return;
|
|
2271
|
-
case "?":
|
|
2272
|
-
h.onToggleHelp?.();
|
|
2273
|
-
e.preventDefault();
|
|
2274
|
-
return;
|
|
2275
|
-
case "f":
|
|
2276
|
-
h.onNextFailure?.();
|
|
2277
|
-
e.preventDefault();
|
|
2278
|
-
return;
|
|
2279
|
-
case "F":
|
|
2280
|
-
h.onPrevFailure?.();
|
|
2281
|
-
e.preventDefault();
|
|
2282
|
-
return;
|
|
2283
|
-
case "e":
|
|
2284
|
-
h.onExpandAll?.();
|
|
2285
|
-
e.preventDefault();
|
|
2286
|
-
return;
|
|
2287
|
-
case "c":
|
|
2288
|
-
h.onCollapseAll?.();
|
|
2289
|
-
e.preventDefault();
|
|
2290
|
-
return;
|
|
2291
|
-
default:
|
|
2292
|
-
return;
|
|
2293
|
-
}
|
|
2294
|
-
}
|
|
2295
|
-
window.addEventListener("keydown", onKeyDown);
|
|
2296
|
-
return () => window.removeEventListener("keydown", onKeyDown);
|
|
2297
|
-
}, []);
|
|
1956
|
+
function scrollToScenarioId(scenarioId, options = {}) {
|
|
1957
|
+
if (typeof document === "undefined") return;
|
|
1958
|
+
const el = document.getElementById(scenarioId);
|
|
1959
|
+
if (!el) return;
|
|
1960
|
+
el.scrollIntoView({
|
|
1961
|
+
behavior: prefersReducedMotion() ? "auto" : "smooth",
|
|
1962
|
+
block: "start"
|
|
1963
|
+
});
|
|
1964
|
+
if (options.updateHash !== false) {
|
|
1965
|
+
writeHash(scenarioId, splitHash(window.location.hash).params.toString());
|
|
1966
|
+
}
|
|
2298
1967
|
}
|
|
2299
1968
|
|
|
2300
|
-
// src/
|
|
2301
|
-
|
|
2302
|
-
function
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
if (!anchor || anchor === lastAnchor.current) return;
|
|
2308
|
-
lastAnchor.current = anchor;
|
|
2309
|
-
requestAnimationFrame(() => {
|
|
2310
|
-
scrollToScenarioId(anchor, { updateHash: false });
|
|
2311
|
-
});
|
|
2312
|
-
}
|
|
2313
|
-
scrollToHash();
|
|
2314
|
-
window.addEventListener("hashchange", scrollToHash);
|
|
2315
|
-
return () => window.removeEventListener("hashchange", scrollToHash);
|
|
2316
|
-
}, []);
|
|
1969
|
+
// src/result.ts
|
|
1970
|
+
var ok = (data) => ({ ok: true, data });
|
|
1971
|
+
function unwrapReport(value) {
|
|
1972
|
+
if (typeof value === "object" && value !== null && "ok" in value && typeof value.ok === "boolean") {
|
|
1973
|
+
return value;
|
|
1974
|
+
}
|
|
1975
|
+
return ok(value);
|
|
2317
1976
|
}
|
|
2318
1977
|
|
|
2319
1978
|
// src/interactive/filter.ts
|
|
@@ -2406,40 +2065,112 @@ function listFailures(report) {
|
|
|
2406
2065
|
out.push(ref);
|
|
2407
2066
|
}
|
|
2408
2067
|
}
|
|
2409
|
-
}
|
|
2410
|
-
return out;
|
|
2068
|
+
}
|
|
2069
|
+
return out;
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
// src/interactive/ReportAgentFilterNotice.tsx
|
|
2073
|
+
import { Bot, X } from "lucide-react";
|
|
2074
|
+
import { Fragment as Fragment11, jsx as jsx39, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
2075
|
+
function ReportAgentFilterNotice({
|
|
2076
|
+
applied,
|
|
2077
|
+
onReset,
|
|
2078
|
+
onDismiss
|
|
2079
|
+
}) {
|
|
2080
|
+
const terms = [];
|
|
2081
|
+
if (applied.search) terms.push(`matching \u201C${applied.search}\u201D`);
|
|
2082
|
+
if (applied.status !== "all") terms.push(applied.status);
|
|
2083
|
+
if (applied.tags.length > 0) terms.push(applied.tags.join(", "));
|
|
2084
|
+
return /* @__PURE__ */ jsxs25(
|
|
2085
|
+
"div",
|
|
2086
|
+
{
|
|
2087
|
+
role: "status",
|
|
2088
|
+
"aria-live": "polite",
|
|
2089
|
+
className: "flex flex-wrap items-center gap-x-3 gap-y-1 rounded-md border border-primary/40 bg-primary/5 px-3 py-2 text-sm",
|
|
2090
|
+
children: [
|
|
2091
|
+
/* @__PURE__ */ jsx39(Bot, { "aria-hidden": true, className: "size-4 shrink-0 text-primary" }),
|
|
2092
|
+
/* @__PURE__ */ jsxs25("span", { className: "min-w-0 flex-1", children: [
|
|
2093
|
+
"An agent filtered this report",
|
|
2094
|
+
terms.length > 0 ? /* @__PURE__ */ jsxs25(Fragment11, { children: [
|
|
2095
|
+
" to ",
|
|
2096
|
+
terms.join(" \xB7 ")
|
|
2097
|
+
] }) : null,
|
|
2098
|
+
" \u2014",
|
|
2099
|
+
" ",
|
|
2100
|
+
/* @__PURE__ */ jsxs25("span", { className: "tabular-nums", children: [
|
|
2101
|
+
applied.matched,
|
|
2102
|
+
" of ",
|
|
2103
|
+
applied.total,
|
|
2104
|
+
" scenarios"
|
|
2105
|
+
] }),
|
|
2106
|
+
"."
|
|
2107
|
+
] }),
|
|
2108
|
+
/* @__PURE__ */ jsx39(
|
|
2109
|
+
"button",
|
|
2110
|
+
{
|
|
2111
|
+
type: "button",
|
|
2112
|
+
onClick: onReset,
|
|
2113
|
+
className: "cursor-pointer rounded-md border border-border px-2 py-1 text-xs hover:bg-muted",
|
|
2114
|
+
children: "Show all"
|
|
2115
|
+
}
|
|
2116
|
+
),
|
|
2117
|
+
/* @__PURE__ */ jsx39(
|
|
2118
|
+
"button",
|
|
2119
|
+
{
|
|
2120
|
+
type: "button",
|
|
2121
|
+
onClick: onDismiss,
|
|
2122
|
+
"aria-label": "Dismiss",
|
|
2123
|
+
className: "cursor-pointer rounded-md p-1 text-muted-foreground hover:text-foreground",
|
|
2124
|
+
children: /* @__PURE__ */ jsx39(X, { "aria-hidden": true, className: "size-4" })
|
|
2125
|
+
}
|
|
2126
|
+
)
|
|
2127
|
+
]
|
|
2128
|
+
}
|
|
2129
|
+
);
|
|
2411
2130
|
}
|
|
2412
2131
|
|
|
2413
|
-
// src/interactive/
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
}
|
|
2417
|
-
|
|
2418
|
-
const
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2132
|
+
// src/interactive/ReportFailureBanner.tsx
|
|
2133
|
+
import { useCallback as useCallback2 } from "react";
|
|
2134
|
+
import { jsx as jsx40, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
2135
|
+
function ReportFailureBanner({ failures }) {
|
|
2136
|
+
const first = failures[0];
|
|
2137
|
+
const jumpToFirst = useCallback2(() => {
|
|
2138
|
+
if (!first) return;
|
|
2139
|
+
scrollToScenarioId(first.scenarioId);
|
|
2140
|
+
}, [first]);
|
|
2141
|
+
if (failures.length === 0) return null;
|
|
2142
|
+
return /* @__PURE__ */ jsxs26(
|
|
2143
|
+
"div",
|
|
2144
|
+
{
|
|
2145
|
+
className: "es-failure-banner",
|
|
2146
|
+
role: "status",
|
|
2147
|
+
"aria-live": "polite",
|
|
2148
|
+
"aria-label": "Failure summary",
|
|
2149
|
+
children: [
|
|
2150
|
+
/* @__PURE__ */ jsxs26("span", { className: "es-failure-banner-text", children: [
|
|
2151
|
+
/* @__PURE__ */ jsx40("strong", { children: failures.length }),
|
|
2152
|
+
" ",
|
|
2153
|
+
"failure",
|
|
2154
|
+
failures.length === 1 ? "" : "s"
|
|
2155
|
+
] }),
|
|
2156
|
+
/* @__PURE__ */ jsx40(
|
|
2157
|
+
"button",
|
|
2158
|
+
{
|
|
2159
|
+
type: "button",
|
|
2160
|
+
className: "es-failure-banner-jump",
|
|
2161
|
+
onClick: jumpToFirst,
|
|
2162
|
+
"aria-label": "View first failure",
|
|
2163
|
+
children: "View first failure \u2193"
|
|
2164
|
+
}
|
|
2165
|
+
)
|
|
2166
|
+
]
|
|
2425
2167
|
}
|
|
2426
|
-
|
|
2427
|
-
return collapsed;
|
|
2428
|
-
}
|
|
2429
|
-
function failuresFirst(report) {
|
|
2430
|
-
if (report.summary.failed === 0) return report;
|
|
2431
|
-
const withFailures = [];
|
|
2432
|
-
const rest = [];
|
|
2433
|
-
for (const feature of report.features) {
|
|
2434
|
-
(feature.scenarios.some((s) => needsAttention(s.status)) ? withFailures : rest).push(feature);
|
|
2435
|
-
}
|
|
2436
|
-
if (withFailures.length === 0) return report;
|
|
2437
|
-
return { ...report, features: [...withFailures, ...rest] };
|
|
2168
|
+
);
|
|
2438
2169
|
}
|
|
2439
2170
|
|
|
2440
2171
|
// src/interactive/ReportFilters.tsx
|
|
2441
2172
|
import { useState as useState3 } from "react";
|
|
2442
|
-
import { jsx as
|
|
2173
|
+
import { jsx as jsx41, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
2443
2174
|
var TAG_LIMIT = 12;
|
|
2444
2175
|
function ReportFilters({
|
|
2445
2176
|
statuses,
|
|
@@ -2453,10 +2184,10 @@ function ReportFilters({
|
|
|
2453
2184
|
if (statuses.length === 0 && tags.length === 0) return null;
|
|
2454
2185
|
const tagsCollapsed = tags.length > TAG_LIMIT && !showAllTags;
|
|
2455
2186
|
const visibleTags = tagsCollapsed ? tags.slice(0, TAG_LIMIT) : tags;
|
|
2456
|
-
return /* @__PURE__ */
|
|
2457
|
-
statuses.length > 0 ? /* @__PURE__ */
|
|
2187
|
+
return /* @__PURE__ */ jsxs27("div", { className: "flex flex-col gap-2", children: [
|
|
2188
|
+
statuses.length > 0 ? /* @__PURE__ */ jsx41("div", { role: "group", "aria-label": "Filter by status", className: "flex flex-wrap gap-1", children: statuses.map((opt) => {
|
|
2458
2189
|
const active = status === opt.key;
|
|
2459
|
-
return /* @__PURE__ */
|
|
2190
|
+
return /* @__PURE__ */ jsxs27(
|
|
2460
2191
|
"button",
|
|
2461
2192
|
{
|
|
2462
2193
|
type: "button",
|
|
@@ -2468,23 +2199,23 @@ function ReportFilters({
|
|
|
2468
2199
|
),
|
|
2469
2200
|
children: [
|
|
2470
2201
|
opt.label,
|
|
2471
|
-
/* @__PURE__ */
|
|
2202
|
+
/* @__PURE__ */ jsx41("span", { className: "ml-1 font-mono opacity-70", children: opt.count })
|
|
2472
2203
|
]
|
|
2473
2204
|
},
|
|
2474
2205
|
opt.key
|
|
2475
2206
|
);
|
|
2476
2207
|
}) }) : null,
|
|
2477
|
-
tags.length > 0 ? /* @__PURE__ */
|
|
2208
|
+
tags.length > 0 ? /* @__PURE__ */ jsxs27("ul", { "aria-label": "Filter by tag", className: "flex flex-wrap items-center gap-1.5", children: [
|
|
2478
2209
|
visibleTags.map((tag) => {
|
|
2479
2210
|
const active = activeTags.includes(tag);
|
|
2480
|
-
return /* @__PURE__ */
|
|
2211
|
+
return /* @__PURE__ */ jsx41("li", { children: /* @__PURE__ */ jsx41(
|
|
2481
2212
|
"button",
|
|
2482
2213
|
{
|
|
2483
2214
|
type: "button",
|
|
2484
2215
|
"aria-pressed": active,
|
|
2485
2216
|
onClick: () => onToggleTag(tag),
|
|
2486
2217
|
className: "cursor-pointer",
|
|
2487
|
-
children: /* @__PURE__ */
|
|
2218
|
+
children: /* @__PURE__ */ jsx41(
|
|
2488
2219
|
Badge,
|
|
2489
2220
|
{
|
|
2490
2221
|
variant: "tag",
|
|
@@ -2497,7 +2228,7 @@ function ReportFilters({
|
|
|
2497
2228
|
}
|
|
2498
2229
|
) }, tag);
|
|
2499
2230
|
}),
|
|
2500
|
-
tags.length > TAG_LIMIT ? /* @__PURE__ */
|
|
2231
|
+
tags.length > TAG_LIMIT ? /* @__PURE__ */ jsx41("li", { children: /* @__PURE__ */ jsx41(
|
|
2501
2232
|
"button",
|
|
2502
2233
|
{
|
|
2503
2234
|
type: "button",
|
|
@@ -2511,9 +2242,296 @@ function ReportFilters({
|
|
|
2511
2242
|
] });
|
|
2512
2243
|
}
|
|
2513
2244
|
|
|
2245
|
+
// src/interactive/ReportFreshness.tsx
|
|
2246
|
+
import { useEffect as useEffect2, useState as useState4 } from "react";
|
|
2247
|
+
import { Fragment as Fragment12, jsx as jsx42, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
2248
|
+
function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
|
|
2249
|
+
const [now, setNow] = useState4(nowMs);
|
|
2250
|
+
useEffect2(() => {
|
|
2251
|
+
if (nowMs === void 0) setNow(Date.now());
|
|
2252
|
+
}, [nowMs]);
|
|
2253
|
+
if (lastRunMs === void 0 || now === void 0) return null;
|
|
2254
|
+
const age = formatRelativeAge(lastRunMs, now);
|
|
2255
|
+
const runLink = ciUrl ? /* @__PURE__ */ jsx42(
|
|
2256
|
+
"a",
|
|
2257
|
+
{
|
|
2258
|
+
href: ciUrl,
|
|
2259
|
+
target: "_blank",
|
|
2260
|
+
rel: "noreferrer noopener",
|
|
2261
|
+
className: "underline underline-offset-2",
|
|
2262
|
+
children: "View CI run"
|
|
2263
|
+
}
|
|
2264
|
+
) : null;
|
|
2265
|
+
if (isReportStale(lastRunMs, staleAfterDays, now)) {
|
|
2266
|
+
return /* @__PURE__ */ jsxs28("div", { className: "es-stale-banner", role: "status", "aria-label": "Report freshness", children: [
|
|
2267
|
+
/* @__PURE__ */ jsxs28("span", { children: [
|
|
2268
|
+
/* @__PURE__ */ jsxs28("strong", { children: [
|
|
2269
|
+
"Last verified ",
|
|
2270
|
+
age,
|
|
2271
|
+
"."
|
|
2272
|
+
] }),
|
|
2273
|
+
" This report may no longer match the current behavior. Look for a newer run."
|
|
2274
|
+
] }),
|
|
2275
|
+
runLink
|
|
2276
|
+
] });
|
|
2277
|
+
}
|
|
2278
|
+
return /* @__PURE__ */ jsxs28("p", { className: "es-freshness", "aria-label": "Report freshness", children: [
|
|
2279
|
+
"Verified ",
|
|
2280
|
+
age,
|
|
2281
|
+
runLink ? /* @__PURE__ */ jsxs28(Fragment12, { children: [
|
|
2282
|
+
" \xB7 ",
|
|
2283
|
+
runLink
|
|
2284
|
+
] }) : null
|
|
2285
|
+
] });
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
// src/interactive/ReportLastRunDelta.tsx
|
|
2289
|
+
import { Fragment as Fragment13, jsx as jsx43, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
2290
|
+
var MAX_LINKS = 5;
|
|
2291
|
+
function ScenarioLinks({ scenarios, tone }) {
|
|
2292
|
+
return /* @__PURE__ */ jsxs29(Fragment13, { children: [
|
|
2293
|
+
scenarios.slice(0, MAX_LINKS).map((s, i) => /* @__PURE__ */ jsxs29("span", { children: [
|
|
2294
|
+
i > 0 ? ", " : "",
|
|
2295
|
+
/* @__PURE__ */ jsx43("a", { href: `#${s.id}`, className: cn("underline underline-offset-2 hover:no-underline", tone), children: s.title })
|
|
2296
|
+
] }, s.id)),
|
|
2297
|
+
scenarios.length > MAX_LINKS ? /* @__PURE__ */ jsxs29("span", { children: [
|
|
2298
|
+
" and ",
|
|
2299
|
+
scenarios.length - MAX_LINKS,
|
|
2300
|
+
" more"
|
|
2301
|
+
] }) : null
|
|
2302
|
+
] });
|
|
2303
|
+
}
|
|
2304
|
+
function ReportLastRunDelta({
|
|
2305
|
+
history,
|
|
2306
|
+
report
|
|
2307
|
+
}) {
|
|
2308
|
+
if (!history) return null;
|
|
2309
|
+
const scenarios = report.features.flatMap((f) => f.scenarios.map((s) => ({ id: s.id, title: s.title })));
|
|
2310
|
+
const delta = diffSinceLastRun(history, scenarios);
|
|
2311
|
+
if (!delta) return null;
|
|
2312
|
+
const { newlyFailing, newlyPassing, added } = delta;
|
|
2313
|
+
if (newlyFailing.length === 0 && newlyPassing.length === 0 && added.length === 0) {
|
|
2314
|
+
return /* @__PURE__ */ jsx43("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: "Since last run: no behavior changes." });
|
|
2315
|
+
}
|
|
2316
|
+
return /* @__PURE__ */ jsxs29("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: [
|
|
2317
|
+
/* @__PURE__ */ jsx43("span", { className: "font-medium text-foreground", children: "Since last run:" }),
|
|
2318
|
+
newlyFailing.length > 0 ? /* @__PURE__ */ jsxs29("span", { className: "ml-2 text-fail", children: [
|
|
2319
|
+
newlyFailing.length,
|
|
2320
|
+
" newly failing (",
|
|
2321
|
+
/* @__PURE__ */ jsx43(ScenarioLinks, { scenarios: newlyFailing, tone: "text-fail" }),
|
|
2322
|
+
")"
|
|
2323
|
+
] }) : null,
|
|
2324
|
+
newlyPassing.length > 0 ? /* @__PURE__ */ jsxs29("span", { className: "ml-2 text-pass", children: [
|
|
2325
|
+
newlyPassing.length,
|
|
2326
|
+
" fixed (",
|
|
2327
|
+
/* @__PURE__ */ jsx43(ScenarioLinks, { scenarios: newlyPassing, tone: "text-pass" }),
|
|
2328
|
+
")"
|
|
2329
|
+
] }) : null,
|
|
2330
|
+
added.length > 0 ? /* @__PURE__ */ jsxs29("span", { className: "ml-2", children: [
|
|
2331
|
+
added.length,
|
|
2332
|
+
" new"
|
|
2333
|
+
] }) : null
|
|
2334
|
+
] });
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
// src/interactive/ReportSearch.tsx
|
|
2338
|
+
import {
|
|
2339
|
+
forwardRef,
|
|
2340
|
+
useId
|
|
2341
|
+
} from "react";
|
|
2342
|
+
import { Search } from "lucide-react";
|
|
2343
|
+
|
|
2344
|
+
// src/components/ui/input.tsx
|
|
2345
|
+
import "react";
|
|
2346
|
+
import { jsx as jsx44 } from "react/jsx-runtime";
|
|
2347
|
+
function Input({ className, type, ...props }) {
|
|
2348
|
+
return /* @__PURE__ */ jsx44(
|
|
2349
|
+
"input",
|
|
2350
|
+
{
|
|
2351
|
+
type,
|
|
2352
|
+
"data-slot": "input",
|
|
2353
|
+
className: cn(
|
|
2354
|
+
"h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30",
|
|
2355
|
+
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
|
|
2356
|
+
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
|
2357
|
+
className
|
|
2358
|
+
),
|
|
2359
|
+
...props
|
|
2360
|
+
}
|
|
2361
|
+
);
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
// src/interactive/ReportSearch.tsx
|
|
2365
|
+
import { jsx as jsx45, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
2366
|
+
var ReportSearch = forwardRef(
|
|
2367
|
+
function ReportSearch2(props, ref) {
|
|
2368
|
+
const {
|
|
2369
|
+
value,
|
|
2370
|
+
onChange,
|
|
2371
|
+
matchedCount,
|
|
2372
|
+
totalCount,
|
|
2373
|
+
placeholder = "Search scenarios or tags\u2026",
|
|
2374
|
+
className
|
|
2375
|
+
} = props;
|
|
2376
|
+
const inputId = useId();
|
|
2377
|
+
const showCounts = value.trim().length > 0 && typeof matchedCount === "number" && typeof totalCount === "number";
|
|
2378
|
+
function handleChange(e) {
|
|
2379
|
+
onChange(e.target.value);
|
|
2380
|
+
}
|
|
2381
|
+
function handleKeyDown(e) {
|
|
2382
|
+
if (e.key === "Escape" && value !== "") {
|
|
2383
|
+
onChange("");
|
|
2384
|
+
e.preventDefault();
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
return /* @__PURE__ */ jsxs30("div", { className: cn("es-search flex flex-col gap-1", className), children: [
|
|
2388
|
+
/* @__PURE__ */ jsx45("label", { htmlFor: inputId, className: "sr-only", children: "Search" }),
|
|
2389
|
+
/* @__PURE__ */ jsxs30("div", { className: "relative w-full sm:w-72", children: [
|
|
2390
|
+
/* @__PURE__ */ jsx45(
|
|
2391
|
+
Search,
|
|
2392
|
+
{
|
|
2393
|
+
"aria-hidden": true,
|
|
2394
|
+
className: "pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground"
|
|
2395
|
+
}
|
|
2396
|
+
),
|
|
2397
|
+
/* @__PURE__ */ jsx45(
|
|
2398
|
+
Input,
|
|
2399
|
+
{
|
|
2400
|
+
ref,
|
|
2401
|
+
id: inputId,
|
|
2402
|
+
type: "search",
|
|
2403
|
+
value,
|
|
2404
|
+
onChange: handleChange,
|
|
2405
|
+
onKeyDown: handleKeyDown,
|
|
2406
|
+
placeholder,
|
|
2407
|
+
autoComplete: "off",
|
|
2408
|
+
spellCheck: false,
|
|
2409
|
+
"aria-keyshortcuts": "/",
|
|
2410
|
+
className: "pl-8"
|
|
2411
|
+
}
|
|
2412
|
+
)
|
|
2413
|
+
] }),
|
|
2414
|
+
showCounts ? /* @__PURE__ */ jsxs30(
|
|
2415
|
+
"span",
|
|
2416
|
+
{
|
|
2417
|
+
className: "text-xs tabular-nums text-muted-foreground",
|
|
2418
|
+
"aria-live": "polite",
|
|
2419
|
+
children: [
|
|
2420
|
+
matchedCount,
|
|
2421
|
+
" of ",
|
|
2422
|
+
totalCount,
|
|
2423
|
+
" scenarios"
|
|
2424
|
+
]
|
|
2425
|
+
}
|
|
2426
|
+
) : null
|
|
2427
|
+
] });
|
|
2428
|
+
}
|
|
2429
|
+
);
|
|
2430
|
+
|
|
2431
|
+
// src/interactive/ReportShortcutsHelp.tsx
|
|
2432
|
+
import { useEffect as useEffect3, useRef } from "react";
|
|
2433
|
+
|
|
2434
|
+
// src/components/ui/button.tsx
|
|
2435
|
+
import "react";
|
|
2436
|
+
import { cva as cva4 } from "class-variance-authority";
|
|
2437
|
+
import { useRender as useRender2 } from "@base-ui/react/use-render";
|
|
2438
|
+
var buttonVariants = cva4(
|
|
2439
|
+
"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,background-color,border-color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
2440
|
+
{
|
|
2441
|
+
variants: {
|
|
2442
|
+
variant: {
|
|
2443
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
2444
|
+
destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
|
|
2445
|
+
outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
|
2446
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
2447
|
+
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
2448
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
2449
|
+
},
|
|
2450
|
+
size: {
|
|
2451
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
2452
|
+
xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
2453
|
+
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
|
|
2454
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
2455
|
+
icon: "size-9",
|
|
2456
|
+
"icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
|
|
2457
|
+
"icon-sm": "size-8",
|
|
2458
|
+
"icon-lg": "size-10"
|
|
2459
|
+
}
|
|
2460
|
+
},
|
|
2461
|
+
defaultVariants: {
|
|
2462
|
+
variant: "default",
|
|
2463
|
+
size: "default"
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
);
|
|
2467
|
+
function Button({
|
|
2468
|
+
className,
|
|
2469
|
+
variant = "default",
|
|
2470
|
+
size = "default",
|
|
2471
|
+
render,
|
|
2472
|
+
...props
|
|
2473
|
+
}) {
|
|
2474
|
+
return useRender2({
|
|
2475
|
+
defaultTagName: "button",
|
|
2476
|
+
render,
|
|
2477
|
+
props: {
|
|
2478
|
+
"data-slot": "button",
|
|
2479
|
+
"data-variant": variant,
|
|
2480
|
+
"data-size": size,
|
|
2481
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
2482
|
+
...props
|
|
2483
|
+
}
|
|
2484
|
+
});
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
// src/interactive/ReportShortcutsHelp.tsx
|
|
2488
|
+
import { jsx as jsx46, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
2489
|
+
var SHORTCUTS = [
|
|
2490
|
+
{ keys: "/", description: "Focus search" },
|
|
2491
|
+
{ keys: "f", description: "Jump to next failure" },
|
|
2492
|
+
{ keys: "Shift F", description: "Jump to previous failure" },
|
|
2493
|
+
{ keys: "e", description: "Expand all" },
|
|
2494
|
+
{ keys: "c", description: "Collapse all" },
|
|
2495
|
+
{ keys: "?", description: "Toggle this help" },
|
|
2496
|
+
{ keys: "Esc", description: "Clear search / close" }
|
|
2497
|
+
];
|
|
2498
|
+
function Keys({ keys }) {
|
|
2499
|
+
const parts = keys.split(" ");
|
|
2500
|
+
return /* @__PURE__ */ jsx46("span", { className: "inline-flex items-center gap-1", children: parts.map((k, i) => /* @__PURE__ */ jsxs31("span", { className: "inline-flex items-center gap-1", children: [
|
|
2501
|
+
i > 0 ? /* @__PURE__ */ jsx46("span", { className: "text-xs text-muted-foreground", children: "+" }) : null,
|
|
2502
|
+
/* @__PURE__ */ jsx46("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 })
|
|
2503
|
+
] }, k)) });
|
|
2504
|
+
}
|
|
2505
|
+
function ReportShortcutsHelp({ open, onClose }) {
|
|
2506
|
+
const dialogRef = useRef(null);
|
|
2507
|
+
useEffect3(() => {
|
|
2508
|
+
const dialog = dialogRef.current;
|
|
2509
|
+
if (!dialog) return;
|
|
2510
|
+
if (open && !dialog.open) dialog.showModal();
|
|
2511
|
+
else if (!open && dialog.open) dialog.close();
|
|
2512
|
+
}, [open]);
|
|
2513
|
+
return /* @__PURE__ */ jsxs31(
|
|
2514
|
+
"dialog",
|
|
2515
|
+
{
|
|
2516
|
+
ref: dialogRef,
|
|
2517
|
+
className: "es-shortcuts-help m-auto w-[min(90vw,28rem)] rounded-lg border border-border bg-card p-6 text-card-foreground shadow-lg [overscroll-behavior:contain] backdrop:bg-black/40",
|
|
2518
|
+
"aria-label": "Keyboard shortcuts",
|
|
2519
|
+
onClose,
|
|
2520
|
+
children: [
|
|
2521
|
+
/* @__PURE__ */ jsx46("h2", { className: "text-base font-semibold", children: "Keyboard shortcuts" }),
|
|
2522
|
+
/* @__PURE__ */ jsx46("dl", { className: "mt-4 grid grid-cols-[auto_1fr] items-center gap-x-4 gap-y-2.5", children: SHORTCUTS.map((s) => /* @__PURE__ */ jsxs31("div", { className: "col-span-2 grid grid-cols-subgrid items-center", children: [
|
|
2523
|
+
/* @__PURE__ */ jsx46("dt", { className: "justify-self-start", children: /* @__PURE__ */ jsx46(Keys, { keys: s.keys }) }),
|
|
2524
|
+
/* @__PURE__ */ jsx46("dd", { className: "text-sm text-muted-foreground", children: s.description })
|
|
2525
|
+
] }, s.keys)) }),
|
|
2526
|
+
/* @__PURE__ */ jsx46("form", { method: "dialog", className: "mt-6", children: /* @__PURE__ */ jsx46(Button, { type: "submit", variant: "outline", size: "sm", children: "Close" }) })
|
|
2527
|
+
]
|
|
2528
|
+
}
|
|
2529
|
+
);
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2514
2532
|
// src/interactive/ReportToc.tsx
|
|
2515
|
-
import { useEffect as
|
|
2516
|
-
import { jsx as
|
|
2533
|
+
import { useEffect as useEffect4, useMemo as useMemo2, useState as useState5 } from "react";
|
|
2534
|
+
import { jsx as jsx47, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
2517
2535
|
var DOT = {
|
|
2518
2536
|
passed: "bg-pass",
|
|
2519
2537
|
failed: "bg-fail",
|
|
@@ -2522,12 +2540,12 @@ var DOT = {
|
|
|
2522
2540
|
};
|
|
2523
2541
|
function TocContent({ onNavigate }) {
|
|
2524
2542
|
const report = useReport();
|
|
2525
|
-
const [activeId, setActiveId] =
|
|
2543
|
+
const [activeId, setActiveId] = useState5(null);
|
|
2526
2544
|
const scenarioIds = useMemo2(
|
|
2527
2545
|
() => report.features.flatMap((f) => f.scenarios.map((s) => s.id)),
|
|
2528
2546
|
[report]
|
|
2529
2547
|
);
|
|
2530
|
-
|
|
2548
|
+
useEffect4(() => {
|
|
2531
2549
|
if (typeof IntersectionObserver === "undefined") return;
|
|
2532
2550
|
const els = scenarioIds.map((id) => document.getElementById(id)).filter((el) => el !== null);
|
|
2533
2551
|
if (els.length === 0) return;
|
|
@@ -2542,11 +2560,11 @@ function TocContent({ onNavigate }) {
|
|
|
2542
2560
|
return () => observer.disconnect();
|
|
2543
2561
|
}, [scenarioIds]);
|
|
2544
2562
|
if (report.features.length === 0) return null;
|
|
2545
|
-
return /* @__PURE__ */
|
|
2546
|
-
/* @__PURE__ */
|
|
2547
|
-
/* @__PURE__ */
|
|
2563
|
+
return /* @__PURE__ */ jsx47("ul", { className: "flex list-none flex-col gap-4 pl-0", children: report.features.map((feature) => /* @__PURE__ */ jsxs32("li", { children: [
|
|
2564
|
+
/* @__PURE__ */ jsx47("p", { className: "mb-1.5 font-semibold leading-snug text-foreground", children: feature.title }),
|
|
2565
|
+
/* @__PURE__ */ jsx47("ul", { className: "flex list-none flex-col gap-0.5 pl-0", children: feature.scenarios.map((scenario) => {
|
|
2548
2566
|
const active = activeId === scenario.id;
|
|
2549
|
-
return /* @__PURE__ */
|
|
2567
|
+
return /* @__PURE__ */ jsx47("li", { children: /* @__PURE__ */ jsxs32(
|
|
2550
2568
|
"a",
|
|
2551
2569
|
{
|
|
2552
2570
|
href: `#${scenario.id}`,
|
|
@@ -2561,14 +2579,14 @@ function TocContent({ onNavigate }) {
|
|
|
2561
2579
|
active ? "border-primary font-medium text-foreground" : "border-border text-muted-foreground"
|
|
2562
2580
|
),
|
|
2563
2581
|
children: [
|
|
2564
|
-
/* @__PURE__ */
|
|
2582
|
+
/* @__PURE__ */ jsx47(
|
|
2565
2583
|
"span",
|
|
2566
2584
|
{
|
|
2567
2585
|
"aria-hidden": true,
|
|
2568
2586
|
className: cn("mt-1 size-1.5 shrink-0 rounded-full", DOT[scenario.status])
|
|
2569
2587
|
}
|
|
2570
2588
|
),
|
|
2571
|
-
/* @__PURE__ */
|
|
2589
|
+
/* @__PURE__ */ jsx47("span", { className: "break-words", children: scenario.title })
|
|
2572
2590
|
]
|
|
2573
2591
|
}
|
|
2574
2592
|
) }, scenario.id);
|
|
@@ -2578,55 +2596,156 @@ function TocContent({ onNavigate }) {
|
|
|
2578
2596
|
function ReportToc() {
|
|
2579
2597
|
const report = useReport();
|
|
2580
2598
|
if (report.features.length === 0) return null;
|
|
2581
|
-
return /* @__PURE__ */
|
|
2599
|
+
return /* @__PURE__ */ jsxs32(
|
|
2582
2600
|
"nav",
|
|
2583
2601
|
{
|
|
2584
2602
|
"aria-label": "Table of contents",
|
|
2585
2603
|
className: "sticky top-4 hidden h-fit w-64 shrink-0 self-start text-xs lg:block",
|
|
2586
2604
|
children: [
|
|
2587
|
-
/* @__PURE__ */
|
|
2588
|
-
/* @__PURE__ */
|
|
2605
|
+
/* @__PURE__ */ jsx47("p", { className: "mb-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Contents" }),
|
|
2606
|
+
/* @__PURE__ */ jsx47(TocContent, {})
|
|
2589
2607
|
]
|
|
2590
2608
|
}
|
|
2591
2609
|
);
|
|
2592
2610
|
}
|
|
2593
2611
|
|
|
2594
2612
|
// src/interactive/ReportTocDrawer.tsx
|
|
2595
|
-
import { useState as
|
|
2613
|
+
import { useState as useState6 } from "react";
|
|
2596
2614
|
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
|
|
2597
2615
|
import { ListIcon, XIcon } from "lucide-react";
|
|
2598
|
-
import { jsx as
|
|
2616
|
+
import { jsx as jsx48, jsxs as jsxs33 } from "react/jsx-runtime";
|
|
2599
2617
|
function ReportTocDrawer() {
|
|
2600
2618
|
const report = useReport();
|
|
2601
|
-
const [open, setOpen] =
|
|
2619
|
+
const [open, setOpen] = useState6(false);
|
|
2602
2620
|
if (report.features.length === 0) return null;
|
|
2603
|
-
return /* @__PURE__ */
|
|
2604
|
-
/* @__PURE__ */
|
|
2605
|
-
/* @__PURE__ */
|
|
2621
|
+
return /* @__PURE__ */ jsxs33(DialogPrimitive.Root, { open, onOpenChange: setOpen, children: [
|
|
2622
|
+
/* @__PURE__ */ jsxs33(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: [
|
|
2623
|
+
/* @__PURE__ */ jsx48(ListIcon, { "aria-hidden": true }),
|
|
2606
2624
|
" Contents"
|
|
2607
2625
|
] }),
|
|
2608
|
-
/* @__PURE__ */
|
|
2609
|
-
/* @__PURE__ */
|
|
2610
|
-
/* @__PURE__ */
|
|
2611
|
-
/* @__PURE__ */
|
|
2612
|
-
/* @__PURE__ */
|
|
2613
|
-
/* @__PURE__ */
|
|
2626
|
+
/* @__PURE__ */ jsxs33(DialogPrimitive.Portal, { children: [
|
|
2627
|
+
/* @__PURE__ */ jsx48(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" }),
|
|
2628
|
+
/* @__PURE__ */ jsxs33(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: [
|
|
2629
|
+
/* @__PURE__ */ jsxs33("div", { className: "flex items-center justify-between", children: [
|
|
2630
|
+
/* @__PURE__ */ jsx48(DialogPrimitive.Title, { className: "text-xs font-semibold tracking-wide text-muted-foreground uppercase", children: "Contents" }),
|
|
2631
|
+
/* @__PURE__ */ jsx48(
|
|
2614
2632
|
DialogPrimitive.Close,
|
|
2615
2633
|
{
|
|
2616
2634
|
"aria-label": "Close contents",
|
|
2617
2635
|
className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
|
|
2618
|
-
children: /* @__PURE__ */
|
|
2636
|
+
children: /* @__PURE__ */ jsx48(XIcon, { "aria-hidden": true })
|
|
2619
2637
|
}
|
|
2620
2638
|
)
|
|
2621
2639
|
] }),
|
|
2622
|
-
/* @__PURE__ */
|
|
2640
|
+
/* @__PURE__ */ jsx48(TocContent, { onNavigate: () => setOpen(false) })
|
|
2623
2641
|
] })
|
|
2624
2642
|
] })
|
|
2625
2643
|
] });
|
|
2626
2644
|
}
|
|
2627
2645
|
|
|
2646
|
+
// src/interactive/triage-default.ts
|
|
2647
|
+
function needsAttention(status) {
|
|
2648
|
+
return status === "failed";
|
|
2649
|
+
}
|
|
2650
|
+
function defaultCollapsedIds(report) {
|
|
2651
|
+
const collapsed = /* @__PURE__ */ new Set();
|
|
2652
|
+
if (report.summary.failed === 0) return collapsed;
|
|
2653
|
+
for (const feature of report.features) {
|
|
2654
|
+
const featureHasFailure = feature.scenarios.some((s) => needsAttention(s.status));
|
|
2655
|
+
if (!featureHasFailure) collapsed.add(feature.id);
|
|
2656
|
+
for (const scenario of feature.scenarios) {
|
|
2657
|
+
if (!needsAttention(scenario.status)) collapsed.add(scenario.id);
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
return collapsed;
|
|
2661
|
+
}
|
|
2662
|
+
function failuresFirst(report) {
|
|
2663
|
+
if (report.summary.failed === 0) return report;
|
|
2664
|
+
const withFailures = [];
|
|
2665
|
+
const rest = [];
|
|
2666
|
+
for (const feature of report.features) {
|
|
2667
|
+
(feature.scenarios.some((s) => needsAttention(s.status)) ? withFailures : rest).push(feature);
|
|
2668
|
+
}
|
|
2669
|
+
if (withFailures.length === 0) return report;
|
|
2670
|
+
return { ...report, features: [...withFailures, ...rest] };
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
// src/interactive/use-deep-link-scroll.ts
|
|
2674
|
+
import { useEffect as useEffect5, useRef as useRef2 } from "react";
|
|
2675
|
+
function useDeepLinkScroll() {
|
|
2676
|
+
const lastAnchor = useRef2(null);
|
|
2677
|
+
useEffect5(() => {
|
|
2678
|
+
function scrollToHash() {
|
|
2679
|
+
const { anchor } = splitHash(window.location.hash);
|
|
2680
|
+
if (!anchor || anchor === lastAnchor.current) return;
|
|
2681
|
+
lastAnchor.current = anchor;
|
|
2682
|
+
requestAnimationFrame(() => {
|
|
2683
|
+
scrollToScenarioId(anchor, { updateHash: false });
|
|
2684
|
+
});
|
|
2685
|
+
}
|
|
2686
|
+
scrollToHash();
|
|
2687
|
+
window.addEventListener("hashchange", scrollToHash);
|
|
2688
|
+
return () => window.removeEventListener("hashchange", scrollToHash);
|
|
2689
|
+
}, []);
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
// src/interactive/use-keyboard-shortcuts.ts
|
|
2693
|
+
import { useEffect as useEffect6, useRef as useRef3 } from "react";
|
|
2694
|
+
function isEditableTarget(target) {
|
|
2695
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
2696
|
+
const tag = target.tagName;
|
|
2697
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
|
|
2698
|
+
if (target.isContentEditable) return true;
|
|
2699
|
+
return false;
|
|
2700
|
+
}
|
|
2701
|
+
function useKeyboardShortcuts(handlers) {
|
|
2702
|
+
const ref = useRef3(handlers);
|
|
2703
|
+
ref.current = handlers;
|
|
2704
|
+
useEffect6(() => {
|
|
2705
|
+
function onKeyDown(e) {
|
|
2706
|
+
const h = ref.current;
|
|
2707
|
+
if (e.key === "Escape") {
|
|
2708
|
+
h.onEscape?.();
|
|
2709
|
+
return;
|
|
2710
|
+
}
|
|
2711
|
+
if (isEditableTarget(e.target)) return;
|
|
2712
|
+
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
|
2713
|
+
switch (e.key) {
|
|
2714
|
+
case "/":
|
|
2715
|
+
h.onFocusSearch?.();
|
|
2716
|
+
e.preventDefault();
|
|
2717
|
+
return;
|
|
2718
|
+
case "?":
|
|
2719
|
+
h.onToggleHelp?.();
|
|
2720
|
+
e.preventDefault();
|
|
2721
|
+
return;
|
|
2722
|
+
case "f":
|
|
2723
|
+
h.onNextFailure?.();
|
|
2724
|
+
e.preventDefault();
|
|
2725
|
+
return;
|
|
2726
|
+
case "F":
|
|
2727
|
+
h.onPrevFailure?.();
|
|
2728
|
+
e.preventDefault();
|
|
2729
|
+
return;
|
|
2730
|
+
case "e":
|
|
2731
|
+
h.onExpandAll?.();
|
|
2732
|
+
e.preventDefault();
|
|
2733
|
+
return;
|
|
2734
|
+
case "c":
|
|
2735
|
+
h.onCollapseAll?.();
|
|
2736
|
+
e.preventDefault();
|
|
2737
|
+
return;
|
|
2738
|
+
default:
|
|
2739
|
+
return;
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
window.addEventListener("keydown", onKeyDown);
|
|
2743
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
2744
|
+
}, []);
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2628
2747
|
// src/interactive/use-theme.ts
|
|
2629
|
-
import { useCallback as useCallback3, useEffect as
|
|
2748
|
+
import { useCallback as useCallback3, useEffect as useEffect7, useState as useState7 } from "react";
|
|
2630
2749
|
var KEY = "es-theme";
|
|
2631
2750
|
function systemDark() {
|
|
2632
2751
|
return typeof matchMedia !== "undefined" && matchMedia("(prefers-color-scheme: dark)").matches;
|
|
@@ -2647,13 +2766,13 @@ function initialPref() {
|
|
|
2647
2766
|
return "system";
|
|
2648
2767
|
}
|
|
2649
2768
|
function useTheme() {
|
|
2650
|
-
const [pref, setPrefState] =
|
|
2651
|
-
|
|
2769
|
+
const [pref, setPrefState] = useState7("system");
|
|
2770
|
+
useEffect7(() => {
|
|
2652
2771
|
const p = initialPref();
|
|
2653
2772
|
setPrefState(p);
|
|
2654
2773
|
applyTheme(p);
|
|
2655
2774
|
}, []);
|
|
2656
|
-
|
|
2775
|
+
useEffect7(() => {
|
|
2657
2776
|
if (typeof matchMedia === "undefined") return;
|
|
2658
2777
|
const mq = matchMedia("(prefers-color-scheme: dark)");
|
|
2659
2778
|
const onChange = () => {
|
|
@@ -2674,19 +2793,19 @@ function useTheme() {
|
|
|
2674
2793
|
}
|
|
2675
2794
|
|
|
2676
2795
|
// src/interactive/use-url-state.ts
|
|
2677
|
-
import { useCallback as useCallback4, useEffect as
|
|
2796
|
+
import { useCallback as useCallback4, useEffect as useEffect8, useRef as useRef4, useState as useState8 } from "react";
|
|
2678
2797
|
var WRITE_DELAY_MS = 250;
|
|
2679
2798
|
function useUrlState() {
|
|
2680
|
-
const [state, setState] =
|
|
2799
|
+
const [state, setState] = useState8(DEFAULT_URL_STATE);
|
|
2681
2800
|
const ready = useRef4(false);
|
|
2682
|
-
|
|
2801
|
+
useEffect8(() => {
|
|
2683
2802
|
const read = () => setState(decodeUrlState(splitHash(window.location.hash).params));
|
|
2684
2803
|
read();
|
|
2685
2804
|
ready.current = true;
|
|
2686
2805
|
window.addEventListener("hashchange", read);
|
|
2687
2806
|
return () => window.removeEventListener("hashchange", read);
|
|
2688
2807
|
}, []);
|
|
2689
|
-
|
|
2808
|
+
useEffect8(() => {
|
|
2690
2809
|
if (!ready.current) return;
|
|
2691
2810
|
const timer = setTimeout(
|
|
2692
2811
|
() => writeHash(splitHash(window.location.hash).anchor, encodeUrlState(state)),
|
|
@@ -2701,23 +2820,254 @@ function useUrlState() {
|
|
|
2701
2820
|
return [state, update];
|
|
2702
2821
|
}
|
|
2703
2822
|
|
|
2823
|
+
// src/interactive/webmcp-tools.ts
|
|
2824
|
+
import {
|
|
2825
|
+
getFeatureSummary,
|
|
2826
|
+
getScenario,
|
|
2827
|
+
listScenarioSummaries,
|
|
2828
|
+
runProvenance,
|
|
2829
|
+
toScenarioSummary
|
|
2830
|
+
} from "executable-stories-core/report-queries";
|
|
2831
|
+
var STATUSES2 = [
|
|
2832
|
+
"passed",
|
|
2833
|
+
"failed",
|
|
2834
|
+
"skipped",
|
|
2835
|
+
"pending"
|
|
2836
|
+
];
|
|
2837
|
+
var VIEW_STATUSES = ["all", ...STATUSES2];
|
|
2838
|
+
var ToolArgumentError = class extends Error {
|
|
2839
|
+
};
|
|
2840
|
+
function asRecord(input) {
|
|
2841
|
+
return typeof input === "object" && input !== null ? input : {};
|
|
2842
|
+
}
|
|
2843
|
+
function readString(input, key) {
|
|
2844
|
+
const value = input[key];
|
|
2845
|
+
if (value === void 0 || value === null) return void 0;
|
|
2846
|
+
if (typeof value !== "string") {
|
|
2847
|
+
throw new ToolArgumentError(`"${key}" must be a string, received ${typeof value}.`);
|
|
2848
|
+
}
|
|
2849
|
+
return value;
|
|
2850
|
+
}
|
|
2851
|
+
function readStringArray(input, key) {
|
|
2852
|
+
const value = input[key];
|
|
2853
|
+
if (value === void 0 || value === null) return void 0;
|
|
2854
|
+
if (typeof value === "string") return [value];
|
|
2855
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
2856
|
+
throw new ToolArgumentError(`"${key}" must be an array of strings.`);
|
|
2857
|
+
}
|
|
2858
|
+
return value;
|
|
2859
|
+
}
|
|
2860
|
+
function readEnum(input, key, allowed) {
|
|
2861
|
+
const value = readString(input, key);
|
|
2862
|
+
if (value === void 0) return void 0;
|
|
2863
|
+
if (!allowed.includes(value)) {
|
|
2864
|
+
throw new ToolArgumentError(
|
|
2865
|
+
`"${key}" must be one of: ${allowed.join(", ")}. Received "${value}".`
|
|
2866
|
+
);
|
|
2867
|
+
}
|
|
2868
|
+
return value;
|
|
2869
|
+
}
|
|
2870
|
+
function readStatuses(input) {
|
|
2871
|
+
const values = readStringArray(input, "statuses");
|
|
2872
|
+
if (values === void 0) return void 0;
|
|
2873
|
+
const invalid = values.filter((v) => !STATUSES2.includes(v));
|
|
2874
|
+
if (invalid.length > 0) {
|
|
2875
|
+
throw new ToolArgumentError(
|
|
2876
|
+
`"statuses" must contain only: ${STATUSES2.join(", ")}. Received "${invalid.join('", "')}".`
|
|
2877
|
+
);
|
|
2878
|
+
}
|
|
2879
|
+
return values;
|
|
2880
|
+
}
|
|
2881
|
+
function guarded(run) {
|
|
2882
|
+
try {
|
|
2883
|
+
return run();
|
|
2884
|
+
} catch (error) {
|
|
2885
|
+
if (error instanceof ToolArgumentError) return `Invalid input \u2014 ${error.message}`;
|
|
2886
|
+
throw error;
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
var scenarioListSchema = {
|
|
2890
|
+
type: "object",
|
|
2891
|
+
properties: {
|
|
2892
|
+
statuses: {
|
|
2893
|
+
type: "array",
|
|
2894
|
+
items: { type: "string", enum: [...STATUSES2] },
|
|
2895
|
+
description: "Keep only scenarios with any of these statuses."
|
|
2896
|
+
},
|
|
2897
|
+
tags: {
|
|
2898
|
+
type: "array",
|
|
2899
|
+
items: { type: "string" },
|
|
2900
|
+
description: "Keep only scenarios carrying any of these tags."
|
|
2901
|
+
},
|
|
2902
|
+
sourceFiles: {
|
|
2903
|
+
type: "array",
|
|
2904
|
+
items: { type: "string" },
|
|
2905
|
+
description: "Keep only scenarios whose source file path contains any of these."
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
};
|
|
2909
|
+
var READ_ONLY = { readOnlyHint: true, untrustedContentHint: true };
|
|
2910
|
+
function reportTools(deps) {
|
|
2911
|
+
const { report, setView, onAgentFilter } = deps;
|
|
2912
|
+
const now = deps.now ?? Date.now;
|
|
2913
|
+
const provenance = () => ({ run: runProvenance(report, now()) });
|
|
2914
|
+
const list = (filters) => {
|
|
2915
|
+
const scenarios = listScenarioSummaries(report, filters);
|
|
2916
|
+
return { ...provenance(), total: scenarios.length, scenarios };
|
|
2917
|
+
};
|
|
2918
|
+
return {
|
|
2919
|
+
list_scenarios: {
|
|
2920
|
+
title: "List scenarios",
|
|
2921
|
+
description: "List the behaviour scenarios in this report, optionally filtered by status, tag, or source file. Returns each scenario with its steps, tags, timing and failure message.",
|
|
2922
|
+
annotations: READ_ONLY,
|
|
2923
|
+
input: scenarioListSchema,
|
|
2924
|
+
execute: (input) => guarded(() => {
|
|
2925
|
+
const args = asRecord(input);
|
|
2926
|
+
const filters = {};
|
|
2927
|
+
const statuses = readStatuses(args);
|
|
2928
|
+
const tags = readStringArray(args, "tags");
|
|
2929
|
+
const sourceFiles = readStringArray(args, "sourceFiles");
|
|
2930
|
+
if (statuses) filters.statuses = statuses;
|
|
2931
|
+
if (tags) filters.tags = tags;
|
|
2932
|
+
if (sourceFiles) filters.sourceFiles = sourceFiles;
|
|
2933
|
+
return list(filters);
|
|
2934
|
+
})
|
|
2935
|
+
},
|
|
2936
|
+
get_failing_scenarios: {
|
|
2937
|
+
title: "Get failing scenarios",
|
|
2938
|
+
description: "List only the scenarios that failed in this run, each with the step that failed and its error message. Use this first when asked what is broken.",
|
|
2939
|
+
annotations: READ_ONLY,
|
|
2940
|
+
execute: () => list({ statuses: ["failed"] })
|
|
2941
|
+
},
|
|
2942
|
+
get_feature_summary: {
|
|
2943
|
+
title: "Get feature summary",
|
|
2944
|
+
description: 'Summarise each feature in the report with its scenario counts by status. Use this for "how are we doing" questions rather than listing every scenario.',
|
|
2945
|
+
annotations: READ_ONLY,
|
|
2946
|
+
execute: () => ({
|
|
2947
|
+
...provenance(),
|
|
2948
|
+
summary: report.summary,
|
|
2949
|
+
features: getFeatureSummary(report)
|
|
2950
|
+
})
|
|
2951
|
+
},
|
|
2952
|
+
get_scenario: {
|
|
2953
|
+
title: "Get scenario",
|
|
2954
|
+
description: "Get one scenario by its id or its exact title, with every step and any failure detail. Attachment and document bodies are omitted \u2014 `docKinds` names what the page shows.",
|
|
2955
|
+
annotations: READ_ONLY,
|
|
2956
|
+
input: {
|
|
2957
|
+
type: "object",
|
|
2958
|
+
properties: {
|
|
2959
|
+
idOrTitle: {
|
|
2960
|
+
type: "string",
|
|
2961
|
+
description: "The scenario id, or its exact title as shown in the report."
|
|
2962
|
+
}
|
|
2963
|
+
},
|
|
2964
|
+
required: ["idOrTitle"]
|
|
2965
|
+
},
|
|
2966
|
+
execute: (input) => guarded(() => {
|
|
2967
|
+
const idOrTitle = readString(asRecord(input), "idOrTitle") ?? "";
|
|
2968
|
+
const found = getScenario(report, idOrTitle);
|
|
2969
|
+
if (!found) {
|
|
2970
|
+
return {
|
|
2971
|
+
...provenance(),
|
|
2972
|
+
error: `No scenario found with id or title "${idOrTitle}". Call list_scenarios to see what this report contains.`
|
|
2973
|
+
};
|
|
2974
|
+
}
|
|
2975
|
+
return {
|
|
2976
|
+
...provenance(),
|
|
2977
|
+
feature: {
|
|
2978
|
+
id: found.feature.id,
|
|
2979
|
+
title: found.feature.title,
|
|
2980
|
+
sourceFile: found.feature.sourceFile
|
|
2981
|
+
},
|
|
2982
|
+
// The summary projection, not the raw scenario: doc entry bodies and
|
|
2983
|
+
// base64 attachments are unbounded, and this payload goes straight
|
|
2984
|
+
// into a model's context window.
|
|
2985
|
+
scenario: toScenarioSummary(found.feature, found.scenario)
|
|
2986
|
+
};
|
|
2987
|
+
})
|
|
2988
|
+
},
|
|
2989
|
+
filter_scenarios: {
|
|
2990
|
+
title: "Filter the report",
|
|
2991
|
+
description: `Change what the reader is looking at: set the report's search text, status filter and tag filter. Omitted fields are left as they are; pass an empty string, "all", or an empty array to clear one.`,
|
|
2992
|
+
input: {
|
|
2993
|
+
type: "object",
|
|
2994
|
+
properties: {
|
|
2995
|
+
search: {
|
|
2996
|
+
type: "string",
|
|
2997
|
+
description: "Free text matched against scenario titles, tags and step text. Empty string clears it."
|
|
2998
|
+
},
|
|
2999
|
+
status: {
|
|
3000
|
+
type: "string",
|
|
3001
|
+
enum: [...VIEW_STATUSES],
|
|
3002
|
+
description: 'Show only scenarios with this status. "all" clears the filter.'
|
|
3003
|
+
},
|
|
3004
|
+
tags: {
|
|
3005
|
+
type: "array",
|
|
3006
|
+
items: { type: "string" },
|
|
3007
|
+
description: "Show only scenarios carrying any of these tags. Empty array clears the filter."
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
},
|
|
3011
|
+
execute: (input) => guarded(() => {
|
|
3012
|
+
const args = asRecord(input);
|
|
3013
|
+
const search = readString(args, "search");
|
|
3014
|
+
const status = readEnum(args, "status", VIEW_STATUSES);
|
|
3015
|
+
const tags = readStringArray(args, "tags");
|
|
3016
|
+
const applied = {
|
|
3017
|
+
search: search ?? deps.view.query,
|
|
3018
|
+
status: status ?? deps.view.status,
|
|
3019
|
+
tags: tags ?? deps.view.tags
|
|
3020
|
+
};
|
|
3021
|
+
const patch = {};
|
|
3022
|
+
if (search !== void 0) patch.query = search;
|
|
3023
|
+
if (status !== void 0) patch.status = status;
|
|
3024
|
+
if (tags !== void 0) patch.tags = tags;
|
|
3025
|
+
setView(patch);
|
|
3026
|
+
const matched = filterReport(report, {
|
|
3027
|
+
query: applied.search,
|
|
3028
|
+
status: applied.status,
|
|
3029
|
+
tags: applied.tags
|
|
3030
|
+
}).summary.total;
|
|
3031
|
+
const result = {
|
|
3032
|
+
...applied,
|
|
3033
|
+
matched,
|
|
3034
|
+
total: report.summary.total
|
|
3035
|
+
};
|
|
3036
|
+
onAgentFilter?.(result);
|
|
3037
|
+
return { ...provenance(), applied: result };
|
|
3038
|
+
})
|
|
3039
|
+
}
|
|
3040
|
+
};
|
|
3041
|
+
}
|
|
3042
|
+
|
|
2704
3043
|
// src/interactive/ReportInteractive.tsx
|
|
2705
|
-
import { Fragment as
|
|
3044
|
+
import { Fragment as Fragment14, jsx as jsx49, jsxs as jsxs34 } from "react/jsx-runtime";
|
|
2706
3045
|
function ReportInteractive(props) {
|
|
2707
3046
|
const { report, className, title, dataTheme } = props;
|
|
2708
3047
|
const result = unwrapReport(report);
|
|
2709
3048
|
if (!result.ok) {
|
|
2710
|
-
return /* @__PURE__ */
|
|
3049
|
+
return /* @__PURE__ */ jsx49(
|
|
3050
|
+
ReportErrorShell,
|
|
3051
|
+
{
|
|
3052
|
+
error: result.error,
|
|
3053
|
+
className,
|
|
3054
|
+
title,
|
|
3055
|
+
dataTheme
|
|
3056
|
+
}
|
|
3057
|
+
);
|
|
2711
3058
|
}
|
|
2712
|
-
return /* @__PURE__ */
|
|
3059
|
+
return /* @__PURE__ */ jsx49(ReportInteractiveView, { ...props, report: result.data });
|
|
2713
3060
|
}
|
|
2714
3061
|
var THEME_OPTIONS = [
|
|
2715
3062
|
{ value: "light", label: "Light", Icon: Sun },
|
|
2716
3063
|
{ value: "system", label: "System", Icon: Monitor },
|
|
2717
3064
|
{ value: "dark", label: "Dark", Icon: Moon }
|
|
2718
3065
|
];
|
|
2719
|
-
function ThemeSegment({
|
|
2720
|
-
|
|
3066
|
+
function ThemeSegment({
|
|
3067
|
+
pref,
|
|
3068
|
+
onPref
|
|
3069
|
+
}) {
|
|
3070
|
+
return /* @__PURE__ */ jsx49(
|
|
2721
3071
|
ToggleGroup,
|
|
2722
3072
|
{
|
|
2723
3073
|
size: "sm",
|
|
@@ -2727,7 +3077,17 @@ function ThemeSegment({ pref, onPref }) {
|
|
|
2727
3077
|
if (next) onPref(next);
|
|
2728
3078
|
},
|
|
2729
3079
|
"aria-label": "Color theme",
|
|
2730
|
-
children: THEME_OPTIONS.map(({ value, label, Icon }) => /* @__PURE__ */
|
|
3080
|
+
children: THEME_OPTIONS.map(({ value, label, Icon }) => /* @__PURE__ */ jsx49(
|
|
3081
|
+
ToggleGroupItem,
|
|
3082
|
+
{
|
|
3083
|
+
value,
|
|
3084
|
+
"aria-label": label,
|
|
3085
|
+
title: label,
|
|
3086
|
+
className: "px-2",
|
|
3087
|
+
children: /* @__PURE__ */ jsx49(Icon, {})
|
|
3088
|
+
},
|
|
3089
|
+
value
|
|
3090
|
+
))
|
|
2731
3091
|
}
|
|
2732
3092
|
);
|
|
2733
3093
|
}
|
|
@@ -2745,7 +3105,10 @@ function ReportInteractiveView({
|
|
|
2745
3105
|
}) {
|
|
2746
3106
|
const [urlState, setUrlState] = useUrlState();
|
|
2747
3107
|
const { query, status: statusFilter, tags: activeTags, detail } = urlState;
|
|
2748
|
-
const setQuery = useCallback5(
|
|
3108
|
+
const setQuery = useCallback5(
|
|
3109
|
+
(next) => setUrlState({ query: next }),
|
|
3110
|
+
[setUrlState]
|
|
3111
|
+
);
|
|
2749
3112
|
const setStatusFilter = useCallback5(
|
|
2750
3113
|
(next) => setUrlState({ status: next }),
|
|
2751
3114
|
[setUrlState]
|
|
@@ -2754,7 +3117,7 @@ function ReportInteractiveView({
|
|
|
2754
3117
|
(next) => setUrlState({ detail: next }),
|
|
2755
3118
|
[setUrlState]
|
|
2756
3119
|
);
|
|
2757
|
-
const [helpOpen, setHelpOpen] =
|
|
3120
|
+
const [helpOpen, setHelpOpen] = useState9(false);
|
|
2758
3121
|
const { pref: themePref, setPref: setThemePref } = useTheme();
|
|
2759
3122
|
const searchRef = useRef5(null);
|
|
2760
3123
|
const expandId = useId2();
|
|
@@ -2764,7 +3127,13 @@ function ReportInteractiveView({
|
|
|
2764
3127
|
const filtered = useMemo3(
|
|
2765
3128
|
// Features carrying failures float to the top so the first thing on screen
|
|
2766
3129
|
// is the thing that needs attention; an all-green run keeps source order.
|
|
2767
|
-
() => failuresFirst(
|
|
3130
|
+
() => failuresFirst(
|
|
3131
|
+
filterReport(report, {
|
|
3132
|
+
query: deferredQuery,
|
|
3133
|
+
status: statusFilter,
|
|
3134
|
+
tags: activeTags
|
|
3135
|
+
})
|
|
3136
|
+
),
|
|
2768
3137
|
[report, deferredQuery, statusFilter, activeTags]
|
|
2769
3138
|
);
|
|
2770
3139
|
const isFiltering = query !== deferredQuery;
|
|
@@ -2773,10 +3142,14 @@ function ReportInteractiveView({
|
|
|
2773
3142
|
const opts = [
|
|
2774
3143
|
{ key: "all", label: "All", count: s.total }
|
|
2775
3144
|
];
|
|
2776
|
-
if (s.passed)
|
|
2777
|
-
|
|
2778
|
-
if (s.
|
|
2779
|
-
|
|
3145
|
+
if (s.passed)
|
|
3146
|
+
opts.push({ key: "passed", label: "Passed", count: s.passed });
|
|
3147
|
+
if (s.failed)
|
|
3148
|
+
opts.push({ key: "failed", label: "Failed", count: s.failed });
|
|
3149
|
+
if (s.skipped)
|
|
3150
|
+
opts.push({ key: "skipped", label: "Skipped", count: s.skipped });
|
|
3151
|
+
if (s.pending)
|
|
3152
|
+
opts.push({ key: "pending", label: "Pending", count: s.pending });
|
|
2780
3153
|
return opts;
|
|
2781
3154
|
}, [report]);
|
|
2782
3155
|
const tagOptions = useMemo3(() => allTags(report), [report]);
|
|
@@ -2786,7 +3159,7 @@ function ReportInteractiveView({
|
|
|
2786
3159
|
}),
|
|
2787
3160
|
[activeTags, setUrlState]
|
|
2788
3161
|
);
|
|
2789
|
-
const [toast, setToast] =
|
|
3162
|
+
const [toast, setToast] = useState9(null);
|
|
2790
3163
|
const toastTimer = useRef5(null);
|
|
2791
3164
|
const showToast = useCallback5((msg) => {
|
|
2792
3165
|
setToast(msg);
|
|
@@ -2796,7 +3169,10 @@ function ReportInteractiveView({
|
|
|
2796
3169
|
const copy = useCallback5(
|
|
2797
3170
|
(text, msg) => {
|
|
2798
3171
|
if (typeof navigator !== "undefined" && navigator.clipboard) {
|
|
2799
|
-
navigator.clipboard.writeText(text).then(
|
|
3172
|
+
navigator.clipboard.writeText(text).then(
|
|
3173
|
+
() => showToast(msg),
|
|
3174
|
+
() => showToast("Copy failed")
|
|
3175
|
+
);
|
|
2800
3176
|
}
|
|
2801
3177
|
},
|
|
2802
3178
|
[showToast]
|
|
@@ -2814,9 +3190,21 @@ function ReportInteractiveView({
|
|
|
2814
3190
|
() => report.features.flatMap((f) => [f.id, ...f.scenarios.map((s) => s.id)]),
|
|
2815
3191
|
[report]
|
|
2816
3192
|
);
|
|
2817
|
-
const collapse = useCollapseState(
|
|
2818
|
-
|
|
2819
|
-
|
|
3193
|
+
const collapse = useCollapseState(
|
|
3194
|
+
useCallback5(() => defaultCollapsedIds(report), [report])
|
|
3195
|
+
);
|
|
3196
|
+
const revealFilteredResults = deferredQuery.trim() !== "" || statusFilter !== "all" || activeTags.length > 0;
|
|
3197
|
+
const visibleCollapse = useMemo3(
|
|
3198
|
+
() => revealFilteredResults ? { ...collapse.api, isCollapsed: () => false } : collapse.api,
|
|
3199
|
+
[collapse.api, revealFilteredResults]
|
|
3200
|
+
);
|
|
3201
|
+
const collapseAll = useCallback5(
|
|
3202
|
+
() => collapse.collapseAll(allCollapsibleIds),
|
|
3203
|
+
[collapse, allCollapsibleIds]
|
|
3204
|
+
);
|
|
3205
|
+
const [expandedAll, setExpandedAll] = useState9(
|
|
3206
|
+
() => report.summary.failed === 0
|
|
3207
|
+
);
|
|
2820
3208
|
const setExpanded = useCallback5(
|
|
2821
3209
|
(expanded) => {
|
|
2822
3210
|
if (expanded) collapse.expandAll();
|
|
@@ -2825,6 +3213,20 @@ function ReportInteractiveView({
|
|
|
2825
3213
|
},
|
|
2826
3214
|
[collapse, collapseAll]
|
|
2827
3215
|
);
|
|
3216
|
+
const [agentFilter, setAgentFilter] = useState9(null);
|
|
3217
|
+
useTools(
|
|
3218
|
+
reportTools({
|
|
3219
|
+
report,
|
|
3220
|
+
view: urlState,
|
|
3221
|
+
setView: setUrlState,
|
|
3222
|
+
onAgentFilter: setAgentFilter
|
|
3223
|
+
})
|
|
3224
|
+
);
|
|
3225
|
+
const showAgentFilter = agentFilter !== null && agentFilter.search === query && agentFilter.status === statusFilter && agentFilter.tags.length === activeTags.length && agentFilter.tags.every((tag) => activeTags.includes(tag));
|
|
3226
|
+
const clearFilters = useCallback5(
|
|
3227
|
+
() => setUrlState({ query: "", status: "all", tags: [] }),
|
|
3228
|
+
[setUrlState]
|
|
3229
|
+
);
|
|
2828
3230
|
const focusSearch = useCallback5(() => {
|
|
2829
3231
|
searchRef.current?.focus();
|
|
2830
3232
|
}, []);
|
|
@@ -2843,7 +3245,7 @@ function ReportInteractiveView({
|
|
|
2843
3245
|
const escape = useCallback5(() => {
|
|
2844
3246
|
if (helpOpen) setHelpOpen(false);
|
|
2845
3247
|
else if (query !== "") setQuery("");
|
|
2846
|
-
}, [helpOpen, query]);
|
|
3248
|
+
}, [helpOpen, query, setQuery]);
|
|
2847
3249
|
useKeyboardShortcuts({
|
|
2848
3250
|
onFocusSearch: focusSearch,
|
|
2849
3251
|
onNextFailure: () => stepFailure(1),
|
|
@@ -2858,13 +3260,13 @@ function ReportInteractiveView({
|
|
|
2858
3260
|
const totalScenarios = report.summary.total;
|
|
2859
3261
|
const matchedScenarios = filtered.summary.total;
|
|
2860
3262
|
const showToc = !hideToc && totalScenarios > 3;
|
|
2861
|
-
return /* @__PURE__ */
|
|
3263
|
+
return /* @__PURE__ */ jsx49(CollapseProvider, { value: visibleCollapse, children: /* @__PURE__ */ jsx49(ScenarioActionsProvider, { value: scenarioActions, children: /* @__PURE__ */ jsx49(ScenarioHistoryProvider, { value: scenarioHistory ?? null, children: /* @__PURE__ */ jsx49(ScenarioFreshnessProvider, { value: { staleAfterDays, report }, children: /* @__PURE__ */ jsx49(
|
|
2862
3264
|
ReportRoot,
|
|
2863
3265
|
{
|
|
2864
3266
|
report: filtered,
|
|
2865
3267
|
customRenderers,
|
|
2866
3268
|
renderers,
|
|
2867
|
-
children: /* @__PURE__ */
|
|
3269
|
+
children: /* @__PURE__ */ jsxs34(
|
|
2868
3270
|
"main",
|
|
2869
3271
|
{
|
|
2870
3272
|
className: cn("es-report", "es-report-interactive", className),
|
|
@@ -2873,8 +3275,8 @@ function ReportInteractiveView({
|
|
|
2873
3275
|
"data-theme": dataTheme,
|
|
2874
3276
|
"data-detail-level": detail,
|
|
2875
3277
|
children: [
|
|
2876
|
-
/* @__PURE__ */
|
|
2877
|
-
/* @__PURE__ */
|
|
3278
|
+
/* @__PURE__ */ jsxs34("header", { className: "es-report-header", children: [
|
|
3279
|
+
/* @__PURE__ */ jsxs34(
|
|
2878
3280
|
"div",
|
|
2879
3281
|
{
|
|
2880
3282
|
"data-es-navbar": true,
|
|
@@ -2883,9 +3285,9 @@ function ReportInteractiveView({
|
|
|
2883
3285
|
hideHeader ? "justify-end" : "justify-between"
|
|
2884
3286
|
),
|
|
2885
3287
|
children: [
|
|
2886
|
-
hideHeader ? null : /* @__PURE__ */
|
|
2887
|
-
/* @__PURE__ */
|
|
2888
|
-
/* @__PURE__ */
|
|
3288
|
+
hideHeader ? null : /* @__PURE__ */ jsx49("h1", { className: "min-w-0", children: title ?? "Story Report" }),
|
|
3289
|
+
/* @__PURE__ */ jsxs34("div", { className: "flex items-center gap-2", children: [
|
|
3290
|
+
/* @__PURE__ */ jsx49(
|
|
2889
3291
|
ReportSearch,
|
|
2890
3292
|
{
|
|
2891
3293
|
ref: searchRef,
|
|
@@ -2895,16 +3297,16 @@ function ReportInteractiveView({
|
|
|
2895
3297
|
totalCount: totalScenarios
|
|
2896
3298
|
}
|
|
2897
3299
|
),
|
|
2898
|
-
/* @__PURE__ */
|
|
3300
|
+
/* @__PURE__ */ jsx49(ThemeSegment, { pref: themePref, onPref: setThemePref })
|
|
2899
3301
|
] })
|
|
2900
3302
|
]
|
|
2901
3303
|
}
|
|
2902
3304
|
),
|
|
2903
|
-
hideHeader ? null : /* @__PURE__ */
|
|
2904
|
-
/* @__PURE__ */
|
|
2905
|
-
/* @__PURE__ */
|
|
3305
|
+
hideHeader ? null : /* @__PURE__ */ jsxs34(Fragment14, { children: [
|
|
3306
|
+
/* @__PURE__ */ jsx49(ReportSummary, {}),
|
|
3307
|
+
/* @__PURE__ */ jsx49(ReportMeta, {})
|
|
2906
3308
|
] }),
|
|
2907
|
-
/* @__PURE__ */
|
|
3309
|
+
/* @__PURE__ */ jsx49(
|
|
2908
3310
|
ReportFreshness,
|
|
2909
3311
|
{
|
|
2910
3312
|
lastRunMs: reportLastRunMs(report),
|
|
@@ -2912,8 +3314,14 @@ function ReportInteractiveView({
|
|
|
2912
3314
|
staleAfterDays
|
|
2913
3315
|
}
|
|
2914
3316
|
),
|
|
2915
|
-
/* @__PURE__ */
|
|
2916
|
-
|
|
3317
|
+
/* @__PURE__ */ jsx49(
|
|
3318
|
+
ReportLastRunDelta,
|
|
3319
|
+
{
|
|
3320
|
+
history: scenarioHistory,
|
|
3321
|
+
report
|
|
3322
|
+
}
|
|
3323
|
+
),
|
|
3324
|
+
/* @__PURE__ */ jsx49(
|
|
2917
3325
|
ReportFilters,
|
|
2918
3326
|
{
|
|
2919
3327
|
statuses: statusOptions,
|
|
@@ -2924,14 +3332,28 @@ function ReportInteractiveView({
|
|
|
2924
3332
|
onToggleTag: toggleTag
|
|
2925
3333
|
}
|
|
2926
3334
|
),
|
|
2927
|
-
/* @__PURE__ */
|
|
2928
|
-
showToc ? /* @__PURE__ */
|
|
2929
|
-
/* @__PURE__ */
|
|
2930
|
-
/* @__PURE__ */
|
|
2931
|
-
|
|
3335
|
+
/* @__PURE__ */ jsxs34("div", { className: "flex flex-wrap items-center gap-x-6 gap-y-2 border-t border-border/60 pt-3", children: [
|
|
3336
|
+
showToc ? /* @__PURE__ */ jsx49(ReportTocDrawer, {}) : null,
|
|
3337
|
+
/* @__PURE__ */ jsxs34("div", { className: "flex items-center gap-2", children: [
|
|
3338
|
+
/* @__PURE__ */ jsx49(
|
|
3339
|
+
Switch,
|
|
3340
|
+
{
|
|
3341
|
+
id: expandId,
|
|
3342
|
+
checked: expandedAll,
|
|
3343
|
+
onCheckedChange: setExpanded
|
|
3344
|
+
}
|
|
3345
|
+
),
|
|
3346
|
+
/* @__PURE__ */ jsx49(
|
|
3347
|
+
"label",
|
|
3348
|
+
{
|
|
3349
|
+
htmlFor: expandId,
|
|
3350
|
+
className: "cursor-pointer text-sm text-muted-foreground select-none",
|
|
3351
|
+
children: "Expand all"
|
|
3352
|
+
}
|
|
3353
|
+
)
|
|
2932
3354
|
] }),
|
|
2933
|
-
/* @__PURE__ */
|
|
2934
|
-
/* @__PURE__ */
|
|
3355
|
+
/* @__PURE__ */ jsxs34("div", { className: "flex items-center gap-2", children: [
|
|
3356
|
+
/* @__PURE__ */ jsx49(
|
|
2935
3357
|
Switch,
|
|
2936
3358
|
{
|
|
2937
3359
|
id: docsId,
|
|
@@ -2939,16 +3361,36 @@ function ReportInteractiveView({
|
|
|
2939
3361
|
onCheckedChange: (checked) => setDetail(checked ? "full" : "minimal")
|
|
2940
3362
|
}
|
|
2941
3363
|
),
|
|
2942
|
-
/* @__PURE__ */
|
|
3364
|
+
/* @__PURE__ */ jsx49(
|
|
3365
|
+
"label",
|
|
3366
|
+
{
|
|
3367
|
+
htmlFor: docsId,
|
|
3368
|
+
className: "cursor-pointer text-sm text-muted-foreground select-none",
|
|
3369
|
+
children: "Show documentation"
|
|
3370
|
+
}
|
|
3371
|
+
)
|
|
2943
3372
|
] })
|
|
2944
3373
|
] })
|
|
2945
3374
|
] }),
|
|
2946
|
-
/* @__PURE__ */
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
3375
|
+
showAgentFilter ? /* @__PURE__ */ jsx49(
|
|
3376
|
+
ReportAgentFilterNotice,
|
|
3377
|
+
{
|
|
3378
|
+
applied: agentFilter,
|
|
3379
|
+
onReset: clearFilters,
|
|
3380
|
+
onDismiss: () => setAgentFilter(null)
|
|
3381
|
+
}
|
|
3382
|
+
) : null,
|
|
3383
|
+
/* @__PURE__ */ jsx49(ReportFailureBanner, { failures }),
|
|
3384
|
+
hasContent ? showToc ? /* @__PURE__ */ jsxs34("div", { className: "flex gap-6", children: [
|
|
3385
|
+
/* @__PURE__ */ jsx49(ReportToc, {}),
|
|
3386
|
+
/* @__PURE__ */ jsx49("div", { className: "flex min-w-0 flex-1 flex-col gap-4", children: /* @__PURE__ */ jsx49(ReportFeatureList, {}) })
|
|
3387
|
+
] }) : /* @__PURE__ */ jsx49("div", { className: "flex flex-col gap-4", children: /* @__PURE__ */ jsx49(ReportFeatureList, {}) }) : /* @__PURE__ */ jsx49(
|
|
3388
|
+
ReportEmpty,
|
|
3389
|
+
{
|
|
3390
|
+
message: query ? "No scenarios match the search." : void 0
|
|
3391
|
+
}
|
|
3392
|
+
),
|
|
3393
|
+
/* @__PURE__ */ jsx49(
|
|
2952
3394
|
"button",
|
|
2953
3395
|
{
|
|
2954
3396
|
type: "button",
|
|
@@ -2959,8 +3401,14 @@ function ReportInteractiveView({
|
|
|
2959
3401
|
children: "?"
|
|
2960
3402
|
}
|
|
2961
3403
|
),
|
|
2962
|
-
/* @__PURE__ */
|
|
2963
|
-
|
|
3404
|
+
/* @__PURE__ */ jsx49(
|
|
3405
|
+
ReportShortcutsHelp,
|
|
3406
|
+
{
|
|
3407
|
+
open: helpOpen,
|
|
3408
|
+
onClose: () => setHelpOpen(false)
|
|
3409
|
+
}
|
|
3410
|
+
),
|
|
3411
|
+
toast ? /* @__PURE__ */ jsx49(
|
|
2964
3412
|
"div",
|
|
2965
3413
|
{
|
|
2966
3414
|
role: "status",
|
|
@@ -2973,18 +3421,18 @@ function ReportInteractiveView({
|
|
|
2973
3421
|
}
|
|
2974
3422
|
)
|
|
2975
3423
|
}
|
|
2976
|
-
) }) }) });
|
|
3424
|
+
) }) }) }) });
|
|
2977
3425
|
}
|
|
2978
3426
|
|
|
2979
3427
|
// src/interactive/ReportInteractiveIsland.tsx
|
|
2980
3428
|
import { useMemo as useMemo4 } from "react";
|
|
2981
3429
|
|
|
2982
3430
|
// src/interactive/island-renderers.tsx
|
|
2983
|
-
import { createElement, useEffect as
|
|
3431
|
+
import { createElement, useEffect as useEffect10, useState as useState11 } from "react";
|
|
2984
3432
|
|
|
2985
3433
|
// src/components/doc/MermaidView.tsx
|
|
2986
|
-
import { useEffect as
|
|
2987
|
-
import { Fragment as
|
|
3434
|
+
import { useEffect as useEffect9, useRef as useRef6, useState as useState10 } from "react";
|
|
3435
|
+
import { Fragment as Fragment15, jsx as jsx50, jsxs as jsxs35 } from "react/jsx-runtime";
|
|
2988
3436
|
function prefersDark(el) {
|
|
2989
3437
|
const themed = el?.closest("[data-theme]");
|
|
2990
3438
|
if (themed?.dataset.theme === "dark") return true;
|
|
@@ -2993,9 +3441,9 @@ function prefersDark(el) {
|
|
|
2993
3441
|
}
|
|
2994
3442
|
function MermaidView({ entry, load }) {
|
|
2995
3443
|
const ref = useRef6(null);
|
|
2996
|
-
const [svg, setSvg] =
|
|
2997
|
-
const [error, setError] =
|
|
2998
|
-
|
|
3444
|
+
const [svg, setSvg] = useState10(null);
|
|
3445
|
+
const [error, setError] = useState10(null);
|
|
3446
|
+
useEffect9(() => {
|
|
2999
3447
|
let cancelled = false;
|
|
3000
3448
|
const draw = async () => {
|
|
3001
3449
|
let mermaid;
|
|
@@ -3038,9 +3486,9 @@ function MermaidView({ entry, load }) {
|
|
|
3038
3486
|
};
|
|
3039
3487
|
}, [entry.code, load]);
|
|
3040
3488
|
if (svg) {
|
|
3041
|
-
return /* @__PURE__ */
|
|
3042
|
-
entry.title ? /* @__PURE__ */
|
|
3043
|
-
/* @__PURE__ */
|
|
3489
|
+
return /* @__PURE__ */ jsxs35("figure", { ref, className: "my-3", children: [
|
|
3490
|
+
entry.title ? /* @__PURE__ */ jsx50("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
|
|
3491
|
+
/* @__PURE__ */ jsx50(
|
|
3044
3492
|
"div",
|
|
3045
3493
|
{
|
|
3046
3494
|
role: "img",
|
|
@@ -3051,8 +3499,8 @@ function MermaidView({ entry, load }) {
|
|
|
3051
3499
|
)
|
|
3052
3500
|
] });
|
|
3053
3501
|
}
|
|
3054
|
-
return /* @__PURE__ */
|
|
3055
|
-
error ? /* @__PURE__ */
|
|
3502
|
+
return /* @__PURE__ */ jsxs35(Fragment15, { children: [
|
|
3503
|
+
error ? /* @__PURE__ */ jsxs35(
|
|
3056
3504
|
"p",
|
|
3057
3505
|
{
|
|
3058
3506
|
role: "alert",
|
|
@@ -3064,12 +3512,12 @@ function MermaidView({ entry, load }) {
|
|
|
3064
3512
|
]
|
|
3065
3513
|
}
|
|
3066
3514
|
) : null,
|
|
3067
|
-
/* @__PURE__ */
|
|
3515
|
+
/* @__PURE__ */ jsx50(MermaidSource, { entry, ref })
|
|
3068
3516
|
] });
|
|
3069
3517
|
}
|
|
3070
3518
|
|
|
3071
3519
|
// src/interactive/island-renderers.tsx
|
|
3072
|
-
import { jsx as
|
|
3520
|
+
import { jsx as jsx51 } from "react/jsx-runtime";
|
|
3073
3521
|
var HLJS_ESM = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/es/highlight.min.js";
|
|
3074
3522
|
var MERMAID_ESM = "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
|
|
3075
3523
|
function cachedCdnModule(url) {
|
|
@@ -3086,8 +3534,8 @@ function cachedCdnModule(url) {
|
|
|
3086
3534
|
var loadMermaidFromCdn = cachedCdnModule(MERMAID_ESM);
|
|
3087
3535
|
var loadHljs = cachedCdnModule(HLJS_ESM);
|
|
3088
3536
|
function CdnHighlightedCode({ entry }) {
|
|
3089
|
-
const [html, setHtml] =
|
|
3090
|
-
|
|
3537
|
+
const [html, setHtml] = useState11(null);
|
|
3538
|
+
useEffect10(() => {
|
|
3091
3539
|
let cancelled = false;
|
|
3092
3540
|
void (async () => {
|
|
3093
3541
|
try {
|
|
@@ -3103,13 +3551,13 @@ function CdnHighlightedCode({ entry }) {
|
|
|
3103
3551
|
cancelled = true;
|
|
3104
3552
|
};
|
|
3105
3553
|
}, [entry.content, entry.lang]);
|
|
3106
|
-
return /* @__PURE__ */
|
|
3554
|
+
return /* @__PURE__ */ jsx51(CodeFigure, { label: entry.label, children: html !== null ? /* @__PURE__ */ jsx51(
|
|
3107
3555
|
"code",
|
|
3108
3556
|
{
|
|
3109
3557
|
className: cn("hljs font-mono text-xs", entry.lang && `language-${entry.lang}`),
|
|
3110
3558
|
dangerouslySetInnerHTML: { __html: html }
|
|
3111
3559
|
}
|
|
3112
|
-
) : /* @__PURE__ */
|
|
3560
|
+
) : /* @__PURE__ */ jsx51("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
|
|
3113
3561
|
}
|
|
3114
3562
|
function buildIslandRenderers(opts) {
|
|
3115
3563
|
const renderers = {};
|
|
@@ -3123,7 +3571,7 @@ function buildIslandRenderers(opts) {
|
|
|
3123
3571
|
}
|
|
3124
3572
|
|
|
3125
3573
|
// src/interactive/ReportInteractiveIsland.tsx
|
|
3126
|
-
import { jsx as
|
|
3574
|
+
import { jsx as jsx52 } from "react/jsx-runtime";
|
|
3127
3575
|
function ReportInteractiveIsland({
|
|
3128
3576
|
report,
|
|
3129
3577
|
title,
|
|
@@ -3137,7 +3585,7 @@ function ReportInteractiveIsland({
|
|
|
3137
3585
|
() => buildIslandRenderers({ syntaxHighlighting, mermaid }),
|
|
3138
3586
|
[syntaxHighlighting, mermaid]
|
|
3139
3587
|
);
|
|
3140
|
-
return /* @__PURE__ */
|
|
3588
|
+
return /* @__PURE__ */ jsx52(
|
|
3141
3589
|
ReportInteractive,
|
|
3142
3590
|
{
|
|
3143
3591
|
report,
|