executable-stories-react 0.10.1 → 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 +482 -451
- 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 +440 -407
- package/dist/index.js.map +1 -1
- package/dist/interactive.cjs +1430 -1246
- 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 +1473 -1286
- package/dist/interactive.js.map +1 -1
- package/dist/parse.cjs +65 -139
- package/dist/parse.cjs.map +1 -1
- package/dist/parse.js +65 -139
- package/dist/parse.js.map +1 -1
- package/dist/report-island.global.js +30 -28
- package/dist/ssr.cjs +367 -254
- package/dist/ssr.cjs.map +1 -1
- package/dist/ssr.js +325 -209
- 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,334 +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
|
-
});
|
|
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();
|
|
164
284
|
}
|
|
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/components/doc/DocSection.tsx
|
|
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/components/doc/DocSection.tsx
|
|
398
|
-
import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
399
285
|
function neutralizeUrl(value) {
|
|
400
286
|
return safeUrl(value) ?? "#";
|
|
401
287
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
}
|
|
288
|
+
|
|
289
|
+
// src/components/doc/DocSection.tsx
|
|
290
|
+
import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
406
291
|
function DocSection({ entry }) {
|
|
407
292
|
const renderers = useBuiltinRenderers();
|
|
408
293
|
if (renderers.section) {
|
|
409
|
-
return /* @__PURE__ */
|
|
294
|
+
return /* @__PURE__ */ jsx10(Fragment2, { children: renderers.section(entry) });
|
|
410
295
|
}
|
|
411
296
|
const html = safeMarkdownHtml(entry.markdown);
|
|
412
|
-
return /* @__PURE__ */
|
|
413
|
-
entry.title ? /* @__PURE__ */
|
|
414
|
-
/* @__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(
|
|
415
300
|
"div",
|
|
416
301
|
{
|
|
417
302
|
className: "es-doc-prose prose prose-sm max-w-none",
|
|
@@ -422,11 +307,11 @@ function DocSection({ entry }) {
|
|
|
422
307
|
}
|
|
423
308
|
|
|
424
309
|
// src/components/doc/DocMermaid.tsx
|
|
425
|
-
import { Fragment as Fragment3, jsx as
|
|
310
|
+
import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
426
311
|
function MermaidSource({ entry, ref }) {
|
|
427
|
-
return /* @__PURE__ */
|
|
428
|
-
entry.title ? /* @__PURE__ */
|
|
429
|
-
/* @__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(
|
|
430
315
|
"pre",
|
|
431
316
|
{
|
|
432
317
|
"data-mermaid": true,
|
|
@@ -440,24 +325,24 @@ function MermaidSource({ entry, ref }) {
|
|
|
440
325
|
function DocMermaid({ entry }) {
|
|
441
326
|
const renderers = useBuiltinRenderers();
|
|
442
327
|
if (renderers.mermaid) {
|
|
443
|
-
return /* @__PURE__ */
|
|
328
|
+
return /* @__PURE__ */ jsx11(Fragment3, { children: renderers.mermaid(entry) });
|
|
444
329
|
}
|
|
445
|
-
return /* @__PURE__ */
|
|
330
|
+
return /* @__PURE__ */ jsx11(MermaidSource, { entry });
|
|
446
331
|
}
|
|
447
332
|
|
|
448
333
|
// src/components/doc/DocScreenshot.tsx
|
|
449
|
-
import { jsx as
|
|
334
|
+
import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
450
335
|
function DocScreenshot({ entry }) {
|
|
451
336
|
const src = isLocalFsPath(entry.path) ? void 0 : safeImageUrl(entry.path);
|
|
452
337
|
if (!src) {
|
|
453
|
-
return /* @__PURE__ */
|
|
454
|
-
/* @__PURE__ */
|
|
455
|
-
/* @__PURE__ */
|
|
456
|
-
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
|
|
457
342
|
] });
|
|
458
343
|
}
|
|
459
|
-
return /* @__PURE__ */
|
|
460
|
-
/* @__PURE__ */
|
|
344
|
+
return /* @__PURE__ */ jsxs6("figure", { className: "my-3", children: [
|
|
345
|
+
/* @__PURE__ */ jsx12(
|
|
461
346
|
"img",
|
|
462
347
|
{
|
|
463
348
|
src,
|
|
@@ -466,24 +351,24 @@ function DocScreenshot({ entry }) {
|
|
|
466
351
|
className: "max-w-full rounded-md border border-border"
|
|
467
352
|
}
|
|
468
353
|
),
|
|
469
|
-
entry.alt ? /* @__PURE__ */
|
|
354
|
+
entry.alt ? /* @__PURE__ */ jsx12("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: entry.alt }) : null
|
|
470
355
|
] });
|
|
471
356
|
}
|
|
472
357
|
|
|
473
358
|
// src/components/doc/DocVideo.tsx
|
|
474
|
-
import { jsx as
|
|
359
|
+
import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
475
360
|
function DocVideo({ entry }) {
|
|
476
361
|
if (isLocalFsPath(entry.path)) {
|
|
477
|
-
return /* @__PURE__ */
|
|
478
|
-
/* @__PURE__ */
|
|
479
|
-
/* @__PURE__ */
|
|
480
|
-
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
|
|
481
366
|
] });
|
|
482
367
|
}
|
|
483
368
|
const src = safeUrl(entry.path);
|
|
484
369
|
const poster = safeUrl(entry.poster);
|
|
485
|
-
return /* @__PURE__ */
|
|
486
|
-
/* @__PURE__ */
|
|
370
|
+
return /* @__PURE__ */ jsxs7("figure", { className: "my-3", children: [
|
|
371
|
+
/* @__PURE__ */ jsx13(
|
|
487
372
|
"video",
|
|
488
373
|
{
|
|
489
374
|
controls: true,
|
|
@@ -493,21 +378,21 @@ function DocVideo({ entry }) {
|
|
|
493
378
|
className: "max-w-full rounded-md border border-border"
|
|
494
379
|
}
|
|
495
380
|
),
|
|
496
|
-
entry.caption ? /* @__PURE__ */
|
|
381
|
+
entry.caption ? /* @__PURE__ */ jsx13("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: entry.caption }) : null
|
|
497
382
|
] });
|
|
498
383
|
}
|
|
499
384
|
|
|
500
385
|
// src/components/doc/DocHtml.tsx
|
|
501
|
-
import { jsx as
|
|
386
|
+
import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
502
387
|
function DocHtml({ entry }) {
|
|
503
388
|
const height = typeof entry.height === "number" ? `${entry.height}px` : entry.height ?? "400px";
|
|
504
389
|
const title = entry.title ?? "Embedded HTML";
|
|
505
390
|
const safeHref = safeUrl(entry.url ?? entry.path);
|
|
506
391
|
const frameProps = entry.content !== void 0 ? { srcDoc: entry.content } : { src: safeHref ?? "" };
|
|
507
|
-
return /* @__PURE__ */
|
|
508
|
-
/* @__PURE__ */
|
|
509
|
-
/* @__PURE__ */
|
|
510
|
-
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(
|
|
511
396
|
"a",
|
|
512
397
|
{
|
|
513
398
|
href: safeHref,
|
|
@@ -520,7 +405,7 @@ function DocHtml({ entry }) {
|
|
|
520
405
|
}
|
|
521
406
|
) : null
|
|
522
407
|
] }),
|
|
523
|
-
/* @__PURE__ */
|
|
408
|
+
/* @__PURE__ */ jsx14(
|
|
524
409
|
"iframe",
|
|
525
410
|
{
|
|
526
411
|
...frameProps,
|
|
@@ -535,7 +420,7 @@ function DocHtml({ entry }) {
|
|
|
535
420
|
}
|
|
536
421
|
|
|
537
422
|
// src/components/doc/DocState.tsx
|
|
538
|
-
import { jsx as
|
|
423
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
539
424
|
function DocState({ entry }) {
|
|
540
425
|
let json;
|
|
541
426
|
try {
|
|
@@ -543,23 +428,23 @@ function DocState({ entry }) {
|
|
|
543
428
|
} catch {
|
|
544
429
|
json = String(entry.value);
|
|
545
430
|
}
|
|
546
|
-
return /* @__PURE__ */
|
|
431
|
+
return /* @__PURE__ */ jsx15(CodeFigure, { label: entry.label ?? "State", children: /* @__PURE__ */ jsx15("code", { className: "font-mono text-xs language-json", children: json }) });
|
|
547
432
|
}
|
|
548
433
|
|
|
549
434
|
// src/components/doc/DocCustom.tsx
|
|
550
|
-
import { Fragment as Fragment4, jsx as
|
|
435
|
+
import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
551
436
|
function DocCustom({ entry }) {
|
|
552
437
|
const renderers = useCustomRenderers();
|
|
553
438
|
const renderer = renderers[entry.type];
|
|
554
439
|
if (renderer) {
|
|
555
|
-
return /* @__PURE__ */
|
|
440
|
+
return /* @__PURE__ */ jsx16(Fragment4, { children: renderer(entry) });
|
|
556
441
|
}
|
|
557
|
-
return /* @__PURE__ */
|
|
558
|
-
/* @__PURE__ */
|
|
559
|
-
/* @__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) })
|
|
560
445
|
] });
|
|
561
446
|
}
|
|
562
|
-
function
|
|
447
|
+
function safeStringify(value) {
|
|
563
448
|
try {
|
|
564
449
|
return JSON.stringify(value, null, 2);
|
|
565
450
|
} catch {
|
|
@@ -568,68 +453,47 @@ function safeStringify2(value) {
|
|
|
568
453
|
}
|
|
569
454
|
|
|
570
455
|
// src/components/doc/DocEntry.tsx
|
|
571
|
-
import { jsx as
|
|
456
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
572
457
|
function DocEntry({ entry }) {
|
|
573
458
|
switch (entry.kind) {
|
|
574
459
|
case "note":
|
|
575
|
-
return /* @__PURE__ */
|
|
460
|
+
return /* @__PURE__ */ jsx17(DocNote, { entry });
|
|
576
461
|
case "tag":
|
|
577
|
-
return /* @__PURE__ */
|
|
462
|
+
return /* @__PURE__ */ jsx17(DocTag, { entry });
|
|
578
463
|
case "kv":
|
|
579
|
-
return /* @__PURE__ */
|
|
464
|
+
return /* @__PURE__ */ jsx17(DocKv, { entry });
|
|
580
465
|
case "code":
|
|
581
|
-
return /* @__PURE__ */
|
|
466
|
+
return /* @__PURE__ */ jsx17(DocCode, { entry });
|
|
582
467
|
case "table":
|
|
583
|
-
return /* @__PURE__ */
|
|
468
|
+
return /* @__PURE__ */ jsx17(DocTable, { entry });
|
|
584
469
|
case "link":
|
|
585
|
-
return /* @__PURE__ */
|
|
470
|
+
return /* @__PURE__ */ jsx17(DocLink, { entry });
|
|
586
471
|
case "section":
|
|
587
|
-
return /* @__PURE__ */
|
|
472
|
+
return /* @__PURE__ */ jsx17(DocSection, { entry });
|
|
588
473
|
case "mermaid":
|
|
589
|
-
return /* @__PURE__ */
|
|
474
|
+
return /* @__PURE__ */ jsx17(DocMermaid, { entry });
|
|
590
475
|
case "screenshot":
|
|
591
|
-
return /* @__PURE__ */
|
|
476
|
+
return /* @__PURE__ */ jsx17(DocScreenshot, { entry });
|
|
592
477
|
case "video":
|
|
593
|
-
return /* @__PURE__ */
|
|
478
|
+
return /* @__PURE__ */ jsx17(DocVideo, { entry });
|
|
594
479
|
case "html":
|
|
595
|
-
return /* @__PURE__ */
|
|
480
|
+
return /* @__PURE__ */ jsx17(DocHtml, { entry });
|
|
596
481
|
case "state":
|
|
597
|
-
return /* @__PURE__ */
|
|
482
|
+
return /* @__PURE__ */ jsx17(DocState, { entry });
|
|
598
483
|
case "custom":
|
|
599
|
-
return /* @__PURE__ */
|
|
484
|
+
return /* @__PURE__ */ jsx17(DocCustom, { entry });
|
|
600
485
|
}
|
|
601
486
|
}
|
|
602
487
|
|
|
603
488
|
// src/components/ReportDocEntries.tsx
|
|
604
|
-
import { Fragment as Fragment5, jsx as
|
|
489
|
+
import { Fragment as Fragment5, jsx as jsx18 } from "react/jsx-runtime";
|
|
605
490
|
function ReportDocEntries({ entries }) {
|
|
606
491
|
if (entries.length === 0) return null;
|
|
607
|
-
return /* @__PURE__ */
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
// src/lib/step-params.tsx
|
|
611
|
-
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
612
|
-
var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.-])\d+(?:\.\d+)?(?![\w.-])/g;
|
|
613
|
-
function highlightStepParams(text) {
|
|
614
|
-
const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
|
|
615
|
-
if (matches.length === 0) return text;
|
|
616
|
-
const nodes = [];
|
|
617
|
-
let lastIndex = 0;
|
|
618
|
-
matches.forEach((match, i) => {
|
|
619
|
-
const start = match.index;
|
|
620
|
-
const end = start + match[0].length;
|
|
621
|
-
if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
|
|
622
|
-
nodes.push(
|
|
623
|
-
/* @__PURE__ */ jsx18("span", { className: "font-medium text-step-param italic", children: match[0] }, i)
|
|
624
|
-
);
|
|
625
|
-
lastIndex = end;
|
|
626
|
-
});
|
|
627
|
-
if (lastIndex < text.length) nodes.push(text.slice(lastIndex));
|
|
628
|
-
return nodes;
|
|
492
|
+
return /* @__PURE__ */ jsx18(Fragment5, { children: entries.map((entry, i) => /* @__PURE__ */ jsx18(DocEntry, { entry }, i)) });
|
|
629
493
|
}
|
|
630
494
|
|
|
631
495
|
// src/components/ReportSteps.tsx
|
|
632
|
-
import { jsx as jsx19, jsxs as
|
|
496
|
+
import { jsx as jsx19, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
633
497
|
var STEP_GLYPH = {
|
|
634
498
|
passed: "\u2713",
|
|
635
499
|
failed: "\u2717",
|
|
@@ -644,20 +508,46 @@ var STEP_GLYPH_COLOR = {
|
|
|
644
508
|
};
|
|
645
509
|
function ReportSteps({ scenario }) {
|
|
646
510
|
if (scenario.steps.length === 0) return null;
|
|
647
|
-
|
|
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
|
+
)) });
|
|
648
522
|
}
|
|
649
|
-
function ReportStepItem({
|
|
523
|
+
function ReportStepItem({
|
|
524
|
+
step,
|
|
525
|
+
unasserted = false
|
|
526
|
+
}) {
|
|
650
527
|
const kw = step.keyword.toLowerCase();
|
|
651
528
|
const continuation = kw === "and" || kw === "but";
|
|
652
|
-
return /* @__PURE__ */
|
|
529
|
+
return /* @__PURE__ */ jsxs10(
|
|
653
530
|
"li",
|
|
654
531
|
{
|
|
655
532
|
id: step.id,
|
|
656
533
|
"data-status": step.status,
|
|
657
|
-
className: cn(
|
|
534
|
+
className: cn(
|
|
535
|
+
"py-1.5 text-[0.8125rem] leading-normal",
|
|
536
|
+
continuation && "pl-5"
|
|
537
|
+
),
|
|
658
538
|
children: [
|
|
659
|
-
/* @__PURE__ */
|
|
660
|
-
/* @__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
|
+
),
|
|
661
551
|
/* @__PURE__ */ jsx19(
|
|
662
552
|
"span",
|
|
663
553
|
{
|
|
@@ -669,6 +559,7 @@ function ReportStepItem({ step }) {
|
|
|
669
559
|
}
|
|
670
560
|
),
|
|
671
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,
|
|
672
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
|
|
673
564
|
] }),
|
|
674
565
|
step.errorMessage ? /* @__PURE__ */ jsx19(
|
|
@@ -689,7 +580,7 @@ function ReportStepItem({ step }) {
|
|
|
689
580
|
import { extractStoryboardFrames } from "executable-stories-core/storyboard";
|
|
690
581
|
import { summarizeStateChanges } from "executable-stories-core/state-diff";
|
|
691
582
|
import { isLocalFsPath as isLocalFsPath2, safeImageUrl as safeImageUrl2 } from "executable-stories-core/utils/url";
|
|
692
|
-
import { jsx as jsx20, jsxs as
|
|
583
|
+
import { jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
693
584
|
var FRAME_GLYPH = {
|
|
694
585
|
passed: "\u2713",
|
|
695
586
|
failed: "\u2717",
|
|
@@ -705,7 +596,7 @@ var FRAME_GLYPH_COLOR = {
|
|
|
705
596
|
function ReportStoryboard({ scenario }) {
|
|
706
597
|
const frames = extractStoryboardFrames(scenario);
|
|
707
598
|
if (frames.length < 2) return null;
|
|
708
|
-
return /* @__PURE__ */
|
|
599
|
+
return /* @__PURE__ */ jsxs11("section", { "data-slot": "storyboard", "aria-label": "Storyboard", className: "mb-3", children: [
|
|
709
600
|
/* @__PURE__ */ jsx20("h4", { className: "mb-1.5 text-xs font-semibold tracking-wide text-muted-foreground uppercase", children: "Storyboard" }),
|
|
710
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)) })
|
|
711
602
|
] });
|
|
@@ -713,9 +604,9 @@ function ReportStoryboard({ scenario }) {
|
|
|
713
604
|
function StoryboardFrameItem({ frame }) {
|
|
714
605
|
const label = `${frame.keyword} ${frame.text}`;
|
|
715
606
|
const hasStates = frame.states.length > 0;
|
|
716
|
-
const caption = /* @__PURE__ */
|
|
607
|
+
const caption = /* @__PURE__ */ jsxs11("p", { className: "mt-1 flex items-baseline gap-1.5 text-[0.6875rem] leading-snug", children: [
|
|
717
608
|
/* @__PURE__ */ jsx20("span", { "aria-hidden": true, className: cn("shrink-0", FRAME_GLYPH_COLOR[frame.status]), children: FRAME_GLYPH[frame.status] ?? "\u25CB" }),
|
|
718
|
-
/* @__PURE__ */
|
|
609
|
+
/* @__PURE__ */ jsxs11("span", { className: "min-w-0", children: [
|
|
719
610
|
/* @__PURE__ */ jsx20("span", { className: "font-mono font-semibold text-keyword", children: frame.keyword }),
|
|
720
611
|
" ",
|
|
721
612
|
/* @__PURE__ */ jsx20("span", { className: "text-muted-foreground", children: frame.screenshot?.alt ?? frame.text })
|
|
@@ -724,7 +615,7 @@ function StoryboardFrameItem({ frame }) {
|
|
|
724
615
|
return (
|
|
725
616
|
// State cards need room for diff rows; screenshot-only frames keep the
|
|
726
617
|
// original narrow film-cell width (and DOM) unchanged.
|
|
727
|
-
/* @__PURE__ */
|
|
618
|
+
/* @__PURE__ */ jsxs11("li", { className: cn("shrink-0", hasStates ? "w-64" : "w-40"), children: [
|
|
728
619
|
frame.screenshot ? /* @__PURE__ */ jsx20(FrameScreenshot, { frame, label }) : null,
|
|
729
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,
|
|
730
621
|
frame.screenshot ? caption : (
|
|
@@ -771,8 +662,8 @@ function prettyJson(value) {
|
|
|
771
662
|
function StateCardView({ card }) {
|
|
772
663
|
const json = prettyJson(card.value);
|
|
773
664
|
const firstCapture = card.changes === void 0;
|
|
774
|
-
return /* @__PURE__ */
|
|
775
|
-
/* @__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: [
|
|
776
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" }),
|
|
777
668
|
firstCapture ? /* @__PURE__ */ jsx20("span", { className: "text-[0.625rem] text-muted-foreground italic", children: "first capture" }) : null
|
|
778
669
|
] }),
|
|
@@ -787,7 +678,7 @@ function StateCardView({ card }) {
|
|
|
787
678
|
},
|
|
788
679
|
i
|
|
789
680
|
)) }) : /* @__PURE__ */ jsx20("p", { className: "mt-1.5 text-[0.625rem] text-muted-foreground", children: "No changes" })),
|
|
790
|
-
/* @__PURE__ */
|
|
681
|
+
/* @__PURE__ */ jsxs11(
|
|
791
682
|
"details",
|
|
792
683
|
{
|
|
793
684
|
open: firstCapture && json.length <= 240,
|
|
@@ -802,7 +693,7 @@ function StateCardView({ card }) {
|
|
|
802
693
|
}
|
|
803
694
|
|
|
804
695
|
// src/components/ReportAttachments.tsx
|
|
805
|
-
import { Fragment as Fragment6, jsx as jsx21, jsxs as
|
|
696
|
+
import { Fragment as Fragment6, jsx as jsx21, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
806
697
|
function ReportAttachments({ attachments }) {
|
|
807
698
|
if (attachments.length === 0) return null;
|
|
808
699
|
return (
|
|
@@ -811,7 +702,7 @@ function ReportAttachments({ attachments }) {
|
|
|
811
702
|
/* @__PURE__ */ jsx21("div", { role: "group", "aria-label": "Attachments", className: "mt-3 flex flex-col gap-2", children: attachments.map((att, i) => {
|
|
812
703
|
const dataUri = att.contentEncoding === "BASE64" ? `data:${att.mediaType};base64,${att.body}` : `data:${att.mediaType},${encodeURIComponent(att.body)}`;
|
|
813
704
|
const isImage = att.mediaType.startsWith("image/");
|
|
814
|
-
return /* @__PURE__ */ jsx21("figure", { className: "m-0", children: isImage ? /* @__PURE__ */
|
|
705
|
+
return /* @__PURE__ */ jsx21("figure", { className: "m-0", children: isImage ? /* @__PURE__ */ jsxs12(Fragment6, { children: [
|
|
815
706
|
/* @__PURE__ */ jsx21(
|
|
816
707
|
"img",
|
|
817
708
|
{
|
|
@@ -822,7 +713,7 @@ function ReportAttachments({ attachments }) {
|
|
|
822
713
|
}
|
|
823
714
|
),
|
|
824
715
|
/* @__PURE__ */ jsx21("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: att.name })
|
|
825
|
-
] }) : /* @__PURE__ */
|
|
716
|
+
] }) : /* @__PURE__ */ jsxs12(
|
|
826
717
|
"a",
|
|
827
718
|
{
|
|
828
719
|
href: dataUri,
|
|
@@ -831,7 +722,7 @@ function ReportAttachments({ attachments }) {
|
|
|
831
722
|
children: [
|
|
832
723
|
att.name,
|
|
833
724
|
" ",
|
|
834
|
-
/* @__PURE__ */
|
|
725
|
+
/* @__PURE__ */ jsxs12("span", { className: "text-muted-foreground", children: [
|
|
835
726
|
"(",
|
|
836
727
|
att.mediaType,
|
|
837
728
|
")"
|
|
@@ -922,7 +813,7 @@ function traceWindow(rows) {
|
|
|
922
813
|
}
|
|
923
814
|
|
|
924
815
|
// src/components/ReportTrace.tsx
|
|
925
|
-
import { jsx as jsx22, jsxs as
|
|
816
|
+
import { jsx as jsx22, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
926
817
|
var BAR_COLOR = {
|
|
927
818
|
ok: "bg-pass",
|
|
928
819
|
error: "bg-fail",
|
|
@@ -933,8 +824,8 @@ function ReportTrace({ spans }) {
|
|
|
933
824
|
const rows = toTraceRows(spans);
|
|
934
825
|
if (rows.length === 0) return null;
|
|
935
826
|
const { minStartMs, totalMs } = traceWindow(rows);
|
|
936
|
-
return /* @__PURE__ */
|
|
937
|
-
/* @__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: [
|
|
938
829
|
"Trace (",
|
|
939
830
|
rows.length,
|
|
940
831
|
" span",
|
|
@@ -946,7 +837,7 @@ function ReportTrace({ spans }) {
|
|
|
946
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) => {
|
|
947
838
|
const leftPct = (row.startTimeMs - minStartMs) / totalMs * 100;
|
|
948
839
|
const widthPct = Math.max(row.durationMs / totalMs * 100, 0.5);
|
|
949
|
-
return /* @__PURE__ */
|
|
840
|
+
return /* @__PURE__ */ jsxs13("li", { className: "flex items-center gap-2", children: [
|
|
950
841
|
/* @__PURE__ */ jsx22(
|
|
951
842
|
"span",
|
|
952
843
|
{
|
|
@@ -1032,7 +923,7 @@ function diffSinceLastRun(history, scenarios) {
|
|
|
1032
923
|
}
|
|
1033
924
|
|
|
1034
925
|
// src/components/ScenarioRunHistory.tsx
|
|
1035
|
-
import { Fragment as Fragment7, jsx as jsx23, jsxs as
|
|
926
|
+
import { Fragment as Fragment7, jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1036
927
|
var MAX_DOTS = 10;
|
|
1037
928
|
var DOT_COLOR = {
|
|
1038
929
|
passed: "bg-pass",
|
|
@@ -1052,7 +943,7 @@ function ScenarioRunHistory({ scenarioId }) {
|
|
|
1052
943
|
const recent = entries.slice(-MAX_DOTS);
|
|
1053
944
|
const flaky = flakinessOf(recent) === "flaky";
|
|
1054
945
|
const summary = flaky ? `${describeRunHistory(recent)} \xB7 Flaky` : describeRunHistory(recent);
|
|
1055
|
-
return /* @__PURE__ */
|
|
946
|
+
return /* @__PURE__ */ jsxs14(Fragment7, { children: [
|
|
1056
947
|
flaky ? /* @__PURE__ */ jsx23(Badge, { variant: "pending", "aria-label": "Flaky: status flips between recent runs", title: summary, children: "Flaky" }) : null,
|
|
1057
948
|
/* @__PURE__ */ jsx23(
|
|
1058
949
|
"span",
|
|
@@ -1082,32 +973,111 @@ function ScenarioRunHistory({ scenarioId }) {
|
|
|
1082
973
|
] });
|
|
1083
974
|
}
|
|
1084
975
|
|
|
1085
|
-
// src/
|
|
1086
|
-
import {
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
function
|
|
1090
|
-
|
|
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;
|
|
1091
984
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
}
|
|
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`;
|
|
1101
994
|
}
|
|
1102
|
-
function
|
|
1103
|
-
if (
|
|
1104
|
-
|
|
1105
|
-
|
|
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) {
|
|
1073
|
+
if (typeof localStorage === "undefined") return;
|
|
1074
|
+
try {
|
|
1075
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify([...ids]));
|
|
1106
1076
|
} catch {
|
|
1107
1077
|
}
|
|
1108
1078
|
}
|
|
1109
1079
|
function useCollapseState(computeDefault) {
|
|
1110
|
-
const [collapsed, setCollapsed] =
|
|
1080
|
+
const [collapsed, setCollapsed] = useState2(
|
|
1111
1081
|
() => readPersisted() ?? computeDefault?.() ?? /* @__PURE__ */ new Set()
|
|
1112
1082
|
);
|
|
1113
1083
|
const isCollapsed = useCallback((id) => collapsed.has(id), [collapsed]);
|
|
@@ -1138,12 +1108,12 @@ function useCollapseState(computeDefault) {
|
|
|
1138
1108
|
}
|
|
1139
1109
|
|
|
1140
1110
|
// src/interactive/scenario-actions.tsx
|
|
1141
|
-
import { createContext as
|
|
1111
|
+
import { createContext as createContext5, useContext as useContext6 } from "react";
|
|
1142
1112
|
import { scenarioToMarkdown as coreScenarioToMarkdown } from "executable-stories-core/utils/scenario-markdown";
|
|
1143
|
-
var ScenarioActionsContext =
|
|
1113
|
+
var ScenarioActionsContext = createContext5(null);
|
|
1144
1114
|
var ScenarioActionsProvider = ScenarioActionsContext.Provider;
|
|
1145
1115
|
function useScenarioActions() {
|
|
1146
|
-
return
|
|
1116
|
+
return useContext6(ScenarioActionsContext);
|
|
1147
1117
|
}
|
|
1148
1118
|
function scenarioToMarkdown(scenario) {
|
|
1149
1119
|
return coreScenarioToMarkdown(scenario, { variant: "compact" });
|
|
@@ -1311,6 +1281,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
|
|
|
1311
1281
|
] }),
|
|
1312
1282
|
/* @__PURE__ */ jsxs17("div", { className: "flex shrink-0 items-center gap-2", children: [
|
|
1313
1283
|
/* @__PURE__ */ jsx26(ScenarioRunHistory, { scenarioId: scenario.id }),
|
|
1284
|
+
/* @__PURE__ */ jsx26(ScenarioStaleness, { scenario }),
|
|
1314
1285
|
scenario.durationMs > 0 ? /* @__PURE__ */ jsx26("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration3(scenario.durationMs) }) : null,
|
|
1315
1286
|
meta.showPill ? /* @__PURE__ */ jsx26(Badge, { variant: meta.badgeVariant, "aria-label": `Status: ${label}`, children: label }) : /* @__PURE__ */ jsx26("span", { className: "sr-only", children: `Status: ${label}` }),
|
|
1316
1287
|
actions ? /* @__PURE__ */ jsxs17(DropdownMenu, { children: [
|
|
@@ -1370,8 +1341,35 @@ function ReportScenarioList({ feature }) {
|
|
|
1370
1341
|
return /* @__PURE__ */ jsx27(Fragment8, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ jsx27(ReportScenario, { scenario }, scenario.id)) });
|
|
1371
1342
|
}
|
|
1372
1343
|
|
|
1344
|
+
// src/components/ReportFeatureIntro.tsx
|
|
1345
|
+
import { jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1346
|
+
function ReportFeatureIntro({ feature }) {
|
|
1347
|
+
const hasNarrative = Boolean(feature.narrative?.trim());
|
|
1348
|
+
const hasGlossary = Boolean(feature.glossary?.length);
|
|
1349
|
+
if (!hasNarrative && !hasGlossary) return null;
|
|
1350
|
+
return /* @__PURE__ */ jsxs18("div", { className: "flex flex-col gap-3 border-l-2 border-border pl-4 text-sm", children: [
|
|
1351
|
+
hasNarrative ? /* @__PURE__ */ jsx28(
|
|
1352
|
+
"div",
|
|
1353
|
+
{
|
|
1354
|
+
className: "es-doc-prose prose prose-sm max-w-none text-muted-foreground",
|
|
1355
|
+
dangerouslySetInnerHTML: { __html: safeMarkdownHtml(dedent(feature.narrative)) }
|
|
1356
|
+
}
|
|
1357
|
+
) : null,
|
|
1358
|
+
hasGlossary ? /* @__PURE__ */ jsx28("dl", { className: "grid gap-1.5", "aria-label": "Glossary", children: feature.glossary.map((entry) => /* @__PURE__ */ jsxs18("div", { className: "flex flex-wrap gap-x-2 text-xs", children: [
|
|
1359
|
+
/* @__PURE__ */ jsx28("dt", { className: "font-medium text-foreground", children: entry.term }),
|
|
1360
|
+
/* @__PURE__ */ jsx28("dd", { className: "flex-1 text-muted-foreground", children: entry.definition })
|
|
1361
|
+
] }, entry.term)) }) : null
|
|
1362
|
+
] });
|
|
1363
|
+
}
|
|
1364
|
+
function featureKindLabel(kind) {
|
|
1365
|
+
if (kind === "ability") return "Ability";
|
|
1366
|
+
if (kind === "business-need") return "Business need";
|
|
1367
|
+
if (kind === "feature") return "Feature";
|
|
1368
|
+
return null;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1373
1371
|
// src/components/ReportFeature.tsx
|
|
1374
|
-
import { Fragment as Fragment9, jsx as
|
|
1372
|
+
import { Fragment as Fragment9, jsx as jsx29, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
1375
1373
|
function ReportFeature({ feature }) {
|
|
1376
1374
|
const titleId = `${feature.id}-title`;
|
|
1377
1375
|
const bodyId = `${feature.id}-body`;
|
|
@@ -1379,10 +1377,11 @@ function ReportFeature({ feature }) {
|
|
|
1379
1377
|
const skipped = s.skipped + s.pending;
|
|
1380
1378
|
const collapse = useCollapse();
|
|
1381
1379
|
const collapsed = collapse?.isCollapsed(feature.id) ?? false;
|
|
1380
|
+
const kindLabel = featureKindLabel(feature.kind);
|
|
1382
1381
|
return (
|
|
1383
1382
|
// Suite = a section heading + a divider, NOT another bordered card. Drops a
|
|
1384
1383
|
// layer of nesting so the scenario cards below read as the primary unit.
|
|
1385
|
-
/* @__PURE__ */
|
|
1384
|
+
/* @__PURE__ */ jsxs19(
|
|
1386
1385
|
"section",
|
|
1387
1386
|
{
|
|
1388
1387
|
id: feature.id,
|
|
@@ -1390,9 +1389,9 @@ function ReportFeature({ feature }) {
|
|
|
1390
1389
|
"aria-labelledby": titleId,
|
|
1391
1390
|
className: "flex flex-col gap-3",
|
|
1392
1391
|
children: [
|
|
1393
|
-
/* @__PURE__ */
|
|
1394
|
-
/* @__PURE__ */
|
|
1395
|
-
/* @__PURE__ */
|
|
1392
|
+
/* @__PURE__ */ jsxs19("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
|
|
1393
|
+
/* @__PURE__ */ jsxs19("div", { className: "min-w-0", children: [
|
|
1394
|
+
/* @__PURE__ */ jsx29("h2", { id: titleId, className: "text-base font-semibold tracking-tight text-foreground", children: collapse ? /* @__PURE__ */ jsxs19(
|
|
1396
1395
|
"button",
|
|
1397
1396
|
{
|
|
1398
1397
|
type: "button",
|
|
@@ -1401,31 +1400,34 @@ function ReportFeature({ feature }) {
|
|
|
1401
1400
|
"aria-controls": bodyId,
|
|
1402
1401
|
className: "flex w-full cursor-pointer items-center gap-2 text-left hover:text-foreground/80",
|
|
1403
1402
|
children: [
|
|
1404
|
-
/* @__PURE__ */
|
|
1405
|
-
/* @__PURE__ */
|
|
1403
|
+
/* @__PURE__ */ jsx29("span", { "aria-hidden": true, className: cn("shrink-0 text-xs text-muted-foreground transition-transform", !collapsed && "rotate-90"), children: "\u25B8" }),
|
|
1404
|
+
/* @__PURE__ */ jsx29("span", { className: "truncate", children: feature.title })
|
|
1406
1405
|
]
|
|
1407
1406
|
}
|
|
1408
1407
|
) : feature.title }),
|
|
1409
|
-
/* @__PURE__ */
|
|
1408
|
+
/* @__PURE__ */ jsxs19("p", { className: cn("mt-0.5 flex items-center gap-2 text-xs text-muted-foreground", collapse && "pl-5"), children: [
|
|
1409
|
+
kindLabel ? /* @__PURE__ */ jsx29("span", { className: "shrink-0 rounded-sm bg-muted px-1.5 py-0.5 font-medium text-foreground", children: kindLabel }) : null,
|
|
1410
|
+
/* @__PURE__ */ jsx29("span", { className: "truncate font-mono", children: feature.sourceFile })
|
|
1411
|
+
] })
|
|
1410
1412
|
] }),
|
|
1411
|
-
/* @__PURE__ */
|
|
1413
|
+
/* @__PURE__ */ jsxs19(
|
|
1412
1414
|
"p",
|
|
1413
1415
|
{
|
|
1414
1416
|
className: "flex shrink-0 items-center gap-1.5 text-xs text-muted-foreground",
|
|
1415
1417
|
"aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
|
|
1416
1418
|
children: [
|
|
1417
|
-
/* @__PURE__ */
|
|
1419
|
+
/* @__PURE__ */ jsxs19("span", { children: [
|
|
1418
1420
|
s.passed,
|
|
1419
1421
|
" passed"
|
|
1420
1422
|
] }),
|
|
1421
|
-
/* @__PURE__ */
|
|
1422
|
-
/* @__PURE__ */
|
|
1423
|
+
/* @__PURE__ */ jsx29("span", { "aria-hidden": true, children: "\xB7" }),
|
|
1424
|
+
/* @__PURE__ */ jsxs19("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
|
|
1423
1425
|
s.failed,
|
|
1424
1426
|
" failed"
|
|
1425
1427
|
] }),
|
|
1426
|
-
skipped > 0 ? /* @__PURE__ */
|
|
1427
|
-
/* @__PURE__ */
|
|
1428
|
-
/* @__PURE__ */
|
|
1428
|
+
skipped > 0 ? /* @__PURE__ */ jsxs19(Fragment9, { children: [
|
|
1429
|
+
/* @__PURE__ */ jsx29("span", { "aria-hidden": true, children: "\xB7" }),
|
|
1430
|
+
/* @__PURE__ */ jsxs19("span", { children: [
|
|
1429
1431
|
skipped,
|
|
1430
1432
|
" skipped"
|
|
1431
1433
|
] })
|
|
@@ -1434,7 +1436,10 @@ function ReportFeature({ feature }) {
|
|
|
1434
1436
|
}
|
|
1435
1437
|
)
|
|
1436
1438
|
] }),
|
|
1437
|
-
/* @__PURE__ */
|
|
1439
|
+
/* @__PURE__ */ jsxs19("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-3", children: [
|
|
1440
|
+
/* @__PURE__ */ jsx29(ReportFeatureIntro, { feature }),
|
|
1441
|
+
/* @__PURE__ */ jsx29(ReportScenarioList, { feature })
|
|
1442
|
+
] })
|
|
1438
1443
|
]
|
|
1439
1444
|
}
|
|
1440
1445
|
)
|
|
@@ -1442,74 +1447,77 @@ function ReportFeature({ feature }) {
|
|
|
1442
1447
|
}
|
|
1443
1448
|
|
|
1444
1449
|
// src/components/ReportFeatureList.tsx
|
|
1445
|
-
import { jsx as
|
|
1450
|
+
import { jsx as jsx30 } from "react/jsx-runtime";
|
|
1446
1451
|
function ReportFeatureList() {
|
|
1447
1452
|
const report = useReport();
|
|
1448
|
-
return /* @__PURE__ */
|
|
1453
|
+
return /* @__PURE__ */ jsx30("div", { className: "flex flex-col gap-8", children: report.features.map((feature) => /* @__PURE__ */ jsx30(ReportFeature, { feature }, feature.id)) });
|
|
1449
1454
|
}
|
|
1450
1455
|
|
|
1451
|
-
// src/components/
|
|
1452
|
-
import {
|
|
1453
|
-
import { jsx as
|
|
1454
|
-
function
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
"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",
|
|
1461
|
-
className
|
|
1462
|
-
),
|
|
1463
|
-
...props
|
|
1464
|
-
}
|
|
1465
|
-
);
|
|
1466
|
-
}
|
|
1467
|
-
function EmptyHeader({ className, ...props }) {
|
|
1468
|
-
return /* @__PURE__ */ jsx30(
|
|
1469
|
-
"div",
|
|
1470
|
-
{
|
|
1471
|
-
"data-slot": "empty-header",
|
|
1472
|
-
className: cn(
|
|
1473
|
-
"flex max-w-sm flex-col items-center gap-2 text-center",
|
|
1474
|
-
className
|
|
1475
|
-
),
|
|
1476
|
-
...props
|
|
1477
|
-
}
|
|
1478
|
-
);
|
|
1479
|
-
}
|
|
1480
|
-
var emptyMediaVariants = cva2(
|
|
1481
|
-
"mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
1482
|
-
{
|
|
1483
|
-
variants: {
|
|
1484
|
-
variant: {
|
|
1485
|
-
default: "bg-transparent",
|
|
1486
|
-
icon: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-6"
|
|
1487
|
-
}
|
|
1488
|
-
},
|
|
1489
|
-
defaultVariants: {
|
|
1490
|
-
variant: "default"
|
|
1491
|
-
}
|
|
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() });
|
|
1492
1465
|
}
|
|
1493
|
-
)
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
{
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
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
|
+
] })
|
|
1502
1516
|
);
|
|
1503
1517
|
}
|
|
1504
1518
|
|
|
1505
|
-
// src/components/ReportEmpty.tsx
|
|
1506
|
-
import { jsx as jsx31 } from "react/jsx-runtime";
|
|
1507
|
-
function ReportEmpty({ message }) {
|
|
1508
|
-
return /* @__PURE__ */ jsx31(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ jsx31(EmptyHeader, { children: /* @__PURE__ */ jsx31(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
1519
|
// src/components/ReportSummary.tsx
|
|
1512
|
-
import { jsx as jsx32, jsxs as
|
|
1520
|
+
import { jsx as jsx32, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
1513
1521
|
function ReportSummary({ className }) {
|
|
1514
1522
|
const report = useReport();
|
|
1515
1523
|
return /* @__PURE__ */ jsx32(
|
|
@@ -1551,13 +1559,13 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
|
|
|
1551
1559
|
"aria-label": ariaLabel,
|
|
1552
1560
|
children: cards.map((c) => {
|
|
1553
1561
|
const tone = TONE[c.status];
|
|
1554
|
-
return /* @__PURE__ */
|
|
1562
|
+
return /* @__PURE__ */ jsxs21(
|
|
1555
1563
|
"div",
|
|
1556
1564
|
{
|
|
1557
1565
|
"data-status": c.status,
|
|
1558
1566
|
className: "rounded-lg border bg-card px-4 py-3",
|
|
1559
1567
|
children: [
|
|
1560
|
-
/* @__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: [
|
|
1561
1569
|
/* @__PURE__ */ jsx32("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
|
|
1562
1570
|
c.label
|
|
1563
1571
|
] }),
|
|
@@ -1571,165 +1579,48 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
|
|
|
1571
1579
|
);
|
|
1572
1580
|
}
|
|
1573
1581
|
|
|
1574
|
-
// src/components/
|
|
1575
|
-
import {
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
if (lastRunMs === void 0 || staleAfterDays <= 0) return false;
|
|
1595
|
-
return nowMs - lastRunMs >= staleAfterDays * 864e5;
|
|
1596
|
-
}
|
|
1597
|
-
var CI_DISPLAY_NAMES = {
|
|
1598
|
-
github: "GitHub Actions",
|
|
1599
|
-
gitlab: "GitLab CI",
|
|
1600
|
-
circleci: "CircleCI",
|
|
1601
|
-
jenkins: "Jenkins",
|
|
1602
|
-
azure: "Azure DevOps",
|
|
1603
|
-
buildkite: "Buildkite",
|
|
1604
|
-
travis: "Travis CI",
|
|
1605
|
-
ci: "CI"
|
|
1606
|
-
};
|
|
1607
|
-
function ciDisplayName(name) {
|
|
1608
|
-
return CI_DISPLAY_NAMES[name] ?? name;
|
|
1609
|
-
}
|
|
1610
|
-
function commitUrl(ci, sha) {
|
|
1611
|
-
const commitSha = ci?.commitSha ?? sha;
|
|
1612
|
-
if (!ci?.url || !commitSha) return void 0;
|
|
1613
|
-
const github = ci.url.match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/actions\/runs\//);
|
|
1614
|
-
if (github) return `${github[1]}/commit/${commitSha}`;
|
|
1615
|
-
const gitlab = ci.url.match(/^(https?:\/\/.+)\/-\/pipelines\//);
|
|
1616
|
-
if (gitlab) return `${gitlab[1]}/-/commit/${commitSha}`;
|
|
1617
|
-
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
|
+
] });
|
|
1618
1602
|
}
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
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 }) });
|
|
1624
1613
|
}
|
|
1625
1614
|
|
|
1626
|
-
// src/components/ReportMeta.tsx
|
|
1627
|
-
import { jsx as jsx33, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
1628
|
-
function ReportMeta() {
|
|
1629
|
-
const report = useReport();
|
|
1630
|
-
const items = [];
|
|
1631
|
-
const linkClass = "text-primary underline underline-offset-2";
|
|
1632
|
-
if (report.startedAtMs) {
|
|
1633
|
-
items.push({ label: "Started", value: new Date(report.startedAtMs).toLocaleString() });
|
|
1634
|
-
}
|
|
1635
|
-
if (report.durationMs) {
|
|
1636
|
-
items.push({ label: "Duration", value: formatDuration4(report.durationMs) });
|
|
1637
|
-
}
|
|
1638
|
-
if (report.packageVersion) {
|
|
1639
|
-
items.push({ label: "Version", value: report.packageVersion });
|
|
1640
|
-
}
|
|
1641
|
-
if (report.ci?.branch) {
|
|
1642
|
-
items.push({ label: "Branch", value: report.ci.branch });
|
|
1643
|
-
}
|
|
1644
|
-
const sha = report.ci?.commitSha ?? report.gitSha;
|
|
1645
|
-
if (sha) {
|
|
1646
|
-
const url = commitUrl(report.ci, report.gitSha);
|
|
1647
|
-
const short = sha.slice(0, 8);
|
|
1648
|
-
items.push({
|
|
1649
|
-
label: "Commit",
|
|
1650
|
-
value: url ? /* @__PURE__ */ jsx33("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
|
|
1651
|
-
});
|
|
1652
|
-
}
|
|
1653
|
-
if (report.ci) {
|
|
1654
|
-
items.push({
|
|
1655
|
-
label: "CI",
|
|
1656
|
-
value: report.ci.url ? /* @__PURE__ */ jsxs20("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
|
|
1657
|
-
ciDisplayName(report.ci.name),
|
|
1658
|
-
report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
|
|
1659
|
-
] }) : `${ciDisplayName(report.ci.name)}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
|
|
1660
|
-
});
|
|
1661
|
-
}
|
|
1662
|
-
if (report.ci?.prNumber) {
|
|
1663
|
-
const url = prUrl(report.ci);
|
|
1664
|
-
const label = `#${report.ci.prNumber}`;
|
|
1665
|
-
items.push({
|
|
1666
|
-
label: "PR",
|
|
1667
|
-
value: url ? /* @__PURE__ */ jsx33("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
|
|
1668
|
-
});
|
|
1669
|
-
}
|
|
1670
|
-
if (items.length === 0) return null;
|
|
1671
|
-
return (
|
|
1672
|
-
// Run metadata is useful but secondary to "what failed" — tuck it into a
|
|
1673
|
-
// collapsed disclosure so it doesn't push the result down. Native <details>
|
|
1674
|
-
// so it works with no JS (the SSR/no-hydration report still toggles).
|
|
1675
|
-
/* @__PURE__ */ jsxs20("details", { "data-es-meta-card": true, className: "es-run-details group rounded-lg border border-border bg-card text-xs", children: [
|
|
1676
|
-
/* @__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: [
|
|
1677
|
-
/* @__PURE__ */ jsx33("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
|
|
1678
|
-
"Run details"
|
|
1679
|
-
] }),
|
|
1680
|
-
/* @__PURE__ */ jsx33("dl", { className: "flex flex-wrap gap-x-5 gap-y-2 border-t border-border px-4 py-3 text-muted-foreground", "aria-label": "Run metadata", children: items.map((item) => /* @__PURE__ */ jsxs20("div", { className: "flex items-baseline gap-1.5", children: [
|
|
1681
|
-
/* @__PURE__ */ jsx33("dt", { className: "font-medium", children: item.label }),
|
|
1682
|
-
/* @__PURE__ */ jsx33("dd", { className: "font-mono text-foreground", children: item.value })
|
|
1683
|
-
] }, item.label)) })
|
|
1684
|
-
] })
|
|
1685
|
-
);
|
|
1686
|
-
}
|
|
1687
|
-
|
|
1688
|
-
// src/components/ReportSchemaError.tsx
|
|
1689
|
-
import { jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
1690
|
-
function ReportSchemaError({ error }) {
|
|
1691
|
-
return /* @__PURE__ */ jsxs21("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
|
|
1692
|
-
/* @__PURE__ */ jsx34("p", { children: /* @__PURE__ */ jsx34("strong", { children: "Report could not be displayed." }) }),
|
|
1693
|
-
/* @__PURE__ */ jsx34("p", { children: error.message }),
|
|
1694
|
-
error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs21("p", { children: [
|
|
1695
|
-
"The report bundle is newer than this version of ",
|
|
1696
|
-
/* @__PURE__ */ jsx34("code", { children: "executable-stories-react" }),
|
|
1697
|
-
". Upgrade the package, or regenerate the report with an older formatters CLI."
|
|
1698
|
-
] }) : null,
|
|
1699
|
-
error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs21("details", { children: [
|
|
1700
|
-
/* @__PURE__ */ jsxs21("summary", { children: [
|
|
1701
|
-
error.issues.length,
|
|
1702
|
-
" validation issue",
|
|
1703
|
-
error.issues.length === 1 ? "" : "s"
|
|
1704
|
-
] }),
|
|
1705
|
-
/* @__PURE__ */ jsx34("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
|
|
1706
|
-
] }) : null
|
|
1707
|
-
] });
|
|
1708
|
-
}
|
|
1709
|
-
|
|
1710
|
-
// src/components/ReportShell.tsx
|
|
1711
|
-
import { Fragment as Fragment10, jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
1712
|
-
function ReportErrorShell({
|
|
1713
|
-
error,
|
|
1714
|
-
className,
|
|
1715
|
-
title,
|
|
1716
|
-
dataTheme
|
|
1717
|
-
}) {
|
|
1718
|
-
return /* @__PURE__ */ jsx35("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ jsx35(ReportSchemaError, { error }) });
|
|
1719
|
-
}
|
|
1720
|
-
|
|
1721
|
-
// src/interactive/ReportInteractive.tsx
|
|
1722
|
-
import { Monitor, Moon, Sun } from "lucide-react";
|
|
1723
|
-
|
|
1724
1615
|
// src/components/ui/switch.tsx
|
|
1725
1616
|
import "react";
|
|
1726
1617
|
import { Switch as SwitchPrimitive } from "@base-ui/react/switch";
|
|
1727
|
-
import { jsx as
|
|
1618
|
+
import { jsx as jsx35 } from "react/jsx-runtime";
|
|
1728
1619
|
function Switch({
|
|
1729
1620
|
className,
|
|
1730
1621
|
...props
|
|
1731
1622
|
}) {
|
|
1732
|
-
return /* @__PURE__ */
|
|
1623
|
+
return /* @__PURE__ */ jsx35(
|
|
1733
1624
|
SwitchPrimitive.Root,
|
|
1734
1625
|
{
|
|
1735
1626
|
"data-slot": "switch",
|
|
@@ -1738,7 +1629,7 @@ function Switch({
|
|
|
1738
1629
|
className
|
|
1739
1630
|
),
|
|
1740
1631
|
...props,
|
|
1741
|
-
children: /* @__PURE__ */
|
|
1632
|
+
children: /* @__PURE__ */ jsx35(
|
|
1742
1633
|
SwitchPrimitive.Thumb,
|
|
1743
1634
|
{
|
|
1744
1635
|
"data-slot": "switch-thumb",
|
|
@@ -1754,7 +1645,7 @@ import * as React5 from "react";
|
|
|
1754
1645
|
import { ToggleGroup as ToggleGroupPrimitive } from "@base-ui/react/toggle-group";
|
|
1755
1646
|
import { Toggle as TogglePrimitive } from "@base-ui/react/toggle";
|
|
1756
1647
|
import { cva as cva3 } from "class-variance-authority";
|
|
1757
|
-
import { jsx as
|
|
1648
|
+
import { jsx as jsx36 } from "react/jsx-runtime";
|
|
1758
1649
|
var toggleItemVariants = cva3(
|
|
1759
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",
|
|
1760
1651
|
{
|
|
@@ -1776,13 +1667,13 @@ function ToggleGroup({
|
|
|
1776
1667
|
children,
|
|
1777
1668
|
...props
|
|
1778
1669
|
}) {
|
|
1779
|
-
return /* @__PURE__ */
|
|
1670
|
+
return /* @__PURE__ */ jsx36(
|
|
1780
1671
|
ToggleGroupPrimitive,
|
|
1781
1672
|
{
|
|
1782
1673
|
"data-slot": "toggle-group",
|
|
1783
1674
|
className: cn("flex w-fit items-center gap-0.5 rounded-lg bg-muted p-0.5", className),
|
|
1784
1675
|
...props,
|
|
1785
|
-
children: /* @__PURE__ */
|
|
1676
|
+
children: /* @__PURE__ */ jsx36(ToggleGroupContext.Provider, { value: { size }, children })
|
|
1786
1677
|
}
|
|
1787
1678
|
);
|
|
1788
1679
|
}
|
|
@@ -1793,7 +1684,7 @@ function ToggleGroupItem({
|
|
|
1793
1684
|
...props
|
|
1794
1685
|
}) {
|
|
1795
1686
|
const context = React5.useContext(ToggleGroupContext);
|
|
1796
|
-
return /* @__PURE__ */
|
|
1687
|
+
return /* @__PURE__ */ jsx36(
|
|
1797
1688
|
TogglePrimitive,
|
|
1798
1689
|
{
|
|
1799
1690
|
"data-slot": "toggle-group-item",
|
|
@@ -1804,129 +1695,234 @@ function ToggleGroupItem({
|
|
|
1804
1695
|
);
|
|
1805
1696
|
}
|
|
1806
1697
|
|
|
1807
|
-
// src/
|
|
1808
|
-
import {
|
|
1809
|
-
forwardRef,
|
|
1810
|
-
useId
|
|
1811
|
-
} from "react";
|
|
1812
|
-
import { Search } from "lucide-react";
|
|
1698
|
+
// src/context/ReportRoot.tsx
|
|
1699
|
+
import { useMemo } from "react";
|
|
1813
1700
|
|
|
1814
|
-
// src/components/
|
|
1815
|
-
import "react";
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
{
|
|
1821
|
-
type,
|
|
1822
|
-
"data-slot": "input",
|
|
1823
|
-
className: cn(
|
|
1824
|
-
"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",
|
|
1825
|
-
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
|
|
1826
|
-
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
|
1827
|
-
className
|
|
1828
|
-
),
|
|
1829
|
-
...props
|
|
1830
|
-
}
|
|
1831
|
-
);
|
|
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";
|
|
1832
1707
|
}
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1708
|
+
function isRecord(value) {
|
|
1709
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1710
|
+
}
|
|
1711
|
+
function str(value) {
|
|
1712
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
1713
|
+
}
|
|
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" });
|
|
1717
|
+
}
|
|
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
|
+
] });
|
|
1730
|
+
}
|
|
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;
|
|
1850
1741
|
}
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
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
|
+
});
|
|
1750
|
+
}
|
|
1751
|
+
if (files.length === 0) return void 0;
|
|
1752
|
+
return { title: str(data.title), authored: str(data.authored), files };
|
|
1753
|
+
}
|
|
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);
|
|
1855
1764
|
}
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1765
|
+
if (index === segments.length - 1) child.file = file;
|
|
1766
|
+
node = child;
|
|
1767
|
+
});
|
|
1768
|
+
}
|
|
1769
|
+
return root;
|
|
1770
|
+
}
|
|
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",
|
|
1862
1787
|
{
|
|
1863
|
-
|
|
1864
|
-
|
|
1788
|
+
className: cn(
|
|
1789
|
+
"font-mono text-xs",
|
|
1790
|
+
isDir ? "text-muted-foreground" : "text-foreground"
|
|
1791
|
+
),
|
|
1792
|
+
children: isDir ? `${child.name}/` : child.name
|
|
1865
1793
|
}
|
|
1866
1794
|
),
|
|
1867
|
-
/* @__PURE__ */
|
|
1868
|
-
|
|
1869
|
-
{
|
|
1870
|
-
ref,
|
|
1871
|
-
id: inputId,
|
|
1872
|
-
type: "search",
|
|
1873
|
-
value,
|
|
1874
|
-
onChange: handleChange,
|
|
1875
|
-
onKeyDown: handleKeyDown,
|
|
1876
|
-
placeholder,
|
|
1877
|
-
autoComplete: "off",
|
|
1878
|
-
spellCheck: false,
|
|
1879
|
-
"aria-keyshortcuts": "/",
|
|
1880
|
-
className: "pl-8"
|
|
1881
|
-
}
|
|
1882
|
-
)
|
|
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
|
|
1883
1797
|
] }),
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
className: "text-xs tabular-nums text-muted-foreground",
|
|
1888
|
-
"aria-live": "polite",
|
|
1889
|
-
children: [
|
|
1890
|
-
matchedCount,
|
|
1891
|
-
" of ",
|
|
1892
|
-
totalCount,
|
|
1893
|
-
" scenarios"
|
|
1894
|
-
]
|
|
1895
|
-
}
|
|
1896
|
-
) : null
|
|
1897
|
-
] });
|
|
1898
|
-
}
|
|
1899
|
-
);
|
|
1900
|
-
|
|
1901
|
-
// src/interactive/ReportFailureBanner.tsx
|
|
1902
|
-
import { useCallback as useCallback2 } from "react";
|
|
1903
|
-
|
|
1904
|
-
// src/lib/hash-state.ts
|
|
1905
|
-
var DEFAULT_URL_STATE = {
|
|
1906
|
-
query: "",
|
|
1907
|
-
status: "all",
|
|
1908
|
-
tags: [],
|
|
1909
|
-
detail: "full"
|
|
1910
|
-
};
|
|
1911
|
-
var STATUSES = ["all", "passed", "failed", "skipped", "pending"];
|
|
1912
|
-
function splitHash(hash) {
|
|
1913
|
-
const raw = hash.replace(/^#/, "");
|
|
1914
|
-
const split = raw.indexOf("?");
|
|
1915
|
-
if (split === -1) return { anchor: raw, params: new URLSearchParams() };
|
|
1916
|
-
return { anchor: raw.slice(0, split), params: new URLSearchParams(raw.slice(split + 1)) };
|
|
1798
|
+
isDir ? /* @__PURE__ */ jsx37(TreeBranch, { node: child, depth: depth + 1 }) : null
|
|
1799
|
+
] }, child.name);
|
|
1800
|
+
}) });
|
|
1917
1801
|
}
|
|
1918
|
-
function
|
|
1919
|
-
const
|
|
1920
|
-
|
|
1921
|
-
return {
|
|
1922
|
-
query: params.get("q") ?? DEFAULT_URL_STATE.query,
|
|
1923
|
-
status: STATUSES.includes(status) ? status : DEFAULT_URL_STATE.status,
|
|
1924
|
-
tags: tags ? tags.split(",").filter(Boolean) : [],
|
|
1925
|
-
detail: params.get("docs") === "0" ? "minimal" : "full"
|
|
1926
|
-
};
|
|
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 }) }) });
|
|
1927
1806
|
}
|
|
1928
|
-
function
|
|
1929
|
-
|
|
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();
|
|
1930
1926
|
if (state.query) params.set("q", state.query);
|
|
1931
1927
|
if (state.status !== "all") params.set("status", state.status);
|
|
1932
1928
|
if (state.tags.length > 0) params.set("tags", state.tags.join(","));
|
|
@@ -1969,8 +1965,112 @@ function scrollToScenarioId(scenarioId, options = {}) {
|
|
|
1969
1965
|
}
|
|
1970
1966
|
}
|
|
1971
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
|
+
|
|
1972
2071
|
// src/interactive/ReportFailureBanner.tsx
|
|
1973
|
-
import {
|
|
2072
|
+
import { useCallback as useCallback2 } from "react";
|
|
2073
|
+
import { jsx as jsx39, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
1974
2074
|
function ReportFailureBanner({ failures }) {
|
|
1975
2075
|
const first = failures[0];
|
|
1976
2076
|
const jumpToFirst = useCallback2(() => {
|
|
@@ -1978,7 +2078,7 @@ function ReportFailureBanner({ failures }) {
|
|
|
1978
2078
|
scrollToScenarioId(first.scenarioId);
|
|
1979
2079
|
}, [first]);
|
|
1980
2080
|
if (failures.length === 0) return null;
|
|
1981
|
-
return /* @__PURE__ */
|
|
2081
|
+
return /* @__PURE__ */ jsxs25(
|
|
1982
2082
|
"div",
|
|
1983
2083
|
{
|
|
1984
2084
|
className: "es-failure-banner",
|
|
@@ -1986,13 +2086,13 @@ function ReportFailureBanner({ failures }) {
|
|
|
1986
2086
|
"aria-live": "polite",
|
|
1987
2087
|
"aria-label": "Failure summary",
|
|
1988
2088
|
children: [
|
|
1989
|
-
/* @__PURE__ */
|
|
1990
|
-
/* @__PURE__ */
|
|
2089
|
+
/* @__PURE__ */ jsxs25("span", { className: "es-failure-banner-text", children: [
|
|
2090
|
+
/* @__PURE__ */ jsx39("strong", { children: failures.length }),
|
|
1991
2091
|
" ",
|
|
1992
2092
|
"failure",
|
|
1993
2093
|
failures.length === 1 ? "" : "s"
|
|
1994
2094
|
] }),
|
|
1995
|
-
/* @__PURE__ */
|
|
2095
|
+
/* @__PURE__ */ jsx39(
|
|
1996
2096
|
"button",
|
|
1997
2097
|
{
|
|
1998
2098
|
type: "button",
|
|
@@ -2007,70 +2107,144 @@ function ReportFailureBanner({ failures }) {
|
|
|
2007
2107
|
);
|
|
2008
2108
|
}
|
|
2009
2109
|
|
|
2010
|
-
// src/interactive/
|
|
2011
|
-
import {
|
|
2012
|
-
import {
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
"
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
}
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
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
|
+
|
|
2184
|
+
// src/interactive/ReportFreshness.tsx
|
|
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";
|
|
2187
|
+
function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
|
|
2188
|
+
const [now, setNow] = useState4(nowMs);
|
|
2189
|
+
useEffect2(() => {
|
|
2190
|
+
if (nowMs === void 0) setNow(Date.now());
|
|
2191
|
+
}, [nowMs]);
|
|
2192
|
+
if (lastRunMs === void 0 || now === void 0) return null;
|
|
2193
|
+
const age = formatRelativeAge(lastRunMs, now);
|
|
2194
|
+
const runLink = ciUrl ? /* @__PURE__ */ jsx41(
|
|
2195
|
+
"a",
|
|
2196
|
+
{
|
|
2197
|
+
href: ciUrl,
|
|
2198
|
+
target: "_blank",
|
|
2199
|
+
rel: "noreferrer noopener",
|
|
2200
|
+
className: "underline underline-offset-2",
|
|
2201
|
+
children: "View CI run"
|
|
2202
|
+
}
|
|
2203
|
+
) : null;
|
|
2204
|
+
if (isReportStale(lastRunMs, staleAfterDays, now)) {
|
|
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: [
|
|
2208
|
+
"Last verified ",
|
|
2209
|
+
age,
|
|
2210
|
+
"."
|
|
2211
|
+
] }),
|
|
2212
|
+
" This report may no longer match the current behavior. Look for a newer run."
|
|
2213
|
+
] }),
|
|
2214
|
+
runLink
|
|
2215
|
+
] });
|
|
2216
|
+
}
|
|
2217
|
+
return /* @__PURE__ */ jsxs27("p", { className: "es-freshness", "aria-label": "Report freshness", children: [
|
|
2218
|
+
"Verified ",
|
|
2219
|
+
age,
|
|
2220
|
+
runLink ? /* @__PURE__ */ jsxs27(Fragment11, { children: [
|
|
2221
|
+
" \xB7 ",
|
|
2222
|
+
runLink
|
|
2223
|
+
] }) : null
|
|
2224
|
+
] });
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
// src/interactive/ReportLastRunDelta.tsx
|
|
2228
|
+
import { Fragment as Fragment12, jsx as jsx42, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
2229
|
+
var MAX_LINKS = 5;
|
|
2230
|
+
function ScenarioLinks({ scenarios, tone }) {
|
|
2231
|
+
return /* @__PURE__ */ jsxs28(Fragment12, { children: [
|
|
2232
|
+
scenarios.slice(0, MAX_LINKS).map((s, i) => /* @__PURE__ */ jsxs28("span", { children: [
|
|
2233
|
+
i > 0 ? ", " : "",
|
|
2234
|
+
/* @__PURE__ */ jsx42("a", { href: `#${s.id}`, className: cn("underline underline-offset-2 hover:no-underline", tone), children: s.title })
|
|
2235
|
+
] }, s.id)),
|
|
2236
|
+
scenarios.length > MAX_LINKS ? /* @__PURE__ */ jsxs28("span", { children: [
|
|
2237
|
+
" and ",
|
|
2238
|
+
scenarios.length - MAX_LINKS,
|
|
2239
|
+
" more"
|
|
2240
|
+
] }) : null
|
|
2241
|
+
] });
|
|
2242
|
+
}
|
|
2243
|
+
function ReportLastRunDelta({
|
|
2244
|
+
history,
|
|
2245
|
+
report
|
|
2246
|
+
}) {
|
|
2247
|
+
if (!history) return null;
|
|
2074
2248
|
const scenarios = report.features.flatMap((f) => f.scenarios.map((s) => ({ id: s.id, title: s.title })));
|
|
2075
2249
|
const delta = diffSinceLastRun(history, scenarios);
|
|
2076
2250
|
if (!delta) return null;
|
|
@@ -2078,29 +2252,123 @@ function ReportLastRunDelta({
|
|
|
2078
2252
|
if (newlyFailing.length === 0 && newlyPassing.length === 0 && added.length === 0) {
|
|
2079
2253
|
return /* @__PURE__ */ jsx42("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: "Since last run: no behavior changes." });
|
|
2080
2254
|
}
|
|
2081
|
-
return /* @__PURE__ */
|
|
2255
|
+
return /* @__PURE__ */ jsxs28("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: [
|
|
2082
2256
|
/* @__PURE__ */ jsx42("span", { className: "font-medium text-foreground", children: "Since last run:" }),
|
|
2083
|
-
newlyFailing.length > 0 ? /* @__PURE__ */
|
|
2257
|
+
newlyFailing.length > 0 ? /* @__PURE__ */ jsxs28("span", { className: "ml-2 text-fail", children: [
|
|
2084
2258
|
newlyFailing.length,
|
|
2085
2259
|
" newly failing (",
|
|
2086
2260
|
/* @__PURE__ */ jsx42(ScenarioLinks, { scenarios: newlyFailing, tone: "text-fail" }),
|
|
2087
2261
|
")"
|
|
2088
2262
|
] }) : null,
|
|
2089
|
-
newlyPassing.length > 0 ? /* @__PURE__ */
|
|
2263
|
+
newlyPassing.length > 0 ? /* @__PURE__ */ jsxs28("span", { className: "ml-2 text-pass", children: [
|
|
2090
2264
|
newlyPassing.length,
|
|
2091
2265
|
" fixed (",
|
|
2092
2266
|
/* @__PURE__ */ jsx42(ScenarioLinks, { scenarios: newlyPassing, tone: "text-pass" }),
|
|
2093
2267
|
")"
|
|
2094
2268
|
] }) : null,
|
|
2095
|
-
added.length > 0 ? /* @__PURE__ */
|
|
2269
|
+
added.length > 0 ? /* @__PURE__ */ jsxs28("span", { className: "ml-2", children: [
|
|
2096
2270
|
added.length,
|
|
2097
2271
|
" new"
|
|
2098
2272
|
] }) : null
|
|
2099
2273
|
] });
|
|
2100
2274
|
}
|
|
2101
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
|
+
] });
|
|
2367
|
+
}
|
|
2368
|
+
);
|
|
2369
|
+
|
|
2102
2370
|
// src/interactive/ReportShortcutsHelp.tsx
|
|
2103
|
-
import { useEffect as
|
|
2371
|
+
import { useEffect as useEffect3, useRef } from "react";
|
|
2104
2372
|
|
|
2105
2373
|
// src/components/ui/button.tsx
|
|
2106
2374
|
import "react";
|
|
@@ -2149,329 +2417,60 @@ function Button({
|
|
|
2149
2417
|
"data-slot": "button",
|
|
2150
2418
|
"data-variant": variant,
|
|
2151
2419
|
"data-size": size,
|
|
2152
|
-
className: cn(buttonVariants({ variant, size, className })),
|
|
2153
|
-
...props
|
|
2154
|
-
}
|
|
2155
|
-
});
|
|
2156
|
-
}
|
|
2157
|
-
|
|
2158
|
-
// src/interactive/ReportShortcutsHelp.tsx
|
|
2159
|
-
import { jsx as jsx43, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
2160
|
-
var SHORTCUTS = [
|
|
2161
|
-
{ keys: "/", description: "Focus search" },
|
|
2162
|
-
{ keys: "f", description: "Jump to next failure" },
|
|
2163
|
-
{ keys: "Shift F", description: "Jump to previous failure" },
|
|
2164
|
-
{ keys: "e", description: "Expand all" },
|
|
2165
|
-
{ keys: "c", description: "Collapse all" },
|
|
2166
|
-
{ keys: "?", description: "Toggle this help" },
|
|
2167
|
-
{ keys: "Esc", description: "Clear search / close" }
|
|
2168
|
-
];
|
|
2169
|
-
function Keys({ keys }) {
|
|
2170
|
-
const parts = keys.split(" ");
|
|
2171
|
-
return /* @__PURE__ */ jsx43("span", { className: "inline-flex items-center gap-1", children: parts.map((k, i) => /* @__PURE__ */ jsxs27("span", { className: "inline-flex items-center gap-1", children: [
|
|
2172
|
-
i > 0 ? /* @__PURE__ */ jsx43("span", { className: "text-xs text-muted-foreground", children: "+" }) : null,
|
|
2173
|
-
/* @__PURE__ */ jsx43("kbd", { className: "inline-flex h-6 min-w-6 items-center justify-center rounded border border-border bg-muted px-1.5 font-mono text-xs font-medium text-muted-foreground", children: k })
|
|
2174
|
-
] }, k)) });
|
|
2175
|
-
}
|
|
2176
|
-
function ReportShortcutsHelp({ open, onClose }) {
|
|
2177
|
-
const dialogRef = useRef(null);
|
|
2178
|
-
useEffect2(() => {
|
|
2179
|
-
const dialog = dialogRef.current;
|
|
2180
|
-
if (!dialog) return;
|
|
2181
|
-
if (open && !dialog.open) dialog.showModal();
|
|
2182
|
-
else if (!open && dialog.open) dialog.close();
|
|
2183
|
-
}, [open]);
|
|
2184
|
-
return /* @__PURE__ */ jsxs27(
|
|
2185
|
-
"dialog",
|
|
2186
|
-
{
|
|
2187
|
-
ref: dialogRef,
|
|
2188
|
-
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",
|
|
2189
|
-
"aria-label": "Keyboard shortcuts",
|
|
2190
|
-
onClose,
|
|
2191
|
-
children: [
|
|
2192
|
-
/* @__PURE__ */ jsx43("h2", { className: "text-base font-semibold", children: "Keyboard shortcuts" }),
|
|
2193
|
-
/* @__PURE__ */ jsx43("dl", { className: "mt-4 grid grid-cols-[auto_1fr] items-center gap-x-4 gap-y-2.5", children: SHORTCUTS.map((s) => /* @__PURE__ */ jsxs27("div", { className: "col-span-2 grid grid-cols-subgrid items-center", children: [
|
|
2194
|
-
/* @__PURE__ */ jsx43("dt", { className: "justify-self-start", children: /* @__PURE__ */ jsx43(Keys, { keys: s.keys }) }),
|
|
2195
|
-
/* @__PURE__ */ jsx43("dd", { className: "text-sm text-muted-foreground", children: s.description })
|
|
2196
|
-
] }, s.keys)) }),
|
|
2197
|
-
/* @__PURE__ */ jsx43("form", { method: "dialog", className: "mt-6", children: /* @__PURE__ */ jsx43(Button, { type: "submit", variant: "outline", size: "sm", children: "Close" }) })
|
|
2198
|
-
]
|
|
2199
|
-
}
|
|
2200
|
-
);
|
|
2201
|
-
}
|
|
2202
|
-
|
|
2203
|
-
// src/interactive/use-keyboard-shortcuts.ts
|
|
2204
|
-
import { useEffect as useEffect3, useRef as useRef2 } from "react";
|
|
2205
|
-
function isEditableTarget(target) {
|
|
2206
|
-
if (!(target instanceof HTMLElement)) return false;
|
|
2207
|
-
const tag = target.tagName;
|
|
2208
|
-
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
|
|
2209
|
-
if (target.isContentEditable) return true;
|
|
2210
|
-
return false;
|
|
2211
|
-
}
|
|
2212
|
-
function useKeyboardShortcuts(handlers) {
|
|
2213
|
-
const ref = useRef2(handlers);
|
|
2214
|
-
ref.current = handlers;
|
|
2215
|
-
useEffect3(() => {
|
|
2216
|
-
function onKeyDown(e) {
|
|
2217
|
-
const h = ref.current;
|
|
2218
|
-
if (e.key === "Escape") {
|
|
2219
|
-
h.onEscape?.();
|
|
2220
|
-
return;
|
|
2221
|
-
}
|
|
2222
|
-
if (isEditableTarget(e.target)) return;
|
|
2223
|
-
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
|
2224
|
-
switch (e.key) {
|
|
2225
|
-
case "/":
|
|
2226
|
-
h.onFocusSearch?.();
|
|
2227
|
-
e.preventDefault();
|
|
2228
|
-
return;
|
|
2229
|
-
case "?":
|
|
2230
|
-
h.onToggleHelp?.();
|
|
2231
|
-
e.preventDefault();
|
|
2232
|
-
return;
|
|
2233
|
-
case "f":
|
|
2234
|
-
h.onNextFailure?.();
|
|
2235
|
-
e.preventDefault();
|
|
2236
|
-
return;
|
|
2237
|
-
case "F":
|
|
2238
|
-
h.onPrevFailure?.();
|
|
2239
|
-
e.preventDefault();
|
|
2240
|
-
return;
|
|
2241
|
-
case "e":
|
|
2242
|
-
h.onExpandAll?.();
|
|
2243
|
-
e.preventDefault();
|
|
2244
|
-
return;
|
|
2245
|
-
case "c":
|
|
2246
|
-
h.onCollapseAll?.();
|
|
2247
|
-
e.preventDefault();
|
|
2248
|
-
return;
|
|
2249
|
-
default:
|
|
2250
|
-
return;
|
|
2251
|
-
}
|
|
2252
|
-
}
|
|
2253
|
-
window.addEventListener("keydown", onKeyDown);
|
|
2254
|
-
return () => window.removeEventListener("keydown", onKeyDown);
|
|
2255
|
-
}, []);
|
|
2256
|
-
}
|
|
2257
|
-
|
|
2258
|
-
// src/interactive/use-deep-link-scroll.ts
|
|
2259
|
-
import { useEffect as useEffect4, useRef as useRef3 } from "react";
|
|
2260
|
-
function useDeepLinkScroll() {
|
|
2261
|
-
const lastAnchor = useRef3(null);
|
|
2262
|
-
useEffect4(() => {
|
|
2263
|
-
function scrollToHash() {
|
|
2264
|
-
const { anchor } = splitHash(window.location.hash);
|
|
2265
|
-
if (!anchor || anchor === lastAnchor.current) return;
|
|
2266
|
-
lastAnchor.current = anchor;
|
|
2267
|
-
requestAnimationFrame(() => {
|
|
2268
|
-
scrollToScenarioId(anchor, { updateHash: false });
|
|
2269
|
-
});
|
|
2270
|
-
}
|
|
2271
|
-
scrollToHash();
|
|
2272
|
-
window.addEventListener("hashchange", scrollToHash);
|
|
2273
|
-
return () => window.removeEventListener("hashchange", scrollToHash);
|
|
2274
|
-
}, []);
|
|
2275
|
-
}
|
|
2276
|
-
|
|
2277
|
-
// src/interactive/filter.ts
|
|
2278
|
-
function normalizeQuery(query) {
|
|
2279
|
-
return query.trim().toLowerCase();
|
|
2280
|
-
}
|
|
2281
|
-
function allTags(report) {
|
|
2282
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2283
|
-
for (const feature of report.features) {
|
|
2284
|
-
for (const scenario of feature.scenarios) {
|
|
2285
|
-
for (const tag of scenario.tags) seen.add(tag);
|
|
2286
|
-
}
|
|
2287
|
-
}
|
|
2288
|
-
return [...seen];
|
|
2289
|
-
}
|
|
2290
|
-
function queryMatches(scenario, q) {
|
|
2291
|
-
if (q === "") return true;
|
|
2292
|
-
if (scenario.title.toLowerCase().includes(q)) return true;
|
|
2293
|
-
for (const tag of scenario.tags) {
|
|
2294
|
-
if (tag.toLowerCase().includes(q)) return true;
|
|
2295
|
-
}
|
|
2296
|
-
for (const step of scenario.steps) {
|
|
2297
|
-
if (step.text.toLowerCase().includes(q)) return true;
|
|
2298
|
-
}
|
|
2299
|
-
return false;
|
|
2300
|
-
}
|
|
2301
|
-
function scenarioMatches(scenario, q, status, tags) {
|
|
2302
|
-
if (status !== "all" && scenario.status !== status) return false;
|
|
2303
|
-
if (tags.length > 0 && !tags.some((t) => scenario.tags.includes(t))) return false;
|
|
2304
|
-
return queryMatches(scenario, q);
|
|
2305
|
-
}
|
|
2306
|
-
function summarizeScenarios(scenarios) {
|
|
2307
|
-
let total = 0, passed = 0, failed = 0, skipped = 0, pending = 0, durationMs = 0;
|
|
2308
|
-
for (const s of scenarios) {
|
|
2309
|
-
total += 1;
|
|
2310
|
-
durationMs += s.durationMs;
|
|
2311
|
-
if (s.status === "passed") passed += 1;
|
|
2312
|
-
else if (s.status === "failed") failed += 1;
|
|
2313
|
-
else if (s.status === "skipped") skipped += 1;
|
|
2314
|
-
else pending += 1;
|
|
2315
|
-
}
|
|
2316
|
-
return { total, passed, failed, skipped, pending, durationMs };
|
|
2317
|
-
}
|
|
2318
|
-
function filterReport(report, criteria) {
|
|
2319
|
-
const c = typeof criteria === "string" ? { query: criteria } : criteria;
|
|
2320
|
-
const q = normalizeQuery(c.query ?? "");
|
|
2321
|
-
const status = c.status ?? "all";
|
|
2322
|
-
const tags = c.tags ?? [];
|
|
2323
|
-
if (q === "" && status === "all" && tags.length === 0) return report;
|
|
2324
|
-
const features = [];
|
|
2325
|
-
let topTotal = 0, topPassed = 0, topFailed = 0, topSkipped = 0, topPending = 0, topDuration = 0;
|
|
2326
|
-
for (const feature of report.features) {
|
|
2327
|
-
const matched = feature.scenarios.filter((s) => scenarioMatches(s, q, status, tags));
|
|
2328
|
-
if (matched.length === 0) continue;
|
|
2329
|
-
const summary = summarizeScenarios(matched);
|
|
2330
|
-
features.push({ ...feature, summary, scenarios: matched });
|
|
2331
|
-
topTotal += summary.total;
|
|
2332
|
-
topPassed += summary.passed;
|
|
2333
|
-
topFailed += summary.failed;
|
|
2334
|
-
topSkipped += summary.skipped;
|
|
2335
|
-
topPending += summary.pending;
|
|
2336
|
-
topDuration += summary.durationMs;
|
|
2337
|
-
}
|
|
2338
|
-
return {
|
|
2339
|
-
...report,
|
|
2340
|
-
summary: {
|
|
2341
|
-
total: topTotal,
|
|
2342
|
-
passed: topPassed,
|
|
2343
|
-
failed: topFailed,
|
|
2344
|
-
skipped: topSkipped,
|
|
2345
|
-
pending: topPending,
|
|
2346
|
-
durationMs: topDuration
|
|
2347
|
-
},
|
|
2348
|
-
features
|
|
2349
|
-
};
|
|
2350
|
-
}
|
|
2351
|
-
function listFailures(report) {
|
|
2352
|
-
const out = [];
|
|
2353
|
-
for (const feature of report.features) {
|
|
2354
|
-
for (const scenario of feature.scenarios) {
|
|
2355
|
-
if (scenario.status === "failed") {
|
|
2356
|
-
const ref = {
|
|
2357
|
-
featureId: feature.id,
|
|
2358
|
-
scenarioId: scenario.id,
|
|
2359
|
-
scenarioTitle: scenario.title
|
|
2360
|
-
};
|
|
2361
|
-
if (scenario.errorMessage !== void 0) {
|
|
2362
|
-
ref.errorMessage = scenario.errorMessage;
|
|
2363
|
-
}
|
|
2364
|
-
out.push(ref);
|
|
2365
|
-
}
|
|
2366
|
-
}
|
|
2367
|
-
}
|
|
2368
|
-
return out;
|
|
2369
|
-
}
|
|
2370
|
-
|
|
2371
|
-
// src/interactive/triage-default.ts
|
|
2372
|
-
function needsAttention(status) {
|
|
2373
|
-
return status === "failed";
|
|
2374
|
-
}
|
|
2375
|
-
function defaultCollapsedIds(report) {
|
|
2376
|
-
const collapsed = /* @__PURE__ */ new Set();
|
|
2377
|
-
if (report.summary.failed === 0) return collapsed;
|
|
2378
|
-
for (const feature of report.features) {
|
|
2379
|
-
const featureHasFailure = feature.scenarios.some((s) => needsAttention(s.status));
|
|
2380
|
-
if (!featureHasFailure) collapsed.add(feature.id);
|
|
2381
|
-
for (const scenario of feature.scenarios) {
|
|
2382
|
-
if (!needsAttention(scenario.status)) collapsed.add(scenario.id);
|
|
2420
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
2421
|
+
...props
|
|
2383
2422
|
}
|
|
2384
|
-
}
|
|
2385
|
-
return collapsed;
|
|
2386
|
-
}
|
|
2387
|
-
function failuresFirst(report) {
|
|
2388
|
-
if (report.summary.failed === 0) return report;
|
|
2389
|
-
const withFailures = [];
|
|
2390
|
-
const rest = [];
|
|
2391
|
-
for (const feature of report.features) {
|
|
2392
|
-
(feature.scenarios.some((s) => needsAttention(s.status)) ? withFailures : rest).push(feature);
|
|
2393
|
-
}
|
|
2394
|
-
if (withFailures.length === 0) return report;
|
|
2395
|
-
return { ...report, features: [...withFailures, ...rest] };
|
|
2423
|
+
});
|
|
2396
2424
|
}
|
|
2397
2425
|
|
|
2398
|
-
// src/interactive/
|
|
2399
|
-
import {
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
}) {
|
|
2410
|
-
const
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
type: "button",
|
|
2442
|
-
"aria-pressed": active,
|
|
2443
|
-
onClick: () => onToggleTag(tag),
|
|
2444
|
-
className: "cursor-pointer",
|
|
2445
|
-
children: /* @__PURE__ */ jsx44(
|
|
2446
|
-
Badge,
|
|
2447
|
-
{
|
|
2448
|
-
variant: "tag",
|
|
2449
|
-
className: cn(
|
|
2450
|
-
active && "border-primary bg-primary text-primary-foreground"
|
|
2451
|
-
),
|
|
2452
|
-
children: tag
|
|
2453
|
-
}
|
|
2454
|
-
)
|
|
2455
|
-
}
|
|
2456
|
-
) }, tag);
|
|
2457
|
-
}),
|
|
2458
|
-
tags.length > TAG_LIMIT ? /* @__PURE__ */ jsx44("li", { children: /* @__PURE__ */ jsx44(
|
|
2459
|
-
"button",
|
|
2460
|
-
{
|
|
2461
|
-
type: "button",
|
|
2462
|
-
"aria-expanded": showAllTags,
|
|
2463
|
-
onClick: () => setShowAllTags((v) => !v),
|
|
2464
|
-
className: "cursor-pointer rounded-full px-2 py-0.5 text-xs font-medium text-muted-foreground hover:text-foreground",
|
|
2465
|
-
children: tagsCollapsed ? `+${tags.length - TAG_LIMIT} more` : "Show fewer"
|
|
2466
|
-
}
|
|
2467
|
-
) }) : null
|
|
2468
|
-
] }) : null
|
|
2469
|
-
] });
|
|
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
|
+
);
|
|
2470
2469
|
}
|
|
2471
2470
|
|
|
2472
2471
|
// src/interactive/ReportToc.tsx
|
|
2473
|
-
import { useEffect as
|
|
2474
|
-
import { jsx 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";
|
|
2475
2474
|
var DOT = {
|
|
2476
2475
|
passed: "bg-pass",
|
|
2477
2476
|
failed: "bg-fail",
|
|
@@ -2480,12 +2479,12 @@ var DOT = {
|
|
|
2480
2479
|
};
|
|
2481
2480
|
function TocContent({ onNavigate }) {
|
|
2482
2481
|
const report = useReport();
|
|
2483
|
-
const [activeId, setActiveId] =
|
|
2482
|
+
const [activeId, setActiveId] = useState5(null);
|
|
2484
2483
|
const scenarioIds = useMemo2(
|
|
2485
2484
|
() => report.features.flatMap((f) => f.scenarios.map((s) => s.id)),
|
|
2486
2485
|
[report]
|
|
2487
2486
|
);
|
|
2488
|
-
|
|
2487
|
+
useEffect4(() => {
|
|
2489
2488
|
if (typeof IntersectionObserver === "undefined") return;
|
|
2490
2489
|
const els = scenarioIds.map((id) => document.getElementById(id)).filter((el) => el !== null);
|
|
2491
2490
|
if (els.length === 0) return;
|
|
@@ -2500,11 +2499,11 @@ function TocContent({ onNavigate }) {
|
|
|
2500
2499
|
return () => observer.disconnect();
|
|
2501
2500
|
}, [scenarioIds]);
|
|
2502
2501
|
if (report.features.length === 0) return null;
|
|
2503
|
-
return /* @__PURE__ */
|
|
2504
|
-
/* @__PURE__ */
|
|
2505
|
-
/* @__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: [
|
|
2503
|
+
/* @__PURE__ */ jsx46("p", { className: "mb-1.5 font-semibold leading-snug text-foreground", children: feature.title }),
|
|
2504
|
+
/* @__PURE__ */ jsx46("ul", { className: "flex list-none flex-col gap-0.5 pl-0", children: feature.scenarios.map((scenario) => {
|
|
2506
2505
|
const active = activeId === scenario.id;
|
|
2507
|
-
return /* @__PURE__ */
|
|
2506
|
+
return /* @__PURE__ */ jsx46("li", { children: /* @__PURE__ */ jsxs31(
|
|
2508
2507
|
"a",
|
|
2509
2508
|
{
|
|
2510
2509
|
href: `#${scenario.id}`,
|
|
@@ -2519,14 +2518,14 @@ function TocContent({ onNavigate }) {
|
|
|
2519
2518
|
active ? "border-primary font-medium text-foreground" : "border-border text-muted-foreground"
|
|
2520
2519
|
),
|
|
2521
2520
|
children: [
|
|
2522
|
-
/* @__PURE__ */
|
|
2521
|
+
/* @__PURE__ */ jsx46(
|
|
2523
2522
|
"span",
|
|
2524
2523
|
{
|
|
2525
2524
|
"aria-hidden": true,
|
|
2526
2525
|
className: cn("mt-1 size-1.5 shrink-0 rounded-full", DOT[scenario.status])
|
|
2527
2526
|
}
|
|
2528
2527
|
),
|
|
2529
|
-
/* @__PURE__ */
|
|
2528
|
+
/* @__PURE__ */ jsx46("span", { className: "break-words", children: scenario.title })
|
|
2530
2529
|
]
|
|
2531
2530
|
}
|
|
2532
2531
|
) }, scenario.id);
|
|
@@ -2536,55 +2535,156 @@ function TocContent({ onNavigate }) {
|
|
|
2536
2535
|
function ReportToc() {
|
|
2537
2536
|
const report = useReport();
|
|
2538
2537
|
if (report.features.length === 0) return null;
|
|
2539
|
-
return /* @__PURE__ */
|
|
2538
|
+
return /* @__PURE__ */ jsxs31(
|
|
2540
2539
|
"nav",
|
|
2541
2540
|
{
|
|
2542
2541
|
"aria-label": "Table of contents",
|
|
2543
2542
|
className: "sticky top-4 hidden h-fit w-64 shrink-0 self-start text-xs lg:block",
|
|
2544
2543
|
children: [
|
|
2545
|
-
/* @__PURE__ */
|
|
2546
|
-
/* @__PURE__ */
|
|
2544
|
+
/* @__PURE__ */ jsx46("p", { className: "mb-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Contents" }),
|
|
2545
|
+
/* @__PURE__ */ jsx46(TocContent, {})
|
|
2547
2546
|
]
|
|
2548
2547
|
}
|
|
2549
2548
|
);
|
|
2550
2549
|
}
|
|
2551
2550
|
|
|
2552
2551
|
// src/interactive/ReportTocDrawer.tsx
|
|
2553
|
-
import { useState as
|
|
2552
|
+
import { useState as useState6 } from "react";
|
|
2554
2553
|
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
|
|
2555
2554
|
import { ListIcon, XIcon } from "lucide-react";
|
|
2556
|
-
import { jsx as
|
|
2555
|
+
import { jsx as jsx47, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
2557
2556
|
function ReportTocDrawer() {
|
|
2558
2557
|
const report = useReport();
|
|
2559
|
-
const [open, setOpen] =
|
|
2558
|
+
const [open, setOpen] = useState6(false);
|
|
2560
2559
|
if (report.features.length === 0) return null;
|
|
2561
|
-
return /* @__PURE__ */
|
|
2562
|
-
/* @__PURE__ */
|
|
2563
|
-
/* @__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: [
|
|
2562
|
+
/* @__PURE__ */ jsx47(ListIcon, { "aria-hidden": true }),
|
|
2564
2563
|
" Contents"
|
|
2565
2564
|
] }),
|
|
2566
|
-
/* @__PURE__ */
|
|
2567
|
-
/* @__PURE__ */
|
|
2568
|
-
/* @__PURE__ */
|
|
2569
|
-
/* @__PURE__ */
|
|
2570
|
-
/* @__PURE__ */
|
|
2571
|
-
/* @__PURE__ */
|
|
2565
|
+
/* @__PURE__ */ jsxs32(DialogPrimitive.Portal, { children: [
|
|
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" }),
|
|
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: [
|
|
2569
|
+
/* @__PURE__ */ jsx47(DialogPrimitive.Title, { className: "text-xs font-semibold tracking-wide text-muted-foreground uppercase", children: "Contents" }),
|
|
2570
|
+
/* @__PURE__ */ jsx47(
|
|
2572
2571
|
DialogPrimitive.Close,
|
|
2573
2572
|
{
|
|
2574
2573
|
"aria-label": "Close contents",
|
|
2575
2574
|
className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
|
|
2576
|
-
children: /* @__PURE__ */
|
|
2575
|
+
children: /* @__PURE__ */ jsx47(XIcon, { "aria-hidden": true })
|
|
2577
2576
|
}
|
|
2578
2577
|
)
|
|
2579
2578
|
] }),
|
|
2580
|
-
/* @__PURE__ */
|
|
2579
|
+
/* @__PURE__ */ jsx47(TocContent, { onNavigate: () => setOpen(false) })
|
|
2581
2580
|
] })
|
|
2582
2581
|
] })
|
|
2583
2582
|
] });
|
|
2584
2583
|
}
|
|
2585
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
|
+
|
|
2586
2686
|
// src/interactive/use-theme.ts
|
|
2587
|
-
import { useCallback as useCallback3, useEffect as
|
|
2687
|
+
import { useCallback as useCallback3, useEffect as useEffect7, useState as useState7 } from "react";
|
|
2588
2688
|
var KEY = "es-theme";
|
|
2589
2689
|
function systemDark() {
|
|
2590
2690
|
return typeof matchMedia !== "undefined" && matchMedia("(prefers-color-scheme: dark)").matches;
|
|
@@ -2605,13 +2705,13 @@ function initialPref() {
|
|
|
2605
2705
|
return "system";
|
|
2606
2706
|
}
|
|
2607
2707
|
function useTheme() {
|
|
2608
|
-
const [pref, setPrefState] =
|
|
2609
|
-
|
|
2708
|
+
const [pref, setPrefState] = useState7("system");
|
|
2709
|
+
useEffect7(() => {
|
|
2610
2710
|
const p = initialPref();
|
|
2611
2711
|
setPrefState(p);
|
|
2612
2712
|
applyTheme(p);
|
|
2613
2713
|
}, []);
|
|
2614
|
-
|
|
2714
|
+
useEffect7(() => {
|
|
2615
2715
|
if (typeof matchMedia === "undefined") return;
|
|
2616
2716
|
const mq = matchMedia("(prefers-color-scheme: dark)");
|
|
2617
2717
|
const onChange = () => {
|
|
@@ -2632,19 +2732,19 @@ function useTheme() {
|
|
|
2632
2732
|
}
|
|
2633
2733
|
|
|
2634
2734
|
// src/interactive/use-url-state.ts
|
|
2635
|
-
import { useCallback as useCallback4, useEffect as
|
|
2735
|
+
import { useCallback as useCallback4, useEffect as useEffect8, useRef as useRef4, useState as useState8 } from "react";
|
|
2636
2736
|
var WRITE_DELAY_MS = 250;
|
|
2637
2737
|
function useUrlState() {
|
|
2638
|
-
const [state, setState] =
|
|
2738
|
+
const [state, setState] = useState8(DEFAULT_URL_STATE);
|
|
2639
2739
|
const ready = useRef4(false);
|
|
2640
|
-
|
|
2740
|
+
useEffect8(() => {
|
|
2641
2741
|
const read = () => setState(decodeUrlState(splitHash(window.location.hash).params));
|
|
2642
2742
|
read();
|
|
2643
2743
|
ready.current = true;
|
|
2644
2744
|
window.addEventListener("hashchange", read);
|
|
2645
2745
|
return () => window.removeEventListener("hashchange", read);
|
|
2646
2746
|
}, []);
|
|
2647
|
-
|
|
2747
|
+
useEffect8(() => {
|
|
2648
2748
|
if (!ready.current) return;
|
|
2649
2749
|
const timer = setTimeout(
|
|
2650
2750
|
() => writeHash(splitHash(window.location.hash).anchor, encodeUrlState(state)),
|
|
@@ -2660,22 +2760,33 @@ function useUrlState() {
|
|
|
2660
2760
|
}
|
|
2661
2761
|
|
|
2662
2762
|
// src/interactive/ReportInteractive.tsx
|
|
2663
|
-
import { Fragment as Fragment13, jsx as
|
|
2763
|
+
import { Fragment as Fragment13, jsx as jsx48, jsxs as jsxs33 } from "react/jsx-runtime";
|
|
2664
2764
|
function ReportInteractive(props) {
|
|
2665
2765
|
const { report, className, title, dataTheme } = props;
|
|
2666
2766
|
const result = unwrapReport(report);
|
|
2667
2767
|
if (!result.ok) {
|
|
2668
|
-
return /* @__PURE__ */
|
|
2768
|
+
return /* @__PURE__ */ jsx48(
|
|
2769
|
+
ReportErrorShell,
|
|
2770
|
+
{
|
|
2771
|
+
error: result.error,
|
|
2772
|
+
className,
|
|
2773
|
+
title,
|
|
2774
|
+
dataTheme
|
|
2775
|
+
}
|
|
2776
|
+
);
|
|
2669
2777
|
}
|
|
2670
|
-
return /* @__PURE__ */
|
|
2778
|
+
return /* @__PURE__ */ jsx48(ReportInteractiveView, { ...props, report: result.data });
|
|
2671
2779
|
}
|
|
2672
2780
|
var THEME_OPTIONS = [
|
|
2673
2781
|
{ value: "light", label: "Light", Icon: Sun },
|
|
2674
2782
|
{ value: "system", label: "System", Icon: Monitor },
|
|
2675
2783
|
{ value: "dark", label: "Dark", Icon: Moon }
|
|
2676
2784
|
];
|
|
2677
|
-
function ThemeSegment({
|
|
2678
|
-
|
|
2785
|
+
function ThemeSegment({
|
|
2786
|
+
pref,
|
|
2787
|
+
onPref
|
|
2788
|
+
}) {
|
|
2789
|
+
return /* @__PURE__ */ jsx48(
|
|
2679
2790
|
ToggleGroup,
|
|
2680
2791
|
{
|
|
2681
2792
|
size: "sm",
|
|
@@ -2685,7 +2796,17 @@ function ThemeSegment({ pref, onPref }) {
|
|
|
2685
2796
|
if (next) onPref(next);
|
|
2686
2797
|
},
|
|
2687
2798
|
"aria-label": "Color theme",
|
|
2688
|
-
children: THEME_OPTIONS.map(({ value, label, Icon }) => /* @__PURE__ */
|
|
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
|
+
))
|
|
2689
2810
|
}
|
|
2690
2811
|
);
|
|
2691
2812
|
}
|
|
@@ -2703,7 +2824,10 @@ function ReportInteractiveView({
|
|
|
2703
2824
|
}) {
|
|
2704
2825
|
const [urlState, setUrlState] = useUrlState();
|
|
2705
2826
|
const { query, status: statusFilter, tags: activeTags, detail } = urlState;
|
|
2706
|
-
const setQuery = useCallback5(
|
|
2827
|
+
const setQuery = useCallback5(
|
|
2828
|
+
(next) => setUrlState({ query: next }),
|
|
2829
|
+
[setUrlState]
|
|
2830
|
+
);
|
|
2707
2831
|
const setStatusFilter = useCallback5(
|
|
2708
2832
|
(next) => setUrlState({ status: next }),
|
|
2709
2833
|
[setUrlState]
|
|
@@ -2712,7 +2836,7 @@ function ReportInteractiveView({
|
|
|
2712
2836
|
(next) => setUrlState({ detail: next }),
|
|
2713
2837
|
[setUrlState]
|
|
2714
2838
|
);
|
|
2715
|
-
const [helpOpen, setHelpOpen] =
|
|
2839
|
+
const [helpOpen, setHelpOpen] = useState9(false);
|
|
2716
2840
|
const { pref: themePref, setPref: setThemePref } = useTheme();
|
|
2717
2841
|
const searchRef = useRef5(null);
|
|
2718
2842
|
const expandId = useId2();
|
|
@@ -2722,7 +2846,13 @@ function ReportInteractiveView({
|
|
|
2722
2846
|
const filtered = useMemo3(
|
|
2723
2847
|
// Features carrying failures float to the top so the first thing on screen
|
|
2724
2848
|
// is the thing that needs attention; an all-green run keeps source order.
|
|
2725
|
-
() => failuresFirst(
|
|
2849
|
+
() => failuresFirst(
|
|
2850
|
+
filterReport(report, {
|
|
2851
|
+
query: deferredQuery,
|
|
2852
|
+
status: statusFilter,
|
|
2853
|
+
tags: activeTags
|
|
2854
|
+
})
|
|
2855
|
+
),
|
|
2726
2856
|
[report, deferredQuery, statusFilter, activeTags]
|
|
2727
2857
|
);
|
|
2728
2858
|
const isFiltering = query !== deferredQuery;
|
|
@@ -2731,10 +2861,14 @@ function ReportInteractiveView({
|
|
|
2731
2861
|
const opts = [
|
|
2732
2862
|
{ key: "all", label: "All", count: s.total }
|
|
2733
2863
|
];
|
|
2734
|
-
if (s.passed)
|
|
2735
|
-
|
|
2736
|
-
if (s.
|
|
2737
|
-
|
|
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 });
|
|
2738
2872
|
return opts;
|
|
2739
2873
|
}, [report]);
|
|
2740
2874
|
const tagOptions = useMemo3(() => allTags(report), [report]);
|
|
@@ -2744,7 +2878,7 @@ function ReportInteractiveView({
|
|
|
2744
2878
|
}),
|
|
2745
2879
|
[activeTags, setUrlState]
|
|
2746
2880
|
);
|
|
2747
|
-
const [toast, setToast] =
|
|
2881
|
+
const [toast, setToast] = useState9(null);
|
|
2748
2882
|
const toastTimer = useRef5(null);
|
|
2749
2883
|
const showToast = useCallback5((msg) => {
|
|
2750
2884
|
setToast(msg);
|
|
@@ -2754,7 +2888,10 @@ function ReportInteractiveView({
|
|
|
2754
2888
|
const copy = useCallback5(
|
|
2755
2889
|
(text, msg) => {
|
|
2756
2890
|
if (typeof navigator !== "undefined" && navigator.clipboard) {
|
|
2757
|
-
navigator.clipboard.writeText(text).then(
|
|
2891
|
+
navigator.clipboard.writeText(text).then(
|
|
2892
|
+
() => showToast(msg),
|
|
2893
|
+
() => showToast("Copy failed")
|
|
2894
|
+
);
|
|
2758
2895
|
}
|
|
2759
2896
|
},
|
|
2760
2897
|
[showToast]
|
|
@@ -2772,9 +2909,21 @@ function ReportInteractiveView({
|
|
|
2772
2909
|
() => report.features.flatMap((f) => [f.id, ...f.scenarios.map((s) => s.id)]),
|
|
2773
2910
|
[report]
|
|
2774
2911
|
);
|
|
2775
|
-
const collapse = useCollapseState(
|
|
2776
|
-
|
|
2777
|
-
|
|
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
|
+
);
|
|
2778
2927
|
const setExpanded = useCallback5(
|
|
2779
2928
|
(expanded) => {
|
|
2780
2929
|
if (expanded) collapse.expandAll();
|
|
@@ -2801,7 +2950,7 @@ function ReportInteractiveView({
|
|
|
2801
2950
|
const escape = useCallback5(() => {
|
|
2802
2951
|
if (helpOpen) setHelpOpen(false);
|
|
2803
2952
|
else if (query !== "") setQuery("");
|
|
2804
|
-
}, [helpOpen, query]);
|
|
2953
|
+
}, [helpOpen, query, setQuery]);
|
|
2805
2954
|
useKeyboardShortcuts({
|
|
2806
2955
|
onFocusSearch: focusSearch,
|
|
2807
2956
|
onNextFailure: () => stepFailure(1),
|
|
@@ -2816,13 +2965,13 @@ function ReportInteractiveView({
|
|
|
2816
2965
|
const totalScenarios = report.summary.total;
|
|
2817
2966
|
const matchedScenarios = filtered.summary.total;
|
|
2818
2967
|
const showToc = !hideToc && totalScenarios > 3;
|
|
2819
|
-
return /* @__PURE__ */
|
|
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(
|
|
2820
2969
|
ReportRoot,
|
|
2821
2970
|
{
|
|
2822
2971
|
report: filtered,
|
|
2823
2972
|
customRenderers,
|
|
2824
2973
|
renderers,
|
|
2825
|
-
children: /* @__PURE__ */
|
|
2974
|
+
children: /* @__PURE__ */ jsxs33(
|
|
2826
2975
|
"main",
|
|
2827
2976
|
{
|
|
2828
2977
|
className: cn("es-report", "es-report-interactive", className),
|
|
@@ -2831,8 +2980,8 @@ function ReportInteractiveView({
|
|
|
2831
2980
|
"data-theme": dataTheme,
|
|
2832
2981
|
"data-detail-level": detail,
|
|
2833
2982
|
children: [
|
|
2834
|
-
/* @__PURE__ */
|
|
2835
|
-
/* @__PURE__ */
|
|
2983
|
+
/* @__PURE__ */ jsxs33("header", { className: "es-report-header", children: [
|
|
2984
|
+
/* @__PURE__ */ jsxs33(
|
|
2836
2985
|
"div",
|
|
2837
2986
|
{
|
|
2838
2987
|
"data-es-navbar": true,
|
|
@@ -2841,9 +2990,9 @@ function ReportInteractiveView({
|
|
|
2841
2990
|
hideHeader ? "justify-end" : "justify-between"
|
|
2842
2991
|
),
|
|
2843
2992
|
children: [
|
|
2844
|
-
hideHeader ? null : /* @__PURE__ */
|
|
2845
|
-
/* @__PURE__ */
|
|
2846
|
-
/* @__PURE__ */
|
|
2993
|
+
hideHeader ? null : /* @__PURE__ */ jsx48("h1", { className: "min-w-0", children: title ?? "Story Report" }),
|
|
2994
|
+
/* @__PURE__ */ jsxs33("div", { className: "flex items-center gap-2", children: [
|
|
2995
|
+
/* @__PURE__ */ jsx48(
|
|
2847
2996
|
ReportSearch,
|
|
2848
2997
|
{
|
|
2849
2998
|
ref: searchRef,
|
|
@@ -2853,16 +3002,16 @@ function ReportInteractiveView({
|
|
|
2853
3002
|
totalCount: totalScenarios
|
|
2854
3003
|
}
|
|
2855
3004
|
),
|
|
2856
|
-
/* @__PURE__ */
|
|
3005
|
+
/* @__PURE__ */ jsx48(ThemeSegment, { pref: themePref, onPref: setThemePref })
|
|
2857
3006
|
] })
|
|
2858
3007
|
]
|
|
2859
3008
|
}
|
|
2860
3009
|
),
|
|
2861
|
-
hideHeader ? null : /* @__PURE__ */
|
|
2862
|
-
/* @__PURE__ */
|
|
2863
|
-
/* @__PURE__ */
|
|
3010
|
+
hideHeader ? null : /* @__PURE__ */ jsxs33(Fragment13, { children: [
|
|
3011
|
+
/* @__PURE__ */ jsx48(ReportSummary, {}),
|
|
3012
|
+
/* @__PURE__ */ jsx48(ReportMeta, {})
|
|
2864
3013
|
] }),
|
|
2865
|
-
/* @__PURE__ */
|
|
3014
|
+
/* @__PURE__ */ jsx48(
|
|
2866
3015
|
ReportFreshness,
|
|
2867
3016
|
{
|
|
2868
3017
|
lastRunMs: reportLastRunMs(report),
|
|
@@ -2870,8 +3019,14 @@ function ReportInteractiveView({
|
|
|
2870
3019
|
staleAfterDays
|
|
2871
3020
|
}
|
|
2872
3021
|
),
|
|
2873
|
-
/* @__PURE__ */
|
|
2874
|
-
|
|
3022
|
+
/* @__PURE__ */ jsx48(
|
|
3023
|
+
ReportLastRunDelta,
|
|
3024
|
+
{
|
|
3025
|
+
history: scenarioHistory,
|
|
3026
|
+
report
|
|
3027
|
+
}
|
|
3028
|
+
),
|
|
3029
|
+
/* @__PURE__ */ jsx48(
|
|
2875
3030
|
ReportFilters,
|
|
2876
3031
|
{
|
|
2877
3032
|
statuses: statusOptions,
|
|
@@ -2882,14 +3037,28 @@ function ReportInteractiveView({
|
|
|
2882
3037
|
onToggleTag: toggleTag
|
|
2883
3038
|
}
|
|
2884
3039
|
),
|
|
2885
|
-
/* @__PURE__ */
|
|
2886
|
-
showToc ? /* @__PURE__ */
|
|
2887
|
-
/* @__PURE__ */
|
|
2888
|
-
/* @__PURE__ */
|
|
2889
|
-
|
|
3040
|
+
/* @__PURE__ */ jsxs33("div", { className: "flex flex-wrap items-center gap-x-6 gap-y-2 border-t border-border/60 pt-3", children: [
|
|
3041
|
+
showToc ? /* @__PURE__ */ jsx48(ReportTocDrawer, {}) : null,
|
|
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
|
+
)
|
|
2890
3059
|
] }),
|
|
2891
|
-
/* @__PURE__ */
|
|
2892
|
-
/* @__PURE__ */
|
|
3060
|
+
/* @__PURE__ */ jsxs33("div", { className: "flex items-center gap-2", children: [
|
|
3061
|
+
/* @__PURE__ */ jsx48(
|
|
2893
3062
|
Switch,
|
|
2894
3063
|
{
|
|
2895
3064
|
id: docsId,
|
|
@@ -2897,16 +3066,28 @@ function ReportInteractiveView({
|
|
|
2897
3066
|
onCheckedChange: (checked) => setDetail(checked ? "full" : "minimal")
|
|
2898
3067
|
}
|
|
2899
3068
|
),
|
|
2900
|
-
/* @__PURE__ */
|
|
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
|
+
)
|
|
2901
3077
|
] })
|
|
2902
3078
|
] })
|
|
2903
3079
|
] }),
|
|
2904
|
-
/* @__PURE__ */
|
|
2905
|
-
hasContent ? showToc ? /* @__PURE__ */
|
|
2906
|
-
/* @__PURE__ */
|
|
2907
|
-
/* @__PURE__ */
|
|
2908
|
-
] }) : /* @__PURE__ */
|
|
2909
|
-
|
|
3080
|
+
/* @__PURE__ */ jsx48(ReportFailureBanner, { failures }),
|
|
3081
|
+
hasContent ? showToc ? /* @__PURE__ */ jsxs33("div", { className: "flex gap-6", children: [
|
|
3082
|
+
/* @__PURE__ */ jsx48(ReportToc, {}),
|
|
3083
|
+
/* @__PURE__ */ jsx48("div", { className: "flex min-w-0 flex-1 flex-col gap-4", children: /* @__PURE__ */ jsx48(ReportFeatureList, {}) })
|
|
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
|
+
),
|
|
3090
|
+
/* @__PURE__ */ jsx48(
|
|
2910
3091
|
"button",
|
|
2911
3092
|
{
|
|
2912
3093
|
type: "button",
|
|
@@ -2917,8 +3098,14 @@ function ReportInteractiveView({
|
|
|
2917
3098
|
children: "?"
|
|
2918
3099
|
}
|
|
2919
3100
|
),
|
|
2920
|
-
/* @__PURE__ */
|
|
2921
|
-
|
|
3101
|
+
/* @__PURE__ */ jsx48(
|
|
3102
|
+
ReportShortcutsHelp,
|
|
3103
|
+
{
|
|
3104
|
+
open: helpOpen,
|
|
3105
|
+
onClose: () => setHelpOpen(false)
|
|
3106
|
+
}
|
|
3107
|
+
),
|
|
3108
|
+
toast ? /* @__PURE__ */ jsx48(
|
|
2922
3109
|
"div",
|
|
2923
3110
|
{
|
|
2924
3111
|
role: "status",
|
|
@@ -2931,18 +3118,18 @@ function ReportInteractiveView({
|
|
|
2931
3118
|
}
|
|
2932
3119
|
)
|
|
2933
3120
|
}
|
|
2934
|
-
) }) }) });
|
|
3121
|
+
) }) }) }) });
|
|
2935
3122
|
}
|
|
2936
3123
|
|
|
2937
3124
|
// src/interactive/ReportInteractiveIsland.tsx
|
|
2938
3125
|
import { useMemo as useMemo4 } from "react";
|
|
2939
3126
|
|
|
2940
3127
|
// src/interactive/island-renderers.tsx
|
|
2941
|
-
import { createElement, useEffect as
|
|
3128
|
+
import { createElement, useEffect as useEffect10, useState as useState11 } from "react";
|
|
2942
3129
|
|
|
2943
3130
|
// src/components/doc/MermaidView.tsx
|
|
2944
|
-
import { useEffect as
|
|
2945
|
-
import { Fragment as Fragment14, jsx 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";
|
|
2946
3133
|
function prefersDark(el) {
|
|
2947
3134
|
const themed = el?.closest("[data-theme]");
|
|
2948
3135
|
if (themed?.dataset.theme === "dark") return true;
|
|
@@ -2951,9 +3138,9 @@ function prefersDark(el) {
|
|
|
2951
3138
|
}
|
|
2952
3139
|
function MermaidView({ entry, load }) {
|
|
2953
3140
|
const ref = useRef6(null);
|
|
2954
|
-
const [svg, setSvg] =
|
|
2955
|
-
const [error, setError] =
|
|
2956
|
-
|
|
3141
|
+
const [svg, setSvg] = useState10(null);
|
|
3142
|
+
const [error, setError] = useState10(null);
|
|
3143
|
+
useEffect9(() => {
|
|
2957
3144
|
let cancelled = false;
|
|
2958
3145
|
const draw = async () => {
|
|
2959
3146
|
let mermaid;
|
|
@@ -2996,9 +3183,9 @@ function MermaidView({ entry, load }) {
|
|
|
2996
3183
|
};
|
|
2997
3184
|
}, [entry.code, load]);
|
|
2998
3185
|
if (svg) {
|
|
2999
|
-
return /* @__PURE__ */
|
|
3000
|
-
entry.title ? /* @__PURE__ */
|
|
3001
|
-
/* @__PURE__ */
|
|
3186
|
+
return /* @__PURE__ */ jsxs34("figure", { ref, className: "my-3", children: [
|
|
3187
|
+
entry.title ? /* @__PURE__ */ jsx49("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
|
|
3188
|
+
/* @__PURE__ */ jsx49(
|
|
3002
3189
|
"div",
|
|
3003
3190
|
{
|
|
3004
3191
|
role: "img",
|
|
@@ -3009,8 +3196,8 @@ function MermaidView({ entry, load }) {
|
|
|
3009
3196
|
)
|
|
3010
3197
|
] });
|
|
3011
3198
|
}
|
|
3012
|
-
return /* @__PURE__ */
|
|
3013
|
-
error ? /* @__PURE__ */
|
|
3199
|
+
return /* @__PURE__ */ jsxs34(Fragment14, { children: [
|
|
3200
|
+
error ? /* @__PURE__ */ jsxs34(
|
|
3014
3201
|
"p",
|
|
3015
3202
|
{
|
|
3016
3203
|
role: "alert",
|
|
@@ -3022,12 +3209,12 @@ function MermaidView({ entry, load }) {
|
|
|
3022
3209
|
]
|
|
3023
3210
|
}
|
|
3024
3211
|
) : null,
|
|
3025
|
-
/* @__PURE__ */
|
|
3212
|
+
/* @__PURE__ */ jsx49(MermaidSource, { entry, ref })
|
|
3026
3213
|
] });
|
|
3027
3214
|
}
|
|
3028
3215
|
|
|
3029
3216
|
// src/interactive/island-renderers.tsx
|
|
3030
|
-
import { jsx as
|
|
3217
|
+
import { jsx as jsx50 } from "react/jsx-runtime";
|
|
3031
3218
|
var HLJS_ESM = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/es/highlight.min.js";
|
|
3032
3219
|
var MERMAID_ESM = "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
|
|
3033
3220
|
function cachedCdnModule(url) {
|
|
@@ -3044,8 +3231,8 @@ function cachedCdnModule(url) {
|
|
|
3044
3231
|
var loadMermaidFromCdn = cachedCdnModule(MERMAID_ESM);
|
|
3045
3232
|
var loadHljs = cachedCdnModule(HLJS_ESM);
|
|
3046
3233
|
function CdnHighlightedCode({ entry }) {
|
|
3047
|
-
const [html, setHtml] =
|
|
3048
|
-
|
|
3234
|
+
const [html, setHtml] = useState11(null);
|
|
3235
|
+
useEffect10(() => {
|
|
3049
3236
|
let cancelled = false;
|
|
3050
3237
|
void (async () => {
|
|
3051
3238
|
try {
|
|
@@ -3061,13 +3248,13 @@ function CdnHighlightedCode({ entry }) {
|
|
|
3061
3248
|
cancelled = true;
|
|
3062
3249
|
};
|
|
3063
3250
|
}, [entry.content, entry.lang]);
|
|
3064
|
-
return /* @__PURE__ */
|
|
3251
|
+
return /* @__PURE__ */ jsx50(CodeFigure, { label: entry.label, children: html !== null ? /* @__PURE__ */ jsx50(
|
|
3065
3252
|
"code",
|
|
3066
3253
|
{
|
|
3067
3254
|
className: cn("hljs font-mono text-xs", entry.lang && `language-${entry.lang}`),
|
|
3068
3255
|
dangerouslySetInnerHTML: { __html: html }
|
|
3069
3256
|
}
|
|
3070
|
-
) : /* @__PURE__ */
|
|
3257
|
+
) : /* @__PURE__ */ jsx50("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
|
|
3071
3258
|
}
|
|
3072
3259
|
function buildIslandRenderers(opts) {
|
|
3073
3260
|
const renderers = {};
|
|
@@ -3081,7 +3268,7 @@ function buildIslandRenderers(opts) {
|
|
|
3081
3268
|
}
|
|
3082
3269
|
|
|
3083
3270
|
// src/interactive/ReportInteractiveIsland.tsx
|
|
3084
|
-
import { jsx as
|
|
3271
|
+
import { jsx as jsx51 } from "react/jsx-runtime";
|
|
3085
3272
|
function ReportInteractiveIsland({
|
|
3086
3273
|
report,
|
|
3087
3274
|
title,
|
|
@@ -3095,7 +3282,7 @@ function ReportInteractiveIsland({
|
|
|
3095
3282
|
() => buildIslandRenderers({ syntaxHighlighting, mermaid }),
|
|
3096
3283
|
[syntaxHighlighting, mermaid]
|
|
3097
3284
|
);
|
|
3098
|
-
return /* @__PURE__ */
|
|
3285
|
+
return /* @__PURE__ */ jsx51(
|
|
3099
3286
|
ReportInteractive,
|
|
3100
3287
|
{
|
|
3101
3288
|
report,
|