executable-stories-react 0.12.0 → 0.14.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 +73 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +73 -52
- package/dist/index.js.map +1 -1
- package/dist/interactive.cjs +737 -260
- package/dist/interactive.cjs.map +1 -1
- package/dist/interactive.d.cts +3 -1
- package/dist/interactive.d.ts +3 -1
- package/dist/interactive.js +698 -215
- package/dist/interactive.js.map +1 -1
- package/dist/report-island.global.js +65 -54
- package/dist/ssr.cjs +74 -53
- package/dist/ssr.cjs.map +1 -1
- package/dist/ssr.js +74 -53
- package/dist/ssr.js.map +1 -1
- package/dist/styles.css +1 -3
- package/dist/tailwind.css +2 -2
- package/package.json +24 -23
package/dist/interactive.cjs
CHANGED
|
@@ -47,8 +47,9 @@ __export(interactive_exports, {
|
|
|
47
47
|
module.exports = __toCommonJS(interactive_exports);
|
|
48
48
|
|
|
49
49
|
// src/interactive/ReportInteractive.tsx
|
|
50
|
-
var
|
|
51
|
-
var
|
|
50
|
+
var import_lucide_react6 = require("lucide-react");
|
|
51
|
+
var import_react22 = require("webmcpable/react");
|
|
52
|
+
var import_react23 = require("react");
|
|
52
53
|
|
|
53
54
|
// src/components/ui/empty.tsx
|
|
54
55
|
var import_class_variance_authority = require("class-variance-authority");
|
|
@@ -241,7 +242,7 @@ function formatValue(value) {
|
|
|
241
242
|
function DocKv({ entry }) {
|
|
242
243
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("dl", { className: "my-2 flex flex-wrap items-baseline gap-x-2 text-sm", children: [
|
|
243
244
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { className: "font-semibold text-muted-foreground", children: entry.label }),
|
|
244
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dd", { className: "font-mono text-foreground", children: formatValue(entry.value) })
|
|
245
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dd", { className: "min-w-0 break-words font-mono text-foreground", children: formatValue(entry.value) })
|
|
245
246
|
] });
|
|
246
247
|
}
|
|
247
248
|
|
|
@@ -449,7 +450,7 @@ function DocHtml({ entry }) {
|
|
|
449
450
|
loading: "lazy",
|
|
450
451
|
title,
|
|
451
452
|
className: "block w-full border-0",
|
|
452
|
-
style: { height }
|
|
453
|
+
style: { height, colorScheme: "light", background: "#fff" }
|
|
453
454
|
}
|
|
454
455
|
)
|
|
455
456
|
] });
|
|
@@ -1173,6 +1174,24 @@ function scenarioToPrompt(scenario) {
|
|
|
1173
1174
|
}
|
|
1174
1175
|
return lines.join("\n");
|
|
1175
1176
|
}
|
|
1177
|
+
function failuresToPrompt(scenarios) {
|
|
1178
|
+
if (scenarios.length === 0) return "";
|
|
1179
|
+
const noun = scenarios.length === 1 ? "test scenario is" : "test scenarios are";
|
|
1180
|
+
const lines = [
|
|
1181
|
+
`${scenarios.length} ${noun} failing. Investigate the likely cause (they may share one) and propose a fix.`
|
|
1182
|
+
];
|
|
1183
|
+
scenarios.forEach((scenario, i) => {
|
|
1184
|
+
lines.push("", `${i + 1}. ${scenario.title}`, "Steps:");
|
|
1185
|
+
for (const step of scenario.steps) {
|
|
1186
|
+
const mark = step.status === "failed" ? " \u27F5 failed here" : "";
|
|
1187
|
+
lines.push(` ${step.keyword} ${step.text}${mark}`);
|
|
1188
|
+
}
|
|
1189
|
+
if (scenario.errorMessage) {
|
|
1190
|
+
lines.push("Error:", scenario.errorMessage.trim());
|
|
1191
|
+
}
|
|
1192
|
+
});
|
|
1193
|
+
return lines.join("\n");
|
|
1194
|
+
}
|
|
1176
1195
|
function scenarioPermalink(scenario) {
|
|
1177
1196
|
if (typeof location === "undefined") return `#${scenario.id}`;
|
|
1178
1197
|
return `${location.href.split("#")[0]}#${scenario.id}`;
|
|
@@ -1298,57 +1317,68 @@ function ReportScenario({ scenario, hideTitle = false }) {
|
|
|
1298
1317
|
className: "gap-0 py-0",
|
|
1299
1318
|
children: [
|
|
1300
1319
|
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(CardHeader, { className: "px-4 py-3", children: [
|
|
1301
|
-
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1320
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
|
|
1321
|
+
"div",
|
|
1322
|
+
{
|
|
1323
|
+
className: cn(
|
|
1324
|
+
// Wraps rather than squeezing: on a phone the status/duration group
|
|
1325
|
+
// is ~160px, which leaves a title column a few characters wide.
|
|
1326
|
+
"flex flex-wrap items-start gap-x-4 gap-y-1",
|
|
1327
|
+
hideTitle ? "justify-end" : "justify-between"
|
|
1328
|
+
),
|
|
1329
|
+
children: [
|
|
1330
|
+
hideTitle ? null : /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("h3", { id: titleId, className: "flex min-w-0 flex-1 basis-48 items-center gap-2 text-sm font-medium text-foreground", children: [
|
|
1331
|
+
collapsible ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
1332
|
+
"button",
|
|
1333
|
+
{
|
|
1334
|
+
type: "button",
|
|
1335
|
+
onClick: () => collapse.toggle(scenario.id),
|
|
1336
|
+
"aria-expanded": !collapsed,
|
|
1337
|
+
"aria-controls": bodyId,
|
|
1338
|
+
"aria-label": `Toggle ${scenario.title}`,
|
|
1339
|
+
className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
|
|
1340
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
|
|
1341
|
+
}
|
|
1342
|
+
) : null,
|
|
1343
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { "aria-hidden": true, className: cn("text-sm leading-none", meta.glyphColor), children: meta.glyph }),
|
|
1344
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { children: scenario.title })
|
|
1345
|
+
] }),
|
|
1346
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex shrink-0 items-center gap-2", children: [
|
|
1347
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ScenarioRunHistory, { scenarioId: scenario.id }),
|
|
1348
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ScenarioStaleness, { scenario }),
|
|
1349
|
+
scenario.durationMs > 0 ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: (0, import_duration3.formatDuration)(scenario.durationMs) }) : null,
|
|
1350
|
+
meta.showPill ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Badge, { variant: meta.badgeVariant, "aria-label": `Status: ${label}`, children: label }) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "sr-only", children: `Status: ${label}` }),
|
|
1351
|
+
actions ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenu, { children: [
|
|
1352
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
1353
|
+
DropdownMenuTrigger,
|
|
1354
|
+
{
|
|
1355
|
+
"aria-label": `Actions for ${scenario.title}`,
|
|
1356
|
+
className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
|
|
1357
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.MoreHorizontalIcon, { "aria-hidden": true })
|
|
1358
|
+
}
|
|
1359
|
+
),
|
|
1360
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuContent, { align: "end", children: [
|
|
1361
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuItem, { onClick: () => actions.copyLink(scenario), children: [
|
|
1362
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.LinkIcon, {}),
|
|
1363
|
+
" Copy link"
|
|
1364
|
+
] }),
|
|
1365
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuItem, { onClick: () => actions.copyMarkdown(scenario), children: [
|
|
1366
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.FileTextIcon, {}),
|
|
1367
|
+
" Copy as Markdown"
|
|
1368
|
+
] }),
|
|
1369
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DropdownMenuItem, { onClick: () => actions.copyPrompt(scenario), children: [
|
|
1370
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react2.SparklesIcon, {}),
|
|
1371
|
+
" Explain with AI"
|
|
1372
|
+
] })
|
|
1373
|
+
] })
|
|
1374
|
+
] }) : null
|
|
1345
1375
|
] })
|
|
1346
|
-
]
|
|
1347
|
-
|
|
1348
|
-
|
|
1376
|
+
]
|
|
1377
|
+
}
|
|
1378
|
+
),
|
|
1349
1379
|
scenario.tags.length > 0 || (scenario.tickets?.length ?? 0) > 0 ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("ul", { "aria-label": "Tags and tickets", className: "flex flex-wrap gap-1.5", children: [
|
|
1350
1380
|
scenario.tags.map((t) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
|
|
1351
|
-
(scenario.tickets ?? []).map((ticket) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("li", { children: ticket.url ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("a", { href: ticket.url, target: "_blank", rel: "noreferrer noopener", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Badge, { variant: "outline", children: ticket.id }) }) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Badge, { variant: "outline", children: ticket.id }) }, `ticket-${ticket.id}`))
|
|
1381
|
+
(scenario.tickets ?? []).map((ticket) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("li", { children: ticket.url ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("a", { href: ticket.url, target: "_blank", rel: "noreferrer noopener", className: "inline-flex min-h-6 items-center", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Badge, { variant: "outline", children: ticket.id }) }) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Badge, { variant: "outline", children: ticket.id }) }, `ticket-${ticket.id}`))
|
|
1352
1382
|
] }) : null
|
|
1353
1383
|
] }),
|
|
1354
1384
|
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
|
|
@@ -1491,6 +1521,7 @@ function ReportFeatureList() {
|
|
|
1491
1521
|
|
|
1492
1522
|
// src/components/ReportMeta.tsx
|
|
1493
1523
|
var import_duration4 = require("executable-stories-core/utils/duration");
|
|
1524
|
+
var import_report_queries = require("executable-stories-core/report-queries");
|
|
1494
1525
|
var import_jsx_runtime32 = require("react/jsx-runtime");
|
|
1495
1526
|
function ReportMeta() {
|
|
1496
1527
|
const report = useReport();
|
|
@@ -1534,7 +1565,9 @@ function ReportMeta() {
|
|
|
1534
1565
|
value: url ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
|
|
1535
1566
|
});
|
|
1536
1567
|
}
|
|
1537
|
-
|
|
1568
|
+
const slowest = (0, import_report_queries.slowestScenarios)(report, 5);
|
|
1569
|
+
const showSlowest = slowest.length >= 3;
|
|
1570
|
+
if (items.length === 0 && !showSlowest) return null;
|
|
1538
1571
|
return (
|
|
1539
1572
|
// Run metadata is useful but secondary to "what failed" — tuck it into a
|
|
1540
1573
|
// collapsed disclosure so it doesn't push the result down. Native <details>
|
|
@@ -1547,7 +1580,14 @@ function ReportMeta() {
|
|
|
1547
1580
|
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("dl", { className: "flex flex-wrap gap-x-5 gap-y-2 border-t border-border px-4 py-3 text-muted-foreground", "aria-label": "Run metadata", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-baseline gap-1.5", children: [
|
|
1548
1581
|
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("dt", { className: "font-medium", children: item.label }),
|
|
1549
1582
|
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("dd", { className: "font-mono text-foreground", children: item.value })
|
|
1550
|
-
] }, item.label)) })
|
|
1583
|
+
] }, item.label)) }),
|
|
1584
|
+
showSlowest ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "border-t border-border px-4 py-3", children: [
|
|
1585
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "mb-1.5 font-medium text-muted-foreground", children: "Slowest scenarios" }),
|
|
1586
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("ol", { className: "flex flex-col gap-1", "aria-label": "Slowest scenarios", children: slowest.map((s) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("li", { className: "flex items-baseline justify-between gap-4", children: [
|
|
1587
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("a", { href: `#${s.id}`, className: "min-w-0 text-primary underline underline-offset-2", children: s.title }),
|
|
1588
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "shrink-0 font-mono tabular-nums text-muted-foreground", children: (0, import_duration4.formatDuration)(s.durationMs) })
|
|
1589
|
+
] }, s.id)) })
|
|
1590
|
+
] }) : null
|
|
1551
1591
|
] })
|
|
1552
1592
|
);
|
|
1553
1593
|
}
|
|
@@ -2023,21 +2063,26 @@ function allTags(report) {
|
|
|
2023
2063
|
}
|
|
2024
2064
|
return [...seen];
|
|
2025
2065
|
}
|
|
2026
|
-
function queryMatches(scenario, q) {
|
|
2066
|
+
function queryMatches(scenario, q, sourceFile) {
|
|
2027
2067
|
if (q === "") return true;
|
|
2028
2068
|
if (scenario.title.toLowerCase().includes(q)) return true;
|
|
2069
|
+
if (sourceFile?.toLowerCase().includes(q)) return true;
|
|
2070
|
+
if (scenario.errorMessage?.toLowerCase().includes(q)) return true;
|
|
2029
2071
|
for (const tag of scenario.tags) {
|
|
2030
2072
|
if (tag.toLowerCase().includes(q)) return true;
|
|
2031
2073
|
}
|
|
2074
|
+
for (const ticket of scenario.tickets ?? []) {
|
|
2075
|
+
if (ticket.id.toLowerCase().includes(q)) return true;
|
|
2076
|
+
}
|
|
2032
2077
|
for (const step of scenario.steps) {
|
|
2033
2078
|
if (step.text.toLowerCase().includes(q)) return true;
|
|
2034
2079
|
}
|
|
2035
2080
|
return false;
|
|
2036
2081
|
}
|
|
2037
|
-
function scenarioMatches(scenario, q, status, tags) {
|
|
2082
|
+
function scenarioMatches(scenario, q, status, tags, sourceFile) {
|
|
2038
2083
|
if (status !== "all" && scenario.status !== status) return false;
|
|
2039
2084
|
if (tags.length > 0 && !tags.some((t) => scenario.tags.includes(t))) return false;
|
|
2040
|
-
return queryMatches(scenario, q);
|
|
2085
|
+
return queryMatches(scenario, q, sourceFile);
|
|
2041
2086
|
}
|
|
2042
2087
|
function summarizeScenarios(scenarios) {
|
|
2043
2088
|
let total = 0, passed = 0, failed = 0, skipped = 0, pending = 0, durationMs = 0;
|
|
@@ -2060,7 +2105,9 @@ function filterReport(report, criteria) {
|
|
|
2060
2105
|
const features = [];
|
|
2061
2106
|
let topTotal = 0, topPassed = 0, topFailed = 0, topSkipped = 0, topPending = 0, topDuration = 0;
|
|
2062
2107
|
for (const feature of report.features) {
|
|
2063
|
-
const matched = feature.scenarios.filter(
|
|
2108
|
+
const matched = feature.scenarios.filter(
|
|
2109
|
+
(s) => scenarioMatches(s, q, status, tags, feature.sourceFile)
|
|
2110
|
+
);
|
|
2064
2111
|
if (matched.length === 0) continue;
|
|
2065
2112
|
const summary = summarizeScenarios(matched);
|
|
2066
2113
|
features.push({ ...feature, summary, scenarios: matched });
|
|
@@ -2084,6 +2131,9 @@ function filterReport(report, criteria) {
|
|
|
2084
2131
|
features
|
|
2085
2132
|
};
|
|
2086
2133
|
}
|
|
2134
|
+
function failedScenarios(report) {
|
|
2135
|
+
return report.features.flatMap((f) => f.scenarios.filter((s) => s.status === "failed"));
|
|
2136
|
+
}
|
|
2087
2137
|
function listFailures(report) {
|
|
2088
2138
|
const out = [];
|
|
2089
2139
|
for (const feature of report.features) {
|
|
@@ -2104,17 +2154,77 @@ function listFailures(report) {
|
|
|
2104
2154
|
return out;
|
|
2105
2155
|
}
|
|
2106
2156
|
|
|
2157
|
+
// src/interactive/ReportAgentFilterNotice.tsx
|
|
2158
|
+
var import_lucide_react3 = require("lucide-react");
|
|
2159
|
+
var import_jsx_runtime40 = require("react/jsx-runtime");
|
|
2160
|
+
function ReportAgentFilterNotice({
|
|
2161
|
+
applied,
|
|
2162
|
+
onReset,
|
|
2163
|
+
onDismiss
|
|
2164
|
+
}) {
|
|
2165
|
+
const terms = [];
|
|
2166
|
+
if (applied.search) terms.push(`matching \u201C${applied.search}\u201D`);
|
|
2167
|
+
if (applied.status !== "all") terms.push(applied.status);
|
|
2168
|
+
if (applied.tags.length > 0) terms.push(applied.tags.join(", "));
|
|
2169
|
+
return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
|
|
2170
|
+
"div",
|
|
2171
|
+
{
|
|
2172
|
+
role: "status",
|
|
2173
|
+
"aria-live": "polite",
|
|
2174
|
+
className: "flex flex-wrap items-center gap-x-3 gap-y-1 rounded-md border border-primary/40 bg-primary/5 px-3 py-2 text-sm",
|
|
2175
|
+
children: [
|
|
2176
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react3.Bot, { "aria-hidden": true, className: "size-4 shrink-0 text-primary" }),
|
|
2177
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "min-w-0 flex-1", children: [
|
|
2178
|
+
"An agent filtered this report",
|
|
2179
|
+
terms.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
|
|
2180
|
+
" to ",
|
|
2181
|
+
terms.join(" \xB7 ")
|
|
2182
|
+
] }) : null,
|
|
2183
|
+
" \u2014",
|
|
2184
|
+
" ",
|
|
2185
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "tabular-nums", children: [
|
|
2186
|
+
applied.matched,
|
|
2187
|
+
" of ",
|
|
2188
|
+
applied.total,
|
|
2189
|
+
" scenarios"
|
|
2190
|
+
] }),
|
|
2191
|
+
"."
|
|
2192
|
+
] }),
|
|
2193
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2194
|
+
"button",
|
|
2195
|
+
{
|
|
2196
|
+
type: "button",
|
|
2197
|
+
onClick: onReset,
|
|
2198
|
+
className: "cursor-pointer rounded-md border border-border px-2 py-1 text-xs hover:bg-muted",
|
|
2199
|
+
children: "Show all"
|
|
2200
|
+
}
|
|
2201
|
+
),
|
|
2202
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2203
|
+
"button",
|
|
2204
|
+
{
|
|
2205
|
+
type: "button",
|
|
2206
|
+
onClick: onDismiss,
|
|
2207
|
+
"aria-label": "Dismiss",
|
|
2208
|
+
className: "cursor-pointer rounded-md p-1 text-muted-foreground hover:text-foreground",
|
|
2209
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react3.X, { "aria-hidden": true, className: "size-4" })
|
|
2210
|
+
}
|
|
2211
|
+
)
|
|
2212
|
+
]
|
|
2213
|
+
}
|
|
2214
|
+
);
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2107
2217
|
// src/interactive/ReportFailureBanner.tsx
|
|
2108
2218
|
var import_react10 = require("react");
|
|
2109
|
-
var
|
|
2110
|
-
function ReportFailureBanner({ failures }) {
|
|
2219
|
+
var import_jsx_runtime41 = require("react/jsx-runtime");
|
|
2220
|
+
function ReportFailureBanner({ failures, onCopyAll }) {
|
|
2111
2221
|
const first = failures[0];
|
|
2112
2222
|
const jumpToFirst = (0, import_react10.useCallback)(() => {
|
|
2113
2223
|
if (!first) return;
|
|
2114
2224
|
scrollToScenarioId(first.scenarioId);
|
|
2115
2225
|
}, [first]);
|
|
2116
2226
|
if (failures.length === 0) return null;
|
|
2117
|
-
return /* @__PURE__ */ (0,
|
|
2227
|
+
return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
|
|
2118
2228
|
"div",
|
|
2119
2229
|
{
|
|
2120
2230
|
className: "es-failure-banner",
|
|
@@ -2122,13 +2232,13 @@ function ReportFailureBanner({ failures }) {
|
|
|
2122
2232
|
"aria-live": "polite",
|
|
2123
2233
|
"aria-label": "Failure summary",
|
|
2124
2234
|
children: [
|
|
2125
|
-
/* @__PURE__ */ (0,
|
|
2126
|
-
/* @__PURE__ */ (0,
|
|
2235
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { className: "es-failure-banner-text", children: [
|
|
2236
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("strong", { children: failures.length }),
|
|
2127
2237
|
" ",
|
|
2128
2238
|
"failure",
|
|
2129
2239
|
failures.length === 1 ? "" : "s"
|
|
2130
2240
|
] }),
|
|
2131
|
-
/* @__PURE__ */ (0,
|
|
2241
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2132
2242
|
"button",
|
|
2133
2243
|
{
|
|
2134
2244
|
type: "button",
|
|
@@ -2137,7 +2247,23 @@ function ReportFailureBanner({ failures }) {
|
|
|
2137
2247
|
"aria-label": "View first failure",
|
|
2138
2248
|
children: "View first failure \u2193"
|
|
2139
2249
|
}
|
|
2140
|
-
)
|
|
2250
|
+
),
|
|
2251
|
+
onCopyAll ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
|
|
2252
|
+
"button",
|
|
2253
|
+
{
|
|
2254
|
+
type: "button",
|
|
2255
|
+
className: "es-failure-banner-jump",
|
|
2256
|
+
onClick: onCopyAll,
|
|
2257
|
+
"aria-label": "Copy failures for an agent",
|
|
2258
|
+
children: [
|
|
2259
|
+
"Copy ",
|
|
2260
|
+
failures.length,
|
|
2261
|
+
" failure",
|
|
2262
|
+
failures.length === 1 ? "" : "s",
|
|
2263
|
+
" for an agent"
|
|
2264
|
+
]
|
|
2265
|
+
}
|
|
2266
|
+
) : null
|
|
2141
2267
|
]
|
|
2142
2268
|
}
|
|
2143
2269
|
);
|
|
@@ -2145,7 +2271,7 @@ function ReportFailureBanner({ failures }) {
|
|
|
2145
2271
|
|
|
2146
2272
|
// src/interactive/ReportFilters.tsx
|
|
2147
2273
|
var import_react11 = require("react");
|
|
2148
|
-
var
|
|
2274
|
+
var import_jsx_runtime42 = require("react/jsx-runtime");
|
|
2149
2275
|
var TAG_LIMIT = 12;
|
|
2150
2276
|
function ReportFilters({
|
|
2151
2277
|
statuses,
|
|
@@ -2159,10 +2285,10 @@ function ReportFilters({
|
|
|
2159
2285
|
if (statuses.length === 0 && tags.length === 0) return null;
|
|
2160
2286
|
const tagsCollapsed = tags.length > TAG_LIMIT && !showAllTags;
|
|
2161
2287
|
const visibleTags = tagsCollapsed ? tags.slice(0, TAG_LIMIT) : tags;
|
|
2162
|
-
return /* @__PURE__ */ (0,
|
|
2163
|
-
statuses.length > 0 ? /* @__PURE__ */ (0,
|
|
2288
|
+
return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex flex-col gap-2", children: [
|
|
2289
|
+
statuses.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { role: "group", "aria-label": "Filter by status", className: "flex flex-wrap gap-1", children: statuses.map((opt) => {
|
|
2164
2290
|
const active = status === opt.key;
|
|
2165
|
-
return /* @__PURE__ */ (0,
|
|
2291
|
+
return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
|
|
2166
2292
|
"button",
|
|
2167
2293
|
{
|
|
2168
2294
|
type: "button",
|
|
@@ -2174,23 +2300,23 @@ function ReportFilters({
|
|
|
2174
2300
|
),
|
|
2175
2301
|
children: [
|
|
2176
2302
|
opt.label,
|
|
2177
|
-
/* @__PURE__ */ (0,
|
|
2303
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "ml-1 font-mono opacity-70", children: opt.count })
|
|
2178
2304
|
]
|
|
2179
2305
|
},
|
|
2180
2306
|
opt.key
|
|
2181
2307
|
);
|
|
2182
2308
|
}) }) : null,
|
|
2183
|
-
tags.length > 0 ? /* @__PURE__ */ (0,
|
|
2309
|
+
tags.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("ul", { "aria-label": "Filter by tag", className: "flex flex-wrap items-center gap-1.5", children: [
|
|
2184
2310
|
visibleTags.map((tag) => {
|
|
2185
2311
|
const active = activeTags.includes(tag);
|
|
2186
|
-
return /* @__PURE__ */ (0,
|
|
2312
|
+
return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
2187
2313
|
"button",
|
|
2188
2314
|
{
|
|
2189
2315
|
type: "button",
|
|
2190
2316
|
"aria-pressed": active,
|
|
2191
2317
|
onClick: () => onToggleTag(tag),
|
|
2192
2318
|
className: "cursor-pointer",
|
|
2193
|
-
children: /* @__PURE__ */ (0,
|
|
2319
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
2194
2320
|
Badge,
|
|
2195
2321
|
{
|
|
2196
2322
|
variant: "tag",
|
|
@@ -2203,7 +2329,7 @@ function ReportFilters({
|
|
|
2203
2329
|
}
|
|
2204
2330
|
) }, tag);
|
|
2205
2331
|
}),
|
|
2206
|
-
tags.length > TAG_LIMIT ? /* @__PURE__ */ (0,
|
|
2332
|
+
tags.length > TAG_LIMIT ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
2207
2333
|
"button",
|
|
2208
2334
|
{
|
|
2209
2335
|
type: "button",
|
|
@@ -2219,7 +2345,7 @@ function ReportFilters({
|
|
|
2219
2345
|
|
|
2220
2346
|
// src/interactive/ReportFreshness.tsx
|
|
2221
2347
|
var import_react12 = require("react");
|
|
2222
|
-
var
|
|
2348
|
+
var import_jsx_runtime43 = require("react/jsx-runtime");
|
|
2223
2349
|
function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
|
|
2224
2350
|
const [now, setNow] = (0, import_react12.useState)(nowMs);
|
|
2225
2351
|
(0, import_react12.useEffect)(() => {
|
|
@@ -2227,7 +2353,7 @@ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
|
|
|
2227
2353
|
}, [nowMs]);
|
|
2228
2354
|
if (lastRunMs === void 0 || now === void 0) return null;
|
|
2229
2355
|
const age = formatRelativeAge(lastRunMs, now);
|
|
2230
|
-
const runLink = ciUrl ? /* @__PURE__ */ (0,
|
|
2356
|
+
const runLink = ciUrl ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
2231
2357
|
"a",
|
|
2232
2358
|
{
|
|
2233
2359
|
href: ciUrl,
|
|
@@ -2238,9 +2364,9 @@ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
|
|
|
2238
2364
|
}
|
|
2239
2365
|
) : null;
|
|
2240
2366
|
if (isReportStale(lastRunMs, staleAfterDays, now)) {
|
|
2241
|
-
return /* @__PURE__ */ (0,
|
|
2242
|
-
/* @__PURE__ */ (0,
|
|
2243
|
-
/* @__PURE__ */ (0,
|
|
2367
|
+
return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "es-stale-banner", role: "status", "aria-label": "Report freshness", children: [
|
|
2368
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("span", { children: [
|
|
2369
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("strong", { children: [
|
|
2244
2370
|
"Last verified ",
|
|
2245
2371
|
age,
|
|
2246
2372
|
"."
|
|
@@ -2250,10 +2376,10 @@ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
|
|
|
2250
2376
|
runLink
|
|
2251
2377
|
] });
|
|
2252
2378
|
}
|
|
2253
|
-
return /* @__PURE__ */ (0,
|
|
2379
|
+
return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("p", { className: "es-freshness", "aria-label": "Report freshness", children: [
|
|
2254
2380
|
"Verified ",
|
|
2255
2381
|
age,
|
|
2256
|
-
runLink ? /* @__PURE__ */ (0,
|
|
2382
|
+
runLink ? /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(import_jsx_runtime43.Fragment, { children: [
|
|
2257
2383
|
" \xB7 ",
|
|
2258
2384
|
runLink
|
|
2259
2385
|
] }) : null
|
|
@@ -2261,15 +2387,15 @@ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
|
|
|
2261
2387
|
}
|
|
2262
2388
|
|
|
2263
2389
|
// src/interactive/ReportLastRunDelta.tsx
|
|
2264
|
-
var
|
|
2390
|
+
var import_jsx_runtime44 = require("react/jsx-runtime");
|
|
2265
2391
|
var MAX_LINKS = 5;
|
|
2266
2392
|
function ScenarioLinks({ scenarios, tone }) {
|
|
2267
|
-
return /* @__PURE__ */ (0,
|
|
2268
|
-
scenarios.slice(0, MAX_LINKS).map((s, i) => /* @__PURE__ */ (0,
|
|
2393
|
+
return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_jsx_runtime44.Fragment, { children: [
|
|
2394
|
+
scenarios.slice(0, MAX_LINKS).map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("span", { children: [
|
|
2269
2395
|
i > 0 ? ", " : "",
|
|
2270
|
-
/* @__PURE__ */ (0,
|
|
2396
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)("a", { href: `#${s.id}`, className: cn("underline underline-offset-2 hover:no-underline", tone), children: s.title })
|
|
2271
2397
|
] }, s.id)),
|
|
2272
|
-
scenarios.length > MAX_LINKS ? /* @__PURE__ */ (0,
|
|
2398
|
+
scenarios.length > MAX_LINKS ? /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("span", { children: [
|
|
2273
2399
|
" and ",
|
|
2274
2400
|
scenarios.length - MAX_LINKS,
|
|
2275
2401
|
" more"
|
|
@@ -2286,23 +2412,23 @@ function ReportLastRunDelta({
|
|
|
2286
2412
|
if (!delta) return null;
|
|
2287
2413
|
const { newlyFailing, newlyPassing, added } = delta;
|
|
2288
2414
|
if (newlyFailing.length === 0 && newlyPassing.length === 0 && added.length === 0) {
|
|
2289
|
-
return /* @__PURE__ */ (0,
|
|
2415
|
+
return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: "Since last run: no behavior changes." });
|
|
2290
2416
|
}
|
|
2291
|
-
return /* @__PURE__ */ (0,
|
|
2292
|
-
/* @__PURE__ */ (0,
|
|
2293
|
-
newlyFailing.length > 0 ? /* @__PURE__ */ (0,
|
|
2417
|
+
return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: [
|
|
2418
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "font-medium text-foreground", children: "Since last run:" }),
|
|
2419
|
+
newlyFailing.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("span", { className: "ml-2 text-fail", children: [
|
|
2294
2420
|
newlyFailing.length,
|
|
2295
2421
|
" newly failing (",
|
|
2296
|
-
/* @__PURE__ */ (0,
|
|
2422
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ScenarioLinks, { scenarios: newlyFailing, tone: "text-fail" }),
|
|
2297
2423
|
")"
|
|
2298
2424
|
] }) : null,
|
|
2299
|
-
newlyPassing.length > 0 ? /* @__PURE__ */ (0,
|
|
2425
|
+
newlyPassing.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("span", { className: "ml-2 text-pass", children: [
|
|
2300
2426
|
newlyPassing.length,
|
|
2301
2427
|
" fixed (",
|
|
2302
|
-
/* @__PURE__ */ (0,
|
|
2428
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ScenarioLinks, { scenarios: newlyPassing, tone: "text-pass" }),
|
|
2303
2429
|
")"
|
|
2304
2430
|
] }) : null,
|
|
2305
|
-
added.length > 0 ? /* @__PURE__ */ (0,
|
|
2431
|
+
added.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("span", { className: "ml-2", children: [
|
|
2306
2432
|
added.length,
|
|
2307
2433
|
" new"
|
|
2308
2434
|
] }) : null
|
|
@@ -2311,13 +2437,13 @@ function ReportLastRunDelta({
|
|
|
2311
2437
|
|
|
2312
2438
|
// src/interactive/ReportSearch.tsx
|
|
2313
2439
|
var import_react13 = require("react");
|
|
2314
|
-
var
|
|
2440
|
+
var import_lucide_react4 = require("lucide-react");
|
|
2315
2441
|
|
|
2316
2442
|
// src/components/ui/input.tsx
|
|
2317
2443
|
var React6 = require("react");
|
|
2318
|
-
var
|
|
2444
|
+
var import_jsx_runtime45 = require("react/jsx-runtime");
|
|
2319
2445
|
function Input({ className, type, ...props }) {
|
|
2320
|
-
return /* @__PURE__ */ (0,
|
|
2446
|
+
return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
2321
2447
|
"input",
|
|
2322
2448
|
{
|
|
2323
2449
|
type,
|
|
@@ -2334,7 +2460,7 @@ function Input({ className, type, ...props }) {
|
|
|
2334
2460
|
}
|
|
2335
2461
|
|
|
2336
2462
|
// src/interactive/ReportSearch.tsx
|
|
2337
|
-
var
|
|
2463
|
+
var import_jsx_runtime46 = require("react/jsx-runtime");
|
|
2338
2464
|
var ReportSearch = (0, import_react13.forwardRef)(
|
|
2339
2465
|
function ReportSearch2(props, ref) {
|
|
2340
2466
|
const {
|
|
@@ -2342,7 +2468,7 @@ var ReportSearch = (0, import_react13.forwardRef)(
|
|
|
2342
2468
|
onChange,
|
|
2343
2469
|
matchedCount,
|
|
2344
2470
|
totalCount,
|
|
2345
|
-
placeholder = "Search scenarios
|
|
2471
|
+
placeholder = "Search scenarios, tags, tickets, errors\u2026",
|
|
2346
2472
|
className
|
|
2347
2473
|
} = props;
|
|
2348
2474
|
const inputId = (0, import_react13.useId)();
|
|
@@ -2356,17 +2482,17 @@ var ReportSearch = (0, import_react13.forwardRef)(
|
|
|
2356
2482
|
e.preventDefault();
|
|
2357
2483
|
}
|
|
2358
2484
|
}
|
|
2359
|
-
return /* @__PURE__ */ (0,
|
|
2360
|
-
/* @__PURE__ */ (0,
|
|
2361
|
-
/* @__PURE__ */ (0,
|
|
2362
|
-
/* @__PURE__ */ (0,
|
|
2363
|
-
|
|
2485
|
+
return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: cn("es-search flex flex-col gap-1", className), children: [
|
|
2486
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("label", { htmlFor: inputId, className: "sr-only", children: "Search" }),
|
|
2487
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "relative w-full sm:w-72", children: [
|
|
2488
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
2489
|
+
import_lucide_react4.Search,
|
|
2364
2490
|
{
|
|
2365
2491
|
"aria-hidden": true,
|
|
2366
2492
|
className: "pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground"
|
|
2367
2493
|
}
|
|
2368
2494
|
),
|
|
2369
|
-
/* @__PURE__ */ (0,
|
|
2495
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
2370
2496
|
Input,
|
|
2371
2497
|
{
|
|
2372
2498
|
ref,
|
|
@@ -2383,7 +2509,7 @@ var ReportSearch = (0, import_react13.forwardRef)(
|
|
|
2383
2509
|
}
|
|
2384
2510
|
)
|
|
2385
2511
|
] }),
|
|
2386
|
-
showCounts ? /* @__PURE__ */ (0,
|
|
2512
|
+
showCounts ? /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
|
|
2387
2513
|
"span",
|
|
2388
2514
|
{
|
|
2389
2515
|
className: "text-xs tabular-nums text-muted-foreground",
|
|
@@ -2400,9 +2526,119 @@ var ReportSearch = (0, import_react13.forwardRef)(
|
|
|
2400
2526
|
}
|
|
2401
2527
|
);
|
|
2402
2528
|
|
|
2403
|
-
// src/interactive/
|
|
2529
|
+
// src/interactive/ReportSinceLastVisit.tsx
|
|
2404
2530
|
var import_react14 = require("react");
|
|
2405
2531
|
|
|
2532
|
+
// src/interactive/last-visit.ts
|
|
2533
|
+
function snapshotOf(report, atMs) {
|
|
2534
|
+
const statuses = {};
|
|
2535
|
+
for (const feature of report.features) {
|
|
2536
|
+
for (const scenario of feature.scenarios) {
|
|
2537
|
+
statuses[scenario.id] = scenario.status;
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
return { runId: report.runId, atMs, statuses };
|
|
2541
|
+
}
|
|
2542
|
+
function diffSinceVisit(previous, report) {
|
|
2543
|
+
if (!previous) return null;
|
|
2544
|
+
if (previous.runId === report.runId) return null;
|
|
2545
|
+
const newlyFailing = [];
|
|
2546
|
+
const newlyPassing = [];
|
|
2547
|
+
const added = [];
|
|
2548
|
+
for (const feature of report.features) {
|
|
2549
|
+
for (const scenario of feature.scenarios) {
|
|
2550
|
+
const ref = { id: scenario.id, title: scenario.title };
|
|
2551
|
+
const before = previous.statuses[scenario.id];
|
|
2552
|
+
if (before === void 0) {
|
|
2553
|
+
added.push(ref);
|
|
2554
|
+
} else if (before !== "failed" && scenario.status === "failed") {
|
|
2555
|
+
newlyFailing.push(ref);
|
|
2556
|
+
} else if (before === "failed" && scenario.status === "passed") {
|
|
2557
|
+
newlyPassing.push(ref);
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
if (newlyFailing.length === 0 && newlyPassing.length === 0 && added.length === 0) return null;
|
|
2562
|
+
return { sinceMs: previous.atMs, newlyFailing, newlyPassing, added };
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
// src/interactive/ReportSinceLastVisit.tsx
|
|
2566
|
+
var import_jsx_runtime47 = require("react/jsx-runtime");
|
|
2567
|
+
var KEY = "es-last-visit";
|
|
2568
|
+
var MAX_LINKS2 = 5;
|
|
2569
|
+
function readVisit() {
|
|
2570
|
+
try {
|
|
2571
|
+
const raw = localStorage.getItem(KEY);
|
|
2572
|
+
if (!raw) return null;
|
|
2573
|
+
const parsed = JSON.parse(raw);
|
|
2574
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
2575
|
+
const v = parsed;
|
|
2576
|
+
if (typeof v.runId !== "string" || typeof v.atMs !== "number" || typeof v.statuses !== "object") {
|
|
2577
|
+
return null;
|
|
2578
|
+
}
|
|
2579
|
+
return { runId: v.runId, atMs: v.atMs, statuses: v.statuses };
|
|
2580
|
+
} catch {
|
|
2581
|
+
return null;
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
function writeVisit(snapshot) {
|
|
2585
|
+
try {
|
|
2586
|
+
localStorage.setItem(KEY, JSON.stringify(snapshot));
|
|
2587
|
+
} catch {
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
function ScenarioLinks2({ scenarios, tone }) {
|
|
2591
|
+
return /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(import_jsx_runtime47.Fragment, { children: [
|
|
2592
|
+
scenarios.slice(0, MAX_LINKS2).map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("span", { children: [
|
|
2593
|
+
i > 0 ? ", " : "",
|
|
2594
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)("a", { href: `#${s.id}`, className: cn("underline underline-offset-2 hover:no-underline", tone), children: s.title })
|
|
2595
|
+
] }, s.id)),
|
|
2596
|
+
scenarios.length > MAX_LINKS2 ? /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("span", { children: [
|
|
2597
|
+
" and ",
|
|
2598
|
+
scenarios.length - MAX_LINKS2,
|
|
2599
|
+
" more"
|
|
2600
|
+
] }) : null
|
|
2601
|
+
] });
|
|
2602
|
+
}
|
|
2603
|
+
function ReportSinceLastVisit({ report }) {
|
|
2604
|
+
const [delta, setDelta] = (0, import_react14.useState)(null);
|
|
2605
|
+
const [nowMs, setNowMs] = (0, import_react14.useState)(0);
|
|
2606
|
+
(0, import_react14.useEffect)(() => {
|
|
2607
|
+
const now = Date.now();
|
|
2608
|
+
setDelta(diffSinceVisit(readVisit(), report));
|
|
2609
|
+
setNowMs(now);
|
|
2610
|
+
writeVisit(snapshotOf(report, now));
|
|
2611
|
+
}, [report]);
|
|
2612
|
+
if (!delta) return null;
|
|
2613
|
+
const { newlyFailing, newlyPassing, added } = delta;
|
|
2614
|
+
return /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("p", { className: "text-xs text-muted-foreground", "data-testid": "es-since-last-visit", children: [
|
|
2615
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("span", { className: "font-medium text-foreground", children: [
|
|
2616
|
+
"Since your last visit (",
|
|
2617
|
+
formatRelativeAge(delta.sinceMs, nowMs),
|
|
2618
|
+
"):"
|
|
2619
|
+
] }),
|
|
2620
|
+
newlyFailing.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("span", { className: "ml-2 text-fail", children: [
|
|
2621
|
+
newlyFailing.length,
|
|
2622
|
+
" started failing (",
|
|
2623
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)(ScenarioLinks2, { scenarios: newlyFailing, tone: "text-fail" }),
|
|
2624
|
+
")"
|
|
2625
|
+
] }) : null,
|
|
2626
|
+
newlyPassing.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("span", { className: "ml-2 text-pass", children: [
|
|
2627
|
+
newlyPassing.length,
|
|
2628
|
+
" fixed (",
|
|
2629
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)(ScenarioLinks2, { scenarios: newlyPassing, tone: "text-pass" }),
|
|
2630
|
+
")"
|
|
2631
|
+
] }) : null,
|
|
2632
|
+
added.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("span", { className: "ml-2", children: [
|
|
2633
|
+
added.length,
|
|
2634
|
+
" new"
|
|
2635
|
+
] }) : null
|
|
2636
|
+
] });
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
// src/interactive/ReportShortcutsHelp.tsx
|
|
2640
|
+
var import_react15 = require("react");
|
|
2641
|
+
|
|
2406
2642
|
// src/components/ui/button.tsx
|
|
2407
2643
|
var React7 = require("react");
|
|
2408
2644
|
var import_class_variance_authority4 = require("class-variance-authority");
|
|
@@ -2457,7 +2693,7 @@ function Button({
|
|
|
2457
2693
|
}
|
|
2458
2694
|
|
|
2459
2695
|
// src/interactive/ReportShortcutsHelp.tsx
|
|
2460
|
-
var
|
|
2696
|
+
var import_jsx_runtime48 = require("react/jsx-runtime");
|
|
2461
2697
|
var SHORTCUTS = [
|
|
2462
2698
|
{ keys: "/", description: "Focus search" },
|
|
2463
2699
|
{ keys: "f", description: "Jump to next failure" },
|
|
@@ -2469,20 +2705,20 @@ var SHORTCUTS = [
|
|
|
2469
2705
|
];
|
|
2470
2706
|
function Keys({ keys }) {
|
|
2471
2707
|
const parts = keys.split(" ");
|
|
2472
|
-
return /* @__PURE__ */ (0,
|
|
2473
|
-
i > 0 ? /* @__PURE__ */ (0,
|
|
2474
|
-
/* @__PURE__ */ (0,
|
|
2708
|
+
return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "inline-flex items-center gap-1", children: parts.map((k, i) => /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("span", { className: "inline-flex items-center gap-1", children: [
|
|
2709
|
+
i > 0 ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "text-xs text-muted-foreground", children: "+" }) : null,
|
|
2710
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("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 })
|
|
2475
2711
|
] }, k)) });
|
|
2476
2712
|
}
|
|
2477
2713
|
function ReportShortcutsHelp({ open, onClose }) {
|
|
2478
|
-
const dialogRef = (0,
|
|
2479
|
-
(0,
|
|
2714
|
+
const dialogRef = (0, import_react15.useRef)(null);
|
|
2715
|
+
(0, import_react15.useEffect)(() => {
|
|
2480
2716
|
const dialog = dialogRef.current;
|
|
2481
2717
|
if (!dialog) return;
|
|
2482
2718
|
if (open && !dialog.open) dialog.showModal();
|
|
2483
2719
|
else if (!open && dialog.open) dialog.close();
|
|
2484
2720
|
}, [open]);
|
|
2485
|
-
return /* @__PURE__ */ (0,
|
|
2721
|
+
return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(
|
|
2486
2722
|
"dialog",
|
|
2487
2723
|
{
|
|
2488
2724
|
ref: dialogRef,
|
|
@@ -2490,20 +2726,20 @@ function ReportShortcutsHelp({ open, onClose }) {
|
|
|
2490
2726
|
"aria-label": "Keyboard shortcuts",
|
|
2491
2727
|
onClose,
|
|
2492
2728
|
children: [
|
|
2493
|
-
/* @__PURE__ */ (0,
|
|
2494
|
-
/* @__PURE__ */ (0,
|
|
2495
|
-
/* @__PURE__ */ (0,
|
|
2496
|
-
/* @__PURE__ */ (0,
|
|
2729
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("h2", { className: "text-base font-semibold", children: "Keyboard shortcuts" }),
|
|
2730
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("dl", { className: "mt-4 grid grid-cols-[auto_1fr] items-center gap-x-4 gap-y-2.5", children: SHORTCUTS.map((s) => /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "col-span-2 grid grid-cols-subgrid items-center", children: [
|
|
2731
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("dt", { className: "justify-self-start", children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(Keys, { keys: s.keys }) }),
|
|
2732
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("dd", { className: "text-sm text-muted-foreground", children: s.description })
|
|
2497
2733
|
] }, s.keys)) }),
|
|
2498
|
-
/* @__PURE__ */ (0,
|
|
2734
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("form", { method: "dialog", className: "mt-6", children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(Button, { type: "submit", variant: "outline", size: "sm", children: "Close" }) })
|
|
2499
2735
|
]
|
|
2500
2736
|
}
|
|
2501
2737
|
);
|
|
2502
2738
|
}
|
|
2503
2739
|
|
|
2504
2740
|
// src/interactive/ReportToc.tsx
|
|
2505
|
-
var
|
|
2506
|
-
var
|
|
2741
|
+
var import_react16 = require("react");
|
|
2742
|
+
var import_jsx_runtime49 = require("react/jsx-runtime");
|
|
2507
2743
|
var DOT = {
|
|
2508
2744
|
passed: "bg-pass",
|
|
2509
2745
|
failed: "bg-fail",
|
|
@@ -2512,12 +2748,12 @@ var DOT = {
|
|
|
2512
2748
|
};
|
|
2513
2749
|
function TocContent({ onNavigate }) {
|
|
2514
2750
|
const report = useReport();
|
|
2515
|
-
const [activeId, setActiveId] = (0,
|
|
2516
|
-
const scenarioIds = (0,
|
|
2751
|
+
const [activeId, setActiveId] = (0, import_react16.useState)(null);
|
|
2752
|
+
const scenarioIds = (0, import_react16.useMemo)(
|
|
2517
2753
|
() => report.features.flatMap((f) => f.scenarios.map((s) => s.id)),
|
|
2518
2754
|
[report]
|
|
2519
2755
|
);
|
|
2520
|
-
(0,
|
|
2756
|
+
(0, import_react16.useEffect)(() => {
|
|
2521
2757
|
if (typeof IntersectionObserver === "undefined") return;
|
|
2522
2758
|
const els = scenarioIds.map((id) => document.getElementById(id)).filter((el) => el !== null);
|
|
2523
2759
|
if (els.length === 0) return;
|
|
@@ -2532,11 +2768,11 @@ function TocContent({ onNavigate }) {
|
|
|
2532
2768
|
return () => observer.disconnect();
|
|
2533
2769
|
}, [scenarioIds]);
|
|
2534
2770
|
if (report.features.length === 0) return null;
|
|
2535
|
-
return /* @__PURE__ */ (0,
|
|
2536
|
-
/* @__PURE__ */ (0,
|
|
2537
|
-
/* @__PURE__ */ (0,
|
|
2771
|
+
return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("ul", { className: "flex list-none flex-col gap-4 pl-0", children: report.features.map((feature) => /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("li", { children: [
|
|
2772
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)("p", { className: "mb-1.5 font-semibold leading-snug text-foreground", children: feature.title }),
|
|
2773
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)("ul", { className: "flex list-none flex-col gap-0.5 pl-0", children: feature.scenarios.map((scenario) => {
|
|
2538
2774
|
const active = activeId === scenario.id;
|
|
2539
|
-
return /* @__PURE__ */ (0,
|
|
2775
|
+
return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
|
|
2540
2776
|
"a",
|
|
2541
2777
|
{
|
|
2542
2778
|
href: `#${scenario.id}`,
|
|
@@ -2551,14 +2787,14 @@ function TocContent({ onNavigate }) {
|
|
|
2551
2787
|
active ? "border-primary font-medium text-foreground" : "border-border text-muted-foreground"
|
|
2552
2788
|
),
|
|
2553
2789
|
children: [
|
|
2554
|
-
/* @__PURE__ */ (0,
|
|
2790
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
2555
2791
|
"span",
|
|
2556
2792
|
{
|
|
2557
2793
|
"aria-hidden": true,
|
|
2558
2794
|
className: cn("mt-1 size-1.5 shrink-0 rounded-full", DOT[scenario.status])
|
|
2559
2795
|
}
|
|
2560
2796
|
),
|
|
2561
|
-
/* @__PURE__ */ (0,
|
|
2797
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "break-words", children: scenario.title })
|
|
2562
2798
|
]
|
|
2563
2799
|
}
|
|
2564
2800
|
) }, scenario.id);
|
|
@@ -2568,48 +2804,48 @@ function TocContent({ onNavigate }) {
|
|
|
2568
2804
|
function ReportToc() {
|
|
2569
2805
|
const report = useReport();
|
|
2570
2806
|
if (report.features.length === 0) return null;
|
|
2571
|
-
return /* @__PURE__ */ (0,
|
|
2807
|
+
return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
|
|
2572
2808
|
"nav",
|
|
2573
2809
|
{
|
|
2574
2810
|
"aria-label": "Table of contents",
|
|
2575
2811
|
className: "sticky top-4 hidden h-fit w-64 shrink-0 self-start text-xs lg:block",
|
|
2576
2812
|
children: [
|
|
2577
|
-
/* @__PURE__ */ (0,
|
|
2578
|
-
/* @__PURE__ */ (0,
|
|
2813
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)("p", { className: "mb-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Contents" }),
|
|
2814
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(TocContent, {})
|
|
2579
2815
|
]
|
|
2580
2816
|
}
|
|
2581
2817
|
);
|
|
2582
2818
|
}
|
|
2583
2819
|
|
|
2584
2820
|
// src/interactive/ReportTocDrawer.tsx
|
|
2585
|
-
var
|
|
2821
|
+
var import_react17 = require("react");
|
|
2586
2822
|
var import_dialog = require("@base-ui/react/dialog");
|
|
2587
|
-
var
|
|
2588
|
-
var
|
|
2823
|
+
var import_lucide_react5 = require("lucide-react");
|
|
2824
|
+
var import_jsx_runtime50 = require("react/jsx-runtime");
|
|
2589
2825
|
function ReportTocDrawer() {
|
|
2590
2826
|
const report = useReport();
|
|
2591
|
-
const [open, setOpen] = (0,
|
|
2827
|
+
const [open, setOpen] = (0, import_react17.useState)(false);
|
|
2592
2828
|
if (report.features.length === 0) return null;
|
|
2593
|
-
return /* @__PURE__ */ (0,
|
|
2594
|
-
/* @__PURE__ */ (0,
|
|
2595
|
-
/* @__PURE__ */ (0,
|
|
2829
|
+
return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_dialog.Dialog.Root, { open, onOpenChange: setOpen, children: [
|
|
2830
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_dialog.Dialog.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: [
|
|
2831
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_lucide_react5.ListIcon, { "aria-hidden": true }),
|
|
2596
2832
|
" Contents"
|
|
2597
2833
|
] }),
|
|
2598
|
-
/* @__PURE__ */ (0,
|
|
2599
|
-
/* @__PURE__ */ (0,
|
|
2600
|
-
/* @__PURE__ */ (0,
|
|
2601
|
-
/* @__PURE__ */ (0,
|
|
2602
|
-
/* @__PURE__ */ (0,
|
|
2603
|
-
/* @__PURE__ */ (0,
|
|
2834
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_dialog.Dialog.Portal, { children: [
|
|
2835
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_dialog.Dialog.Backdrop, { className: "fixed inset-0 z-50 bg-black/50 transition-opacity duration-200 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0" }),
|
|
2836
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_dialog.Dialog.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: [
|
|
2837
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { className: "flex items-center justify-between", children: [
|
|
2838
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_dialog.Dialog.Title, { className: "text-xs font-semibold tracking-wide text-muted-foreground uppercase", children: "Contents" }),
|
|
2839
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
|
|
2604
2840
|
import_dialog.Dialog.Close,
|
|
2605
2841
|
{
|
|
2606
2842
|
"aria-label": "Close contents",
|
|
2607
2843
|
className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
|
|
2608
|
-
children: /* @__PURE__ */ (0,
|
|
2844
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_lucide_react5.XIcon, { "aria-hidden": true })
|
|
2609
2845
|
}
|
|
2610
2846
|
)
|
|
2611
2847
|
] }),
|
|
2612
|
-
/* @__PURE__ */ (0,
|
|
2848
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)(TocContent, { onNavigate: () => setOpen(false) })
|
|
2613
2849
|
] })
|
|
2614
2850
|
] })
|
|
2615
2851
|
] });
|
|
@@ -2643,10 +2879,10 @@ function failuresFirst(report) {
|
|
|
2643
2879
|
}
|
|
2644
2880
|
|
|
2645
2881
|
// src/interactive/use-deep-link-scroll.ts
|
|
2646
|
-
var
|
|
2882
|
+
var import_react18 = require("react");
|
|
2647
2883
|
function useDeepLinkScroll() {
|
|
2648
|
-
const lastAnchor = (0,
|
|
2649
|
-
(0,
|
|
2884
|
+
const lastAnchor = (0, import_react18.useRef)(null);
|
|
2885
|
+
(0, import_react18.useEffect)(() => {
|
|
2650
2886
|
function scrollToHash() {
|
|
2651
2887
|
const { anchor } = splitHash(window.location.hash);
|
|
2652
2888
|
if (!anchor || anchor === lastAnchor.current) return;
|
|
@@ -2662,7 +2898,7 @@ function useDeepLinkScroll() {
|
|
|
2662
2898
|
}
|
|
2663
2899
|
|
|
2664
2900
|
// src/interactive/use-keyboard-shortcuts.ts
|
|
2665
|
-
var
|
|
2901
|
+
var import_react19 = require("react");
|
|
2666
2902
|
function isEditableTarget(target) {
|
|
2667
2903
|
if (!(target instanceof HTMLElement)) return false;
|
|
2668
2904
|
const tag = target.tagName;
|
|
@@ -2671,9 +2907,9 @@ function isEditableTarget(target) {
|
|
|
2671
2907
|
return false;
|
|
2672
2908
|
}
|
|
2673
2909
|
function useKeyboardShortcuts(handlers) {
|
|
2674
|
-
const ref = (0,
|
|
2910
|
+
const ref = (0, import_react19.useRef)(handlers);
|
|
2675
2911
|
ref.current = handlers;
|
|
2676
|
-
(0,
|
|
2912
|
+
(0, import_react19.useEffect)(() => {
|
|
2677
2913
|
function onKeyDown(e) {
|
|
2678
2914
|
const h = ref.current;
|
|
2679
2915
|
if (e.key === "Escape") {
|
|
@@ -2717,8 +2953,8 @@ function useKeyboardShortcuts(handlers) {
|
|
|
2717
2953
|
}
|
|
2718
2954
|
|
|
2719
2955
|
// src/interactive/use-theme.ts
|
|
2720
|
-
var
|
|
2721
|
-
var
|
|
2956
|
+
var import_react20 = require("react");
|
|
2957
|
+
var KEY2 = "es-theme";
|
|
2722
2958
|
function systemDark() {
|
|
2723
2959
|
return typeof matchMedia !== "undefined" && matchMedia("(prefers-color-scheme: dark)").matches;
|
|
2724
2960
|
}
|
|
@@ -2732,19 +2968,19 @@ function applyTheme(pref) {
|
|
|
2732
2968
|
}
|
|
2733
2969
|
function initialPref() {
|
|
2734
2970
|
if (typeof localStorage !== "undefined") {
|
|
2735
|
-
const stored = localStorage.getItem(
|
|
2971
|
+
const stored = localStorage.getItem(KEY2);
|
|
2736
2972
|
if (stored === "light" || stored === "dark" || stored === "system") return stored;
|
|
2737
2973
|
}
|
|
2738
2974
|
return "system";
|
|
2739
2975
|
}
|
|
2740
2976
|
function useTheme() {
|
|
2741
|
-
const [pref, setPrefState] = (0,
|
|
2742
|
-
(0,
|
|
2977
|
+
const [pref, setPrefState] = (0, import_react20.useState)("system");
|
|
2978
|
+
(0, import_react20.useEffect)(() => {
|
|
2743
2979
|
const p = initialPref();
|
|
2744
2980
|
setPrefState(p);
|
|
2745
2981
|
applyTheme(p);
|
|
2746
2982
|
}, []);
|
|
2747
|
-
(0,
|
|
2983
|
+
(0, import_react20.useEffect)(() => {
|
|
2748
2984
|
if (typeof matchMedia === "undefined") return;
|
|
2749
2985
|
const mq = matchMedia("(prefers-color-scheme: dark)");
|
|
2750
2986
|
const onChange = () => {
|
|
@@ -2753,11 +2989,11 @@ function useTheme() {
|
|
|
2753
2989
|
mq.addEventListener("change", onChange);
|
|
2754
2990
|
return () => mq.removeEventListener("change", onChange);
|
|
2755
2991
|
}, [pref]);
|
|
2756
|
-
const setPref = (0,
|
|
2992
|
+
const setPref = (0, import_react20.useCallback)((p) => {
|
|
2757
2993
|
setPrefState(p);
|
|
2758
2994
|
applyTheme(p);
|
|
2759
2995
|
try {
|
|
2760
|
-
localStorage.setItem(
|
|
2996
|
+
localStorage.setItem(KEY2, p);
|
|
2761
2997
|
} catch {
|
|
2762
2998
|
}
|
|
2763
2999
|
}, []);
|
|
@@ -2765,19 +3001,19 @@ function useTheme() {
|
|
|
2765
3001
|
}
|
|
2766
3002
|
|
|
2767
3003
|
// src/interactive/use-url-state.ts
|
|
2768
|
-
var
|
|
3004
|
+
var import_react21 = require("react");
|
|
2769
3005
|
var WRITE_DELAY_MS = 250;
|
|
2770
3006
|
function useUrlState() {
|
|
2771
|
-
const [state, setState] = (0,
|
|
2772
|
-
const ready = (0,
|
|
2773
|
-
(0,
|
|
3007
|
+
const [state, setState] = (0, import_react21.useState)(DEFAULT_URL_STATE);
|
|
3008
|
+
const ready = (0, import_react21.useRef)(false);
|
|
3009
|
+
(0, import_react21.useEffect)(() => {
|
|
2774
3010
|
const read = () => setState(decodeUrlState(splitHash(window.location.hash).params));
|
|
2775
3011
|
read();
|
|
2776
3012
|
ready.current = true;
|
|
2777
3013
|
window.addEventListener("hashchange", read);
|
|
2778
3014
|
return () => window.removeEventListener("hashchange", read);
|
|
2779
3015
|
}, []);
|
|
2780
|
-
(0,
|
|
3016
|
+
(0, import_react21.useEffect)(() => {
|
|
2781
3017
|
if (!ready.current) return;
|
|
2782
3018
|
const timer = setTimeout(
|
|
2783
3019
|
() => writeHash(splitHash(window.location.hash).anchor, encodeUrlState(state)),
|
|
@@ -2785,20 +3021,234 @@ function useUrlState() {
|
|
|
2785
3021
|
);
|
|
2786
3022
|
return () => clearTimeout(timer);
|
|
2787
3023
|
}, [state]);
|
|
2788
|
-
const update = (0,
|
|
3024
|
+
const update = (0, import_react21.useCallback)(
|
|
2789
3025
|
(patch) => setState((prev) => ({ ...prev, ...patch })),
|
|
2790
3026
|
[]
|
|
2791
3027
|
);
|
|
2792
3028
|
return [state, update];
|
|
2793
3029
|
}
|
|
2794
3030
|
|
|
3031
|
+
// src/interactive/webmcp-tools.ts
|
|
3032
|
+
var import_report_queries2 = require("executable-stories-core/report-queries");
|
|
3033
|
+
var STATUSES2 = [
|
|
3034
|
+
"passed",
|
|
3035
|
+
"failed",
|
|
3036
|
+
"skipped",
|
|
3037
|
+
"pending"
|
|
3038
|
+
];
|
|
3039
|
+
var VIEW_STATUSES = ["all", ...STATUSES2];
|
|
3040
|
+
var ToolArgumentError = class extends Error {
|
|
3041
|
+
};
|
|
3042
|
+
function asRecord(input) {
|
|
3043
|
+
return typeof input === "object" && input !== null ? input : {};
|
|
3044
|
+
}
|
|
3045
|
+
function readString(input, key) {
|
|
3046
|
+
const value = input[key];
|
|
3047
|
+
if (value === void 0 || value === null) return void 0;
|
|
3048
|
+
if (typeof value !== "string") {
|
|
3049
|
+
throw new ToolArgumentError(`"${key}" must be a string, received ${typeof value}.`);
|
|
3050
|
+
}
|
|
3051
|
+
return value;
|
|
3052
|
+
}
|
|
3053
|
+
function readStringArray(input, key) {
|
|
3054
|
+
const value = input[key];
|
|
3055
|
+
if (value === void 0 || value === null) return void 0;
|
|
3056
|
+
if (typeof value === "string") return [value];
|
|
3057
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
3058
|
+
throw new ToolArgumentError(`"${key}" must be an array of strings.`);
|
|
3059
|
+
}
|
|
3060
|
+
return value;
|
|
3061
|
+
}
|
|
3062
|
+
function readEnum(input, key, allowed) {
|
|
3063
|
+
const value = readString(input, key);
|
|
3064
|
+
if (value === void 0) return void 0;
|
|
3065
|
+
if (!allowed.includes(value)) {
|
|
3066
|
+
throw new ToolArgumentError(
|
|
3067
|
+
`"${key}" must be one of: ${allowed.join(", ")}. Received "${value}".`
|
|
3068
|
+
);
|
|
3069
|
+
}
|
|
3070
|
+
return value;
|
|
3071
|
+
}
|
|
3072
|
+
function readStatuses(input) {
|
|
3073
|
+
const values = readStringArray(input, "statuses");
|
|
3074
|
+
if (values === void 0) return void 0;
|
|
3075
|
+
const invalid = values.filter((v) => !STATUSES2.includes(v));
|
|
3076
|
+
if (invalid.length > 0) {
|
|
3077
|
+
throw new ToolArgumentError(
|
|
3078
|
+
`"statuses" must contain only: ${STATUSES2.join(", ")}. Received "${invalid.join('", "')}".`
|
|
3079
|
+
);
|
|
3080
|
+
}
|
|
3081
|
+
return values;
|
|
3082
|
+
}
|
|
3083
|
+
function guarded(run) {
|
|
3084
|
+
try {
|
|
3085
|
+
return run();
|
|
3086
|
+
} catch (error) {
|
|
3087
|
+
if (error instanceof ToolArgumentError) return `Invalid input \u2014 ${error.message}`;
|
|
3088
|
+
throw error;
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
var scenarioListSchema = {
|
|
3092
|
+
type: "object",
|
|
3093
|
+
properties: {
|
|
3094
|
+
statuses: {
|
|
3095
|
+
type: "array",
|
|
3096
|
+
items: { type: "string", enum: [...STATUSES2] },
|
|
3097
|
+
description: "Keep only scenarios with any of these statuses."
|
|
3098
|
+
},
|
|
3099
|
+
tags: {
|
|
3100
|
+
type: "array",
|
|
3101
|
+
items: { type: "string" },
|
|
3102
|
+
description: "Keep only scenarios carrying any of these tags."
|
|
3103
|
+
},
|
|
3104
|
+
sourceFiles: {
|
|
3105
|
+
type: "array",
|
|
3106
|
+
items: { type: "string" },
|
|
3107
|
+
description: "Keep only scenarios whose source file path contains any of these."
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
};
|
|
3111
|
+
var READ_ONLY = { readOnlyHint: true, untrustedContentHint: true };
|
|
3112
|
+
function reportTools(deps) {
|
|
3113
|
+
const { report, setView, onAgentFilter } = deps;
|
|
3114
|
+
const now = deps.now ?? Date.now;
|
|
3115
|
+
const provenance = () => ({ run: (0, import_report_queries2.runProvenance)(report, now()) });
|
|
3116
|
+
const list = (filters) => {
|
|
3117
|
+
const scenarios = (0, import_report_queries2.listScenarioSummaries)(report, filters);
|
|
3118
|
+
return { ...provenance(), total: scenarios.length, scenarios };
|
|
3119
|
+
};
|
|
3120
|
+
return {
|
|
3121
|
+
list_scenarios: {
|
|
3122
|
+
title: "List scenarios",
|
|
3123
|
+
description: "List the behaviour scenarios in this report, optionally filtered by status, tag, or source file. Returns each scenario with its steps, tags, timing and failure message.",
|
|
3124
|
+
annotations: READ_ONLY,
|
|
3125
|
+
input: scenarioListSchema,
|
|
3126
|
+
execute: (input) => guarded(() => {
|
|
3127
|
+
const args = asRecord(input);
|
|
3128
|
+
const filters = {};
|
|
3129
|
+
const statuses = readStatuses(args);
|
|
3130
|
+
const tags = readStringArray(args, "tags");
|
|
3131
|
+
const sourceFiles = readStringArray(args, "sourceFiles");
|
|
3132
|
+
if (statuses) filters.statuses = statuses;
|
|
3133
|
+
if (tags) filters.tags = tags;
|
|
3134
|
+
if (sourceFiles) filters.sourceFiles = sourceFiles;
|
|
3135
|
+
return list(filters);
|
|
3136
|
+
})
|
|
3137
|
+
},
|
|
3138
|
+
get_failing_scenarios: {
|
|
3139
|
+
title: "Get failing scenarios",
|
|
3140
|
+
description: "List only the scenarios that failed in this run, each with the step that failed and its error message. Use this first when asked what is broken.",
|
|
3141
|
+
annotations: READ_ONLY,
|
|
3142
|
+
execute: () => list({ statuses: ["failed"] })
|
|
3143
|
+
},
|
|
3144
|
+
get_feature_summary: {
|
|
3145
|
+
title: "Get feature summary",
|
|
3146
|
+
description: 'Summarise each feature in the report with its scenario counts by status. Use this for "how are we doing" questions rather than listing every scenario.',
|
|
3147
|
+
annotations: READ_ONLY,
|
|
3148
|
+
execute: () => ({
|
|
3149
|
+
...provenance(),
|
|
3150
|
+
summary: report.summary,
|
|
3151
|
+
features: (0, import_report_queries2.getFeatureSummary)(report)
|
|
3152
|
+
})
|
|
3153
|
+
},
|
|
3154
|
+
get_scenario: {
|
|
3155
|
+
title: "Get scenario",
|
|
3156
|
+
description: "Get one scenario by its id or its exact title, with every step and any failure detail. Attachment and document bodies are omitted \u2014 `docKinds` names what the page shows.",
|
|
3157
|
+
annotations: READ_ONLY,
|
|
3158
|
+
input: {
|
|
3159
|
+
type: "object",
|
|
3160
|
+
properties: {
|
|
3161
|
+
idOrTitle: {
|
|
3162
|
+
type: "string",
|
|
3163
|
+
description: "The scenario id, or its exact title as shown in the report."
|
|
3164
|
+
}
|
|
3165
|
+
},
|
|
3166
|
+
required: ["idOrTitle"]
|
|
3167
|
+
},
|
|
3168
|
+
execute: (input) => guarded(() => {
|
|
3169
|
+
const idOrTitle = readString(asRecord(input), "idOrTitle") ?? "";
|
|
3170
|
+
const found = (0, import_report_queries2.getScenario)(report, idOrTitle);
|
|
3171
|
+
if (!found) {
|
|
3172
|
+
return {
|
|
3173
|
+
...provenance(),
|
|
3174
|
+
error: `No scenario found with id or title "${idOrTitle}". Call list_scenarios to see what this report contains.`
|
|
3175
|
+
};
|
|
3176
|
+
}
|
|
3177
|
+
return {
|
|
3178
|
+
...provenance(),
|
|
3179
|
+
feature: {
|
|
3180
|
+
id: found.feature.id,
|
|
3181
|
+
title: found.feature.title,
|
|
3182
|
+
sourceFile: found.feature.sourceFile
|
|
3183
|
+
},
|
|
3184
|
+
// The summary projection, not the raw scenario: doc entry bodies and
|
|
3185
|
+
// base64 attachments are unbounded, and this payload goes straight
|
|
3186
|
+
// into a model's context window.
|
|
3187
|
+
scenario: (0, import_report_queries2.toScenarioSummary)(found.feature, found.scenario)
|
|
3188
|
+
};
|
|
3189
|
+
})
|
|
3190
|
+
},
|
|
3191
|
+
filter_scenarios: {
|
|
3192
|
+
title: "Filter the report",
|
|
3193
|
+
description: `Change what the reader is looking at: set the report's search text, status filter and tag filter. Omitted fields are left as they are; pass an empty string, "all", or an empty array to clear one.`,
|
|
3194
|
+
input: {
|
|
3195
|
+
type: "object",
|
|
3196
|
+
properties: {
|
|
3197
|
+
search: {
|
|
3198
|
+
type: "string",
|
|
3199
|
+
description: "Free text matched against scenario titles, tags and step text. Empty string clears it."
|
|
3200
|
+
},
|
|
3201
|
+
status: {
|
|
3202
|
+
type: "string",
|
|
3203
|
+
enum: [...VIEW_STATUSES],
|
|
3204
|
+
description: 'Show only scenarios with this status. "all" clears the filter.'
|
|
3205
|
+
},
|
|
3206
|
+
tags: {
|
|
3207
|
+
type: "array",
|
|
3208
|
+
items: { type: "string" },
|
|
3209
|
+
description: "Show only scenarios carrying any of these tags. Empty array clears the filter."
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
},
|
|
3213
|
+
execute: (input) => guarded(() => {
|
|
3214
|
+
const args = asRecord(input);
|
|
3215
|
+
const search = readString(args, "search");
|
|
3216
|
+
const status = readEnum(args, "status", VIEW_STATUSES);
|
|
3217
|
+
const tags = readStringArray(args, "tags");
|
|
3218
|
+
const applied = {
|
|
3219
|
+
search: search ?? deps.view.query,
|
|
3220
|
+
status: status ?? deps.view.status,
|
|
3221
|
+
tags: tags ?? deps.view.tags
|
|
3222
|
+
};
|
|
3223
|
+
const patch = {};
|
|
3224
|
+
if (search !== void 0) patch.query = search;
|
|
3225
|
+
if (status !== void 0) patch.status = status;
|
|
3226
|
+
if (tags !== void 0) patch.tags = tags;
|
|
3227
|
+
setView(patch);
|
|
3228
|
+
const matched = filterReport(report, {
|
|
3229
|
+
query: applied.search,
|
|
3230
|
+
status: applied.status,
|
|
3231
|
+
tags: applied.tags
|
|
3232
|
+
}).summary.total;
|
|
3233
|
+
const result = {
|
|
3234
|
+
...applied,
|
|
3235
|
+
matched,
|
|
3236
|
+
total: report.summary.total
|
|
3237
|
+
};
|
|
3238
|
+
onAgentFilter?.(result);
|
|
3239
|
+
return { ...provenance(), applied: result };
|
|
3240
|
+
})
|
|
3241
|
+
}
|
|
3242
|
+
};
|
|
3243
|
+
}
|
|
3244
|
+
|
|
2795
3245
|
// src/interactive/ReportInteractive.tsx
|
|
2796
|
-
var
|
|
3246
|
+
var import_jsx_runtime51 = require("react/jsx-runtime");
|
|
2797
3247
|
function ReportInteractive(props) {
|
|
2798
3248
|
const { report, className, title, dataTheme } = props;
|
|
2799
3249
|
const result = unwrapReport(report);
|
|
2800
3250
|
if (!result.ok) {
|
|
2801
|
-
return /* @__PURE__ */ (0,
|
|
3251
|
+
return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
2802
3252
|
ReportErrorShell,
|
|
2803
3253
|
{
|
|
2804
3254
|
error: result.error,
|
|
@@ -2808,18 +3258,18 @@ function ReportInteractive(props) {
|
|
|
2808
3258
|
}
|
|
2809
3259
|
);
|
|
2810
3260
|
}
|
|
2811
|
-
return /* @__PURE__ */ (0,
|
|
3261
|
+
return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ReportInteractiveView, { ...props, report: result.data });
|
|
2812
3262
|
}
|
|
2813
3263
|
var THEME_OPTIONS = [
|
|
2814
|
-
{ value: "light", label: "Light", Icon:
|
|
2815
|
-
{ value: "system", label: "System", Icon:
|
|
2816
|
-
{ value: "dark", label: "Dark", Icon:
|
|
3264
|
+
{ value: "light", label: "Light", Icon: import_lucide_react6.Sun },
|
|
3265
|
+
{ value: "system", label: "System", Icon: import_lucide_react6.Monitor },
|
|
3266
|
+
{ value: "dark", label: "Dark", Icon: import_lucide_react6.Moon }
|
|
2817
3267
|
];
|
|
2818
3268
|
function ThemeSegment({
|
|
2819
3269
|
pref,
|
|
2820
3270
|
onPref
|
|
2821
3271
|
}) {
|
|
2822
|
-
return /* @__PURE__ */ (0,
|
|
3272
|
+
return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
2823
3273
|
ToggleGroup,
|
|
2824
3274
|
{
|
|
2825
3275
|
size: "sm",
|
|
@@ -2829,14 +3279,14 @@ function ThemeSegment({
|
|
|
2829
3279
|
if (next) onPref(next);
|
|
2830
3280
|
},
|
|
2831
3281
|
"aria-label": "Color theme",
|
|
2832
|
-
children: THEME_OPTIONS.map(({ value, label, Icon }) => /* @__PURE__ */ (0,
|
|
3282
|
+
children: THEME_OPTIONS.map(({ value, label, Icon }) => /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
2833
3283
|
ToggleGroupItem,
|
|
2834
3284
|
{
|
|
2835
3285
|
value,
|
|
2836
3286
|
"aria-label": label,
|
|
2837
3287
|
title: label,
|
|
2838
3288
|
className: "px-2",
|
|
2839
|
-
children: /* @__PURE__ */ (0,
|
|
3289
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(Icon, {})
|
|
2840
3290
|
},
|
|
2841
3291
|
value
|
|
2842
3292
|
))
|
|
@@ -2857,26 +3307,26 @@ function ReportInteractiveView({
|
|
|
2857
3307
|
}) {
|
|
2858
3308
|
const [urlState, setUrlState] = useUrlState();
|
|
2859
3309
|
const { query, status: statusFilter, tags: activeTags, detail } = urlState;
|
|
2860
|
-
const setQuery = (0,
|
|
3310
|
+
const setQuery = (0, import_react23.useCallback)(
|
|
2861
3311
|
(next) => setUrlState({ query: next }),
|
|
2862
3312
|
[setUrlState]
|
|
2863
3313
|
);
|
|
2864
|
-
const setStatusFilter = (0,
|
|
3314
|
+
const setStatusFilter = (0, import_react23.useCallback)(
|
|
2865
3315
|
(next) => setUrlState({ status: next }),
|
|
2866
3316
|
[setUrlState]
|
|
2867
3317
|
);
|
|
2868
|
-
const setDetail = (0,
|
|
3318
|
+
const setDetail = (0, import_react23.useCallback)(
|
|
2869
3319
|
(next) => setUrlState({ detail: next }),
|
|
2870
3320
|
[setUrlState]
|
|
2871
3321
|
);
|
|
2872
|
-
const [helpOpen, setHelpOpen] = (0,
|
|
3322
|
+
const [helpOpen, setHelpOpen] = (0, import_react23.useState)(false);
|
|
2873
3323
|
const { pref: themePref, setPref: setThemePref } = useTheme();
|
|
2874
|
-
const searchRef = (0,
|
|
2875
|
-
const expandId = (0,
|
|
2876
|
-
const docsId = (0,
|
|
2877
|
-
const deferredQuery = (0,
|
|
2878
|
-
const failures = (0,
|
|
2879
|
-
const filtered = (0,
|
|
3324
|
+
const searchRef = (0, import_react23.useRef)(null);
|
|
3325
|
+
const expandId = (0, import_react23.useId)();
|
|
3326
|
+
const docsId = (0, import_react23.useId)();
|
|
3327
|
+
const deferredQuery = (0, import_react23.useDeferredValue)(query);
|
|
3328
|
+
const failures = (0, import_react23.useMemo)(() => listFailures(report), [report]);
|
|
3329
|
+
const filtered = (0, import_react23.useMemo)(
|
|
2880
3330
|
// Features carrying failures float to the top so the first thing on screen
|
|
2881
3331
|
// is the thing that needs attention; an all-green run keeps source order.
|
|
2882
3332
|
() => failuresFirst(
|
|
@@ -2889,7 +3339,7 @@ function ReportInteractiveView({
|
|
|
2889
3339
|
[report, deferredQuery, statusFilter, activeTags]
|
|
2890
3340
|
);
|
|
2891
3341
|
const isFiltering = query !== deferredQuery;
|
|
2892
|
-
const statusOptions = (0,
|
|
3342
|
+
const statusOptions = (0, import_react23.useMemo)(() => {
|
|
2893
3343
|
const s = report.summary;
|
|
2894
3344
|
const opts = [
|
|
2895
3345
|
{ key: "all", label: "All", count: s.total }
|
|
@@ -2904,21 +3354,21 @@ function ReportInteractiveView({
|
|
|
2904
3354
|
opts.push({ key: "pending", label: "Pending", count: s.pending });
|
|
2905
3355
|
return opts;
|
|
2906
3356
|
}, [report]);
|
|
2907
|
-
const tagOptions = (0,
|
|
2908
|
-
const toggleTag = (0,
|
|
3357
|
+
const tagOptions = (0, import_react23.useMemo)(() => allTags(report), [report]);
|
|
3358
|
+
const toggleTag = (0, import_react23.useCallback)(
|
|
2909
3359
|
(tag) => setUrlState({
|
|
2910
3360
|
tags: activeTags.includes(tag) ? activeTags.filter((t) => t !== tag) : [...activeTags, tag]
|
|
2911
3361
|
}),
|
|
2912
3362
|
[activeTags, setUrlState]
|
|
2913
3363
|
);
|
|
2914
|
-
const [toast, setToast] = (0,
|
|
2915
|
-
const toastTimer = (0,
|
|
2916
|
-
const showToast = (0,
|
|
3364
|
+
const [toast, setToast] = (0, import_react23.useState)(null);
|
|
3365
|
+
const toastTimer = (0, import_react23.useRef)(null);
|
|
3366
|
+
const showToast = (0, import_react23.useCallback)((msg) => {
|
|
2917
3367
|
setToast(msg);
|
|
2918
3368
|
if (toastTimer.current) clearTimeout(toastTimer.current);
|
|
2919
3369
|
toastTimer.current = setTimeout(() => setToast(null), 1800);
|
|
2920
3370
|
}, []);
|
|
2921
|
-
const copy = (0,
|
|
3371
|
+
const copy = (0, import_react23.useCallback)(
|
|
2922
3372
|
(text, msg) => {
|
|
2923
3373
|
if (typeof navigator !== "undefined" && navigator.clipboard) {
|
|
2924
3374
|
navigator.clipboard.writeText(text).then(
|
|
@@ -2929,7 +3379,7 @@ function ReportInteractiveView({
|
|
|
2929
3379
|
},
|
|
2930
3380
|
[showToast]
|
|
2931
3381
|
);
|
|
2932
|
-
const scenarioActions = (0,
|
|
3382
|
+
const scenarioActions = (0, import_react23.useMemo)(
|
|
2933
3383
|
() => ({
|
|
2934
3384
|
copyLink: (s) => copy(scenarioPermalink(s), "Link copied"),
|
|
2935
3385
|
copyMarkdown: (s) => copy(scenarioToMarkdown(s), "Markdown copied"),
|
|
@@ -2937,27 +3387,31 @@ function ReportInteractiveView({
|
|
|
2937
3387
|
}),
|
|
2938
3388
|
[copy]
|
|
2939
3389
|
);
|
|
2940
|
-
const
|
|
2941
|
-
|
|
3390
|
+
const copyAllFailures = (0, import_react23.useCallback)(
|
|
3391
|
+
() => copy(failuresToPrompt(failedScenarios(filtered)), "Failures copied"),
|
|
3392
|
+
[copy, filtered]
|
|
3393
|
+
);
|
|
3394
|
+
const failureIndexRef = (0, import_react23.useRef)(0);
|
|
3395
|
+
const allCollapsibleIds = (0, import_react23.useMemo)(
|
|
2942
3396
|
() => report.features.flatMap((f) => [f.id, ...f.scenarios.map((s) => s.id)]),
|
|
2943
3397
|
[report]
|
|
2944
3398
|
);
|
|
2945
3399
|
const collapse = useCollapseState(
|
|
2946
|
-
(0,
|
|
3400
|
+
(0, import_react23.useCallback)(() => defaultCollapsedIds(report), [report])
|
|
2947
3401
|
);
|
|
2948
3402
|
const revealFilteredResults = deferredQuery.trim() !== "" || statusFilter !== "all" || activeTags.length > 0;
|
|
2949
|
-
const visibleCollapse = (0,
|
|
3403
|
+
const visibleCollapse = (0, import_react23.useMemo)(
|
|
2950
3404
|
() => revealFilteredResults ? { ...collapse.api, isCollapsed: () => false } : collapse.api,
|
|
2951
3405
|
[collapse.api, revealFilteredResults]
|
|
2952
3406
|
);
|
|
2953
|
-
const collapseAll = (0,
|
|
3407
|
+
const collapseAll = (0, import_react23.useCallback)(
|
|
2954
3408
|
() => collapse.collapseAll(allCollapsibleIds),
|
|
2955
3409
|
[collapse, allCollapsibleIds]
|
|
2956
3410
|
);
|
|
2957
|
-
const [expandedAll, setExpandedAll] = (0,
|
|
3411
|
+
const [expandedAll, setExpandedAll] = (0, import_react23.useState)(
|
|
2958
3412
|
() => report.summary.failed === 0
|
|
2959
3413
|
);
|
|
2960
|
-
const setExpanded = (0,
|
|
3414
|
+
const setExpanded = (0, import_react23.useCallback)(
|
|
2961
3415
|
(expanded) => {
|
|
2962
3416
|
if (expanded) collapse.expandAll();
|
|
2963
3417
|
else collapseAll();
|
|
@@ -2965,10 +3419,24 @@ function ReportInteractiveView({
|
|
|
2965
3419
|
},
|
|
2966
3420
|
[collapse, collapseAll]
|
|
2967
3421
|
);
|
|
2968
|
-
const
|
|
3422
|
+
const [agentFilter, setAgentFilter] = (0, import_react23.useState)(null);
|
|
3423
|
+
(0, import_react22.useTools)(
|
|
3424
|
+
reportTools({
|
|
3425
|
+
report,
|
|
3426
|
+
view: urlState,
|
|
3427
|
+
setView: setUrlState,
|
|
3428
|
+
onAgentFilter: setAgentFilter
|
|
3429
|
+
})
|
|
3430
|
+
);
|
|
3431
|
+
const showAgentFilter = agentFilter !== null && agentFilter.search === query && agentFilter.status === statusFilter && agentFilter.tags.length === activeTags.length && agentFilter.tags.every((tag) => activeTags.includes(tag));
|
|
3432
|
+
const clearFilters = (0, import_react23.useCallback)(
|
|
3433
|
+
() => setUrlState({ query: "", status: "all", tags: [] }),
|
|
3434
|
+
[setUrlState]
|
|
3435
|
+
);
|
|
3436
|
+
const focusSearch = (0, import_react23.useCallback)(() => {
|
|
2969
3437
|
searchRef.current?.focus();
|
|
2970
3438
|
}, []);
|
|
2971
|
-
const stepFailure = (0,
|
|
3439
|
+
const stepFailure = (0, import_react23.useCallback)(
|
|
2972
3440
|
(direction) => {
|
|
2973
3441
|
if (failures.length === 0) return;
|
|
2974
3442
|
failureIndexRef.current = (failureIndexRef.current + direction + failures.length) % failures.length;
|
|
@@ -2977,10 +3445,10 @@ function ReportInteractiveView({
|
|
|
2977
3445
|
},
|
|
2978
3446
|
[failures]
|
|
2979
3447
|
);
|
|
2980
|
-
const toggleHelp = (0,
|
|
3448
|
+
const toggleHelp = (0, import_react23.useCallback)(() => {
|
|
2981
3449
|
setHelpOpen((v) => !v);
|
|
2982
3450
|
}, []);
|
|
2983
|
-
const escape = (0,
|
|
3451
|
+
const escape = (0, import_react23.useCallback)(() => {
|
|
2984
3452
|
if (helpOpen) setHelpOpen(false);
|
|
2985
3453
|
else if (query !== "") setQuery("");
|
|
2986
3454
|
}, [helpOpen, query, setQuery]);
|
|
@@ -2998,13 +3466,13 @@ function ReportInteractiveView({
|
|
|
2998
3466
|
const totalScenarios = report.summary.total;
|
|
2999
3467
|
const matchedScenarios = filtered.summary.total;
|
|
3000
3468
|
const showToc = !hideToc && totalScenarios > 3;
|
|
3001
|
-
return /* @__PURE__ */ (0,
|
|
3469
|
+
return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(CollapseProvider, { value: visibleCollapse, children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ScenarioActionsProvider, { value: scenarioActions, children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ScenarioHistoryProvider, { value: scenarioHistory ?? null, children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ScenarioFreshnessProvider, { value: { staleAfterDays, report }, children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
3002
3470
|
ReportRoot,
|
|
3003
3471
|
{
|
|
3004
3472
|
report: filtered,
|
|
3005
3473
|
customRenderers,
|
|
3006
3474
|
renderers,
|
|
3007
|
-
children: /* @__PURE__ */ (0,
|
|
3475
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(
|
|
3008
3476
|
"main",
|
|
3009
3477
|
{
|
|
3010
3478
|
className: cn("es-report", "es-report-interactive", className),
|
|
@@ -3013,8 +3481,8 @@ function ReportInteractiveView({
|
|
|
3013
3481
|
"data-theme": dataTheme,
|
|
3014
3482
|
"data-detail-level": detail,
|
|
3015
3483
|
children: [
|
|
3016
|
-
/* @__PURE__ */ (0,
|
|
3017
|
-
/* @__PURE__ */ (0,
|
|
3484
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("header", { className: "es-report-header", children: [
|
|
3485
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(
|
|
3018
3486
|
"div",
|
|
3019
3487
|
{
|
|
3020
3488
|
"data-es-navbar": true,
|
|
@@ -3023,9 +3491,9 @@ function ReportInteractiveView({
|
|
|
3023
3491
|
hideHeader ? "justify-end" : "justify-between"
|
|
3024
3492
|
),
|
|
3025
3493
|
children: [
|
|
3026
|
-
hideHeader ? null : /* @__PURE__ */ (0,
|
|
3027
|
-
/* @__PURE__ */ (0,
|
|
3028
|
-
/* @__PURE__ */ (0,
|
|
3494
|
+
hideHeader ? null : /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("h1", { className: "min-w-0", children: title ?? "Story Report" }),
|
|
3495
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
3496
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
3029
3497
|
ReportSearch,
|
|
3030
3498
|
{
|
|
3031
3499
|
ref: searchRef,
|
|
@@ -3035,16 +3503,16 @@ function ReportInteractiveView({
|
|
|
3035
3503
|
totalCount: totalScenarios
|
|
3036
3504
|
}
|
|
3037
3505
|
),
|
|
3038
|
-
/* @__PURE__ */ (0,
|
|
3506
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ThemeSegment, { pref: themePref, onPref: setThemePref })
|
|
3039
3507
|
] })
|
|
3040
3508
|
]
|
|
3041
3509
|
}
|
|
3042
3510
|
),
|
|
3043
|
-
hideHeader ? null : /* @__PURE__ */ (0,
|
|
3044
|
-
/* @__PURE__ */ (0,
|
|
3045
|
-
/* @__PURE__ */ (0,
|
|
3511
|
+
hideHeader ? null : /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(import_jsx_runtime51.Fragment, { children: [
|
|
3512
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ReportSummary, {}),
|
|
3513
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ReportMeta, {})
|
|
3046
3514
|
] }),
|
|
3047
|
-
/* @__PURE__ */ (0,
|
|
3515
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
3048
3516
|
ReportFreshness,
|
|
3049
3517
|
{
|
|
3050
3518
|
lastRunMs: reportLastRunMs(report),
|
|
@@ -3052,14 +3520,15 @@ function ReportInteractiveView({
|
|
|
3052
3520
|
staleAfterDays
|
|
3053
3521
|
}
|
|
3054
3522
|
),
|
|
3055
|
-
/* @__PURE__ */ (0,
|
|
3523
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
3056
3524
|
ReportLastRunDelta,
|
|
3057
3525
|
{
|
|
3058
3526
|
history: scenarioHistory,
|
|
3059
3527
|
report
|
|
3060
3528
|
}
|
|
3061
3529
|
),
|
|
3062
|
-
/* @__PURE__ */ (0,
|
|
3530
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ReportSinceLastVisit, { report }),
|
|
3531
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
3063
3532
|
ReportFilters,
|
|
3064
3533
|
{
|
|
3065
3534
|
statuses: statusOptions,
|
|
@@ -3070,10 +3539,10 @@ function ReportInteractiveView({
|
|
|
3070
3539
|
onToggleTag: toggleTag
|
|
3071
3540
|
}
|
|
3072
3541
|
),
|
|
3073
|
-
/* @__PURE__ */ (0,
|
|
3074
|
-
showToc ? /* @__PURE__ */ (0,
|
|
3075
|
-
/* @__PURE__ */ (0,
|
|
3076
|
-
/* @__PURE__ */ (0,
|
|
3542
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "flex flex-wrap items-center gap-x-6 gap-y-2 border-t border-border/60 pt-3", children: [
|
|
3543
|
+
showToc ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ReportTocDrawer, {}) : null,
|
|
3544
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
3545
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
3077
3546
|
Switch,
|
|
3078
3547
|
{
|
|
3079
3548
|
id: expandId,
|
|
@@ -3081,7 +3550,7 @@ function ReportInteractiveView({
|
|
|
3081
3550
|
onCheckedChange: setExpanded
|
|
3082
3551
|
}
|
|
3083
3552
|
),
|
|
3084
|
-
/* @__PURE__ */ (0,
|
|
3553
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
3085
3554
|
"label",
|
|
3086
3555
|
{
|
|
3087
3556
|
htmlFor: expandId,
|
|
@@ -3090,8 +3559,8 @@ function ReportInteractiveView({
|
|
|
3090
3559
|
}
|
|
3091
3560
|
)
|
|
3092
3561
|
] }),
|
|
3093
|
-
/* @__PURE__ */ (0,
|
|
3094
|
-
/* @__PURE__ */ (0,
|
|
3562
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
3563
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
3095
3564
|
Switch,
|
|
3096
3565
|
{
|
|
3097
3566
|
id: docsId,
|
|
@@ -3099,7 +3568,7 @@ function ReportInteractiveView({
|
|
|
3099
3568
|
onCheckedChange: (checked) => setDetail(checked ? "full" : "minimal")
|
|
3100
3569
|
}
|
|
3101
3570
|
),
|
|
3102
|
-
/* @__PURE__ */ (0,
|
|
3571
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
3103
3572
|
"label",
|
|
3104
3573
|
{
|
|
3105
3574
|
htmlFor: docsId,
|
|
@@ -3110,17 +3579,25 @@ function ReportInteractiveView({
|
|
|
3110
3579
|
] })
|
|
3111
3580
|
] })
|
|
3112
3581
|
] }),
|
|
3113
|
-
/* @__PURE__ */ (0,
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3582
|
+
showAgentFilter ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
3583
|
+
ReportAgentFilterNotice,
|
|
3584
|
+
{
|
|
3585
|
+
applied: agentFilter,
|
|
3586
|
+
onReset: clearFilters,
|
|
3587
|
+
onDismiss: () => setAgentFilter(null)
|
|
3588
|
+
}
|
|
3589
|
+
) : null,
|
|
3590
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ReportFailureBanner, { failures, onCopyAll: copyAllFailures }),
|
|
3591
|
+
hasContent ? showToc ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "flex gap-6", children: [
|
|
3592
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ReportToc, {}),
|
|
3593
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("div", { className: "flex min-w-0 flex-1 flex-col gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ReportFeatureList, {}) })
|
|
3594
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("div", { className: "flex flex-col gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(ReportFeatureList, {}) }) : /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
3118
3595
|
ReportEmpty,
|
|
3119
3596
|
{
|
|
3120
3597
|
message: query ? "No scenarios match the search." : void 0
|
|
3121
3598
|
}
|
|
3122
3599
|
),
|
|
3123
|
-
/* @__PURE__ */ (0,
|
|
3600
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
3124
3601
|
"button",
|
|
3125
3602
|
{
|
|
3126
3603
|
type: "button",
|
|
@@ -3131,14 +3608,14 @@ function ReportInteractiveView({
|
|
|
3131
3608
|
children: "?"
|
|
3132
3609
|
}
|
|
3133
3610
|
),
|
|
3134
|
-
/* @__PURE__ */ (0,
|
|
3611
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
3135
3612
|
ReportShortcutsHelp,
|
|
3136
3613
|
{
|
|
3137
3614
|
open: helpOpen,
|
|
3138
3615
|
onClose: () => setHelpOpen(false)
|
|
3139
3616
|
}
|
|
3140
3617
|
),
|
|
3141
|
-
toast ? /* @__PURE__ */ (0,
|
|
3618
|
+
toast ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
3142
3619
|
"div",
|
|
3143
3620
|
{
|
|
3144
3621
|
role: "status",
|
|
@@ -3155,14 +3632,14 @@ function ReportInteractiveView({
|
|
|
3155
3632
|
}
|
|
3156
3633
|
|
|
3157
3634
|
// src/interactive/ReportInteractiveIsland.tsx
|
|
3158
|
-
var
|
|
3635
|
+
var import_react26 = require("react");
|
|
3159
3636
|
|
|
3160
3637
|
// src/interactive/island-renderers.tsx
|
|
3161
|
-
var
|
|
3638
|
+
var import_react25 = require("react");
|
|
3162
3639
|
|
|
3163
3640
|
// src/components/doc/MermaidView.tsx
|
|
3164
|
-
var
|
|
3165
|
-
var
|
|
3641
|
+
var import_react24 = require("react");
|
|
3642
|
+
var import_jsx_runtime52 = require("react/jsx-runtime");
|
|
3166
3643
|
function prefersDark(el) {
|
|
3167
3644
|
const themed = el?.closest("[data-theme]");
|
|
3168
3645
|
if (themed?.dataset.theme === "dark") return true;
|
|
@@ -3170,10 +3647,10 @@ function prefersDark(el) {
|
|
|
3170
3647
|
return typeof window !== "undefined" && !!window.matchMedia?.("(prefers-color-scheme: dark)").matches;
|
|
3171
3648
|
}
|
|
3172
3649
|
function MermaidView({ entry, load }) {
|
|
3173
|
-
const ref = (0,
|
|
3174
|
-
const [svg, setSvg] = (0,
|
|
3175
|
-
const [error, setError] = (0,
|
|
3176
|
-
(0,
|
|
3650
|
+
const ref = (0, import_react24.useRef)(null);
|
|
3651
|
+
const [svg, setSvg] = (0, import_react24.useState)(null);
|
|
3652
|
+
const [error, setError] = (0, import_react24.useState)(null);
|
|
3653
|
+
(0, import_react24.useEffect)(() => {
|
|
3177
3654
|
let cancelled = false;
|
|
3178
3655
|
const draw = async () => {
|
|
3179
3656
|
let mermaid;
|
|
@@ -3216,9 +3693,9 @@ function MermaidView({ entry, load }) {
|
|
|
3216
3693
|
};
|
|
3217
3694
|
}, [entry.code, load]);
|
|
3218
3695
|
if (svg) {
|
|
3219
|
-
return /* @__PURE__ */ (0,
|
|
3220
|
-
entry.title ? /* @__PURE__ */ (0,
|
|
3221
|
-
/* @__PURE__ */ (0,
|
|
3696
|
+
return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("figure", { ref, className: "my-3", children: [
|
|
3697
|
+
entry.title ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
|
|
3698
|
+
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
|
|
3222
3699
|
"div",
|
|
3223
3700
|
{
|
|
3224
3701
|
role: "img",
|
|
@@ -3229,8 +3706,8 @@ function MermaidView({ entry, load }) {
|
|
|
3229
3706
|
)
|
|
3230
3707
|
] });
|
|
3231
3708
|
}
|
|
3232
|
-
return /* @__PURE__ */ (0,
|
|
3233
|
-
error ? /* @__PURE__ */ (0,
|
|
3709
|
+
return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(import_jsx_runtime52.Fragment, { children: [
|
|
3710
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
|
|
3234
3711
|
"p",
|
|
3235
3712
|
{
|
|
3236
3713
|
role: "alert",
|
|
@@ -3242,12 +3719,12 @@ function MermaidView({ entry, load }) {
|
|
|
3242
3719
|
]
|
|
3243
3720
|
}
|
|
3244
3721
|
) : null,
|
|
3245
|
-
/* @__PURE__ */ (0,
|
|
3722
|
+
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)(MermaidSource, { entry, ref })
|
|
3246
3723
|
] });
|
|
3247
3724
|
}
|
|
3248
3725
|
|
|
3249
3726
|
// src/interactive/island-renderers.tsx
|
|
3250
|
-
var
|
|
3727
|
+
var import_jsx_runtime53 = require("react/jsx-runtime");
|
|
3251
3728
|
var HLJS_ESM = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/es/highlight.min.js";
|
|
3252
3729
|
var MERMAID_ESM = "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
|
|
3253
3730
|
function cachedCdnModule(url) {
|
|
@@ -3264,8 +3741,8 @@ function cachedCdnModule(url) {
|
|
|
3264
3741
|
var loadMermaidFromCdn = cachedCdnModule(MERMAID_ESM);
|
|
3265
3742
|
var loadHljs = cachedCdnModule(HLJS_ESM);
|
|
3266
3743
|
function CdnHighlightedCode({ entry }) {
|
|
3267
|
-
const [html, setHtml] = (0,
|
|
3268
|
-
(0,
|
|
3744
|
+
const [html, setHtml] = (0, import_react25.useState)(null);
|
|
3745
|
+
(0, import_react25.useEffect)(() => {
|
|
3269
3746
|
let cancelled = false;
|
|
3270
3747
|
void (async () => {
|
|
3271
3748
|
try {
|
|
@@ -3281,27 +3758,27 @@ function CdnHighlightedCode({ entry }) {
|
|
|
3281
3758
|
cancelled = true;
|
|
3282
3759
|
};
|
|
3283
3760
|
}, [entry.content, entry.lang]);
|
|
3284
|
-
return /* @__PURE__ */ (0,
|
|
3761
|
+
return /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(CodeFigure, { label: entry.label, children: html !== null ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
|
|
3285
3762
|
"code",
|
|
3286
3763
|
{
|
|
3287
3764
|
className: cn("hljs font-mono text-xs", entry.lang && `language-${entry.lang}`),
|
|
3288
3765
|
dangerouslySetInnerHTML: { __html: html }
|
|
3289
3766
|
}
|
|
3290
|
-
) : /* @__PURE__ */ (0,
|
|
3767
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
|
|
3291
3768
|
}
|
|
3292
3769
|
function buildIslandRenderers(opts) {
|
|
3293
3770
|
const renderers = {};
|
|
3294
3771
|
if (opts.mermaid) {
|
|
3295
|
-
renderers.mermaid = (entry) => (0,
|
|
3772
|
+
renderers.mermaid = (entry) => (0, import_react25.createElement)(MermaidView, { entry, load: loadMermaidFromCdn });
|
|
3296
3773
|
}
|
|
3297
3774
|
if (opts.syntaxHighlighting) {
|
|
3298
|
-
renderers.code = (entry) => (0,
|
|
3775
|
+
renderers.code = (entry) => (0, import_react25.createElement)(CdnHighlightedCode, { entry });
|
|
3299
3776
|
}
|
|
3300
3777
|
return renderers;
|
|
3301
3778
|
}
|
|
3302
3779
|
|
|
3303
3780
|
// src/interactive/ReportInteractiveIsland.tsx
|
|
3304
|
-
var
|
|
3781
|
+
var import_jsx_runtime54 = require("react/jsx-runtime");
|
|
3305
3782
|
function ReportInteractiveIsland({
|
|
3306
3783
|
report,
|
|
3307
3784
|
title,
|
|
@@ -3311,11 +3788,11 @@ function ReportInteractiveIsland({
|
|
|
3311
3788
|
syntaxHighlighting = true,
|
|
3312
3789
|
mermaid = true
|
|
3313
3790
|
}) {
|
|
3314
|
-
const renderers = (0,
|
|
3791
|
+
const renderers = (0, import_react26.useMemo)(
|
|
3315
3792
|
() => buildIslandRenderers({ syntaxHighlighting, mermaid }),
|
|
3316
3793
|
[syntaxHighlighting, mermaid]
|
|
3317
3794
|
);
|
|
3318
|
-
return /* @__PURE__ */ (0,
|
|
3795
|
+
return /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
|
|
3319
3796
|
ReportInteractive,
|
|
3320
3797
|
{
|
|
3321
3798
|
report,
|