executable-stories-react 0.10.0 → 0.11.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.
@@ -295,23 +295,11 @@ function useReport() {
295
295
  return ctx.report;
296
296
  }
297
297
 
298
- // ../executable-stories-core/dist/utils/duration.js
299
- function formatDuration(ms) {
300
- if (ms < 1e3) {
301
- return `${Math.round(ms)} ms`;
302
- }
303
- if (ms < 6e4) {
304
- return `${(ms / 1e3).toFixed(2)} s`;
305
- }
306
- const minutes = Math.floor(ms / 6e4);
307
- const seconds = Math.floor(ms % 6e4 / 1e3);
308
- if (minutes < 60) {
309
- return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
310
- }
311
- const hours = Math.floor(minutes / 60);
312
- const remainingMinutes = minutes % 60;
313
- return `${hours}h ${remainingMinutes}m`;
314
- }
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";
315
303
 
316
304
  // src/components/doc/DocNote.tsx
317
305
  import { jsx as jsx3 } from "react/jsx-runtime";
@@ -380,7 +368,7 @@ function DocTable({ entry }) {
380
368
  entry.label ? /* @__PURE__ */ jsx7("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
381
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: [
382
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)) }) }),
383
- /* @__PURE__ */ jsx7("tbody", { children: entry.rows.map((row, i) => /* @__PURE__ */ jsx7("tr", { children: row.map((cell2, j) => /* @__PURE__ */ jsx7("td", { className: "border-b border-border px-3 py-2 text-foreground", children: cell2 }, j)) }, i)) })
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)) })
384
372
  ] }) })
385
373
  ] });
386
374
  }
@@ -400,36 +388,29 @@ function DocLink({ entry }) {
400
388
  );
401
389
  }
402
390
 
403
- // src/components/doc/DocSection.tsx
391
+ // src/lib/markdown.ts
404
392
  import { marked } from "marked";
405
393
 
406
- // ../executable-stories-core/dist/utils/url.js
407
- function safeUrl(value) {
408
- if (!value) return void 0;
409
- const trimmed = value.trim();
410
- const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(trimmed);
411
- if (scheme && !/^https?$/i.test(scheme[1] ?? "")) return void 0;
412
- return trimmed;
394
+ // src/lib/url.ts
395
+ import { safeUrl, safeImageUrl, isLocalFsPath } from "executable-stories-core/utils/url";
396
+
397
+ // src/lib/markdown.ts
398
+ function safeMarkdownHtml(markdown) {
399
+ const raw = marked.parse(markdown, { async: false });
400
+ return raw.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<\/?(?:iframe|object|embed|form|base)\b[^>]*>/gi, "").replace(/\son[a-z]+\s*=\s*"[^"]*"/gi, "").replace(/\son[a-z]+\s*=\s*'[^']*'/gi, "").replace(/\son[a-z]+\s*=\s*[^\s>]+/gi, "").replace(/(href|src)\s*=\s*"([^"]*)"/gi, (_m, attr, val) => `${attr}="${neutralizeUrl(val)}"`).replace(/(href|src)\s*=\s*'([^']*)'/gi, (_m, attr, val) => `${attr}='${neutralizeUrl(val)}'`);
413
401
  }
414
- function safeImageUrl(value) {
415
- if (!value) return void 0;
416
- const trimmed = value.trim();
417
- if (/^data:image\//i.test(trimmed)) return trimmed;
418
- return safeUrl(trimmed);
402
+ function dedent(text) {
403
+ const lines = text.replace(/\t/g, " ").split("\n");
404
+ const indents = lines.filter((line) => line.trim().length > 0).map((line) => line.length - line.trimStart().length);
405
+ const shortest = indents.length > 0 ? Math.min(...indents) : 0;
406
+ return lines.map((line) => line.slice(shortest)).join("\n").trim();
419
407
  }
420
- function isLocalFsPath(value) {
421
- return /^(?:[/\\]|[A-Za-z]:[/\\])/.test(value);
408
+ function neutralizeUrl(value) {
409
+ return safeUrl(value) ?? "#";
422
410
  }
423
411
 
424
412
  // src/components/doc/DocSection.tsx
425
413
  import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
426
- function neutralizeUrl(value) {
427
- return safeUrl(value) ?? "#";
428
- }
429
- function safeMarkdownHtml(markdown) {
430
- const raw = marked.parse(markdown, { async: false });
431
- 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)}'`);
432
- }
433
414
  function DocSection({ entry }) {
434
415
  const renderers = useBuiltinRenderers();
435
416
  if (renderers.section) {
@@ -712,125 +693,10 @@ function ReportStepItem({ step }) {
712
693
  );
713
694
  }
714
695
 
715
- // ../executable-stories-core/dist/state-diff.js
716
- var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
717
- function deepEqual(a, b) {
718
- if (Object.is(a, b)) return true;
719
- if (Array.isArray(a) && Array.isArray(b)) {
720
- return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
721
- }
722
- if (isPlainObject(a) && isPlainObject(b)) {
723
- const ak = Object.keys(a);
724
- const bk = Object.keys(b);
725
- return ak.length === bk.length && ak.every((k) => k in b && deepEqual(a[k], b[k]));
726
- }
727
- return false;
728
- }
729
- var joinKey = (path, key) => path === "" ? key : `${path}.${key}`;
730
- function walk(path, before, after, out) {
731
- if (Object.is(before, after)) return;
732
- if (Array.isArray(before) && Array.isArray(after)) {
733
- const shared = Math.min(before.length, after.length);
734
- for (let i = 0; i < shared; i++) walk(`${path}[${i}]`, before[i], after[i], out);
735
- for (let i = shared; i < after.length; i++) out.push({ path: `${path}[${i}]`, kind: "added", after: after[i] });
736
- for (let i = shared; i < before.length; i++) out.push({ path: `${path}[${i}]`, kind: "removed", before: before[i] });
737
- return;
738
- }
739
- if (isPlainObject(before) && isPlainObject(after)) {
740
- for (const key of Object.keys(after)) {
741
- const p = joinKey(path, key);
742
- if (key in before) walk(p, before[key], after[key], out);
743
- else out.push({ path: p, kind: "added", after: after[key] });
744
- }
745
- for (const key of Object.keys(before)) {
746
- if (!(key in after)) out.push({ path: joinKey(path, key), kind: "removed", before: before[key] });
747
- }
748
- return;
749
- }
750
- if (!deepEqual(before, after)) out.push({ path, kind: "changed", before, after });
751
- }
752
- function diffStateValues(before, after) {
753
- const out = [];
754
- walk("", before, after, out);
755
- return out;
756
- }
757
- function formatStateValue(value, maxLen = 60) {
758
- const s = value === void 0 ? "undefined" : JSON.stringify(value);
759
- return s.length > maxLen ? `${s.slice(0, maxLen - 1)}\u2026` : s;
760
- }
761
- function summarizeStateChanges(changes) {
762
- return changes.map((c) => {
763
- const at = c.path === "" ? "(root)" : c.path;
764
- switch (c.kind) {
765
- case "added":
766
- return `+ ${at}: ${formatStateValue(c.after)}`;
767
- case "removed":
768
- return `\u2212 ${at}`;
769
- case "changed":
770
- return `${at}: ${formatStateValue(c.before)} \u2192 ${formatStateValue(c.after)}`;
771
- }
772
- });
773
- }
774
-
775
- // ../executable-stories-core/dist/storyboard.js
776
- function findScreenshot(entries) {
777
- for (const entry of entries) {
778
- if (entry.kind === "screenshot") return entry;
779
- if (entry.children) {
780
- const nested = findScreenshot(entry.children);
781
- if (nested) return nested;
782
- }
783
- }
784
- return void 0;
785
- }
786
- function findStates(entries, out = []) {
787
- for (const entry of entries) {
788
- if (entry.kind === "state") out.push(entry);
789
- if (entry.children) findStates(entry.children, out);
790
- }
791
- return out;
792
- }
793
- var laneOf = (label) => label ?? "";
794
- function extractStoryboardFrames(scenario) {
795
- const frames = [];
796
- const laneOrder = [];
797
- const previous = /* @__PURE__ */ new Map();
798
- for (const step of scenario.steps) {
799
- const shot = findScreenshot(step.docEntries);
800
- const stateDocs = findStates(step.docEntries);
801
- if (!shot && stateDocs.length === 0) continue;
802
- const byLane = /* @__PURE__ */ new Map();
803
- for (const doc of stateDocs) {
804
- const lane = laneOf(doc.label);
805
- if (!byLane.has(lane) && !laneOrder.includes(lane)) laneOrder.push(lane);
806
- byLane.set(lane, doc);
807
- }
808
- const states = [];
809
- for (const lane of laneOrder) {
810
- const doc = byLane.get(lane);
811
- if (!doc) continue;
812
- const card = {
813
- ...doc.label !== void 0 && { label: doc.label },
814
- value: doc.value
815
- };
816
- if (previous.has(lane)) card.changes = diffStateValues(previous.get(lane), doc.value);
817
- previous.set(lane, doc.value);
818
- states.push(card);
819
- }
820
- frames.push({
821
- stepId: step.id,
822
- stepIndex: step.index,
823
- keyword: step.keyword,
824
- text: step.text,
825
- status: step.status,
826
- ...shot && { screenshot: { path: shot.path, ...shot.alt !== void 0 && { alt: shot.alt } } },
827
- states
828
- });
829
- }
830
- return frames;
831
- }
832
-
833
696
  // src/components/ReportStoryboard.tsx
697
+ import { extractStoryboardFrames } from "executable-stories-core/storyboard";
698
+ import { summarizeStateChanges } from "executable-stories-core/state-diff";
699
+ import { isLocalFsPath as isLocalFsPath2, safeImageUrl as safeImageUrl2 } from "executable-stories-core/utils/url";
834
700
  import { jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
835
701
  var FRAME_GLYPH = {
836
702
  passed: "\u2713",
@@ -879,7 +745,7 @@ function StoryboardFrameItem({ frame }) {
879
745
  }
880
746
  function FrameScreenshot({ frame, label }) {
881
747
  const path = frame.screenshot?.path ?? "";
882
- const src = isLocalFsPath(path) ? void 0 : safeImageUrl(path);
748
+ const src = isLocalFsPath2(path) ? void 0 : safeImageUrl2(path);
883
749
  return /* @__PURE__ */ jsx20(
884
750
  "a",
885
751
  {
@@ -985,6 +851,9 @@ function ReportAttachments({ attachments }) {
985
851
  );
986
852
  }
987
853
 
854
+ // src/components/ReportTrace.tsx
855
+ import { formatDuration as formatDuration2 } from "executable-stories-core/utils/duration";
856
+
988
857
  // src/lib/trace.ts
989
858
  var VALID = /* @__PURE__ */ new Set(["ok", "error", "unset"]);
990
859
  var safeStatus = (s) => VALID.has(s) ? s : "unset";
@@ -1037,15 +906,15 @@ function toTraceRows(spans) {
1037
906
  roots.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
1038
907
  const rows = [];
1039
908
  const seen = /* @__PURE__ */ new Set();
1040
- const walk2 = (node, depth) => {
909
+ const walk = (node, depth) => {
1041
910
  if (seen.has(node.span.spanId)) return;
1042
911
  seen.add(node.span.spanId);
1043
912
  rows.push({ ...node.span, depth });
1044
- for (const child of node.children) walk2(child, depth + 1);
913
+ for (const child of node.children) walk(child, depth + 1);
1045
914
  };
1046
- for (const root of roots) walk2(root, 0);
915
+ for (const root of roots) walk(root, 0);
1047
916
  for (const node of byId.values()) {
1048
- if (!seen.has(node.span.spanId)) walk2(node, 0);
917
+ if (!seen.has(node.span.spanId)) walk(node, 0);
1049
918
  }
1050
919
  return rows;
1051
920
  }
@@ -1079,7 +948,7 @@ function ReportTrace({ spans }) {
1079
948
  " span",
1080
949
  rows.length === 1 ? "" : "s",
1081
950
  ", ",
1082
- formatDuration(totalMs),
951
+ formatDuration2(totalMs),
1083
952
  ")"
1084
953
  ] }),
1085
954
  /* @__PURE__ */ jsx22("ul", { className: "flex flex-col gap-0.5 px-3 pb-3 font-mono text-[0.6875rem]", children: rows.map((row, i) => {
@@ -1102,7 +971,7 @@ function ReportTrace({ spans }) {
1102
971
  style: { left: `${leftPct}%`, width: `${widthPct}%` }
1103
972
  }
1104
973
  ) }),
1105
- /* @__PURE__ */ jsx22("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: formatDuration(row.durationMs) })
974
+ /* @__PURE__ */ jsx22("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: formatDuration2(row.durationMs) })
1106
975
  ] }, `${row.spanId}-${i}`);
1107
976
  }) })
1108
977
  ] });
