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