@@ -1278,130 +1147,14 @@ function useCollapseState(computeDefault) {
1278
1147
 
1279
1148
  // src/interactive/scenario-actions.tsx
1280
1149
  import { createContext as createContext4, useContext as useContext5 } from "react";
1281
-
1282
- // ../executable-stories-core/dist/utils/scenario-markdown.js
1283
- function cell(value) {
1284
- return value.replaceAll("|", "\\|").replaceAll("\n", " ");
1285
- }
1286
- function stringify(value) {
1287
- if (typeof value === "string") return value;
1288
- try {
1289
- return JSON.stringify(value, null, 2) ?? String(value);
1290
- } catch {
1291
- return String(value);
1292
- }
1293
- }
1294
- function docEntryToMarkdown(entry, stateLanes) {
1295
- const lines = [];
1296
- switch (entry.kind) {
1297
- case "note":
1298
- lines.push(entry.text);
1299
- break;
1300
- case "tag":
1301
- lines.push(entry.names.map((n) => `\`@${n}\``).join(" "));
1302
- break;
1303
- case "kv":
1304
- lines.push(`**${entry.label}:** ${stringify(entry.value)}`);
1305
- break;
1306
- case "code":
1307
- lines.push(`**${entry.label}**`, "", "```" + (entry.lang ?? ""), entry.content, "```");
1308
- break;
1309
- case "table": {
1310
- lines.push(`**${entry.label}**`, "");
1311
- lines.push(`| ${entry.columns.map(cell).join(" | ")} |`);
1312
- lines.push(`| ${entry.columns.map(() => "---").join(" | ")} |`);
1313
- for (const row of entry.rows) lines.push(`| ${row.map(cell).join(" | ")} |`);
1314
- break;
1315
- }
1316
- case "link":
1317
- lines.push(`[${entry.label}](${entry.url})`);
1318
- break;
1319
- case "section":
1320
- lines.push(`### ${entry.title}`, "", entry.markdown);
1321
- break;
1322
- case "mermaid":
1323
- if (entry.title) lines.push(`**${entry.title}**`, "");
1324
- lines.push("```mermaid", entry.code, "```");
1325
- break;
1326
- case "screenshot":
1327
- lines.push(`![${entry.alt ?? "screenshot"}](${entry.path})`);
1328
- break;
1329
- case "video":
1330
- lines.push(`Video: ${entry.path}${entry.caption ? ` \u2014 ${entry.caption}` : ""}`);
1331
- break;
1332
- case "html": {
1333
- const htmlTitle = entry.title ?? "Embedded HTML";
1334
- if (entry.content) {
1335
- lines.push(`**${htmlTitle}**`, "", "```html", entry.content, "```");
1336
- } else if (entry.url) {
1337
- lines.push(`[${htmlTitle}](${entry.url})`);
1338
- } else {
1339
- lines.push(`Embedded HTML: ${entry.path ?? "(inline)"}`);
1340
- }
1341
- break;
1342
- }
1343
- case "custom":
1344
- lines.push(`**${entry.type}**`, "", "```json", stringify(entry.data), "```");
1345
- break;
1346
- case "state": {
1347
- const lane = entry.label ?? "";
1348
- lines.push(`**${entry.label ?? "State"}**`);
1349
- if (stateLanes?.has(lane)) {
1350
- const summary = summarizeStateChanges(diffStateValues(stateLanes.get(lane), entry.value));
1351
- if (summary.length > 0) lines.push("", ...summary.map((l) => `- ${l}`));
1352
- }
1353
- stateLanes?.set(lane, entry.value);
1354
- lines.push("", "<details>", "<summary>snapshot</summary>", "", "```json", stringify(entry.value ?? null), "```", "", "</details>");
1355
- break;
1356
- }
1357
- }
1358
- for (const child of entry.children ?? []) {
1359
- lines.push("", ...docEntryToMarkdown(child, stateLanes));
1360
- }
1361
- return lines;
1362
- }
1363
- function stepLines(step, ordinal, full, stateLanes) {
1364
- const status = step.status === "passed" ? "" : ` \u2014 **${step.status}**`;
1365
- const lines = [`${ordinal}. **${step.keyword}** ${step.text}${status}`];
1366
- if (!full) return lines;
1367
- if (step.errorMessage) {
1368
- lines.push(...step.errorMessage.split("\n").map((l) => ` > ${l}`));
1369
- }
1370
- for (const entry of step.docEntries) {
1371
- lines.push("", ...docEntryToMarkdown(entry, stateLanes).map((l) => l === "" ? l : ` ${l}`));
1372
- }
1373
- return lines;
1374
- }
1375
- function scenarioToMarkdown(scenario, options = {}) {
1376
- const { variant = "full", meta = [] } = options;
1377
- const full = variant === "full";
1378
- const lines = full ? [`# ${scenario.title}`, ""] : [`## ${scenario.title} _(${scenario.status})_`, ""];
1379
- if (full && meta.length > 0) lines.push(...meta);
1380
- const stateLanes = /* @__PURE__ */ new Map();
1381
- if (scenario.steps.length > 0) {
1382
- if (full) lines.push("", "## Steps", "");
1383
- scenario.steps.forEach((step, i) => lines.push(...stepLines(step, i + 1, full, stateLanes)));
1384
- }
1385
- if (scenario.errorMessage) {
1386
- if (full) lines.push("", "## Failure", "");
1387
- else lines.push("");
1388
- lines.push("```", scenario.errorMessage.trim(), "```");
1389
- }
1390
- if (full && scenario.docEntries.length > 0) {
1391
- lines.push("", "## Docs", "");
1392
- for (const entry of scenario.docEntries) lines.push(...docEntryToMarkdown(entry, stateLanes), "");
1393
- }
1394
- return lines.join("\n").replaceAll(/\n{3,}/g, "\n\n").trimEnd() + (full ? "\n" : "");
1395
- }
1396
-
1397
- // src/interactive/scenario-actions.tsx
1150
+ import { scenarioToMarkdown as coreScenarioToMarkdown } from "executable-stories-core/utils/scenario-markdown";
1398
1151
  var ScenarioActionsContext = createContext4(null);
1399
1152
  var ScenarioActionsProvider = ScenarioActionsContext.Provider;
1400
1153
  function useScenarioActions() {
1401
1154
  return useContext5(ScenarioActionsContext);
1402
1155
  }
1403
- function scenarioToMarkdown2(scenario) {
1404
- return scenarioToMarkdown(scenario, { variant: "compact" });
1156
+ function scenarioToMarkdown(scenario) {
1157
+ return coreScenarioToMarkdown(scenario, { variant: "compact" });
1405
1158
  }
1406
1159
  function scenarioToPrompt(scenario) {
1407
1160
  const lines = [
@@ -1566,7 +1319,7 @@ function ReportScenario({ scenario, hideTitle = false }) {
1566
1319
  ] }),
1567
1320
  /* @__PURE__ */ jsxs17("div", { className: "flex shrink-0 items-center gap-2", children: [
1568
1321
  /* @__PURE__ */ jsx26(ScenarioRunHistory, { scenarioId: scenario.id }),
1569
- scenario.durationMs > 0 ? /* @__PURE__ */ jsx26("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration(scenario.durationMs) }) : null,
1322
+ scenario.durationMs > 0 ? /* @__PURE__ */ jsx26("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: formatDuration3(scenario.durationMs) }) : null,
1570
1323
  meta.showPill ? /* @__PURE__ */ jsx26(Badge, { variant: meta.badgeVariant, "aria-label": `Status: ${label}`, children: label }) : /* @__PURE__ */ jsx26("span", { className: "sr-only", children: `Status: ${label}` }),
1571
1324
  actions ? /* @__PURE__ */ jsxs17(DropdownMenu, { children: [
1572
1325
  /* @__PURE__ */ jsx26(
@@ -1625,8 +1378,35 @@ function ReportScenarioList({ feature }) {
1625
1378
  return /* @__PURE__ */ jsx27(Fragment8, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ jsx27(ReportScenario, { scenario }, scenario.id)) });
1626
1379
  }
1627
1380
 
1381
+ // src/components/ReportFeatureIntro.tsx
1382
+ import { jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
1383
+ function ReportFeatureIntro({ feature }) {
1384
+ const hasNarrative = Boolean(feature.narrative?.trim());
1385
+ const hasGlossary = Boolean(feature.glossary?.length);
1386
+ if (!hasNarrative && !hasGlossary) return null;
1387
+ return /* @__PURE__ */ jsxs18("div", { className: "flex flex-col gap-3 border-l-2 border-border pl-4 text-sm", children: [
1388
+ hasNarrative ? /* @__PURE__ */ jsx28(
1389
+ "div",
1390
+ {
1391
+ className: "es-doc-prose prose prose-sm max-w-none text-muted-foreground",
1392
+ dangerouslySetInnerHTML: { __html: safeMarkdownHtml(dedent(feature.narrative)) }
1393
+ }
1394
+ ) : null,
1395
+ 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: [
1396
+ /* @__PURE__ */ jsx28("dt", { className: "font-medium text-foreground", children: entry.term }),
1397
+ /* @__PURE__ */ jsx28("dd", { className: "flex-1 text-muted-foreground", children: entry.definition })
1398
+ ] }, entry.term)) }) : null
1399
+ ] });
1400
+ }
1401
+ function featureKindLabel(kind) {
1402
+ if (kind === "ability") return "Ability";
1403
+ if (kind === "business-need") return "Business need";
1404
+ if (kind === "feature") return "Feature";
1405
+ return null;
1406
+ }
1407
+
1628
1408
  // src/components/ReportFeature.tsx
1629
- import { Fragment as Fragment9, jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
1409
+ import { Fragment as Fragment9, jsx as jsx29, jsxs as jsxs19 } from "react/jsx-runtime";
1630
1410
  function ReportFeature({ feature }) {
1631
1411
  const titleId = `${feature.id}-title`;
1632
1412
  const bodyId = `${feature.id}-body`;
@@ -1634,10 +1414,11 @@ function ReportFeature({ feature }) {
1634
1414
  const skipped = s.skipped + s.pending;
1635
1415
  const collapse = useCollapse();
1636
1416
  const collapsed = collapse?.isCollapsed(feature.id) ?? false;
1417
+ const kindLabel = featureKindLabel(feature.kind);
1637
1418
  return (
1638
1419
  // Suite = a section heading + a divider, NOT another bordered card. Drops a
1639
1420
  // layer of nesting so the scenario cards below read as the primary unit.
1640
- /* @__PURE__ */ jsxs18(
1421
+ /* @__PURE__ */ jsxs19(
1641
1422
  "section",
1642
1423
  {
1643
1424
  id: feature.id,
@@ -1645,9 +1426,9 @@ function ReportFeature({ feature }) {
1645
1426
  "aria-labelledby": titleId,
1646
1427
  className: "flex flex-col gap-3",
1647
1428
  children: [
1648
- /* @__PURE__ */ jsxs18("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1649
- /* @__PURE__ */ jsxs18("div", { className: "min-w-0", children: [
1650
- /* @__PURE__ */ jsx28("h2", { id: titleId, className: "text-base font-semibold tracking-tight text-foreground", children: collapse ? /* @__PURE__ */ jsxs18(
1429
+ /* @__PURE__ */ jsxs19("div", { className: "flex items-end justify-between gap-4 border-b border-border pb-2", children: [
1430
+ /* @__PURE__ */ jsxs19("div", { className: "min-w-0", children: [
1431
+ /* @__PURE__ */ jsx29("h2", { id: titleId, className: "text-base font-semibold tracking-tight text-foreground", children: collapse ? /* @__PURE__ */ jsxs19(
1651
1432
  "button",
1652
1433
  {
1653
1434
  type: "button",
@@ -1656,31 +1437,34 @@ function ReportFeature({ feature }) {
1656
1437
  "aria-controls": bodyId,
1657
1438
  className: "flex w-full cursor-pointer items-center gap-2 text-left hover:text-foreground/80",
1658
1439
  children: [
1659
- /* @__PURE__ */ jsx28("span", { "aria-hidden": true, className: cn("shrink-0 text-xs text-muted-foreground transition-transform", !collapsed && "rotate-90"), children: "\u25B8" }),
1660
- /* @__PURE__ */ jsx28("span", { className: "truncate", children: feature.title })
1440
+ /* @__PURE__ */ jsx29("span", { "aria-hidden": true, className: cn("shrink-0 text-xs text-muted-foreground transition-transform", !collapsed && "rotate-90"), children: "\u25B8" }),
1441
+ /* @__PURE__ */ jsx29("span", { className: "truncate", children: feature.title })
1661
1442
  ]
1662
1443
  }
1663
1444
  ) : feature.title }),
1664
- /* @__PURE__ */ jsx28("p", { className: cn("mt-0.5 truncate font-mono text-xs text-muted-foreground", collapse && "pl-5"), children: feature.sourceFile })
1445
+ /* @__PURE__ */ jsxs19("p", { className: cn("mt-0.5 flex items-center gap-2 text-xs text-muted-foreground", collapse && "pl-5"), children: [
1446
+ kindLabel ? /* @__PURE__ */ jsx29("span", { className: "shrink-0 rounded-sm bg-muted px-1.5 py-0.5 font-medium text-foreground", children: kindLabel }) : null,
1447
+ /* @__PURE__ */ jsx29("span", { className: "truncate font-mono", children: feature.sourceFile })
1448
+ ] })
1665
1449
  ] }),
1666
- /* @__PURE__ */ jsxs18(
1450
+ /* @__PURE__ */ jsxs19(
1667
1451
  "p",
1668
1452
  {
1669
1453
  className: "flex shrink-0 items-center gap-1.5 text-xs text-muted-foreground",
1670
1454
  "aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
1671
1455
  children: [
1672
- /* @__PURE__ */ jsxs18("span", { children: [
1456
+ /* @__PURE__ */ jsxs19("span", { children: [
1673
1457
  s.passed,
1674
1458
  " passed"
1675
1459
  ] }),
1676
- /* @__PURE__ */ jsx28("span", { "aria-hidden": true, children: "\xB7" }),
1677
- /* @__PURE__ */ jsxs18("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
1460
+ /* @__PURE__ */ jsx29("span", { "aria-hidden": true, children: "\xB7" }),
1461
+ /* @__PURE__ */ jsxs19("span", { className: cn(s.failed > 0 && "font-medium text-fail"), children: [
1678
1462
  s.failed,
1679
1463
  " failed"
1680
1464
  ] }),
1681
- skipped > 0 ? /* @__PURE__ */ jsxs18(Fragment9, { children: [
1682
- /* @__PURE__ */ jsx28("span", { "aria-hidden": true, children: "\xB7" }),
1683
- /* @__PURE__ */ jsxs18("span", { children: [
1465
+ skipped > 0 ? /* @__PURE__ */ jsxs19(Fragment9, { children: [
1466
+ /* @__PURE__ */ jsx29("span", { "aria-hidden": true, children: "\xB7" }),
1467
+ /* @__PURE__ */ jsxs19("span", { children: [
1684
1468
  skipped,
1685
1469
  " skipped"
1686
1470
  ] })
@@ -1689,7 +1473,10 @@ function ReportFeature({ feature }) {
1689
1473
  }
1690
1474
  )
1691
1475
  ] }),
1692
- /* @__PURE__ */ jsx28("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-3", children: /* @__PURE__ */ jsx28(ReportScenarioList, { feature }) })
1476
+ /* @__PURE__ */ jsxs19("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-3", children: [
1477
+ /* @__PURE__ */ jsx29(ReportFeatureIntro, { feature }),
1478
+ /* @__PURE__ */ jsx29(ReportScenarioList, { feature })
1479
+ ] })
1693
1480
  ]
1694
1481
  }
1695
1482
  )
@@ -1697,17 +1484,17 @@ function ReportFeature({ feature }) {
1697
1484
  }
1698
1485
 
1699
1486
  // src/components/ReportFeatureList.tsx
1700
- import { jsx as jsx29 } from "react/jsx-runtime";
1487
+ import { jsx as jsx30 } from "react/jsx-runtime";
1701
1488
  function ReportFeatureList() {
1702
1489
  const report = useReport();
1703
- return /* @__PURE__ */ jsx29("div", { className: "flex flex-col gap-8", children: report.features.map((feature) => /* @__PURE__ */ jsx29(ReportFeature, { feature }, feature.id)) });
1490
+ return /* @__PURE__ */ jsx30("div", { className: "flex flex-col gap-8", children: report.features.map((feature) => /* @__PURE__ */ jsx30(ReportFeature, { feature }, feature.id)) });
1704
1491
  }
1705
1492
 
1706
1493
  // src/components/ui/empty.tsx
1707
1494
  import { cva as cva2 } from "class-variance-authority";
1708
- import { jsx as jsx30 } from "react/jsx-runtime";
1495
+ import { jsx as jsx31 } from "react/jsx-runtime";
1709
1496
  function Empty({ className, ...props }) {
1710
- return /* @__PURE__ */ jsx30(
1497
+ return /* @__PURE__ */ jsx31(
1711
1498
  "div",
1712
1499
  {
1713
1500
  "data-slot": "empty",
@@ -1720,7 +1507,7 @@ function Empty({ className, ...props }) {
1720
1507
  );
1721
1508
  }
1722
1509
  function EmptyHeader({ className, ...props }) {
1723
- return /* @__PURE__ */ jsx30(
1510
+ return /* @__PURE__ */ jsx31(
1724
1511
  "div",
1725
1512
  {
1726
1513
  "data-slot": "empty-header",
@@ -1747,7 +1534,7 @@ var emptyMediaVariants = cva2(
1747
1534
  }
1748
1535
  );
1749
1536
  function EmptyTitle({ className, ...props }) {
1750
- return /* @__PURE__ */ jsx30(
1537
+ return /* @__PURE__ */ jsx31(
1751
1538
  "div",
1752
1539
  {
1753
1540
  "data-slot": "empty-title",
@@ -1758,16 +1545,16 @@ function EmptyTitle({ className, ...props }) {
1758
1545
  }
1759
1546
 
1760
1547
  // src/components/ReportEmpty.tsx
1761
- import { jsx as jsx31 } from "react/jsx-runtime";
1548
+ import { jsx as jsx32 } from "react/jsx-runtime";
1762
1549
  function ReportEmpty({ message }) {
1763
- return /* @__PURE__ */ jsx31(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ jsx31(EmptyHeader, { children: /* @__PURE__ */ jsx31(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1550
+ return /* @__PURE__ */ jsx32(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ jsx32(EmptyHeader, { children: /* @__PURE__ */ jsx32(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
1764
1551
  }
1765
1552
 
1766
1553
  // src/components/ReportSummary.tsx
1767
- import { jsx as jsx32, jsxs as jsxs19 } from "react/jsx-runtime";
1554
+ import { jsx as jsx33, jsxs as jsxs20 } from "react/jsx-runtime";
1768
1555
  function ReportSummary({ className }) {
1769
1556
  const report = useReport();
1770
- return /* @__PURE__ */ jsx32(
1557
+ return /* @__PURE__ */ jsx33(
1771
1558
  ReportSummaryView,
1772
1559
  {
1773
1560
  summary: report.summary,
@@ -1793,7 +1580,7 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1793
1580
  if (summary.pending > 0) {
1794
1581
  cards.push({ status: "pending", label: "Pending", value: summary.pending });
1795
1582
  }
1796
- return /* @__PURE__ */ jsx32(
1583
+ return /* @__PURE__ */ jsx33(
1797
1584
  "dl",
1798
1585
  {
1799
1586
  className: cn(
@@ -1806,17 +1593,17 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1806
1593
  "aria-label": ariaLabel,
1807
1594
  children: cards.map((c) => {
1808
1595
  const tone = TONE[c.status];
1809
- return /* @__PURE__ */ jsxs19(
1596
+ return /* @__PURE__ */ jsxs20(
1810
1597
  "div",
1811
1598
  {
1812
1599
  "data-status": c.status,
1813
1600
  className: "rounded-lg border bg-card px-4 py-3",
1814
1601
  children: [
1815
- /* @__PURE__ */ jsxs19("dt", { className: "flex items-center gap-1.5 font-mono text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: [
1816
- /* @__PURE__ */ jsx32("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
1602
+ /* @__PURE__ */ jsxs20("dt", { className: "flex items-center gap-1.5 font-mono text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: [
1603
+ /* @__PURE__ */ jsx33("span", { className: cn("size-1.5 shrink-0 rounded-full", tone.dot), "aria-hidden": "true" }),
1817
1604
  c.label
1818
1605
  ] }),
1819
- /* @__PURE__ */ jsx32("dd", { className: cn("mt-2 text-3xl font-semibold leading-none tracking-tight tabular-nums", tone.num), children: c.value })
1606
+ /* @__PURE__ */ jsx33("dd", { className: cn("mt-2 text-3xl font-semibold leading-none tracking-tight tabular-nums", tone.num), children: c.value })
1820
1607
  ]
1821
1608
  },
1822
1609
  c.status
@@ -1826,6 +1613,9 @@ function ReportSummaryView({ summary, className, ariaLabel }) {
1826
1613
  );
1827
1614
  }
1828
1615
 
1616
+ // src/components/ReportMeta.tsx
1617
+ import { formatDuration as formatDuration4 } from "executable-stories-core/utils/duration";
1618
+
1829
1619
  // src/lib/provenance.ts
1830
1620
  function reportLastRunMs(report) {
1831
1621
  if (report.finishedAtMs && report.finishedAtMs > 0) return report.finishedAtMs;
@@ -1876,7 +1666,7 @@ function prUrl(ci) {
1876
1666
  }
1877
1667
 
1878
1668
  // src/components/ReportMeta.tsx
1879
- import { jsx as jsx33, jsxs as jsxs20 } from "react/jsx-runtime";
1669
+ import { jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
1880
1670
  function ReportMeta() {
1881
1671
  const report = useReport();
1882
1672
  const items = [];
@@ -1885,7 +1675,7 @@ function ReportMeta() {
1885
1675
  items.push({ label: "Started", value: new Date(report.startedAtMs).toLocaleString() });
1886
1676
  }
1887
1677
  if (report.durationMs) {
1888
- items.push({ label: "Duration", value: formatDuration(report.durationMs) });
1678
+ items.push({ label: "Duration", value: formatDuration4(report.durationMs) });
1889
1679
  }
1890
1680
  if (report.packageVersion) {
1891
1681
  items.push({ label: "Version", value: report.packageVersion });
@@ -1899,13 +1689,13 @@ function ReportMeta() {
1899
1689
  const short = sha.slice(0, 8);
1900
1690
  items.push({
1901
1691
  label: "Commit",
1902
- value: url ? /* @__PURE__ */ jsx33("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1692
+ value: url ? /* @__PURE__ */ jsx34("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: short }) : short
1903
1693
  });
1904
1694
  }
1905
1695
  if (report.ci) {
1906
1696
  items.push({
1907
1697
  label: "CI",
1908
- value: report.ci.url ? /* @__PURE__ */ jsxs20("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1698
+ value: report.ci.url ? /* @__PURE__ */ jsxs21("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: [
1909
1699
  ciDisplayName(report.ci.name),
1910
1700
  report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
1911
1701
  ] }) : `${ciDisplayName(report.ci.name)}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
@@ -1916,7 +1706,7 @@ function ReportMeta() {
1916
1706
  const label = `#${report.ci.prNumber}`;
1917
1707
  items.push({
1918
1708
  label: "PR",
1919
- value: url ? /* @__PURE__ */ jsx33("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1709
+ value: url ? /* @__PURE__ */ jsx34("a", { href: url, target: "_blank", rel: "noreferrer noopener", className: linkClass, children: label }) : label
1920
1710
  });
1921
1711
  }
1922
1712
  if (items.length === 0) return null;
@@ -1924,50 +1714,50 @@ function ReportMeta() {
1924
1714
  // Run metadata is useful but secondary to "what failed" — tuck it into a
1925
1715
  // collapsed disclosure so it doesn't push the result down. Native <details>
1926
1716
  // so it works with no JS (the SSR/no-hydration report still toggles).
1927
- /* @__PURE__ */ jsxs20("details", { "data-es-meta-card": true, className: "es-run-details group rounded-lg border border-border bg-card text-xs", children: [
1928
- /* @__PURE__ */ jsxs20("summary", { className: "flex cursor-pointer list-none select-none items-center gap-1.5 px-4 py-2 font-medium text-muted-foreground [&::-webkit-details-marker]:hidden", children: [
1929
- /* @__PURE__ */ jsx33("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
1717
+ /* @__PURE__ */ jsxs21("details", { "data-es-meta-card": true, className: "es-run-details group rounded-lg border border-border bg-card text-xs", children: [
1718
+ /* @__PURE__ */ jsxs21("summary", { className: "flex cursor-pointer list-none select-none items-center gap-1.5 px-4 py-2 font-medium text-muted-foreground [&::-webkit-details-marker]:hidden", children: [
1719
+ /* @__PURE__ */ jsx34("span", { "aria-hidden": true, className: "text-xs transition-transform group-open:rotate-90", children: "\u25B8" }),
1930
1720
  "Run details"
1931
1721
  ] }),
1932
- /* @__PURE__ */ jsx33("dl", { className: "flex flex-wrap gap-x-5 gap-y-2 border-t border-border px-4 py-3 text-muted-foreground", "aria-label": "Run metadata", children: items.map((item) => /* @__PURE__ */ jsxs20("div", { className: "flex items-baseline gap-1.5", children: [
1933
- /* @__PURE__ */ jsx33("dt", { className: "font-medium", children: item.label }),
1934
- /* @__PURE__ */ jsx33("dd", { className: "font-mono text-foreground", children: item.value })
1722
+ /* @__PURE__ */ jsx34("dl", { className: "flex flex-wrap gap-x-5 gap-y-2 border-t border-border px-4 py-3 text-muted-foreground", "aria-label": "Run metadata", children: items.map((item) => /* @__PURE__ */ jsxs21("div", { className: "flex items-baseline gap-1.5", children: [
1723
+ /* @__PURE__ */ jsx34("dt", { className: "font-medium", children: item.label }),
1724
+ /* @__PURE__ */ jsx34("dd", { className: "font-mono text-foreground", children: item.value })
1935
1725
  ] }, item.label)) })
1936
1726
  ] })
1937
1727
  );
1938
1728
  }
1939
1729
 
1940
1730
  // src/components/ReportSchemaError.tsx
1941
- import { jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
1731
+ import { jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
1942
1732
  function ReportSchemaError({ error }) {
1943
- return /* @__PURE__ */ jsxs21("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1944
- /* @__PURE__ */ jsx34("p", { children: /* @__PURE__ */ jsx34("strong", { children: "Report could not be displayed." }) }),
1945
- /* @__PURE__ */ jsx34("p", { children: error.message }),
1946
- error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs21("p", { children: [
1733
+ return /* @__PURE__ */ jsxs22("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
1734
+ /* @__PURE__ */ jsx35("p", { children: /* @__PURE__ */ jsx35("strong", { children: "Report could not be displayed." }) }),
1735
+ /* @__PURE__ */ jsx35("p", { children: error.message }),
1736
+ error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs22("p", { children: [
1947
1737
  "The report bundle is newer than this version of ",
1948
- /* @__PURE__ */ jsx34("code", { children: "executable-stories-react" }),
1738
+ /* @__PURE__ */ jsx35("code", { children: "executable-stories-react" }),
1949
1739
  ". Upgrade the package, or regenerate the report with an older formatters CLI."
1950
1740
  ] }) : null,
1951
- error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs21("details", { children: [
1952
- /* @__PURE__ */ jsxs21("summary", { children: [
1741
+ error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs22("details", { children: [
1742
+ /* @__PURE__ */ jsxs22("summary", { children: [
1953
1743
  error.issues.length,
1954
1744
  " validation issue",
1955
1745
  error.issues.length === 1 ? "" : "s"
1956
1746
  ] }),
1957
- /* @__PURE__ */ jsx34("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1747
+ /* @__PURE__ */ jsx35("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
1958
1748
  ] }) : null
1959
1749
  ] });
1960
1750
  }
1961
1751
 
1962
1752
  // src/components/ReportShell.tsx
1963
- import { Fragment as Fragment10, jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
1753
+ import { Fragment as Fragment10, jsx as jsx36, jsxs as jsxs23 } from "react/jsx-runtime";
1964
1754
  function ReportErrorShell({
1965
1755
  error,
1966
1756
  className,
1967
1757
  title,
1968
1758
  dataTheme
1969
1759
  }) {
1970
- return /* @__PURE__ */ jsx35("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ jsx35(ReportSchemaError, { error }) });
1760
+ return /* @__PURE__ */ jsx36("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ jsx36(ReportSchemaError, { error }) });
1971
1761
  }
1972
1762
 
1973
1763
  // src/interactive/ReportInteractive.tsx
@@ -1976,12 +1766,12 @@ import { Monitor, Moon, Sun } from "lucide-react";
1976
1766
  // src/components/ui/switch.tsx
1977
1767
  import "react";
1978
1768
  import { Switch as SwitchPrimitive } from "@base-ui/react/switch";
1979
- import { jsx as jsx36 } from "react/jsx-runtime";
1769
+ import { jsx as jsx37 } from "react/jsx-runtime";
1980
1770
  function Switch({
1981
1771
  className,
1982
1772
  ...props
1983
1773
  }) {
1984
- return /* @__PURE__ */ jsx36(
1774
+ return /* @__PURE__ */ jsx37(
1985
1775
  SwitchPrimitive.Root,
1986
1776
  {
1987
1777
  "data-slot": "switch",
@@ -1990,7 +1780,7 @@ function Switch({
1990
1780
  className
1991
1781
  ),
1992
1782
  ...props,
1993
- children: /* @__PURE__ */ jsx36(
1783
+ children: /* @__PURE__ */ jsx37(
1994
1784
  SwitchPrimitive.Thumb,
1995
1785
  {
1996
1786
  "data-slot": "switch-thumb",
@@ -2006,7 +1796,7 @@ import * as React5 from "react";
2006
1796
  import { ToggleGroup as ToggleGroupPrimitive } from "@base-ui/react/toggle-group";
2007
1797
  import { Toggle as TogglePrimitive } from "@base-ui/react/toggle";
2008
1798
  import { cva as cva3 } from "class-variance-authority";
2009
- import { jsx as jsx37 } from "react/jsx-runtime";
1799
+ import { jsx as jsx38 } from "react/jsx-runtime";
2010
1800
  var toggleItemVariants = cva3(
2011
1801
  "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",
2012
1802
  {
@@ -2028,13 +1818,13 @@ function ToggleGroup({
2028
1818
  children,
2029
1819
  ...props
2030
1820
  }) {
2031
- return /* @__PURE__ */ jsx37(
1821
+ return /* @__PURE__ */ jsx38(
2032
1822
  ToggleGroupPrimitive,
2033
1823
  {
2034
1824
  "data-slot": "toggle-group",
2035
1825
  className: cn("flex w-fit items-center gap-0.5 rounded-lg bg-muted p-0.5", className),
2036
1826
  ...props,
2037
- children: /* @__PURE__ */ jsx37(ToggleGroupContext.Provider, { value: { size }, children })
1827
+ children: /* @__PURE__ */ jsx38(ToggleGroupContext.Provider, { value: { size }, children })
2038
1828
  }
2039
1829
  );
2040
1830
  }
@@ -2045,7 +1835,7 @@ function ToggleGroupItem({
2045
1835
  ...props
2046
1836
  }) {
2047
1837
  const context = React5.useContext(ToggleGroupContext);
2048
- return /* @__PURE__ */ jsx37(
1838
+ return /* @__PURE__ */ jsx38(
2049
1839
  TogglePrimitive,
2050
1840
  {
2051
1841
  "data-slot": "toggle-group-item",
@@ -2065,9 +1855,9 @@ import { Search } from "lucide-react";
2065
1855
 
2066
1856
  // src/components/ui/input.tsx
2067
1857
  import "react";
2068
- import { jsx as jsx38 } from "react/jsx-runtime";
1858
+ import { jsx as jsx39 } from "react/jsx-runtime";
2069
1859
  function Input({ className, type, ...props }) {
2070
- return /* @__PURE__ */ jsx38(
1860
+ return /* @__PURE__ */ jsx39(
2071
1861
  "input",
2072
1862
  {
2073
1863
  type,
@@ -2084,7 +1874,7 @@ function Input({ className, type, ...props }) {
2084
1874
  }
2085
1875
 
2086
1876
  // src/interactive/ReportSearch.tsx
2087
- import { jsx as jsx39, jsxs as jsxs23 } from "react/jsx-runtime";
1877
+ import { jsx as jsx40, jsxs as jsxs24 } from "react/jsx-runtime";
2088
1878
  var ReportSearch = forwardRef(
2089
1879
  function ReportSearch2(props, ref) {
2090
1880
  const {
@@ -2106,17 +1896,17 @@ var ReportSearch = forwardRef(
2106
1896
  e.preventDefault();
2107
1897
  }
2108
1898
  }
2109
- return /* @__PURE__ */ jsxs23("div", { className: cn("es-search flex flex-col gap-1", className), children: [
2110
- /* @__PURE__ */ jsx39("label", { htmlFor: inputId, className: "sr-only", children: "Search" }),
2111
- /* @__PURE__ */ jsxs23("div", { className: "relative w-full sm:w-72", children: [
2112
- /* @__PURE__ */ jsx39(
1899
+ return /* @__PURE__ */ jsxs24("div", { className: cn("es-search flex flex-col gap-1", className), children: [
1900
+ /* @__PURE__ */ jsx40("label", { htmlFor: inputId, className: "sr-only", children: "Search" }),
1901
+ /* @__PURE__ */ jsxs24("div", { className: "relative w-full sm:w-72", children: [
1902
+ /* @__PURE__ */ jsx40(
2113
1903
  Search,
2114
1904
  {
2115
1905
  "aria-hidden": true,
2116
1906
  className: "pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground"
2117
1907
  }
2118
1908
  ),
2119
- /* @__PURE__ */ jsx39(
1909
+ /* @__PURE__ */ jsx40(
2120
1910
  Input,
2121
1911
  {
2122
1912
  ref,
@@ -2133,7 +1923,7 @@ var ReportSearch = forwardRef(
2133
1923
  }
2134
1924
  )
2135
1925
  ] }),
2136
- showCounts ? /* @__PURE__ */ jsxs23(
1926
+ showCounts ? /* @__PURE__ */ jsxs24(
2137
1927
  "span",
2138
1928
  {
2139
1929
  className: "text-xs tabular-nums text-muted-foreground",
@@ -2222,7 +2012,7 @@ function scrollToScenarioId(scenarioId, options = {}) {
2222
2012
  }
2223
2013
 
2224
2014
  // src/interactive/ReportFailureBanner.tsx
2225
- import { jsx as jsx40, jsxs as jsxs24 } from "react/jsx-runtime";
2015
+ import { jsx as jsx41, jsxs as jsxs25 } from "react/jsx-runtime";
2226
2016
  function ReportFailureBanner({ failures }) {
2227
2017
  const first = failures[0];
2228
2018
  const jumpToFirst = useCallback2(() => {
@@ -2230,7 +2020,7 @@ function ReportFailureBanner({ failures }) {
2230
2020
  scrollToScenarioId(first.scenarioId);
2231
2021
  }, [first]);
2232
2022
  if (failures.length === 0) return null;
2233
- return /* @__PURE__ */ jsxs24(
2023
+ return /* @__PURE__ */ jsxs25(
2234
2024
  "div",
2235
2025
  {
2236
2026
  className: "es-failure-banner",
@@ -2238,13 +2028,13 @@ function ReportFailureBanner({ failures }) {
2238
2028
  "aria-live": "polite",
2239
2029
  "aria-label": "Failure summary",
2240
2030
  children: [
2241
- /* @__PURE__ */ jsxs24("span", { className: "es-failure-banner-text", children: [
2242
- /* @__PURE__ */ jsx40("strong", { children: failures.length }),
2031
+ /* @__PURE__ */ jsxs25("span", { className: "es-failure-banner-text", children: [
2032
+ /* @__PURE__ */ jsx41("strong", { children: failures.length }),
2243
2033
  " ",
2244
2034
  "failure",
2245
2035
  failures.length === 1 ? "" : "s"
2246
2036
  ] }),
2247
- /* @__PURE__ */ jsx40(
2037
+ /* @__PURE__ */ jsx41(
2248
2038
  "button",
2249
2039
  {
2250
2040
  type: "button",
@@ -2261,7 +2051,7 @@ function ReportFailureBanner({ failures }) {
2261
2051
 
2262
2052
  // src/interactive/ReportFreshness.tsx
2263
2053
  import { useEffect, useState as useState2 } from "react";
2264
- import { Fragment as Fragment11, jsx as jsx41, jsxs as jsxs25 } from "react/jsx-runtime";
2054
+ import { Fragment as Fragment11, jsx as jsx42, jsxs as jsxs26 } from "react/jsx-runtime";
2265
2055
  function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
2266
2056
  const [now, setNow] = useState2(nowMs);
2267
2057
  useEffect(() => {
@@ -2269,7 +2059,7 @@ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
2269
2059
  }, [nowMs]);
2270
2060
  if (lastRunMs === void 0 || now === void 0) return null;
2271
2061
  const age = formatRelativeAge(lastRunMs, now);
2272
- const runLink = ciUrl ? /* @__PURE__ */ jsx41(
2062
+ const runLink = ciUrl ? /* @__PURE__ */ jsx42(
2273
2063
  "a",
2274
2064
  {
2275
2065
  href: ciUrl,
@@ -2280,9 +2070,9 @@ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
2280
2070
  }
2281
2071
  ) : null;
2282
2072
  if (isReportStale(lastRunMs, staleAfterDays, now)) {
2283
- return /* @__PURE__ */ jsxs25("div", { className: "es-stale-banner", role: "status", "aria-label": "Report freshness", children: [
2284
- /* @__PURE__ */ jsxs25("span", { children: [
2285
- /* @__PURE__ */ jsxs25("strong", { children: [
2073
+ return /* @__PURE__ */ jsxs26("div", { className: "es-stale-banner", role: "status", "aria-label": "Report freshness", children: [
2074
+ /* @__PURE__ */ jsxs26("span", { children: [
2075
+ /* @__PURE__ */ jsxs26("strong", { children: [
2286
2076
  "Last verified ",
2287
2077
  age,
2288
2078
  "."
@@ -2292,10 +2082,10 @@ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
2292
2082
  runLink
2293
2083
  ] });
2294
2084
  }
2295
- return /* @__PURE__ */ jsxs25("p", { className: "es-freshness", "aria-label": "Report freshness", children: [
2085
+ return /* @__PURE__ */ jsxs26("p", { className: "es-freshness", "aria-label": "Report freshness", children: [
2296
2086
  "Verified ",
2297
2087
  age,
2298
- runLink ? /* @__PURE__ */ jsxs25(Fragment11, { children: [
2088
+ runLink ? /* @__PURE__ */ jsxs26(Fragment11, { children: [
2299
2089
  " \xB7 ",
2300
2090
  runLink
2301
2091
  ] }) : null
@@ -2303,15 +2093,15 @@ function ReportFreshness({ lastRunMs, ciUrl, staleAfterDays, nowMs }) {
2303
2093
  }
2304
2094
 
2305
2095
  // src/interactive/ReportLastRunDelta.tsx
2306
- import { Fragment as Fragment12, jsx as jsx42, jsxs as jsxs26 } from "react/jsx-runtime";
2096
+ import { Fragment as Fragment12, jsx as jsx43, jsxs as jsxs27 } from "react/jsx-runtime";
2307
2097
  var MAX_LINKS = 5;
2308
2098
  function ScenarioLinks({ scenarios, tone }) {
2309
- return /* @__PURE__ */ jsxs26(Fragment12, { children: [
2310
- scenarios.slice(0, MAX_LINKS).map((s, i) => /* @__PURE__ */ jsxs26("span", { children: [
2099
+ return /* @__PURE__ */ jsxs27(Fragment12, { children: [
2100
+ scenarios.slice(0, MAX_LINKS).map((s, i) => /* @__PURE__ */ jsxs27("span", { children: [
2311
2101
  i > 0 ? ", " : "",
2312
- /* @__PURE__ */ jsx42("a", { href: `#${s.id}`, className: cn("underline underline-offset-2 hover:no-underline", tone), children: s.title })
2102
+ /* @__PURE__ */ jsx43("a", { href: `#${s.id}`, className: cn("underline underline-offset-2 hover:no-underline", tone), children: s.title })
2313
2103
  ] }, s.id)),
2314
- scenarios.length > MAX_LINKS ? /* @__PURE__ */ jsxs26("span", { children: [
2104
+ scenarios.length > MAX_LINKS ? /* @__PURE__ */ jsxs27("span", { children: [
2315
2105
  " and ",
2316
2106
  scenarios.length - MAX_LINKS,
2317
2107
  " more"
@@ -2328,23 +2118,23 @@ function ReportLastRunDelta({
2328
2118
  if (!delta) return null;
2329
2119
  const { newlyFailing, newlyPassing, added } = delta;
2330
2120
  if (newlyFailing.length === 0 && newlyPassing.length === 0 && added.length === 0) {
2331
- return /* @__PURE__ */ jsx42("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: "Since last run: no behavior changes." });
2121
+ return /* @__PURE__ */ jsx43("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: "Since last run: no behavior changes." });
2332
2122
  }
2333
- return /* @__PURE__ */ jsxs26("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: [
2334
- /* @__PURE__ */ jsx42("span", { className: "font-medium text-foreground", children: "Since last run:" }),
2335
- newlyFailing.length > 0 ? /* @__PURE__ */ jsxs26("span", { className: "ml-2 text-fail", children: [
2123
+ return /* @__PURE__ */ jsxs27("p", { className: "text-xs text-muted-foreground", "data-testid": "es-last-run-delta", children: [
2124
+ /* @__PURE__ */ jsx43("span", { className: "font-medium text-foreground", children: "Since last run:" }),
2125
+ newlyFailing.length > 0 ? /* @__PURE__ */ jsxs27("span", { className: "ml-2 text-fail", children: [
2336
2126
  newlyFailing.length,
2337
2127
  " newly failing (",
2338
- /* @__PURE__ */ jsx42(ScenarioLinks, { scenarios: newlyFailing, tone: "text-fail" }),
2128
+ /* @__PURE__ */ jsx43(ScenarioLinks, { scenarios: newlyFailing, tone: "text-fail" }),
2339
2129
  ")"
2340
2130
  ] }) : null,
2341
- newlyPassing.length > 0 ? /* @__PURE__ */ jsxs26("span", { className: "ml-2 text-pass", children: [
2131
+ newlyPassing.length > 0 ? /* @__PURE__ */ jsxs27("span", { className: "ml-2 text-pass", children: [
2342
2132
  newlyPassing.length,
2343
2133
  " fixed (",
2344
- /* @__PURE__ */ jsx42(ScenarioLinks, { scenarios: newlyPassing, tone: "text-pass" }),
2134
+ /* @__PURE__ */ jsx43(ScenarioLinks, { scenarios: newlyPassing, tone: "text-pass" }),
2345
2135
  ")"
2346
2136
  ] }) : null,
2347
- added.length > 0 ? /* @__PURE__ */ jsxs26("span", { className: "ml-2", children: [
2137
+ added.length > 0 ? /* @__PURE__ */ jsxs27("span", { className: "ml-2", children: [
2348
2138
  added.length,
2349
2139
  " new"
2350
2140
  ] }) : null
@@ -2408,7 +2198,7 @@ function Button({
2408
2198
  }
2409
2199
 
2410
2200
  // src/interactive/ReportShortcutsHelp.tsx
2411
- import { jsx as jsx43, jsxs as jsxs27 } from "react/jsx-runtime";
2201
+ import { jsx as jsx44, jsxs as jsxs28 } from "react/jsx-runtime";
2412
2202
  var SHORTCUTS = [
2413
2203
  { keys: "/", description: "Focus search" },
2414
2204
  { keys: "f", description: "Jump to next failure" },
@@ -2420,9 +2210,9 @@ var SHORTCUTS = [
2420
2210
  ];
2421
2211
  function Keys({ keys }) {
2422
2212
  const parts = keys.split(" ");
2423
- return /* @__PURE__ */ jsx43("span", { className: "inline-flex items-center gap-1", children: parts.map((k, i) => /* @__PURE__ */ jsxs27("span", { className: "inline-flex items-center gap-1", children: [
2424
- i > 0 ? /* @__PURE__ */ jsx43("span", { className: "text-xs text-muted-foreground", children: "+" }) : null,
2425
- /* @__PURE__ */ jsx43("kbd", { className: "inline-flex h-6 min-w-6 items-center justify-center rounded border border-border bg-muted px-1.5 font-mono text-xs font-medium text-muted-foreground", children: k })
2213
+ return /* @__PURE__ */ jsx44("span", { className: "inline-flex items-center gap-1", children: parts.map((k, i) => /* @__PURE__ */ jsxs28("span", { className: "inline-flex items-center gap-1", children: [
2214
+ i > 0 ? /* @__PURE__ */ jsx44("span", { className: "text-xs text-muted-foreground", children: "+" }) : null,
2215
+ /* @__PURE__ */ jsx44("kbd", { className: "inline-flex h-6 min-w-6 items-center justify-center rounded border border-border bg-muted px-1.5 font-mono text-xs font-medium text-muted-foreground", children: k })
2426
2216
  ] }, k)) });
2427
2217
  }
2428
2218
  function ReportShortcutsHelp({ open, onClose }) {
@@ -2433,7 +2223,7 @@ function ReportShortcutsHelp({ open, onClose }) {
2433
2223
  if (open && !dialog.open) dialog.showModal();
2434
2224
  else if (!open && dialog.open) dialog.close();
2435
2225
  }, [open]);
2436
- return /* @__PURE__ */ jsxs27(
2226
+ return /* @__PURE__ */ jsxs28(
2437
2227
  "dialog",
2438
2228
  {
2439
2229
  ref: dialogRef,
@@ -2441,12 +2231,12 @@ function ReportShortcutsHelp({ open, onClose }) {
2441
2231
  "aria-label": "Keyboard shortcuts",
2442
2232
  onClose,
2443
2233
  children: [
2444
- /* @__PURE__ */ jsx43("h2", { className: "text-base font-semibold", children: "Keyboard shortcuts" }),
2445
- /* @__PURE__ */ jsx43("dl", { className: "mt-4 grid grid-cols-[auto_1fr] items-center gap-x-4 gap-y-2.5", children: SHORTCUTS.map((s) => /* @__PURE__ */ jsxs27("div", { className: "col-span-2 grid grid-cols-subgrid items-center", children: [
2446
- /* @__PURE__ */ jsx43("dt", { className: "justify-self-start", children: /* @__PURE__ */ jsx43(Keys, { keys: s.keys }) }),
2447
- /* @__PURE__ */ jsx43("dd", { className: "text-sm text-muted-foreground", children: s.description })
2234
+ /* @__PURE__ */ jsx44("h2", { className: "text-base font-semibold", children: "Keyboard shortcuts" }),
2235
+ /* @__PURE__ */ jsx44("dl", { className: "mt-4 grid grid-cols-[auto_1fr] items-center gap-x-4 gap-y-2.5", children: SHORTCUTS.map((s) => /* @__PURE__ */ jsxs28("div", { className: "col-span-2 grid grid-cols-subgrid items-center", children: [
2236
+ /* @__PURE__ */ jsx44("dt", { className: "justify-self-start", children: /* @__PURE__ */ jsx44(Keys, { keys: s.keys }) }),
2237
+ /* @__PURE__ */ jsx44("dd", { className: "text-sm text-muted-foreground", children: s.description })
2448
2238
  ] }, s.keys)) }),
2449
- /* @__PURE__ */ jsx43("form", { method: "dialog", className: "mt-6", children: /* @__PURE__ */ jsx43(Button, { type: "submit", variant: "outline", size: "sm", children: "Close" }) })
2239
+ /* @__PURE__ */ jsx44("form", { method: "dialog", className: "mt-6", children: /* @__PURE__ */ jsx44(Button, { type: "submit", variant: "outline", size: "sm", children: "Close" }) })
2450
2240
  ]
2451
2241
  }
2452
2242
  );
@@ -2649,7 +2439,7 @@ function failuresFirst(report) {
2649
2439
 
2650
2440
  // src/interactive/ReportFilters.tsx
2651
2441
  import { useState as useState3 } from "react";
2652
- import { jsx as jsx44, jsxs as jsxs28 } from "react/jsx-runtime";
2442
+ import { jsx as jsx45, jsxs as jsxs29 } from "react/jsx-runtime";
2653
2443
  var TAG_LIMIT = 12;
2654
2444
  function ReportFilters({
2655
2445
  statuses,
@@ -2663,10 +2453,10 @@ function ReportFilters({
2663
2453
  if (statuses.length === 0 && tags.length === 0) return null;
2664
2454
  const tagsCollapsed = tags.length > TAG_LIMIT && !showAllTags;
2665
2455
  const visibleTags = tagsCollapsed ? tags.slice(0, TAG_LIMIT) : tags;
2666
- return /* @__PURE__ */ jsxs28("div", { className: "flex flex-col gap-2", children: [
2667
- statuses.length > 0 ? /* @__PURE__ */ jsx44("div", { role: "group", "aria-label": "Filter by status", className: "flex flex-wrap gap-1", children: statuses.map((opt) => {
2456
+ return /* @__PURE__ */ jsxs29("div", { className: "flex flex-col gap-2", children: [
2457
+ statuses.length > 0 ? /* @__PURE__ */ jsx45("div", { role: "group", "aria-label": "Filter by status", className: "flex flex-wrap gap-1", children: statuses.map((opt) => {
2668
2458
  const active = status === opt.key;
2669
- return /* @__PURE__ */ jsxs28(
2459
+ return /* @__PURE__ */ jsxs29(
2670
2460
  "button",
2671
2461
  {
2672
2462
  type: "button",
@@ -2678,23 +2468,23 @@ function ReportFilters({
2678
2468
  ),
2679
2469
  children: [
2680
2470
  opt.label,
2681
- /* @__PURE__ */ jsx44("span", { className: "ml-1 font-mono opacity-70", children: opt.count })
2471
+ /* @__PURE__ */ jsx45("span", { className: "ml-1 font-mono opacity-70", children: opt.count })
2682
2472
  ]
2683
2473
  },
2684
2474
  opt.key
2685
2475
  );
2686
2476
  }) }) : null,
2687
- tags.length > 0 ? /* @__PURE__ */ jsxs28("ul", { "aria-label": "Filter by tag", className: "flex flex-wrap items-center gap-1.5", children: [
2477
+ tags.length > 0 ? /* @__PURE__ */ jsxs29("ul", { "aria-label": "Filter by tag", className: "flex flex-wrap items-center gap-1.5", children: [
2688
2478
  visibleTags.map((tag) => {
2689
2479
  const active = activeTags.includes(tag);
2690
- return /* @__PURE__ */ jsx44("li", { children: /* @__PURE__ */ jsx44(
2480
+ return /* @__PURE__ */ jsx45("li", { children: /* @__PURE__ */ jsx45(
2691
2481
  "button",
2692
2482
  {
2693
2483
  type: "button",
2694
2484
  "aria-pressed": active,
2695
2485
  onClick: () => onToggleTag(tag),
2696
2486
  className: "cursor-pointer",
2697
- children: /* @__PURE__ */ jsx44(
2487
+ children: /* @__PURE__ */ jsx45(
2698
2488
  Badge,
2699
2489
  {
2700
2490
  variant: "tag",
@@ -2707,7 +2497,7 @@ function ReportFilters({
2707
2497
  }
2708
2498
  ) }, tag);
2709
2499
  }),
2710
- tags.length > TAG_LIMIT ? /* @__PURE__ */ jsx44("li", { children: /* @__PURE__ */ jsx44(
2500
+ tags.length > TAG_LIMIT ? /* @__PURE__ */ jsx45("li", { children: /* @__PURE__ */ jsx45(
2711
2501
  "button",
2712
2502
  {
2713
2503
  type: "button",
@@ -2723,7 +2513,7 @@ function ReportFilters({
2723
2513
 
2724
2514
  // src/interactive/ReportToc.tsx
2725
2515
  import { useEffect as useEffect5, useMemo as useMemo2, useState as useState4 } from "react";
2726
- import { jsx as jsx45, jsxs as jsxs29 } from "react/jsx-runtime";
2516
+ import { jsx as jsx46, jsxs as jsxs30 } from "react/jsx-runtime";
2727
2517
  var DOT = {
2728
2518
  passed: "bg-pass",
2729
2519
  failed: "bg-fail",
@@ -2752,11 +2542,11 @@ function TocContent({ onNavigate }) {
2752
2542
  return () => observer.disconnect();
2753
2543
  }, [scenarioIds]);
2754
2544
  if (report.features.length === 0) return null;
2755
- return /* @__PURE__ */ jsx45("ul", { className: "flex list-none flex-col gap-4 pl-0", children: report.features.map((feature) => /* @__PURE__ */ jsxs29("li", { children: [
2756
- /* @__PURE__ */ jsx45("p", { className: "mb-1.5 font-semibold leading-snug text-foreground", children: feature.title }),
2757
- /* @__PURE__ */ jsx45("ul", { className: "flex list-none flex-col gap-0.5 pl-0", children: feature.scenarios.map((scenario) => {
2545
+ return /* @__PURE__ */ jsx46("ul", { className: "flex list-none flex-col gap-4 pl-0", children: report.features.map((feature) => /* @__PURE__ */ jsxs30("li", { children: [
2546
+ /* @__PURE__ */ jsx46("p", { className: "mb-1.5 font-semibold leading-snug text-foreground", children: feature.title }),
2547
+ /* @__PURE__ */ jsx46("ul", { className: "flex list-none flex-col gap-0.5 pl-0", children: feature.scenarios.map((scenario) => {
2758
2548
  const active = activeId === scenario.id;
2759
- return /* @__PURE__ */ jsx45("li", { children: /* @__PURE__ */ jsxs29(
2549
+ return /* @__PURE__ */ jsx46("li", { children: /* @__PURE__ */ jsxs30(
2760
2550
  "a",
2761
2551
  {
2762
2552
  href: `#${scenario.id}`,
@@ -2771,14 +2561,14 @@ function TocContent({ onNavigate }) {
2771
2561
  active ? "border-primary font-medium text-foreground" : "border-border text-muted-foreground"
2772
2562
  ),
2773
2563
  children: [
2774
- /* @__PURE__ */ jsx45(
2564
+ /* @__PURE__ */ jsx46(
2775
2565
  "span",
2776
2566
  {
2777
2567
  "aria-hidden": true,
2778
2568
  className: cn("mt-1 size-1.5 shrink-0 rounded-full", DOT[scenario.status])
2779
2569
  }
2780
2570
  ),
2781
- /* @__PURE__ */ jsx45("span", { className: "break-words", children: scenario.title })
2571
+ /* @__PURE__ */ jsx46("span", { className: "break-words", children: scenario.title })
2782
2572
  ]
2783
2573
  }
2784
2574
  ) }, scenario.id);
@@ -2788,14 +2578,14 @@ function TocContent({ onNavigate }) {
2788
2578
  function ReportToc() {
2789
2579
  const report = useReport();
2790
2580
  if (report.features.length === 0) return null;
2791
- return /* @__PURE__ */ jsxs29(
2581
+ return /* @__PURE__ */ jsxs30(
2792
2582
  "nav",
2793
2583
  {
2794
2584
  "aria-label": "Table of contents",
2795
2585
  className: "sticky top-4 hidden h-fit w-64 shrink-0 self-start text-xs lg:block",
2796
2586
  children: [
2797
- /* @__PURE__ */ jsx45("p", { className: "mb-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Contents" }),
2798
- /* @__PURE__ */ jsx45(TocContent, {})
2587
+ /* @__PURE__ */ jsx46("p", { className: "mb-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Contents" }),
2588
+ /* @__PURE__ */ jsx46(TocContent, {})
2799
2589
  ]
2800
2590
  }
2801
2591
  );
@@ -2805,31 +2595,31 @@ function ReportToc() {
2805
2595
  import { useState as useState5 } from "react";
2806
2596
  import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
2807
2597
  import { ListIcon, XIcon } from "lucide-react";
2808
- import { jsx as jsx46, jsxs as jsxs30 } from "react/jsx-runtime";
2598
+ import { jsx as jsx47, jsxs as jsxs31 } from "react/jsx-runtime";
2809
2599
  function ReportTocDrawer() {
2810
2600
  const report = useReport();
2811
2601
  const [open, setOpen] = useState5(false);
2812
2602
  if (report.features.length === 0) return null;
2813
- return /* @__PURE__ */ jsxs30(DialogPrimitive.Root, { open, onOpenChange: setOpen, children: [
2814
- /* @__PURE__ */ jsxs30(DialogPrimitive.Trigger, { className: "flex cursor-pointer items-center gap-1 rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground lg:hidden [&_svg]:size-3.5", children: [
2815
- /* @__PURE__ */ jsx46(ListIcon, { "aria-hidden": true }),
2603
+ return /* @__PURE__ */ jsxs31(DialogPrimitive.Root, { open, onOpenChange: setOpen, children: [
2604
+ /* @__PURE__ */ jsxs31(DialogPrimitive.Trigger, { className: "flex cursor-pointer items-center gap-1 rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground lg:hidden [&_svg]:size-3.5", children: [
2605
+ /* @__PURE__ */ jsx47(ListIcon, { "aria-hidden": true }),
2816
2606
  " Contents"
2817
2607
  ] }),
2818
- /* @__PURE__ */ jsxs30(DialogPrimitive.Portal, { children: [
2819
- /* @__PURE__ */ jsx46(DialogPrimitive.Backdrop, { className: "fixed inset-0 z-50 bg-black/50 transition-opacity duration-200 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0" }),
2820
- /* @__PURE__ */ jsxs30(DialogPrimitive.Popup, { className: "fixed inset-y-0 left-0 z-50 flex w-72 max-w-[80vw] flex-col gap-3 overflow-y-auto border-r border-border bg-background p-4 font-sans text-xs shadow-lg outline-none transition-transform duration-200 data-[ending-style]:-translate-x-full data-[starting-style]:-translate-x-full", children: [
2821
- /* @__PURE__ */ jsxs30("div", { className: "flex items-center justify-between", children: [
2822
- /* @__PURE__ */ jsx46(DialogPrimitive.Title, { className: "text-xs font-semibold tracking-wide text-muted-foreground uppercase", children: "Contents" }),
2823
- /* @__PURE__ */ jsx46(
2608
+ /* @__PURE__ */ jsxs31(DialogPrimitive.Portal, { children: [
2609
+ /* @__PURE__ */ jsx47(DialogPrimitive.Backdrop, { className: "fixed inset-0 z-50 bg-black/50 transition-opacity duration-200 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0" }),
2610
+ /* @__PURE__ */ jsxs31(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: [
2611
+ /* @__PURE__ */ jsxs31("div", { className: "flex items-center justify-between", children: [
2612
+ /* @__PURE__ */ jsx47(DialogPrimitive.Title, { className: "text-xs font-semibold tracking-wide text-muted-foreground uppercase", children: "Contents" }),
2613
+ /* @__PURE__ */ jsx47(
2824
2614
  DialogPrimitive.Close,
2825
2615
  {
2826
2616
  "aria-label": "Close contents",
2827
2617
  className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground [&_svg]:size-4",
2828
- children: /* @__PURE__ */ jsx46(XIcon, { "aria-hidden": true })
2618
+ children: /* @__PURE__ */ jsx47(XIcon, { "aria-hidden": true })
2829
2619
  }
2830
2620
  )
2831
2621
  ] }),
2832
- /* @__PURE__ */ jsx46(TocContent, { onNavigate: () => setOpen(false) })
2622
+ /* @__PURE__ */ jsx47(TocContent, { onNavigate: () => setOpen(false) })
2833
2623
  ] })
2834
2624
  ] })
2835
2625
  ] });
@@ -2912,14 +2702,14 @@ function useUrlState() {
2912
2702
  }
2913
2703
 
2914
2704
  // src/interactive/ReportInteractive.tsx
2915
- import { Fragment as Fragment13, jsx as jsx47, jsxs as jsxs31 } from "react/jsx-runtime";
2705
+ import { Fragment as Fragment13, jsx as jsx48, jsxs as jsxs32 } from "react/jsx-runtime";
2916
2706
  function ReportInteractive(props) {
2917
2707
  const { report, className, title, dataTheme } = props;
2918
2708
  const result = unwrapReport(report);
2919
2709
  if (!result.ok) {
2920
- return /* @__PURE__ */ jsx47(ReportErrorShell, { error: result.error, className, title, dataTheme });
2710
+ return /* @__PURE__ */ jsx48(ReportErrorShell, { error: result.error, className, title, dataTheme });
2921
2711
  }
2922
- return /* @__PURE__ */ jsx47(ReportInteractiveView, { ...props, report: result.data });
2712
+ return /* @__PURE__ */ jsx48(ReportInteractiveView, { ...props, report: result.data });
2923
2713
  }
2924
2714
  var THEME_OPTIONS = [
2925
2715
  { value: "light", label: "Light", Icon: Sun },
@@ -2927,7 +2717,7 @@ var THEME_OPTIONS = [
2927
2717
  { value: "dark", label: "Dark", Icon: Moon }
2928
2718
  ];
2929
2719
  function ThemeSegment({ pref, onPref }) {
2930
- return /* @__PURE__ */ jsx47(
2720
+ return /* @__PURE__ */ jsx48(
2931
2721
  ToggleGroup,
2932
2722
  {
2933
2723
  size: "sm",
@@ -2937,7 +2727,7 @@ function ThemeSegment({ pref, onPref }) {
2937
2727
  if (next) onPref(next);
2938
2728
  },
2939
2729
  "aria-label": "Color theme",
2940
- children: THEME_OPTIONS.map(({ value, label, Icon }) => /* @__PURE__ */ jsx47(ToggleGroupItem, { value, "aria-label": label, title: label, className: "px-2", children: /* @__PURE__ */ jsx47(Icon, {}) }, value))
2730
+ children: THEME_OPTIONS.map(({ value, label, Icon }) => /* @__PURE__ */ jsx48(ToggleGroupItem, { value, "aria-label": label, title: label, className: "px-2", children: /* @__PURE__ */ jsx48(Icon, {}) }, value))
2941
2731
  }
2942
2732
  );
2943
2733
  }
@@ -3014,7 +2804,7 @@ function ReportInteractiveView({
3014
2804
  const scenarioActions = useMemo3(
3015
2805
  () => ({
3016
2806
  copyLink: (s) => copy(scenarioPermalink(s), "Link copied"),
3017
- copyMarkdown: (s) => copy(scenarioToMarkdown2(s), "Markdown copied"),
2807
+ copyMarkdown: (s) => copy(scenarioToMarkdown(s), "Markdown copied"),
3018
2808
  copyPrompt: (s) => copy(scenarioToPrompt(s), "Prompt copied")
3019
2809
  }),
3020
2810
  [copy]
@@ -3068,13 +2858,13 @@ function ReportInteractiveView({
3068
2858
  const totalScenarios = report.summary.total;
3069
2859
  const matchedScenarios = filtered.summary.total;
3070
2860
  const showToc = !hideToc && totalScenarios > 3;
3071
- return /* @__PURE__ */ jsx47(CollapseProvider, { value: collapse.api, children: /* @__PURE__ */ jsx47(ScenarioActionsProvider, { value: scenarioActions, children: /* @__PURE__ */ jsx47(ScenarioHistoryProvider, { value: scenarioHistory ?? null, children: /* @__PURE__ */ jsx47(
2861
+ return /* @__PURE__ */ jsx48(CollapseProvider, { value: collapse.api, children: /* @__PURE__ */ jsx48(ScenarioActionsProvider, { value: scenarioActions, children: /* @__PURE__ */ jsx48(ScenarioHistoryProvider, { value: scenarioHistory ?? null, children: /* @__PURE__ */ jsx48(
3072
2862
  ReportRoot,
3073
2863
  {
3074
2864
  report: filtered,
3075
2865
  customRenderers,
3076
2866
  renderers,
3077
- children: /* @__PURE__ */ jsxs31(
2867
+ children: /* @__PURE__ */ jsxs32(
3078
2868
  "main",
3079
2869
  {
3080
2870
  className: cn("es-report", "es-report-interactive", className),
@@ -3083,8 +2873,8 @@ function ReportInteractiveView({
3083
2873
  "data-theme": dataTheme,
3084
2874
  "data-detail-level": detail,
3085
2875
  children: [
3086
- /* @__PURE__ */ jsxs31("header", { className: "es-report-header", children: [
3087
- /* @__PURE__ */ jsxs31(
2876
+ /* @__PURE__ */ jsxs32("header", { className: "es-report-header", children: [
2877
+ /* @__PURE__ */ jsxs32(
3088
2878
  "div",
3089
2879
  {
3090
2880
  "data-es-navbar": true,
@@ -3093,9 +2883,9 @@ function ReportInteractiveView({
3093
2883
  hideHeader ? "justify-end" : "justify-between"
3094
2884
  ),
3095
2885
  children: [
3096
- hideHeader ? null : /* @__PURE__ */ jsx47("h1", { className: "min-w-0", children: title ?? "Story Report" }),
3097
- /* @__PURE__ */ jsxs31("div", { className: "flex items-center gap-2", children: [
3098
- /* @__PURE__ */ jsx47(
2886
+ hideHeader ? null : /* @__PURE__ */ jsx48("h1", { className: "min-w-0", children: title ?? "Story Report" }),
2887
+ /* @__PURE__ */ jsxs32("div", { className: "flex items-center gap-2", children: [
2888
+ /* @__PURE__ */ jsx48(
3099
2889
  ReportSearch,
3100
2890
  {
3101
2891
  ref: searchRef,
@@ -3105,16 +2895,16 @@ function ReportInteractiveView({
3105
2895
  totalCount: totalScenarios
3106
2896
  }
3107
2897
  ),
3108
- /* @__PURE__ */ jsx47(ThemeSegment, { pref: themePref, onPref: setThemePref })
2898
+ /* @__PURE__ */ jsx48(ThemeSegment, { pref: themePref, onPref: setThemePref })
3109
2899
  ] })
3110
2900
  ]
3111
2901
  }
3112
2902
  ),
3113
- hideHeader ? null : /* @__PURE__ */ jsxs31(Fragment13, { children: [
3114
- /* @__PURE__ */ jsx47(ReportSummary, {}),
3115
- /* @__PURE__ */ jsx47(ReportMeta, {})
2903
+ hideHeader ? null : /* @__PURE__ */ jsxs32(Fragment13, { children: [
2904
+ /* @__PURE__ */ jsx48(ReportSummary, {}),
2905
+ /* @__PURE__ */ jsx48(ReportMeta, {})
3116
2906
  ] }),
3117
- /* @__PURE__ */ jsx47(
2907
+ /* @__PURE__ */ jsx48(
3118
2908
  ReportFreshness,
3119
2909
  {
3120
2910
  lastRunMs: reportLastRunMs(report),
@@ -3122,8 +2912,8 @@ function ReportInteractiveView({
3122
2912
  staleAfterDays
3123
2913
  }
3124
2914
  ),
3125
- /* @__PURE__ */ jsx47(ReportLastRunDelta, { history: scenarioHistory, report }),
3126
- /* @__PURE__ */ jsx47(
2915
+ /* @__PURE__ */ jsx48(ReportLastRunDelta, { history: scenarioHistory, report }),
2916
+ /* @__PURE__ */ jsx48(
3127
2917
  ReportFilters,
3128
2918
  {
3129
2919
  statuses: statusOptions,
@@ -3134,14 +2924,14 @@ function ReportInteractiveView({
3134
2924
  onToggleTag: toggleTag
3135
2925
  }
3136
2926
  ),
3137
- /* @__PURE__ */ jsxs31("div", { className: "flex flex-wrap items-center gap-x-6 gap-y-2 border-t border-border/60 pt-3", children: [
3138
- showToc ? /* @__PURE__ */ jsx47(ReportTocDrawer, {}) : null,
3139
- /* @__PURE__ */ jsxs31("div", { className: "flex items-center gap-2", children: [
3140
- /* @__PURE__ */ jsx47(Switch, { id: expandId, checked: expandedAll, onCheckedChange: setExpanded }),
3141
- /* @__PURE__ */ jsx47("label", { htmlFor: expandId, className: "cursor-pointer text-sm text-muted-foreground select-none", children: "Expand all" })
2927
+ /* @__PURE__ */ jsxs32("div", { className: "flex flex-wrap items-center gap-x-6 gap-y-2 border-t border-border/60 pt-3", children: [
2928
+ showToc ? /* @__PURE__ */ jsx48(ReportTocDrawer, {}) : null,
2929
+ /* @__PURE__ */ jsxs32("div", { className: "flex items-center gap-2", children: [
2930
+ /* @__PURE__ */ jsx48(Switch, { id: expandId, checked: expandedAll, onCheckedChange: setExpanded }),
2931
+ /* @__PURE__ */ jsx48("label", { htmlFor: expandId, className: "cursor-pointer text-sm text-muted-foreground select-none", children: "Expand all" })
3142
2932
  ] }),
3143
- /* @__PURE__ */ jsxs31("div", { className: "flex items-center gap-2", children: [
3144
- /* @__PURE__ */ jsx47(
2933
+ /* @__PURE__ */ jsxs32("div", { className: "flex items-center gap-2", children: [
2934
+ /* @__PURE__ */ jsx48(
3145
2935
  Switch,
3146
2936
  {
3147
2937
  id: docsId,
@@ -3149,16 +2939,16 @@ function ReportInteractiveView({
3149
2939
  onCheckedChange: (checked) => setDetail(checked ? "full" : "minimal")
3150
2940
  }
3151
2941
  ),
3152
- /* @__PURE__ */ jsx47("label", { htmlFor: docsId, className: "cursor-pointer text-sm text-muted-foreground select-none", children: "Show documentation" })
2942
+ /* @__PURE__ */ jsx48("label", { htmlFor: docsId, className: "cursor-pointer text-sm text-muted-foreground select-none", children: "Show documentation" })
3153
2943
  ] })
3154
2944
  ] })
3155
2945
  ] }),
3156
- /* @__PURE__ */ jsx47(ReportFailureBanner, { failures }),
3157
- hasContent ? showToc ? /* @__PURE__ */ jsxs31("div", { className: "flex gap-6", children: [
3158
- /* @__PURE__ */ jsx47(ReportToc, {}),
3159
- /* @__PURE__ */ jsx47("div", { className: "flex min-w-0 flex-1 flex-col gap-4", children: /* @__PURE__ */ jsx47(ReportFeatureList, {}) })
3160
- ] }) : /* @__PURE__ */ jsx47("div", { className: "flex flex-col gap-4", children: /* @__PURE__ */ jsx47(ReportFeatureList, {}) }) : /* @__PURE__ */ jsx47(ReportEmpty, { message: query ? "No scenarios match the search." : void 0 }),
3161
- /* @__PURE__ */ jsx47(
2946
+ /* @__PURE__ */ jsx48(ReportFailureBanner, { failures }),
2947
+ hasContent ? showToc ? /* @__PURE__ */ jsxs32("div", { className: "flex gap-6", children: [
2948
+ /* @__PURE__ */ jsx48(ReportToc, {}),
2949
+ /* @__PURE__ */ jsx48("div", { className: "flex min-w-0 flex-1 flex-col gap-4", children: /* @__PURE__ */ jsx48(ReportFeatureList, {}) })
2950
+ ] }) : /* @__PURE__ */ jsx48("div", { className: "flex flex-col gap-4", children: /* @__PURE__ */ jsx48(ReportFeatureList, {}) }) : /* @__PURE__ */ jsx48(ReportEmpty, { message: query ? "No scenarios match the search." : void 0 }),
2951
+ /* @__PURE__ */ jsx48(
3162
2952
  "button",
3163
2953
  {
3164
2954
  type: "button",
@@ -3169,8 +2959,8 @@ function ReportInteractiveView({
3169
2959
  children: "?"
3170
2960
  }
3171
2961
  ),
3172
- /* @__PURE__ */ jsx47(ReportShortcutsHelp, { open: helpOpen, onClose: () => setHelpOpen(false) }),
3173
- toast ? /* @__PURE__ */ jsx47(
2962
+ /* @__PURE__ */ jsx48(ReportShortcutsHelp, { open: helpOpen, onClose: () => setHelpOpen(false) }),
2963
+ toast ? /* @__PURE__ */ jsx48(
3174
2964
  "div",
3175
2965
  {
3176
2966
  role: "status",
@@ -3194,7 +2984,7 @@ import { createElement, useEffect as useEffect9, useState as useState10 } from "
3194
2984
 
3195
2985
  // src/components/doc/MermaidView.tsx
3196
2986
  import { useEffect as useEffect8, useRef as useRef6, useState as useState9 } from "react";
3197
- import { Fragment as Fragment14, jsx as jsx48, jsxs as jsxs32 } from "react/jsx-runtime";
2987
+ import { Fragment as Fragment14, jsx as jsx49, jsxs as jsxs33 } from "react/jsx-runtime";
3198
2988
  function prefersDark(el) {
3199
2989
  const themed = el?.closest("[data-theme]");
3200
2990
  if (themed?.dataset.theme === "dark") return true;
@@ -3248,9 +3038,9 @@ function MermaidView({ entry, load }) {
3248
3038
  };
3249
3039
  }, [entry.code, load]);
3250
3040
  if (svg) {
3251
- return /* @__PURE__ */ jsxs32("figure", { ref, className: "my-3", children: [
3252
- entry.title ? /* @__PURE__ */ jsx48("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
3253
- /* @__PURE__ */ jsx48(
3041
+ return /* @__PURE__ */ jsxs33("figure", { ref, className: "my-3", children: [
3042
+ entry.title ? /* @__PURE__ */ jsx49("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
3043
+ /* @__PURE__ */ jsx49(
3254
3044
  "div",
3255
3045
  {
3256
3046
  role: "img",
@@ -3261,8 +3051,8 @@ function MermaidView({ entry, load }) {
3261
3051
  )
3262
3052
  ] });
3263
3053
  }
3264
- return /* @__PURE__ */ jsxs32(Fragment14, { children: [
3265
- error ? /* @__PURE__ */ jsxs32(
3054
+ return /* @__PURE__ */ jsxs33(Fragment14, { children: [
3055
+ error ? /* @__PURE__ */ jsxs33(
3266
3056
  "p",
3267
3057
  {
3268
3058
  role: "alert",
@@ -3274,12 +3064,12 @@ function MermaidView({ entry, load }) {
3274
3064
  ]
3275
3065
  }
3276
3066
  ) : null,
3277
- /* @__PURE__ */ jsx48(MermaidSource, { entry, ref })
3067
+ /* @__PURE__ */ jsx49(MermaidSource, { entry, ref })
3278
3068
  ] });
3279
3069
  }
3280
3070
 
3281
3071
  // src/interactive/island-renderers.tsx
3282
- import { jsx as jsx49 } from "react/jsx-runtime";
3072
+ import { jsx as jsx50 } from "react/jsx-runtime";
3283
3073
  var HLJS_ESM = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/es/highlight.min.js";
3284
3074
  var MERMAID_ESM = "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
3285
3075
  function cachedCdnModule(url) {
@@ -3313,13 +3103,13 @@ function CdnHighlightedCode({ entry }) {
3313
3103
  cancelled = true;
3314
3104
  };
3315
3105
  }, [entry.content, entry.lang]);
3316
- return /* @__PURE__ */ jsx49(CodeFigure, { label: entry.label, children: html !== null ? /* @__PURE__ */ jsx49(
3106
+ return /* @__PURE__ */ jsx50(CodeFigure, { label: entry.label, children: html !== null ? /* @__PURE__ */ jsx50(
3317
3107
  "code",
3318
3108
  {
3319
3109
  className: cn("hljs font-mono text-xs", entry.lang && `language-${entry.lang}`),
3320
3110
  dangerouslySetInnerHTML: { __html: html }
3321
3111
  }
3322
- ) : /* @__PURE__ */ jsx49("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
3112
+ ) : /* @__PURE__ */ jsx50("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
3323
3113
  }
3324
3114
  function buildIslandRenderers(opts) {
3325
3115
  const renderers = {};
@@ -3333,7 +3123,7 @@ function buildIslandRenderers(opts) {
3333
3123
  }
3334
3124
 
3335
3125
  // src/interactive/ReportInteractiveIsland.tsx
3336
- import { jsx as jsx50 } from "react/jsx-runtime";
3126
+ import { jsx as jsx51 } from "react/jsx-runtime";
3337
3127
  function ReportInteractiveIsland({
3338
3128
  report,
3339
3129
  title,
@@ -3347,7 +3137,7 @@ function ReportInteractiveIsland({
3347
3137
  () => buildIslandRenderers({ syntaxHighlighting, mermaid }),
3348
3138
  [syntaxHighlighting, mermaid]
3349
3139
  );
3350
- return /* @__PURE__ */ jsx50(
3140
+ return /* @__PURE__ */ jsx51(
3351
3141
  ReportInteractive,
3352
3142
  {
3353
3143
  report,