@flanksource/clicky-ui 0.3.52 → 0.3.53

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.
Files changed (57) hide show
  1. package/dist/components/clipboard.cjs +76 -11
  2. package/dist/components/clipboard.cjs.map +1 -1
  3. package/dist/components/clipboard.d.ts +37 -6
  4. package/dist/components/clipboard.d.ts.map +1 -1
  5. package/dist/components/clipboard.js +75 -12
  6. package/dist/components/clipboard.js.map +1 -1
  7. package/dist/components.cjs +2 -0
  8. package/dist/components.d.ts +1 -1
  9. package/dist/components.d.ts.map +1 -1
  10. package/dist/components.js +2 -2
  11. package/dist/data/MatrixTreeTable.cjs +94 -0
  12. package/dist/data/MatrixTreeTable.cjs.map +1 -0
  13. package/dist/data/MatrixTreeTable.d.ts +40 -0
  14. package/dist/data/MatrixTreeTable.d.ts.map +1 -0
  15. package/dist/data/MatrixTreeTable.js +92 -0
  16. package/dist/data/MatrixTreeTable.js.map +1 -0
  17. package/dist/data/TreeDataTable.cjs +52 -0
  18. package/dist/data/TreeDataTable.cjs.map +1 -0
  19. package/dist/data/TreeDataTable.d.ts +17 -0
  20. package/dist/data/TreeDataTable.d.ts.map +1 -0
  21. package/dist/data/TreeDataTable.js +52 -0
  22. package/dist/data/TreeDataTable.js.map +1 -0
  23. package/dist/data/test-runner/MatrixMarks.cjs +212 -0
  24. package/dist/data/test-runner/MatrixMarks.cjs.map +1 -0
  25. package/dist/data/test-runner/MatrixMarks.d.ts +76 -0
  26. package/dist/data/test-runner/MatrixMarks.d.ts.map +1 -0
  27. package/dist/data/test-runner/MatrixMarks.js +205 -0
  28. package/dist/data/test-runner/MatrixMarks.js.map +1 -0
  29. package/dist/data/test-runner/TestDetailPanel.cjs +3 -0
  30. package/dist/data/test-runner/TestDetailPanel.cjs.map +1 -1
  31. package/dist/data/test-runner/TestDetailPanel.d.ts.map +1 -1
  32. package/dist/data/test-runner/TestDetailPanel.js +3 -0
  33. package/dist/data/test-runner/TestDetailPanel.js.map +1 -1
  34. package/dist/data/test-runner/index.d.ts +2 -0
  35. package/dist/data/test-runner/index.d.ts.map +1 -1
  36. package/dist/data/test-runner/matrix-marks.cjs +119 -0
  37. package/dist/data/test-runner/matrix-marks.cjs.map +1 -0
  38. package/dist/data/test-runner/matrix-marks.d.ts +154 -0
  39. package/dist/data/test-runner/matrix-marks.d.ts.map +1 -0
  40. package/dist/data/test-runner/matrix-marks.js +113 -0
  41. package/dist/data/test-runner/matrix-marks.js.map +1 -0
  42. package/dist/data/version-info.cjs +3 -3
  43. package/dist/data/version-info.js +3 -3
  44. package/dist/data.cjs +27 -0
  45. package/dist/data.d.ts +3 -0
  46. package/dist/data.d.ts.map +1 -1
  47. package/dist/data.js +6 -1
  48. package/dist/index.cjs +29 -0
  49. package/dist/index.js +7 -2
  50. package/dist/lib/tree-rows.cjs +63 -0
  51. package/dist/lib/tree-rows.cjs.map +1 -0
  52. package/dist/lib/tree-rows.d.ts +36 -0
  53. package/dist/lib/tree-rows.d.ts.map +1 -0
  54. package/dist/lib/tree-rows.js +60 -0
  55. package/dist/lib/tree-rows.js.map +1 -0
  56. package/dist/styles.css +1 -1
  57. package/package.json +1 -1
@@ -0,0 +1,205 @@
1
+ import { cn } from "../../lib/utils.js";
2
+ import { UiServer } from "../../icons/components/UiServer.js";
3
+ import { UiClock } from "../../icons/components/UiClock.js";
4
+ import { ProgressBar } from "../ProgressBar.js";
5
+ import { MATRIX_MARKS, MATRIX_ORDER, MATRIX_PLAN_BAR, formatMatrixDuration, matrixBarClass } from "./matrix-marks.js";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ // src/data/test-runner/MatrixMarks.tsx
8
+ function MatrixMark({ mark, label, className, static: isStatic = false }) {
9
+ const token = MATRIX_MARKS[mark];
10
+ const Glyph = token.icon;
11
+ return /* @__PURE__ */ jsx(Glyph, {
12
+ ...label === "" ? {} : { title: label ?? token.label },
13
+ className: cn("size-3.5 shrink-0", token.tone, token.spin && !isStatic && "animate-spin", className),
14
+ "data-mark": mark
15
+ });
16
+ }
17
+ /** Outcome counts as glyph + number, in matrix order. */
18
+ function MatrixMix({ counts, unit, always = [], static: isStatic = false }) {
19
+ return /* @__PURE__ */ jsx("span", {
20
+ role: "group",
21
+ "aria-label": `${unit} by outcome`,
22
+ className: "flex shrink-0 items-center gap-2 text-xs tabular-nums",
23
+ "data-mix": unit,
24
+ children: MATRIX_ORDER.filter((key) => (counts[key] ?? 0) > 0 || always.includes(key)).map((key) => {
25
+ const text = `${counts[key] ?? 0} ${MATRIX_MARKS[key].label} ${unit}`;
26
+ return /* @__PURE__ */ jsxs("span", {
27
+ role: "img",
28
+ "aria-label": text,
29
+ title: text,
30
+ className: cn("inline-flex items-center gap-0.5", MATRIX_MARKS[key].tone),
31
+ "data-mix-outcome": key,
32
+ children: [/* @__PURE__ */ jsx(MatrixMark, {
33
+ mark: key,
34
+ label: "",
35
+ static: isStatic
36
+ }), counts[key] ?? 0]
37
+ }, key);
38
+ })
39
+ });
40
+ }
41
+ function MatrixElapsed({ ms, running, clock = false }) {
42
+ return /* @__PURE__ */ jsxs("span", {
43
+ className: cn("inline-flex shrink-0 items-center gap-1 whitespace-nowrap text-[11px] tabular-nums", running ? "text-blue-700 dark:text-blue-300" : "text-muted-foreground"),
44
+ title: running ? "elapsed so far" : "total duration",
45
+ "data-duration": "",
46
+ children: [clock && /* @__PURE__ */ jsx(UiClock, {
47
+ className: "size-3",
48
+ "aria-hidden": "true"
49
+ }), formatMatrixDuration(ms)]
50
+ });
51
+ }
52
+ /**
53
+ * A plan with no run in an environment: pending while queued, error when it
54
+ * never started, hollow when not part of that run.
55
+ */
56
+ function EmptyMatrixCell({ outcome, detail, title, bare = false }) {
57
+ const neverStarted = detail === "never started";
58
+ if (!neverStarted && outcome !== "queued" && outcome !== "absent") throw new Error(`unexpected empty matrix cell ${outcome}: ${title}`);
59
+ const mark = neverStarted ? "error" : outcome === "queued" ? "pending" : "absent";
60
+ const label = neverStarted ? "never started" : MATRIX_MARKS[mark].label;
61
+ return /* @__PURE__ */ jsxs("span", {
62
+ title,
63
+ className: "flex h-5 min-w-0 items-center gap-1.5 text-xs",
64
+ "data-empty-cell": mark,
65
+ children: [/* @__PURE__ */ jsx(MatrixMark, {
66
+ mark,
67
+ label
68
+ }), !bare && /* @__PURE__ */ jsx("span", {
69
+ className: MATRIX_MARKS[mark].tone,
70
+ children: label
71
+ })]
72
+ });
73
+ }
74
+ function MatrixLegend({ static: isStatic = false }) {
75
+ return /* @__PURE__ */ jsxs("div", {
76
+ className: "flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground",
77
+ "data-batch-legend": "",
78
+ children: [MATRIX_ORDER.map((key) => {
79
+ const token = MATRIX_MARKS[key];
80
+ return /* @__PURE__ */ jsxs("span", {
81
+ className: "inline-flex items-center gap-1",
82
+ "data-legend-mark": key,
83
+ children: [/* @__PURE__ */ jsx(MatrixMark, {
84
+ mark: key,
85
+ label: "",
86
+ static: isStatic
87
+ }), token.legend ?? token.label]
88
+ }, key);
89
+ }), /* @__PURE__ */ jsxs("span", {
90
+ className: "inline-flex items-center gap-1",
91
+ children: [/* @__PURE__ */ jsx(UiClock, {
92
+ className: "size-3",
93
+ "aria-hidden": "true"
94
+ }), "elapsed · total duration"]
95
+ })]
96
+ });
97
+ }
98
+ /** A context name, always in the same chip, so an environment never reads as a plan or a status. */
99
+ function EnvChip({ context, className }) {
100
+ return /* @__PURE__ */ jsxs("span", {
101
+ className: cn("inline-flex items-center gap-1 rounded border border-border bg-muted/50 px-1.5 py-0.5 font-mono text-[11px] text-foreground", className),
102
+ "data-env-chip": context,
103
+ children: [/* @__PURE__ */ jsx(UiServer, {
104
+ className: "size-3 text-muted-foreground",
105
+ "aria-hidden": "true"
106
+ }), context]
107
+ });
108
+ }
109
+ var HEADER_GRID = "grid min-w-48 grid-rows-[auto_auto_auto] gap-1.5";
110
+ var HEADER_ROW = "flex h-5 min-w-0 items-center gap-2";
111
+ /**
112
+ * One environment's column head in three fixed rows: its chip, the plans'
113
+ * progress with the elapsed time, and the plan verdict naming its units.
114
+ */
115
+ function MatrixEnvHeader({ context, counts, durationMs, running, renderChip, aside, static: isStatic = false }) {
116
+ const done = counts.passed + counts.warned + counts.failed + counts.error;
117
+ const chip = /* @__PURE__ */ jsx(EnvChip, { context });
118
+ return /* @__PURE__ */ jsxs("div", {
119
+ className: HEADER_GRID,
120
+ "data-env-header": context,
121
+ children: [
122
+ /* @__PURE__ */ jsxs("div", {
123
+ className: HEADER_ROW,
124
+ children: [renderChip && !isStatic ? renderChip(chip) : chip, aside]
125
+ }),
126
+ /* @__PURE__ */ jsxs("div", {
127
+ className: HEADER_ROW,
128
+ title: `${done} of ${counts.total} plans finished`,
129
+ "data-env-progress": "",
130
+ children: [
131
+ /* @__PURE__ */ jsx("span", {
132
+ className: "min-w-0 flex-1",
133
+ children: /* @__PURE__ */ jsx(ProgressBar, {
134
+ total: counts.total,
135
+ height: "h-1.5",
136
+ segments: MATRIX_PLAN_BAR.map((key) => ({
137
+ count: counts[key],
138
+ color: matrixBarClass(key, isStatic),
139
+ label: `${MATRIX_MARKS[key].label} plans`
140
+ }))
141
+ })
142
+ }),
143
+ /* @__PURE__ */ jsxs("span", {
144
+ className: "shrink-0 whitespace-nowrap text-[11px] tabular-nums text-foreground",
145
+ children: [
146
+ done,
147
+ "/",
148
+ counts.total,
149
+ " plans"
150
+ ]
151
+ }),
152
+ /* @__PURE__ */ jsx(MatrixElapsed, {
153
+ ms: durationMs,
154
+ running,
155
+ clock: true
156
+ })
157
+ ]
158
+ }),
159
+ /* @__PURE__ */ jsx("div", {
160
+ className: HEADER_ROW,
161
+ "data-env-verdict": "",
162
+ children: /* @__PURE__ */ jsx(MatrixMix, {
163
+ counts: {
164
+ passed: counts.passed,
165
+ warned: counts.warned,
166
+ running: counts.running,
167
+ pending: counts.queued,
168
+ failed: counts.failed,
169
+ error: counts.error
170
+ },
171
+ unit: "plans",
172
+ always: ["failed"],
173
+ static: isStatic
174
+ })
175
+ })
176
+ ]
177
+ });
178
+ }
179
+ /** An environment whose member run did not load, in the same three rows as MatrixEnvHeader. */
180
+ function MatrixUnavailableEnvHeader({ context }) {
181
+ return /* @__PURE__ */ jsxs("div", {
182
+ className: HEADER_GRID,
183
+ "data-env-header": context,
184
+ children: [
185
+ /* @__PURE__ */ jsx("div", {
186
+ className: HEADER_ROW,
187
+ children: /* @__PURE__ */ jsx(EnvChip, { context })
188
+ }),
189
+ /* @__PURE__ */ jsxs("div", {
190
+ className: `${HEADER_ROW} text-[11px] font-medium ${MATRIX_MARKS.error.tone}`,
191
+ children: [/* @__PURE__ */ jsx(MatrixMark, {
192
+ mark: "error",
193
+ label: ""
194
+ }), "did not load"]
195
+ }),
196
+ /* @__PURE__ */ jsx("div", {
197
+ className: HEADER_ROW,
198
+ "aria-hidden": "true"
199
+ })
200
+ ]
201
+ });
202
+ }
203
+ export { EmptyMatrixCell, EnvChip, MatrixElapsed, MatrixEnvHeader, MatrixLegend, MatrixMark, MatrixMix, MatrixUnavailableEnvHeader };
204
+
205
+ //# sourceMappingURL=MatrixMarks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MatrixMarks.js","names":[],"sources":["../../../src/data/test-runner/MatrixMarks.tsx"],"sourcesContent":["// Renders the matrix-marks.ts vocabulary: one outcome's glyph, outcome mixes,\n// durations, empty cells, the legend, environment chips and the environment\n// column header. Props-only, so a live web matrix and a static print template\n// draw the same marks; `static` drops animation and links for print.\n\nimport type { ReactNode } from \"react\";\nimport { UiClock, UiServer } from \"../../icons\";\nimport { cn } from \"../../lib/utils\";\nimport { ProgressBar } from \"../ProgressBar\";\nimport {\n MATRIX_MARKS,\n MATRIX_ORDER,\n MATRIX_PLAN_BAR,\n formatMatrixDuration,\n matrixBarClass,\n type MatrixCounts,\n type MatrixEnvCounts,\n type MatrixMarkKey,\n type MatrixMarkToken,\n} from \"./matrix-marks\";\n\nexport type MatrixMarkProps = {\n mark: MatrixMarkKey;\n /** Hover title; \"\" leaves the glyph untitled where the surrounding element already names it. */\n label?: string;\n className?: string;\n /** Print: never spins. */\n static?: boolean;\n};\n\nexport function MatrixMark({ mark, label, className, static: isStatic = false }: MatrixMarkProps) {\n const token: MatrixMarkToken = MATRIX_MARKS[mark];\n const Glyph = token.icon;\n return (\n <Glyph\n {...(label === \"\" ? {} : { title: label ?? token.label })}\n className={cn(\"size-3.5 shrink-0\", token.tone, token.spin && !isStatic && \"animate-spin\", className)}\n data-mark={mark}\n />\n );\n}\n\nexport type MatrixMixProps = {\n counts: MatrixCounts;\n unit: \"steps\" | \"plans\";\n /** Outcomes drawn even at zero; every other zero count is dropped. */\n always?: readonly MatrixMarkKey[];\n static?: boolean;\n};\n\n/** Outcome counts as glyph + number, in matrix order. */\nexport function MatrixMix({ counts, unit, always = [], static: isStatic = false }: MatrixMixProps) {\n return (\n <span role=\"group\" aria-label={`${unit} by outcome`} className=\"flex shrink-0 items-center gap-2 text-xs tabular-nums\" data-mix={unit}>\n {MATRIX_ORDER.filter((key) => (counts[key] ?? 0) > 0 || always.includes(key)).map((key) => {\n const text = `${counts[key] ?? 0} ${MATRIX_MARKS[key].label} ${unit}`;\n return (\n <span key={key} role=\"img\" aria-label={text} title={text} className={cn(\"inline-flex items-center gap-0.5\", MATRIX_MARKS[key].tone)} data-mix-outcome={key}>\n <MatrixMark mark={key} label=\"\" static={isStatic} />\n {counts[key] ?? 0}\n </span>\n );\n })}\n </span>\n );\n}\n\nexport type MatrixElapsedProps = {\n ms: number;\n /** Still counting: drawn blue and titled \"elapsed so far\". */\n running: boolean;\n /** Prefix a clock glyph. */\n clock?: boolean;\n};\n\nexport function MatrixElapsed({ ms, running, clock = false }: MatrixElapsedProps) {\n return (\n <span\n className={cn(\"inline-flex shrink-0 items-center gap-1 whitespace-nowrap text-[11px] tabular-nums\", running ? \"text-blue-700 dark:text-blue-300\" : \"text-muted-foreground\")}\n title={running ? \"elapsed so far\" : \"total duration\"}\n data-duration=\"\"\n >\n {clock && <UiClock className=\"size-3\" aria-hidden=\"true\" />}\n {formatMatrixDuration(ms)}\n </span>\n );\n}\n\nexport type EmptyMatrixCellProps = {\n /** The cell outcome: \"queued\", \"absent\", or \"error\" with detail \"never started\". Anything else throws. */\n outcome: string;\n /** Why a cell has no run: \"never started\". */\n detail?: string;\n /** Hover title of the cell. */\n title: string;\n /** The glyph alone (sub-step rows), its meaning on hover. */\n bare?: boolean;\n};\n\n/**\n * A plan with no run in an environment: pending while queued, error when it\n * never started, hollow when not part of that run.\n */\nexport function EmptyMatrixCell({ outcome, detail, title, bare = false }: EmptyMatrixCellProps) {\n const neverStarted = detail === \"never started\";\n if (!neverStarted && outcome !== \"queued\" && outcome !== \"absent\") throw new Error(`unexpected empty matrix cell ${outcome}: ${title}`);\n const mark: MatrixMarkKey = neverStarted ? \"error\" : outcome === \"queued\" ? \"pending\" : \"absent\";\n const label = neverStarted ? \"never started\" : MATRIX_MARKS[mark].label;\n return (\n <span title={title} className=\"flex h-5 min-w-0 items-center gap-1.5 text-xs\" data-empty-cell={mark}>\n <MatrixMark mark={mark} label={label} />\n {!bare && <span className={MATRIX_MARKS[mark].tone}>{label}</span>}\n </span>\n );\n}\n\nexport type MatrixLegendProps = { static?: boolean };\n\nexport function MatrixLegend({ static: isStatic = false }: MatrixLegendProps) {\n return (\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground\" data-batch-legend=\"\">\n {MATRIX_ORDER.map((key) => {\n const token: MatrixMarkToken = MATRIX_MARKS[key];\n return (\n <span key={key} className=\"inline-flex items-center gap-1\" data-legend-mark={key}>\n <MatrixMark mark={key} label=\"\" static={isStatic} />\n {token.legend ?? token.label}\n </span>\n );\n })}\n <span className=\"inline-flex items-center gap-1\">\n <UiClock className=\"size-3\" aria-hidden=\"true\" />\n elapsed · total duration\n </span>\n </div>\n );\n}\n\nexport type EnvChipProps = { context: string; className?: string };\n\n/** A context name, always in the same chip, so an environment never reads as a plan or a status. */\nexport function EnvChip({ context, className }: EnvChipProps) {\n return (\n <span\n className={cn(\"inline-flex items-center gap-1 rounded border border-border bg-muted/50 px-1.5 py-0.5 font-mono text-[11px] text-foreground\", className)}\n data-env-chip={context}\n >\n <UiServer className=\"size-3 text-muted-foreground\" aria-hidden=\"true\" />\n {context}\n </span>\n );\n}\n\nconst HEADER_GRID = \"grid min-w-48 grid-rows-[auto_auto_auto] gap-1.5\";\nconst HEADER_ROW = \"flex h-5 min-w-0 items-center gap-2\";\n\nexport type MatrixEnvHeaderProps = {\n context: string;\n counts: MatrixEnvCounts;\n durationMs: number;\n /** The environment's run is still running: its elapsed time is still counting. */\n running: boolean;\n /** Wraps the chip, e.g. in the host router's link to the member run. Never called when static. */\n renderChip?: (chip: ReactNode) => ReactNode;\n /** Drawn beside the chip, e.g. how often the member was retried. */\n aside?: ReactNode;\n static?: boolean;\n};\n\n/**\n * One environment's column head in three fixed rows: its chip, the plans'\n * progress with the elapsed time, and the plan verdict naming its units.\n */\nexport function MatrixEnvHeader({ context, counts, durationMs, running, renderChip, aside, static: isStatic = false }: MatrixEnvHeaderProps) {\n const done = counts.passed + counts.warned + counts.failed + counts.error;\n const chip = <EnvChip context={context} />;\n return (\n <div className={HEADER_GRID} data-env-header={context}>\n <div className={HEADER_ROW}>\n {renderChip && !isStatic ? renderChip(chip) : chip}\n {aside}\n </div>\n <div className={HEADER_ROW} title={`${done} of ${counts.total} plans finished`} data-env-progress=\"\">\n <span className=\"min-w-0 flex-1\">\n <ProgressBar\n total={counts.total}\n height=\"h-1.5\"\n segments={MATRIX_PLAN_BAR.map((key) => ({ count: counts[key], color: matrixBarClass(key, isStatic), label: `${MATRIX_MARKS[key].label} plans` }))}\n />\n </span>\n <span className=\"shrink-0 whitespace-nowrap text-[11px] tabular-nums text-foreground\">\n {done}/{counts.total} plans\n </span>\n <MatrixElapsed ms={durationMs} running={running} clock />\n </div>\n <div className={HEADER_ROW} data-env-verdict=\"\">\n <MatrixMix\n counts={{ passed: counts.passed, warned: counts.warned, running: counts.running, pending: counts.queued, failed: counts.failed, error: counts.error }}\n unit=\"plans\"\n always={[\"failed\"]}\n static={isStatic}\n />\n </div>\n </div>\n );\n}\n\nexport type MatrixUnavailableEnvHeaderProps = { context: string };\n\n/** An environment whose member run did not load, in the same three rows as MatrixEnvHeader. */\nexport function MatrixUnavailableEnvHeader({ context }: MatrixUnavailableEnvHeaderProps) {\n return (\n <div className={HEADER_GRID} data-env-header={context}>\n <div className={HEADER_ROW}>\n <EnvChip context={context} />\n </div>\n <div className={`${HEADER_ROW} text-[11px] font-medium ${MATRIX_MARKS.error.tone}`}>\n <MatrixMark mark=\"error\" label=\"\" />\n did not load\n </div>\n <div className={HEADER_ROW} aria-hidden=\"true\" />\n </div>\n );\n}\n"],"mappings":"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"}
@@ -7,6 +7,7 @@ const require_UiJson = require("../../icons/components/UiJson.cjs");
7
7
  const require_UiCopy = require("../../icons/components/UiCopy.cjs");
8
8
  const require_UiRefresh = require("../../icons/components/UiRefresh.cjs");
9
9
  const require_UiClock = require("../../icons/components/UiClock.cjs");
10
+ const require_UiHourglass = require("../../icons/components/UiHourglass.cjs");
10
11
  const require_Icon = require("../Icon.cjs");
11
12
  const require_button = require("../../components/button.cjs");
12
13
  const require_clipboard = require("../../components/clipboard.cjs");
@@ -79,11 +80,13 @@ function copyMenuItem(item, copy) {
79
80
  }
80
81
  var COPY_LABEL = {
81
82
  idle: "Copy details",
83
+ pending: "Copying…",
82
84
  copied: "Copied",
83
85
  error: "Copy failed"
84
86
  };
85
87
  var COPY_ICON = {
86
88
  idle: require_UiCopy.UiCopy,
89
+ pending: require_UiHourglass.UiHourglass,
87
90
  copied: require_UiCheck.UiCheck,
88
91
  error: require_UiWarningCircle.UiWarningCircle
89
92
  };
@@ -1 +1 @@
1
- {"version":3,"file":"TestDetailPanel.cjs","names":["useTestRunner","sum","TestFailureDetail","TestOutput","TestAttemptDetail","JsonView","UiCopy","UiCheck","UiWarningCircle","useCopyFlash","useMemo","UiJson","buildNodeJson","SplitButton","buildNodeBrief","outcomeFor","OutcomeIcon","Icon","UiClass","ToolIcon","toolKeyFor","UiClock","formatTestDuration","Button","UiRefresh","UiStop","useState","Select","DetailTabSelectProvider","cn","TabButton"],"sources":["../../../src/data/test-runner/TestDetailPanel.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from \"react\";\nimport { Icon } from \"../Icon\";\nimport { Button } from \"../../components/button\";\nimport { Select } from \"../../components/select\";\nimport { SplitButton } from \"../../components/SplitButton\";\nimport { useCopyFlash } from \"../../components/clipboard\";\nimport type { DropdownMenuItem } from \"../../overlay/DropdownMenu\";\nimport { cn } from \"../../lib/utils\";\nimport { JsonView } from \"../JsonView\";\nimport { TabButton } from \"../TabButton\";\nimport {\n UiCheck,\n UiClass,\n UiClock,\n UiCopy,\n UiJson,\n UiRefresh,\n UiStop,\n UiWarningCircle,\n} from \"../../icons\";\nimport type { Test } from \"./types\";\nimport { useTestRunner } from \"./context\";\nimport { DetailTabSelectProvider } from \"./detailTabContext\";\nimport { buildNodeBrief, buildNodeJson, type CopyItem } from \"./copy\";\nimport { formatTestDuration, outcomeFor, sum } from \"./status\";\nimport { toolKeyFor } from \"./marks\";\nimport { OutcomeIcon, ToolIcon } from \"./MarkIcons\";\nimport { TestFailureDetail, TestOutput } from \"./TestFailureDetail\";\nimport { TestAttemptDetail } from \"./TestAttemptDetail\";\n\n/** Built-in detail body — used when no adapter contributes `renderDetail`. */\nfunction DefaultDetailBody({ node }: { node: Test }) {\n const runner = useTestRunner();\n const attempt = node.attempts?.at(-1);\n const hasChildren = (node.children?.length ?? 0) > 0;\n const counts = hasChildren ? sum(node) : null;\n return (\n <div className=\"space-y-4 p-density-4\">\n {node.message && !node.failure_detail && (\n <p className=\"whitespace-pre-wrap break-words text-sm\">{node.message}</p>\n )}\n {node.failure_detail && <TestFailureDetail failure={node.failure_detail} />}\n <TestOutput node={node} />\n {attempt && (\n <TestAttemptDetail\n key={`${attempt.run_kind ?? \"\"}:${attempt.sequence}`}\n attempt={attempt}\n {...(runner.fetchProcessMetrics ? { fetchProcessMetrics: runner.fetchProcessMetrics } : {})}\n />\n )}\n {counts && counts.total > 0 && (\n <div className=\"flex flex-wrap gap-3 text-xs text-muted-foreground\">\n <span>{counts.total} total</span>\n {counts.passed > 0 && <span>{counts.passed} passed</span>}\n {counts.failed > 0 && <span>{counts.failed} failed</span>}\n {counts.skipped > 0 && <span>{counts.skipped} skipped</span>}\n </div>\n )}\n {node.detail != null && (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">Detail</div>\n <JsonView data={node.detail} />\n </div>\n )}\n </div>\n );\n}\n\nfunction copyMenuItem(item: CopyItem, copy: (text: string) => void): DropdownMenuItem {\n const icon = item.icon ? { icon: item.icon } : {};\n if (\"reason\" in item) {\n return { label: item.label, ...icon, disabled: true, title: item.reason, onSelect: () => {} };\n }\n return { label: item.label, ...icon, onSelect: () => copy(item.value) };\n}\n\nconst COPY_LABEL = { idle: \"Copy details\", copied: \"Copied\", error: \"Copy failed\" } as const;\nconst COPY_ICON = { idle: UiCopy, copied: UiCheck, error: UiWarningCircle } as const;\n\n/**\n * \"Copy details\" split button: the primary action copies a Markdown brief of\n * the generic sections plus every matching adapter's `copy` sections; the menu\n * offers the node as JSON and the adapters' items, disabled with a reason when\n * they have nothing to copy. The label flips to \"Copied\" or \"Copy failed\".\n */\nfunction CopyDetailsButton({ node }: { node: Test }) {\n const runner = useTestRunner();\n const { state, copy } = useCopyFlash();\n const sections = useMemo(() => runner.adapters.resolveCopy(node, runner), [node, runner]);\n const items: DropdownMenuItem[] = [\n { label: \"Copy as JSON\", icon: UiJson, onSelect: () => copy(buildNodeJson(node)) },\n ...sections.items.map((item) => copyMenuItem(item, copy)),\n ];\n return (\n <SplitButton\n label={COPY_LABEL[state]}\n icon={COPY_ICON[state]}\n variant=\"outline\"\n size=\"sm\"\n align=\"right\"\n title=\"More copy options\"\n onClick={() => copy(buildNodeBrief(node, sections))}\n items={items}\n />\n );\n}\n\nfunction DetailHeader({ node, actionNode, actions, attemptChooser }: { node: Test; actionNode: Test; actions?: ReactNode; attemptChooser?: ReactNode }) {\n const runner = useTestRunner();\n const outcome = outcomeFor(node);\n const canRerun = !!runner.onRerun && actionNode.framework !== \"task\";\n const canStop = !!runner.onStop && !!actionNode.can_stop && !!actionNode.task_id;\n const location = node.file ? `${node.file}${node.line ? `:${node.line}` : \"\"}` : undefined;\n return (\n <div\n className=\"flex min-w-0 items-center gap-2 border-b border-border px-density-4 py-density-2\"\n data-detail-header=\"\"\n >\n {outcome ? (\n <OutcomeIcon outcome={outcome} className=\"text-lg\" />\n ) : (\n <Icon icon={UiClass} className=\"shrink-0 text-lg text-muted-foreground\" />\n )}\n <h2 className=\"min-w-0 shrink truncate text-base font-semibold\" title={node.name}>\n {node.name}\n </h2>\n {node.framework && (\n <span className=\"inline-flex shrink-0 items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground\">\n <ToolIcon tool={toolKeyFor(node)} className=\"text-sm\" />\n {node.framework}\n </span>\n )}\n {node.duration ? (\n <span className=\"inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground\">\n <Icon icon={UiClock} className=\"text-xs\" />\n {formatTestDuration(node.duration)}\n </span>\n ) : null}\n {location && (\n <span\n className=\"min-w-0 shrink-[100] truncate font-mono text-xs text-muted-foreground\"\n title={location}\n >\n {location}\n </span>\n )}\n <div className=\"ml-auto flex shrink-0 items-center gap-1\">\n {attemptChooser}\n {actions}\n <CopyDetailsButton node={node} />\n {canRerun && (\n <Button\n size=\"sm\"\n disabled={runner.busy.rerun}\n onClick={() => runner.onRerun!(actionNode)}\n title=\"Rerun this test or subtree\"\n >\n <Icon icon={UiRefresh} className=\"mr-1 text-sm\" />\n {runner.busy.rerun ? \"Running...\" : \"Rerun\"}\n </Button>\n )}\n {canStop && (\n <Button\n size=\"sm\"\n variant=\"destructive\"\n disabled={runner.busy.stop}\n onClick={() => runner.onStop!(actionNode)}\n title=\"Stop this running task\"\n >\n <Icon icon={UiStop} className=\"mr-1 text-sm\" />\n {runner.busy.stop ? \"Stopping...\" : \"Stop\"}\n </Button>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * The right pane. Resolves the node's adapter for body / tabs / header actions,\n * falling back to the built-in default body. Shows an empty state when nothing\n * is selected.\n */\nexport function TestDetailPanel() {\n const runner = useTestRunner();\n const selected = runner.selected;\n const [attemptSelection, setAttemptSelection] = useState<{ key: string; id: number } | null>(null);\n // Tab state is controlled when the host supplies activeTab (e.g. to mirror it\n // into the URL); otherwise the panel owns it. The render below tolerates an\n // unknown id by falling back to the built-in \"detail\" tab.\n const controlled = runner.activeTab !== undefined;\n const [internalTab, setInternalTab] = useState<string>(\"detail\");\n const activeTab = controlled ? runner.activeTab! : internalTab;\n const setActiveTab = (id: string) => {\n if (controlled) runner.onTabChange?.(id);\n else setInternalTab(id);\n };\n\n if (!selected) {\n return (\n <div className=\"flex h-full items-center justify-center text-sm text-muted-foreground\">\n <div className=\"text-center\">\n <Icon icon={UiClass} className=\"mb-2 block text-4xl\" />\n Select a test to view its details.\n </div>\n </div>\n );\n }\n\n const attempts = runner.adapters.resolveAttempts(selected, runner);\n const key = selected.task_id ?? selected.name;\n const attemptId = runner.activeAttempt ?? (attemptSelection?.key === key ? attemptSelection.id : undefined);\n const attempt = attempts.find((a) => a.id === attemptId) ?? attempts.at(-1);\n const node = attempts.length > 1 && attempt ? attempt.node : selected;\n const attemptChooser = attempts.length > 1 && attempt ? (\n <div className=\"w-44 shrink-0\">\n <Select aria-label=\"Attempt\" value={String(attempt.id)} className=\"h-7 text-xs\" onChange={(event) => {\n const id = Number(event.target.value);\n if (runner.onAttemptChange) runner.onAttemptChange(id);\n else setAttemptSelection({ key, id });\n }} options={attempts.map((a) => ({ value: String(a.id), label: `Attempt ${a.id}/${attempts.length} · ${a.status}${a.durationMs !== undefined ? ` · ${formatTestDuration(a.durationMs * 1e6)}` : \"\"}` }))} />\n </div>\n ) : null;\n\n const detailAdapter = runner.adapters.resolveDetail(node, runner);\n const actionAdapter = runner.adapters.resolveActions(selected, runner);\n const tabs = runner.adapters.resolveTabs(node, runner);\n const ownsScroll = runner.adapters.ownsScroll(node, runner);\n\n const body = (\n <DetailTabSelectProvider value={setActiveTab}>\n {detailAdapter?.renderDetail\n ? detailAdapter.renderDetail({ node, runner })\n : <DefaultDetailBody node={node} />}\n </DetailTabSelectProvider>\n );\n const actions = actionAdapter?.nodeActions?.({ node: selected, runner });\n\n // A size container, so log blocks can cap themselves at a share of the pane (cqh).\n const detailRegion = cn(\"min-h-0 flex-1 [container-type:size]\", !ownsScroll && \"overflow-y-auto\");\n\n // No extra tabs → just the body under the header.\n if (tabs.length === 0) {\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className={detailRegion}>{body}</div>\n </div>\n );\n }\n\n const detailTab = { id: \"detail\", label: \"Detail\", render: () => body };\n const allTabs = [detailTab, ...tabs];\n const active = allTabs.find((t) => t.id === activeTab) ?? detailTab;\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className=\"flex shrink-0 items-center gap-1 border-b border-border px-density-2 py-density-1\">\n {allTabs.map((t) => (\n <TabButton\n key={t.id}\n active={active.id === t.id}\n onClick={() => setActiveTab(t.id)}\n label={t.label}\n />\n ))}\n </div>\n <div className={detailRegion}>{active.render()}</div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TestDetailPanel.cjs","names":["useTestRunner","sum","TestFailureDetail","TestOutput","TestAttemptDetail","JsonView","UiCopy","UiHourglass","UiCheck","UiWarningCircle","useCopyFlash","useMemo","UiJson","buildNodeJson","SplitButton","buildNodeBrief","outcomeFor","OutcomeIcon","Icon","UiClass","ToolIcon","toolKeyFor","UiClock","formatTestDuration","Button","UiRefresh","UiStop","useState","Select","DetailTabSelectProvider","cn","TabButton"],"sources":["../../../src/data/test-runner/TestDetailPanel.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from \"react\";\nimport { Icon } from \"../Icon\";\nimport { Button } from \"../../components/button\";\nimport { Select } from \"../../components/select\";\nimport { SplitButton } from \"../../components/SplitButton\";\nimport { useCopyFlash, type CopyFlashState } from \"../../components/clipboard\";\nimport type { DropdownMenuItem } from \"../../overlay/DropdownMenu\";\nimport { cn } from \"../../lib/utils\";\nimport { JsonView } from \"../JsonView\";\nimport { TabButton } from \"../TabButton\";\nimport {\n UiCheck,\n UiClass,\n UiClock,\n UiCopy,\n UiHourglass,\n UiJson,\n UiRefresh,\n UiStop,\n UiWarningCircle,\n} from \"../../icons\";\nimport type { Test } from \"./types\";\nimport { useTestRunner } from \"./context\";\nimport { DetailTabSelectProvider } from \"./detailTabContext\";\nimport { buildNodeBrief, buildNodeJson, type CopyItem } from \"./copy\";\nimport { formatTestDuration, outcomeFor, sum } from \"./status\";\nimport { toolKeyFor } from \"./marks\";\nimport { OutcomeIcon, ToolIcon } from \"./MarkIcons\";\nimport { TestFailureDetail, TestOutput } from \"./TestFailureDetail\";\nimport { TestAttemptDetail } from \"./TestAttemptDetail\";\n\n/** Built-in detail body — used when no adapter contributes `renderDetail`. */\nfunction DefaultDetailBody({ node }: { node: Test }) {\n const runner = useTestRunner();\n const attempt = node.attempts?.at(-1);\n const hasChildren = (node.children?.length ?? 0) > 0;\n const counts = hasChildren ? sum(node) : null;\n return (\n <div className=\"space-y-4 p-density-4\">\n {node.message && !node.failure_detail && (\n <p className=\"whitespace-pre-wrap break-words text-sm\">{node.message}</p>\n )}\n {node.failure_detail && <TestFailureDetail failure={node.failure_detail} />}\n <TestOutput node={node} />\n {attempt && (\n <TestAttemptDetail\n key={`${attempt.run_kind ?? \"\"}:${attempt.sequence}`}\n attempt={attempt}\n {...(runner.fetchProcessMetrics ? { fetchProcessMetrics: runner.fetchProcessMetrics } : {})}\n />\n )}\n {counts && counts.total > 0 && (\n <div className=\"flex flex-wrap gap-3 text-xs text-muted-foreground\">\n <span>{counts.total} total</span>\n {counts.passed > 0 && <span>{counts.passed} passed</span>}\n {counts.failed > 0 && <span>{counts.failed} failed</span>}\n {counts.skipped > 0 && <span>{counts.skipped} skipped</span>}\n </div>\n )}\n {node.detail != null && (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">Detail</div>\n <JsonView data={node.detail} />\n </div>\n )}\n </div>\n );\n}\n\nfunction copyMenuItem(item: CopyItem, copy: (text: string) => void): DropdownMenuItem {\n const icon = item.icon ? { icon: item.icon } : {};\n if (\"reason\" in item) {\n return { label: item.label, ...icon, disabled: true, title: item.reason, onSelect: () => {} };\n }\n return { label: item.label, ...icon, onSelect: () => copy(item.value) };\n}\n\nconst COPY_LABEL = {\n idle: \"Copy details\",\n pending: \"Copying…\",\n copied: \"Copied\",\n error: \"Copy failed\",\n} as const satisfies Record<CopyFlashState, string>;\nconst COPY_ICON = {\n idle: UiCopy,\n pending: UiHourglass,\n copied: UiCheck,\n error: UiWarningCircle,\n} as const satisfies Record<CopyFlashState, unknown>;\n\n/**\n * \"Copy details\" split button: the primary action copies a Markdown brief of\n * the generic sections plus every matching adapter's `copy` sections; the menu\n * offers the node as JSON and the adapters' items, disabled with a reason when\n * they have nothing to copy. The label flips to \"Copied\" or \"Copy failed\".\n */\nfunction CopyDetailsButton({ node }: { node: Test }) {\n const runner = useTestRunner();\n const { state, copy } = useCopyFlash();\n const sections = useMemo(() => runner.adapters.resolveCopy(node, runner), [node, runner]);\n const items: DropdownMenuItem[] = [\n { label: \"Copy as JSON\", icon: UiJson, onSelect: () => copy(buildNodeJson(node)) },\n ...sections.items.map((item) => copyMenuItem(item, copy)),\n ];\n return (\n <SplitButton\n label={COPY_LABEL[state]}\n icon={COPY_ICON[state]}\n variant=\"outline\"\n size=\"sm\"\n align=\"right\"\n title=\"More copy options\"\n onClick={() => copy(buildNodeBrief(node, sections))}\n items={items}\n />\n );\n}\n\nfunction DetailHeader({ node, actionNode, actions, attemptChooser }: { node: Test; actionNode: Test; actions?: ReactNode; attemptChooser?: ReactNode }) {\n const runner = useTestRunner();\n const outcome = outcomeFor(node);\n const canRerun = !!runner.onRerun && actionNode.framework !== \"task\";\n const canStop = !!runner.onStop && !!actionNode.can_stop && !!actionNode.task_id;\n const location = node.file ? `${node.file}${node.line ? `:${node.line}` : \"\"}` : undefined;\n return (\n <div\n className=\"flex min-w-0 items-center gap-2 border-b border-border px-density-4 py-density-2\"\n data-detail-header=\"\"\n >\n {outcome ? (\n <OutcomeIcon outcome={outcome} className=\"text-lg\" />\n ) : (\n <Icon icon={UiClass} className=\"shrink-0 text-lg text-muted-foreground\" />\n )}\n <h2 className=\"min-w-0 shrink truncate text-base font-semibold\" title={node.name}>\n {node.name}\n </h2>\n {node.framework && (\n <span className=\"inline-flex shrink-0 items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground\">\n <ToolIcon tool={toolKeyFor(node)} className=\"text-sm\" />\n {node.framework}\n </span>\n )}\n {node.duration ? (\n <span className=\"inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground\">\n <Icon icon={UiClock} className=\"text-xs\" />\n {formatTestDuration(node.duration)}\n </span>\n ) : null}\n {location && (\n <span\n className=\"min-w-0 shrink-[100] truncate font-mono text-xs text-muted-foreground\"\n title={location}\n >\n {location}\n </span>\n )}\n <div className=\"ml-auto flex shrink-0 items-center gap-1\">\n {attemptChooser}\n {actions}\n <CopyDetailsButton node={node} />\n {canRerun && (\n <Button\n size=\"sm\"\n disabled={runner.busy.rerun}\n onClick={() => runner.onRerun!(actionNode)}\n title=\"Rerun this test or subtree\"\n >\n <Icon icon={UiRefresh} className=\"mr-1 text-sm\" />\n {runner.busy.rerun ? \"Running...\" : \"Rerun\"}\n </Button>\n )}\n {canStop && (\n <Button\n size=\"sm\"\n variant=\"destructive\"\n disabled={runner.busy.stop}\n onClick={() => runner.onStop!(actionNode)}\n title=\"Stop this running task\"\n >\n <Icon icon={UiStop} className=\"mr-1 text-sm\" />\n {runner.busy.stop ? \"Stopping...\" : \"Stop\"}\n </Button>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * The right pane. Resolves the node's adapter for body / tabs / header actions,\n * falling back to the built-in default body. Shows an empty state when nothing\n * is selected.\n */\nexport function TestDetailPanel() {\n const runner = useTestRunner();\n const selected = runner.selected;\n const [attemptSelection, setAttemptSelection] = useState<{ key: string; id: number } | null>(null);\n // Tab state is controlled when the host supplies activeTab (e.g. to mirror it\n // into the URL); otherwise the panel owns it. The render below tolerates an\n // unknown id by falling back to the built-in \"detail\" tab.\n const controlled = runner.activeTab !== undefined;\n const [internalTab, setInternalTab] = useState<string>(\"detail\");\n const activeTab = controlled ? runner.activeTab! : internalTab;\n const setActiveTab = (id: string) => {\n if (controlled) runner.onTabChange?.(id);\n else setInternalTab(id);\n };\n\n if (!selected) {\n return (\n <div className=\"flex h-full items-center justify-center text-sm text-muted-foreground\">\n <div className=\"text-center\">\n <Icon icon={UiClass} className=\"mb-2 block text-4xl\" />\n Select a test to view its details.\n </div>\n </div>\n );\n }\n\n const attempts = runner.adapters.resolveAttempts(selected, runner);\n const key = selected.task_id ?? selected.name;\n const attemptId = runner.activeAttempt ?? (attemptSelection?.key === key ? attemptSelection.id : undefined);\n const attempt = attempts.find((a) => a.id === attemptId) ?? attempts.at(-1);\n const node = attempts.length > 1 && attempt ? attempt.node : selected;\n const attemptChooser = attempts.length > 1 && attempt ? (\n <div className=\"w-44 shrink-0\">\n <Select aria-label=\"Attempt\" value={String(attempt.id)} className=\"h-7 text-xs\" onChange={(event) => {\n const id = Number(event.target.value);\n if (runner.onAttemptChange) runner.onAttemptChange(id);\n else setAttemptSelection({ key, id });\n }} options={attempts.map((a) => ({ value: String(a.id), label: `Attempt ${a.id}/${attempts.length} · ${a.status}${a.durationMs !== undefined ? ` · ${formatTestDuration(a.durationMs * 1e6)}` : \"\"}` }))} />\n </div>\n ) : null;\n\n const detailAdapter = runner.adapters.resolveDetail(node, runner);\n const actionAdapter = runner.adapters.resolveActions(selected, runner);\n const tabs = runner.adapters.resolveTabs(node, runner);\n const ownsScroll = runner.adapters.ownsScroll(node, runner);\n\n const body = (\n <DetailTabSelectProvider value={setActiveTab}>\n {detailAdapter?.renderDetail\n ? detailAdapter.renderDetail({ node, runner })\n : <DefaultDetailBody node={node} />}\n </DetailTabSelectProvider>\n );\n const actions = actionAdapter?.nodeActions?.({ node: selected, runner });\n\n // A size container, so log blocks can cap themselves at a share of the pane (cqh).\n const detailRegion = cn(\"min-h-0 flex-1 [container-type:size]\", !ownsScroll && \"overflow-y-auto\");\n\n // No extra tabs → just the body under the header.\n if (tabs.length === 0) {\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className={detailRegion}>{body}</div>\n </div>\n );\n }\n\n const detailTab = { id: \"detail\", label: \"Detail\", render: () => body };\n const allTabs = [detailTab, ...tabs];\n const active = allTabs.find((t) => t.id === activeTab) ?? detailTab;\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className=\"flex shrink-0 items-center gap-1 border-b border-border px-density-2 py-density-1\">\n {allTabs.map((t) => (\n <TabButton\n key={t.id}\n active={active.id === t.id}\n onClick={() => setActiveTab(t.id)}\n label={t.label}\n />\n ))}\n </div>\n <div className={detailRegion}>{active.render()}</div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,SAAS,kBAAkB,EAAE,QAAwB;CACnD,MAAM,SAASA,gBAAAA,cAAc;CAC7B,MAAM,UAAU,KAAK,UAAU,GAAG,EAAE;CAEpC,MAAM,UADe,KAAK,UAAU,UAAU,KAAK,IACtBC,eAAAA,IAAI,IAAI,IAAI;CACzC,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;EAAK,WAAU;EAAf,UAAA;GACG,KAAK,WAAW,CAAC,KAAK,kBACrB,iBAAA,GAAA,kBAAA,IAAA,CAAC,KAAD;IAAG,WAAU;IAA2C,UAAA,KAAK;GAAW,CAAA;GAEzE,KAAK,kBAAkB,iBAAA,GAAA,kBAAA,IAAA,CAACC,0BAAAA,mBAAD,EAAmB,SAAS,KAAK,eAAiB,CAAA;GAC1E,iBAAA,GAAA,kBAAA,IAAA,CAACC,0BAAAA,YAAD,EAAkB,KAAO,CAAA;GACxB,WACC,iBAAA,GAAA,kBAAA,IAAA,CAACC,0BAAAA,mBAAD;IAEW;IACT,GAAK,OAAO,sBAAsB,EAAE,qBAAqB,OAAO,oBAAoB,IAAI,CAAC;GAC1F,GAHM,GAAG,QAAQ,YAAY,GAAG,GAAG,QAAQ,UAG3C;GAEF,UAAU,OAAO,QAAQ,KACxB,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,QAAD,EAAA,UAAA,CAAO,OAAO,OAAM,QAAY,EAAA,CAAA;KAC/B,OAAO,SAAS,KAAK,iBAAA,GAAA,kBAAA,KAAA,CAAC,QAAD,EAAA,UAAA,CAAO,OAAO,QAAO,SAAa,EAAA,CAAA;KACvD,OAAO,SAAS,KAAK,iBAAA,GAAA,kBAAA,KAAA,CAAC,QAAD,EAAA,UAAA,CAAO,OAAO,QAAO,SAAa,EAAA,CAAA;KACvD,OAAO,UAAU,KAAK,iBAAA,GAAA,kBAAA,KAAA,CAAC,QAAD,EAAA,UAAA,CAAO,OAAO,SAAQ,UAAc,EAAA,CAAA;IACxD;;GAEN,KAAK,UAAU,QACd,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;KAAK,WAAU;KAA4D,UAAA;IAAW,CAAA,GACtF,iBAAA,GAAA,kBAAA,IAAA,CAACC,iBAAAA,UAAD,EAAU,MAAM,KAAK,OAAS,CAAA,CAC3B;;EAEJ;;AAET;AAEA,SAAS,aAAa,MAAgB,MAAgD;CACpF,MAAM,OAAO,KAAK,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;CAChD,IAAI,YAAY,MACd,OAAO;EAAE,OAAO,KAAK;EAAO,GAAG;EAAM,UAAU;EAAM,OAAO,KAAK;EAAQ,gBAAgB,CAAC;CAAE;CAE9F,OAAO;EAAE,OAAO,KAAK;EAAO,GAAG;EAAM,gBAAgB,KAAK,KAAK,KAAK;CAAE;AACxE;AAEA,IAAM,aAAa;CACjB,MAAM;CACN,SAAS;CACT,QAAQ;CACR,OAAO;AACT;AACA,IAAM,YAAY;CAChB,MAAMC,eAAAA;CACN,SAASC,oBAAAA;CACT,QAAQC,gBAAAA;CACR,OAAOC,wBAAAA;AACT;;;;;;;AAQA,SAAS,kBAAkB,EAAE,QAAwB;CACnD,MAAM,SAAST,gBAAAA,cAAc;CAC7B,MAAM,EAAE,OAAO,SAASU,kBAAAA,aAAa;CACrC,MAAM,YAAA,GAAWC,MAAAA,QAAAA,OAAc,OAAO,SAAS,YAAY,MAAM,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC;CACxF,MAAM,QAA4B,CAChC;EAAE,OAAO;EAAgB,MAAMC,eAAAA;EAAQ,gBAAgB,KAAKC,aAAAA,cAAc,IAAI,CAAC;CAAE,GACjF,GAAG,SAAS,MAAM,KAAK,SAAS,aAAa,MAAM,IAAI,CAAC,CAC1D;CACA,OACE,iBAAA,GAAA,kBAAA,IAAA,CAACC,oBAAAA,aAAD;EACE,OAAO,WAAW;EAClB,MAAM,UAAU;EAChB,SAAQ;EACR,MAAK;EACL,OAAM;EACN,OAAM;EACN,eAAe,KAAKC,aAAAA,eAAe,MAAM,QAAQ,CAAC;EAC3C;CACR,CAAA;AAEL;AAEA,SAAS,aAAa,EAAE,MAAM,YAAY,SAAS,kBAAqG;CACtJ,MAAM,SAASf,gBAAAA,cAAc;CAC7B,MAAM,UAAUgB,eAAAA,WAAW,IAAI;CAC/B,MAAM,WAAW,CAAC,CAAC,OAAO,WAAW,WAAW,cAAc;CAC9D,MAAM,UAAU,CAAC,CAAC,OAAO,UAAU,CAAC,CAAC,WAAW,YAAY,CAAC,CAAC,WAAW;CACzE,MAAM,WAAW,KAAK,OAAO,GAAG,KAAK,OAAO,KAAK,OAAO,IAAI,KAAK,SAAS,OAAO,KAAA;CACjF,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;EACE,WAAU;EACV,sBAAmB;EAFrB,UAAA;GAIG,UACC,iBAAA,GAAA,kBAAA,IAAA,CAACC,kBAAAA,aAAD;IAAsB;IAAS,WAAU;GAAW,CAAA,IAEpD,iBAAA,GAAA,kBAAA,IAAA,CAACC,aAAAA,MAAD;IAAM,MAAMC,gBAAAA;IAAS,WAAU;GAA0C,CAAA;GAE3E,iBAAA,GAAA,kBAAA,IAAA,CAAC,MAAD;IAAI,WAAU;IAAkD,OAAO,KAAK;IACzE,UAAA,KAAK;GACJ,CAAA;GACH,KAAK,aACJ,iBAAA,GAAA,kBAAA,KAAA,CAAC,QAAD;IAAM,WAAU;IAAhB,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAACC,kBAAAA,UAAD;KAAU,MAAMC,cAAAA,WAAW,IAAI;KAAG,WAAU;IAAW,CAAA,GACtD,KAAK,SACF;;GAEP,KAAK,WACJ,iBAAA,GAAA,kBAAA,KAAA,CAAC,QAAD;IAAM,WAAU;IAAhB,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAACH,aAAAA,MAAD;KAAM,MAAMI,gBAAAA;KAAS,WAAU;IAAW,CAAA,GACzCC,eAAAA,mBAAmB,KAAK,QAAQ,CAC7B;GACJ,CAAA,IAAA;GACH,YACC,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;IACE,WAAU;IACV,OAAO;IAEN,UAAA;GACG,CAAA;GAER,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACG;KACA;KACD,iBAAA,GAAA,kBAAA,IAAA,CAAC,mBAAD,EAAyB,KAAO,CAAA;KAC/B,YACC,iBAAA,GAAA,kBAAA,KAAA,CAACC,eAAAA,QAAD;MACE,MAAK;MACL,UAAU,OAAO,KAAK;MACtB,eAAe,OAAO,QAAS,UAAU;MACzC,OAAM;MAJR,UAAA,CAME,iBAAA,GAAA,kBAAA,IAAA,CAACN,aAAAA,MAAD;OAAM,MAAMO,kBAAAA;OAAW,WAAU;MAAgB,CAAA,GAChD,OAAO,KAAK,QAAQ,eAAe,OAC9B;;KAET,WACC,iBAAA,GAAA,kBAAA,KAAA,CAACD,eAAAA,QAAD;MACE,MAAK;MACL,SAAQ;MACR,UAAU,OAAO,KAAK;MACtB,eAAe,OAAO,OAAQ,UAAU;MACxC,OAAM;MALR,UAAA,CAOE,iBAAA,GAAA,kBAAA,IAAA,CAACN,aAAAA,MAAD;OAAM,MAAMQ,eAAAA;OAAQ,WAAU;MAAgB,CAAA,GAC7C,OAAO,KAAK,OAAO,gBAAgB,MAC9B;;IAEP;;EACF;;AAET;;;;;;AAOA,SAAgB,kBAAkB;CAChC,MAAM,SAAS1B,gBAAAA,cAAc;CAC7B,MAAM,WAAW,OAAO;CACxB,MAAM,CAAC,kBAAkB,wBAAA,GAAuB2B,MAAAA,SAAAA,CAA6C,IAAI;CAIjG,MAAM,aAAa,OAAO,cAAc,KAAA;CACxC,MAAM,CAAC,aAAa,mBAAA,GAAkBA,MAAAA,SAAAA,CAAiB,QAAQ;CAC/D,MAAM,YAAY,aAAa,OAAO,YAAa;CACnD,MAAM,gBAAgB,OAAe;EACnC,IAAI,YAAY,OAAO,cAAc,EAAE;OAClC,eAAe,EAAE;CACxB;CAEA,IAAI,CAAC,UACH,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;EAAK,WAAU;EACb,UAAA,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAACT,aAAAA,MAAD;IAAM,MAAMC,gBAAAA;IAAS,WAAU;GAAuB,CAAA,GAAC,oCAEpD;;CACF,CAAA;CAIT,MAAM,WAAW,OAAO,SAAS,gBAAgB,UAAU,MAAM;CACjE,MAAM,MAAM,SAAS,WAAW,SAAS;CACzC,MAAM,YAAY,OAAO,kBAAkB,kBAAkB,QAAQ,MAAM,iBAAiB,KAAK,KAAA;CACjG,MAAM,UAAU,SAAS,MAAM,MAAM,EAAE,OAAO,SAAS,KAAK,SAAS,GAAG,EAAE;CAC1E,MAAM,OAAO,SAAS,SAAS,KAAK,UAAU,QAAQ,OAAO;CAC7D,MAAM,iBAAiB,SAAS,SAAS,KAAK,UAC5C,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;EAAK,WAAU;EACb,UAAA,iBAAA,GAAA,kBAAA,IAAA,CAACS,eAAAA,QAAD;GAAQ,cAAW;GAAU,OAAO,OAAO,QAAQ,EAAE;GAAG,WAAU;GAAc,WAAW,UAAU;IACnG,MAAM,KAAK,OAAO,MAAM,OAAO,KAAK;IACpC,IAAI,OAAO,iBAAiB,OAAO,gBAAgB,EAAE;SAChD,oBAAoB;KAAE;KAAK;IAAG,CAAC;GACtC;GAAG,SAAS,SAAS,KAAK,OAAO;IAAE,OAAO,OAAO,EAAE,EAAE;IAAG,OAAO,WAAW,EAAE,GAAG,GAAG,SAAS,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,KAAA,IAAY,MAAML,eAAAA,mBAAmB,EAAE,aAAa,GAAG,MAAM;GAAK,EAAE;EAAI,CAAA;CACxM,CAAA,IACH;CAEJ,MAAM,gBAAgB,OAAO,SAAS,cAAc,MAAM,MAAM;CAChE,MAAM,gBAAgB,OAAO,SAAS,eAAe,UAAU,MAAM;CACrE,MAAM,OAAO,OAAO,SAAS,YAAY,MAAM,MAAM;CACrD,MAAM,aAAa,OAAO,SAAS,WAAW,MAAM,MAAM;CAE1D,MAAM,OACJ,iBAAA,GAAA,kBAAA,IAAA,CAACM,yBAAAA,yBAAD;EAAyB,OAAO;EAC7B,UAAA,eAAe,eACZ,cAAc,aAAa;GAAE;GAAM;EAAO,CAAC,IAC3C,iBAAA,GAAA,kBAAA,IAAA,CAAC,mBAAD,EAAyB,KAAO,CAAA;CACb,CAAA;CAE3B,MAAM,UAAU,eAAe,cAAc;EAAE,MAAM;EAAU;CAAO,CAAC;CAGvE,MAAM,eAAeC,cAAAA,GAAG,wCAAwC,CAAC,cAAc,iBAAiB;CAGhG,IAAI,KAAK,WAAW,GAClB,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,cAAD;GAAoB;GAAM,YAAY;GAAmB;GAAyB;EAAiB,CAAA,GACnG,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;GAAK,WAAW;GAAe,UAAA;EAAU,CAAA,CACtC;;CAIT,MAAM,YAAY;EAAE,IAAI;EAAU,OAAO;EAAU,cAAc;CAAK;CACtE,MAAM,UAAU,CAAC,WAAW,GAAG,IAAI;CACnC,MAAM,SAAS,QAAQ,MAAM,MAAM,EAAE,OAAO,SAAS,KAAK;CAE1D,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;EAAK,WAAU;EAAf,UAAA;GACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,cAAD;IAAoB;IAAM,YAAY;IAAmB;IAAyB;GAAiB,CAAA;GACnG,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;IAAK,WAAU;IACZ,UAAA,QAAQ,KAAK,MACZ,iBAAA,GAAA,kBAAA,IAAA,CAACC,kBAAAA,WAAD;KAEE,QAAQ,OAAO,OAAO,EAAE;KACxB,eAAe,aAAa,EAAE,EAAE;KAChC,OAAO,EAAE;IACV,GAJM,EAAE,EAIR,CACF;GACE,CAAA;GACL,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;IAAK,WAAW;IAAe,UAAA,OAAO,OAAO;GAAO,CAAA;EACjD;;AAET"}
@@ -1 +1 @@
1
- {"version":3,"file":"TestDetailPanel.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/TestDetailPanel.tsx"],"names":[],"mappings":"AAkLA;;;;GAIG;AACH,wBAAgB,eAAe,gCAwF9B"}
1
+ {"version":3,"file":"TestDetailPanel.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/TestDetailPanel.tsx"],"names":[],"mappings":"AA6LA;;;;GAIG;AACH,wBAAgB,eAAe,gCAwF9B"}
@@ -7,6 +7,7 @@ import { UiJson } from "../../icons/components/UiJson.js";
7
7
  import { UiCopy } from "../../icons/components/UiCopy.js";
8
8
  import { UiRefresh } from "../../icons/components/UiRefresh.js";
9
9
  import { UiClock } from "../../icons/components/UiClock.js";
10
+ import { UiHourglass } from "../../icons/components/UiHourglass.js";
10
11
  import { Icon } from "../Icon.js";
11
12
  import { Button } from "../../components/button.js";
12
13
  import { useCopyFlash } from "../../components/clipboard.js";
@@ -79,11 +80,13 @@ function copyMenuItem(item, copy) {
79
80
  }
80
81
  var COPY_LABEL = {
81
82
  idle: "Copy details",
83
+ pending: "Copying…",
82
84
  copied: "Copied",
83
85
  error: "Copy failed"
84
86
  };
85
87
  var COPY_ICON = {
86
88
  idle: UiCopy,
89
+ pending: UiHourglass,
87
90
  copied: UiCheck,
88
91
  error: UiWarningCircle
89
92
  };
@@ -1 +1 @@
1
- {"version":3,"file":"TestDetailPanel.js","names":[],"sources":["../../../src/data/test-runner/TestDetailPanel.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from \"react\";\nimport { Icon } from \"../Icon\";\nimport { Button } from \"../../components/button\";\nimport { Select } from \"../../components/select\";\nimport { SplitButton } from \"../../components/SplitButton\";\nimport { useCopyFlash } from \"../../components/clipboard\";\nimport type { DropdownMenuItem } from \"../../overlay/DropdownMenu\";\nimport { cn } from \"../../lib/utils\";\nimport { JsonView } from \"../JsonView\";\nimport { TabButton } from \"../TabButton\";\nimport {\n UiCheck,\n UiClass,\n UiClock,\n UiCopy,\n UiJson,\n UiRefresh,\n UiStop,\n UiWarningCircle,\n} from \"../../icons\";\nimport type { Test } from \"./types\";\nimport { useTestRunner } from \"./context\";\nimport { DetailTabSelectProvider } from \"./detailTabContext\";\nimport { buildNodeBrief, buildNodeJson, type CopyItem } from \"./copy\";\nimport { formatTestDuration, outcomeFor, sum } from \"./status\";\nimport { toolKeyFor } from \"./marks\";\nimport { OutcomeIcon, ToolIcon } from \"./MarkIcons\";\nimport { TestFailureDetail, TestOutput } from \"./TestFailureDetail\";\nimport { TestAttemptDetail } from \"./TestAttemptDetail\";\n\n/** Built-in detail body — used when no adapter contributes `renderDetail`. */\nfunction DefaultDetailBody({ node }: { node: Test }) {\n const runner = useTestRunner();\n const attempt = node.attempts?.at(-1);\n const hasChildren = (node.children?.length ?? 0) > 0;\n const counts = hasChildren ? sum(node) : null;\n return (\n <div className=\"space-y-4 p-density-4\">\n {node.message && !node.failure_detail && (\n <p className=\"whitespace-pre-wrap break-words text-sm\">{node.message}</p>\n )}\n {node.failure_detail && <TestFailureDetail failure={node.failure_detail} />}\n <TestOutput node={node} />\n {attempt && (\n <TestAttemptDetail\n key={`${attempt.run_kind ?? \"\"}:${attempt.sequence}`}\n attempt={attempt}\n {...(runner.fetchProcessMetrics ? { fetchProcessMetrics: runner.fetchProcessMetrics } : {})}\n />\n )}\n {counts && counts.total > 0 && (\n <div className=\"flex flex-wrap gap-3 text-xs text-muted-foreground\">\n <span>{counts.total} total</span>\n {counts.passed > 0 && <span>{counts.passed} passed</span>}\n {counts.failed > 0 && <span>{counts.failed} failed</span>}\n {counts.skipped > 0 && <span>{counts.skipped} skipped</span>}\n </div>\n )}\n {node.detail != null && (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">Detail</div>\n <JsonView data={node.detail} />\n </div>\n )}\n </div>\n );\n}\n\nfunction copyMenuItem(item: CopyItem, copy: (text: string) => void): DropdownMenuItem {\n const icon = item.icon ? { icon: item.icon } : {};\n if (\"reason\" in item) {\n return { label: item.label, ...icon, disabled: true, title: item.reason, onSelect: () => {} };\n }\n return { label: item.label, ...icon, onSelect: () => copy(item.value) };\n}\n\nconst COPY_LABEL = { idle: \"Copy details\", copied: \"Copied\", error: \"Copy failed\" } as const;\nconst COPY_ICON = { idle: UiCopy, copied: UiCheck, error: UiWarningCircle } as const;\n\n/**\n * \"Copy details\" split button: the primary action copies a Markdown brief of\n * the generic sections plus every matching adapter's `copy` sections; the menu\n * offers the node as JSON and the adapters' items, disabled with a reason when\n * they have nothing to copy. The label flips to \"Copied\" or \"Copy failed\".\n */\nfunction CopyDetailsButton({ node }: { node: Test }) {\n const runner = useTestRunner();\n const { state, copy } = useCopyFlash();\n const sections = useMemo(() => runner.adapters.resolveCopy(node, runner), [node, runner]);\n const items: DropdownMenuItem[] = [\n { label: \"Copy as JSON\", icon: UiJson, onSelect: () => copy(buildNodeJson(node)) },\n ...sections.items.map((item) => copyMenuItem(item, copy)),\n ];\n return (\n <SplitButton\n label={COPY_LABEL[state]}\n icon={COPY_ICON[state]}\n variant=\"outline\"\n size=\"sm\"\n align=\"right\"\n title=\"More copy options\"\n onClick={() => copy(buildNodeBrief(node, sections))}\n items={items}\n />\n );\n}\n\nfunction DetailHeader({ node, actionNode, actions, attemptChooser }: { node: Test; actionNode: Test; actions?: ReactNode; attemptChooser?: ReactNode }) {\n const runner = useTestRunner();\n const outcome = outcomeFor(node);\n const canRerun = !!runner.onRerun && actionNode.framework !== \"task\";\n const canStop = !!runner.onStop && !!actionNode.can_stop && !!actionNode.task_id;\n const location = node.file ? `${node.file}${node.line ? `:${node.line}` : \"\"}` : undefined;\n return (\n <div\n className=\"flex min-w-0 items-center gap-2 border-b border-border px-density-4 py-density-2\"\n data-detail-header=\"\"\n >\n {outcome ? (\n <OutcomeIcon outcome={outcome} className=\"text-lg\" />\n ) : (\n <Icon icon={UiClass} className=\"shrink-0 text-lg text-muted-foreground\" />\n )}\n <h2 className=\"min-w-0 shrink truncate text-base font-semibold\" title={node.name}>\n {node.name}\n </h2>\n {node.framework && (\n <span className=\"inline-flex shrink-0 items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground\">\n <ToolIcon tool={toolKeyFor(node)} className=\"text-sm\" />\n {node.framework}\n </span>\n )}\n {node.duration ? (\n <span className=\"inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground\">\n <Icon icon={UiClock} className=\"text-xs\" />\n {formatTestDuration(node.duration)}\n </span>\n ) : null}\n {location && (\n <span\n className=\"min-w-0 shrink-[100] truncate font-mono text-xs text-muted-foreground\"\n title={location}\n >\n {location}\n </span>\n )}\n <div className=\"ml-auto flex shrink-0 items-center gap-1\">\n {attemptChooser}\n {actions}\n <CopyDetailsButton node={node} />\n {canRerun && (\n <Button\n size=\"sm\"\n disabled={runner.busy.rerun}\n onClick={() => runner.onRerun!(actionNode)}\n title=\"Rerun this test or subtree\"\n >\n <Icon icon={UiRefresh} className=\"mr-1 text-sm\" />\n {runner.busy.rerun ? \"Running...\" : \"Rerun\"}\n </Button>\n )}\n {canStop && (\n <Button\n size=\"sm\"\n variant=\"destructive\"\n disabled={runner.busy.stop}\n onClick={() => runner.onStop!(actionNode)}\n title=\"Stop this running task\"\n >\n <Icon icon={UiStop} className=\"mr-1 text-sm\" />\n {runner.busy.stop ? \"Stopping...\" : \"Stop\"}\n </Button>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * The right pane. Resolves the node's adapter for body / tabs / header actions,\n * falling back to the built-in default body. Shows an empty state when nothing\n * is selected.\n */\nexport function TestDetailPanel() {\n const runner = useTestRunner();\n const selected = runner.selected;\n const [attemptSelection, setAttemptSelection] = useState<{ key: string; id: number } | null>(null);\n // Tab state is controlled when the host supplies activeTab (e.g. to mirror it\n // into the URL); otherwise the panel owns it. The render below tolerates an\n // unknown id by falling back to the built-in \"detail\" tab.\n const controlled = runner.activeTab !== undefined;\n const [internalTab, setInternalTab] = useState<string>(\"detail\");\n const activeTab = controlled ? runner.activeTab! : internalTab;\n const setActiveTab = (id: string) => {\n if (controlled) runner.onTabChange?.(id);\n else setInternalTab(id);\n };\n\n if (!selected) {\n return (\n <div className=\"flex h-full items-center justify-center text-sm text-muted-foreground\">\n <div className=\"text-center\">\n <Icon icon={UiClass} className=\"mb-2 block text-4xl\" />\n Select a test to view its details.\n </div>\n </div>\n );\n }\n\n const attempts = runner.adapters.resolveAttempts(selected, runner);\n const key = selected.task_id ?? selected.name;\n const attemptId = runner.activeAttempt ?? (attemptSelection?.key === key ? attemptSelection.id : undefined);\n const attempt = attempts.find((a) => a.id === attemptId) ?? attempts.at(-1);\n const node = attempts.length > 1 && attempt ? attempt.node : selected;\n const attemptChooser = attempts.length > 1 && attempt ? (\n <div className=\"w-44 shrink-0\">\n <Select aria-label=\"Attempt\" value={String(attempt.id)} className=\"h-7 text-xs\" onChange={(event) => {\n const id = Number(event.target.value);\n if (runner.onAttemptChange) runner.onAttemptChange(id);\n else setAttemptSelection({ key, id });\n }} options={attempts.map((a) => ({ value: String(a.id), label: `Attempt ${a.id}/${attempts.length} · ${a.status}${a.durationMs !== undefined ? ` · ${formatTestDuration(a.durationMs * 1e6)}` : \"\"}` }))} />\n </div>\n ) : null;\n\n const detailAdapter = runner.adapters.resolveDetail(node, runner);\n const actionAdapter = runner.adapters.resolveActions(selected, runner);\n const tabs = runner.adapters.resolveTabs(node, runner);\n const ownsScroll = runner.adapters.ownsScroll(node, runner);\n\n const body = (\n <DetailTabSelectProvider value={setActiveTab}>\n {detailAdapter?.renderDetail\n ? detailAdapter.renderDetail({ node, runner })\n : <DefaultDetailBody node={node} />}\n </DetailTabSelectProvider>\n );\n const actions = actionAdapter?.nodeActions?.({ node: selected, runner });\n\n // A size container, so log blocks can cap themselves at a share of the pane (cqh).\n const detailRegion = cn(\"min-h-0 flex-1 [container-type:size]\", !ownsScroll && \"overflow-y-auto\");\n\n // No extra tabs → just the body under the header.\n if (tabs.length === 0) {\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className={detailRegion}>{body}</div>\n </div>\n );\n }\n\n const detailTab = { id: \"detail\", label: \"Detail\", render: () => body };\n const allTabs = [detailTab, ...tabs];\n const active = allTabs.find((t) => t.id === activeTab) ?? detailTab;\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className=\"flex shrink-0 items-center gap-1 border-b border-border px-density-2 py-density-1\">\n {allTabs.map((t) => (\n <TabButton\n key={t.id}\n active={active.id === t.id}\n onClick={() => setActiveTab(t.id)}\n label={t.label}\n />\n ))}\n </div>\n <div className={detailRegion}>{active.render()}</div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TestDetailPanel.js","names":[],"sources":["../../../src/data/test-runner/TestDetailPanel.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from \"react\";\nimport { Icon } from \"../Icon\";\nimport { Button } from \"../../components/button\";\nimport { Select } from \"../../components/select\";\nimport { SplitButton } from \"../../components/SplitButton\";\nimport { useCopyFlash, type CopyFlashState } from \"../../components/clipboard\";\nimport type { DropdownMenuItem } from \"../../overlay/DropdownMenu\";\nimport { cn } from \"../../lib/utils\";\nimport { JsonView } from \"../JsonView\";\nimport { TabButton } from \"../TabButton\";\nimport {\n UiCheck,\n UiClass,\n UiClock,\n UiCopy,\n UiHourglass,\n UiJson,\n UiRefresh,\n UiStop,\n UiWarningCircle,\n} from \"../../icons\";\nimport type { Test } from \"./types\";\nimport { useTestRunner } from \"./context\";\nimport { DetailTabSelectProvider } from \"./detailTabContext\";\nimport { buildNodeBrief, buildNodeJson, type CopyItem } from \"./copy\";\nimport { formatTestDuration, outcomeFor, sum } from \"./status\";\nimport { toolKeyFor } from \"./marks\";\nimport { OutcomeIcon, ToolIcon } from \"./MarkIcons\";\nimport { TestFailureDetail, TestOutput } from \"./TestFailureDetail\";\nimport { TestAttemptDetail } from \"./TestAttemptDetail\";\n\n/** Built-in detail body — used when no adapter contributes `renderDetail`. */\nfunction DefaultDetailBody({ node }: { node: Test }) {\n const runner = useTestRunner();\n const attempt = node.attempts?.at(-1);\n const hasChildren = (node.children?.length ?? 0) > 0;\n const counts = hasChildren ? sum(node) : null;\n return (\n <div className=\"space-y-4 p-density-4\">\n {node.message && !node.failure_detail && (\n <p className=\"whitespace-pre-wrap break-words text-sm\">{node.message}</p>\n )}\n {node.failure_detail && <TestFailureDetail failure={node.failure_detail} />}\n <TestOutput node={node} />\n {attempt && (\n <TestAttemptDetail\n key={`${attempt.run_kind ?? \"\"}:${attempt.sequence}`}\n attempt={attempt}\n {...(runner.fetchProcessMetrics ? { fetchProcessMetrics: runner.fetchProcessMetrics } : {})}\n />\n )}\n {counts && counts.total > 0 && (\n <div className=\"flex flex-wrap gap-3 text-xs text-muted-foreground\">\n <span>{counts.total} total</span>\n {counts.passed > 0 && <span>{counts.passed} passed</span>}\n {counts.failed > 0 && <span>{counts.failed} failed</span>}\n {counts.skipped > 0 && <span>{counts.skipped} skipped</span>}\n </div>\n )}\n {node.detail != null && (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">Detail</div>\n <JsonView data={node.detail} />\n </div>\n )}\n </div>\n );\n}\n\nfunction copyMenuItem(item: CopyItem, copy: (text: string) => void): DropdownMenuItem {\n const icon = item.icon ? { icon: item.icon } : {};\n if (\"reason\" in item) {\n return { label: item.label, ...icon, disabled: true, title: item.reason, onSelect: () => {} };\n }\n return { label: item.label, ...icon, onSelect: () => copy(item.value) };\n}\n\nconst COPY_LABEL = {\n idle: \"Copy details\",\n pending: \"Copying…\",\n copied: \"Copied\",\n error: \"Copy failed\",\n} as const satisfies Record<CopyFlashState, string>;\nconst COPY_ICON = {\n idle: UiCopy,\n pending: UiHourglass,\n copied: UiCheck,\n error: UiWarningCircle,\n} as const satisfies Record<CopyFlashState, unknown>;\n\n/**\n * \"Copy details\" split button: the primary action copies a Markdown brief of\n * the generic sections plus every matching adapter's `copy` sections; the menu\n * offers the node as JSON and the adapters' items, disabled with a reason when\n * they have nothing to copy. The label flips to \"Copied\" or \"Copy failed\".\n */\nfunction CopyDetailsButton({ node }: { node: Test }) {\n const runner = useTestRunner();\n const { state, copy } = useCopyFlash();\n const sections = useMemo(() => runner.adapters.resolveCopy(node, runner), [node, runner]);\n const items: DropdownMenuItem[] = [\n { label: \"Copy as JSON\", icon: UiJson, onSelect: () => copy(buildNodeJson(node)) },\n ...sections.items.map((item) => copyMenuItem(item, copy)),\n ];\n return (\n <SplitButton\n label={COPY_LABEL[state]}\n icon={COPY_ICON[state]}\n variant=\"outline\"\n size=\"sm\"\n align=\"right\"\n title=\"More copy options\"\n onClick={() => copy(buildNodeBrief(node, sections))}\n items={items}\n />\n );\n}\n\nfunction DetailHeader({ node, actionNode, actions, attemptChooser }: { node: Test; actionNode: Test; actions?: ReactNode; attemptChooser?: ReactNode }) {\n const runner = useTestRunner();\n const outcome = outcomeFor(node);\n const canRerun = !!runner.onRerun && actionNode.framework !== \"task\";\n const canStop = !!runner.onStop && !!actionNode.can_stop && !!actionNode.task_id;\n const location = node.file ? `${node.file}${node.line ? `:${node.line}` : \"\"}` : undefined;\n return (\n <div\n className=\"flex min-w-0 items-center gap-2 border-b border-border px-density-4 py-density-2\"\n data-detail-header=\"\"\n >\n {outcome ? (\n <OutcomeIcon outcome={outcome} className=\"text-lg\" />\n ) : (\n <Icon icon={UiClass} className=\"shrink-0 text-lg text-muted-foreground\" />\n )}\n <h2 className=\"min-w-0 shrink truncate text-base font-semibold\" title={node.name}>\n {node.name}\n </h2>\n {node.framework && (\n <span className=\"inline-flex shrink-0 items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground\">\n <ToolIcon tool={toolKeyFor(node)} className=\"text-sm\" />\n {node.framework}\n </span>\n )}\n {node.duration ? (\n <span className=\"inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground\">\n <Icon icon={UiClock} className=\"text-xs\" />\n {formatTestDuration(node.duration)}\n </span>\n ) : null}\n {location && (\n <span\n className=\"min-w-0 shrink-[100] truncate font-mono text-xs text-muted-foreground\"\n title={location}\n >\n {location}\n </span>\n )}\n <div className=\"ml-auto flex shrink-0 items-center gap-1\">\n {attemptChooser}\n {actions}\n <CopyDetailsButton node={node} />\n {canRerun && (\n <Button\n size=\"sm\"\n disabled={runner.busy.rerun}\n onClick={() => runner.onRerun!(actionNode)}\n title=\"Rerun this test or subtree\"\n >\n <Icon icon={UiRefresh} className=\"mr-1 text-sm\" />\n {runner.busy.rerun ? \"Running...\" : \"Rerun\"}\n </Button>\n )}\n {canStop && (\n <Button\n size=\"sm\"\n variant=\"destructive\"\n disabled={runner.busy.stop}\n onClick={() => runner.onStop!(actionNode)}\n title=\"Stop this running task\"\n >\n <Icon icon={UiStop} className=\"mr-1 text-sm\" />\n {runner.busy.stop ? \"Stopping...\" : \"Stop\"}\n </Button>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * The right pane. Resolves the node's adapter for body / tabs / header actions,\n * falling back to the built-in default body. Shows an empty state when nothing\n * is selected.\n */\nexport function TestDetailPanel() {\n const runner = useTestRunner();\n const selected = runner.selected;\n const [attemptSelection, setAttemptSelection] = useState<{ key: string; id: number } | null>(null);\n // Tab state is controlled when the host supplies activeTab (e.g. to mirror it\n // into the URL); otherwise the panel owns it. The render below tolerates an\n // unknown id by falling back to the built-in \"detail\" tab.\n const controlled = runner.activeTab !== undefined;\n const [internalTab, setInternalTab] = useState<string>(\"detail\");\n const activeTab = controlled ? runner.activeTab! : internalTab;\n const setActiveTab = (id: string) => {\n if (controlled) runner.onTabChange?.(id);\n else setInternalTab(id);\n };\n\n if (!selected) {\n return (\n <div className=\"flex h-full items-center justify-center text-sm text-muted-foreground\">\n <div className=\"text-center\">\n <Icon icon={UiClass} className=\"mb-2 block text-4xl\" />\n Select a test to view its details.\n </div>\n </div>\n );\n }\n\n const attempts = runner.adapters.resolveAttempts(selected, runner);\n const key = selected.task_id ?? selected.name;\n const attemptId = runner.activeAttempt ?? (attemptSelection?.key === key ? attemptSelection.id : undefined);\n const attempt = attempts.find((a) => a.id === attemptId) ?? attempts.at(-1);\n const node = attempts.length > 1 && attempt ? attempt.node : selected;\n const attemptChooser = attempts.length > 1 && attempt ? (\n <div className=\"w-44 shrink-0\">\n <Select aria-label=\"Attempt\" value={String(attempt.id)} className=\"h-7 text-xs\" onChange={(event) => {\n const id = Number(event.target.value);\n if (runner.onAttemptChange) runner.onAttemptChange(id);\n else setAttemptSelection({ key, id });\n }} options={attempts.map((a) => ({ value: String(a.id), label: `Attempt ${a.id}/${attempts.length} · ${a.status}${a.durationMs !== undefined ? ` · ${formatTestDuration(a.durationMs * 1e6)}` : \"\"}` }))} />\n </div>\n ) : null;\n\n const detailAdapter = runner.adapters.resolveDetail(node, runner);\n const actionAdapter = runner.adapters.resolveActions(selected, runner);\n const tabs = runner.adapters.resolveTabs(node, runner);\n const ownsScroll = runner.adapters.ownsScroll(node, runner);\n\n const body = (\n <DetailTabSelectProvider value={setActiveTab}>\n {detailAdapter?.renderDetail\n ? detailAdapter.renderDetail({ node, runner })\n : <DefaultDetailBody node={node} />}\n </DetailTabSelectProvider>\n );\n const actions = actionAdapter?.nodeActions?.({ node: selected, runner });\n\n // A size container, so log blocks can cap themselves at a share of the pane (cqh).\n const detailRegion = cn(\"min-h-0 flex-1 [container-type:size]\", !ownsScroll && \"overflow-y-auto\");\n\n // No extra tabs → just the body under the header.\n if (tabs.length === 0) {\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className={detailRegion}>{body}</div>\n </div>\n );\n }\n\n const detailTab = { id: \"detail\", label: \"Detail\", render: () => body };\n const allTabs = [detailTab, ...tabs];\n const active = allTabs.find((t) => t.id === activeTab) ?? detailTab;\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className=\"flex shrink-0 items-center gap-1 border-b border-border px-density-2 py-density-1\">\n {allTabs.map((t) => (\n <TabButton\n key={t.id}\n active={active.id === t.id}\n onClick={() => setActiveTab(t.id)}\n label={t.label}\n />\n ))}\n </div>\n <div className={detailRegion}>{active.render()}</div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -17,6 +17,8 @@ export { genericCopySections, mergeCopySections, buildBrief, buildNodeBrief, bui
17
17
  export { type StatusCounts, type TestStatus, sum, sumNonTaskTests, aggregateStatusCounts, STATUS_LABELS, hasFailed, hasPending, testStatus, totalDuration, outcomeFor, type TestOutcome, statusToneFor, formatTestDuration, formatCount, humanizeName, collectFrameworks, collapseSingleChildChains, filterTests, } from './status.js';
18
18
  export { TEST_OUTCOMES, LINT_SEVERITIES, TEST_TOOLS, toolMark, toolKeyFor, type ResultMark, type LintSeverity, type ToolKind, type ToolMark, } from './marks.js';
19
19
  export { OutcomeIcon, LintSeverityIcon, ToolIcon } from './MarkIcons.js';
20
+ export { MATRIX_MARKS, MATRIX_ORDER, MATRIX_PLAN_BAR, matrixMarkKey, repeatsMark, matrixBarClass, formatMatrixDuration, type MatrixMarkToken, type MatrixMarkKey, type MatrixCounts, type MatrixEnvCounts, } from './matrix-marks.js';
21
+ export { MatrixMark, MatrixMix, MatrixElapsed, EmptyMatrixCell, MatrixLegend, EnvChip, MatrixEnvHeader, MatrixUnavailableEnvHeader, type MatrixMarkProps, type MatrixMixProps, type MatrixElapsedProps, type EmptyMatrixCellProps, type MatrixLegendProps, type EnvChipProps, type MatrixEnvHeaderProps, type MatrixUnavailableEnvHeaderProps, } from './MatrixMarks.js';
20
22
  export { slugify, annotateRoutePaths, findNodeByRoutePath } from './routePath.js';
21
23
  export { type FilterMode, type FilterState, type TestFilters, emptyTestFilters, decodeFilterState, encodeFilterState, cycleFilterState, matchesFilterState, filterStateToRecord, recordToFilterState, } from './filterState.js';
22
24
  export type { Test, TestSummary, TestAttempt, FailureDetail, FailureKind, GoTestContext, GinkgoContext, FixtureContext, TaskContext, RunMeta, Snapshot, SnapshotGit, SnapshotStatus, } from './types.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACrF,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EACL,oBAAoB,EACpB,KAAK,mBAAmB,EACxB,KAAK,cAAc,GACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAChG,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEzE,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,KAAK,iBAAiB,GACvB,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAEjF,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,aAAa,GACnB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,UAAU,EACV,cAAc,EACd,aAAa,EACb,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,QAAQ,EACb,KAAK,YAAY,GAClB,MAAM,QAAQ,CAAC;AAEhB,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,GAAG,EACH,eAAe,EACf,qBAAqB,EACrB,aAAa,EACb,SAAS,EACT,UAAU,EACV,UAAU,EACV,aAAa,EACb,UAAU,EACV,KAAK,WAAW,EAChB,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,yBAAyB,EACzB,WAAW,GACZ,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,aAAa,EACb,eAAe,EACf,UAAU,EACV,QAAQ,EACR,UAAU,EACV,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,QAAQ,EACb,KAAK,QAAQ,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEtE,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAE/E,OAAO,EACL,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,IAAI,EACJ,WAAW,EACX,WAAW,EACX,aAAa,EACb,WAAW,EACX,aAAa,EACb,aAAa,EACb,cAAc,EACd,WAAW,EACX,OAAO,EACP,QAAQ,EACR,WAAW,EACX,cAAc,GACf,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,119 @@
1
+ const require_UiWarningCircle = require("../../icons/components/UiWarningCircle.cjs");
2
+ const require_UiWarningTriangle = require("../../icons/components/UiWarningTriangle.cjs");
3
+ const require_UiCircleX = require("../../icons/components/UiCircleX.cjs");
4
+ const require_UiCircleOutline = require("../../icons/components/UiCircleOutline.cjs");
5
+ const require_UiLoader = require("../../icons/components/UiLoader.cjs");
6
+ const require_UiPass = require("../../icons/components/UiPass.cjs");
7
+ const require_UiSkipForward = require("../../icons/components/UiSkipForward.cjs");
8
+ const require_UiHourglass = require("../../icons/components/UiHourglass.cjs");
9
+ const require_UiProhibit = require("../../icons/components/UiProhibit.cjs");
10
+ // src/data/test-runner/matrix-marks.ts
11
+ var MATRIX_MARKS = {
12
+ passed: {
13
+ icon: require_UiPass.UiPass,
14
+ tone: "text-emerald-600 dark:text-emerald-400",
15
+ bar: "bg-emerald-500",
16
+ label: "passed",
17
+ attention: false
18
+ },
19
+ warned: {
20
+ icon: require_UiWarningCircle.UiWarningCircle,
21
+ tone: "text-amber-600 dark:text-amber-400",
22
+ bar: "bg-amber-400",
23
+ label: "warned",
24
+ attention: true
25
+ },
26
+ skipped: {
27
+ icon: require_UiSkipForward.UiSkipForward,
28
+ tone: "text-slate-400 dark:text-slate-500",
29
+ bar: "bg-slate-300 dark:bg-slate-600",
30
+ label: "skipped",
31
+ attention: false
32
+ },
33
+ running: {
34
+ icon: require_UiLoader.UiLoader,
35
+ tone: "text-blue-600 dark:text-blue-400",
36
+ bar: "bg-blue-500 animate-pulse",
37
+ label: "running",
38
+ attention: true,
39
+ spin: true
40
+ },
41
+ aborted: {
42
+ icon: require_UiProhibit.UiProhibit,
43
+ tone: "text-red-400 dark:text-red-300",
44
+ bar: "bg-red-300 dark:bg-red-800",
45
+ label: "aborted",
46
+ attention: true
47
+ },
48
+ failed: {
49
+ icon: require_UiCircleX.UiCircleX,
50
+ tone: "text-red-600 dark:text-red-400",
51
+ bar: "bg-red-600",
52
+ label: "failed",
53
+ attention: true
54
+ },
55
+ error: {
56
+ icon: require_UiWarningTriangle.UiWarningTriangleFilled,
57
+ tone: "text-red-600 dark:text-red-400",
58
+ bar: "bg-red-800",
59
+ label: "error",
60
+ legend: "error · never started",
61
+ attention: true
62
+ },
63
+ pending: {
64
+ icon: require_UiHourglass.UiHourglass,
65
+ tone: "text-muted-foreground",
66
+ label: "pending",
67
+ legend: "pending · queued",
68
+ attention: false
69
+ },
70
+ absent: {
71
+ icon: require_UiCircleOutline.UiCircleOutline,
72
+ tone: "text-muted-foreground/60",
73
+ label: "not in this run",
74
+ attention: false
75
+ }
76
+ };
77
+ var MATRIX_ORDER = Object.keys(MATRIX_MARKS);
78
+ /** The plan outcomes an environment header's progress bar fills, left to right. */
79
+ var MATRIX_PLAN_BAR = [
80
+ "passed",
81
+ "warned",
82
+ "failed",
83
+ "error",
84
+ "running"
85
+ ];
86
+ function matrixMarkKey(outcome) {
87
+ if (!Object.hasOwn(MATRIX_MARKS, outcome)) throw new Error(`matrix has no mark for outcome "${outcome}"`);
88
+ return outcome;
89
+ }
90
+ /**
91
+ * Whether a mix holds only the outcome a mark beside it already shows, so
92
+ * drawing both would repeat the glyph.
93
+ */
94
+ function repeatsMark(counts, mark) {
95
+ const nonZero = MATRIX_ORDER.filter((key) => (counts[key] ?? 0) > 0);
96
+ return nonZero.length === 1 && nonZero[0] === mark;
97
+ }
98
+ /** A mark's progress-bar fill; static (print) drops its animate-* classes. */
99
+ function matrixBarClass(mark, isStatic) {
100
+ const bar = MATRIX_MARKS[mark].bar;
101
+ return isStatic ? bar.split(" ").filter((c) => !c.startsWith("animate-")).join(" ") : bar;
102
+ }
103
+ /** The matrix's one duration format: 42s, 4m 12s, 1h 03m. */
104
+ function formatMatrixDuration(ms) {
105
+ const s = Math.round(ms / 1e3);
106
+ if (s < 60) return `${s}s`;
107
+ const m = Math.floor(s / 60);
108
+ if (m < 60) return `${m}m ${String(s % 60).padStart(2, "0")}s`;
109
+ return `${Math.floor(m / 60)}h ${String(m % 60).padStart(2, "0")}m`;
110
+ }
111
+ exports.MATRIX_MARKS = MATRIX_MARKS;
112
+ exports.MATRIX_ORDER = MATRIX_ORDER;
113
+ exports.MATRIX_PLAN_BAR = MATRIX_PLAN_BAR;
114
+ exports.formatMatrixDuration = formatMatrixDuration;
115
+ exports.matrixBarClass = matrixBarClass;
116
+ exports.matrixMarkKey = matrixMarkKey;
117
+ exports.repeatsMark = repeatsMark;
118
+
119
+ //# sourceMappingURL=matrix-marks.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matrix-marks.cjs","names":["UiPass","UiWarningCircle","UiSkipForward","UiLoader","UiProhibit","UiCircleX","UiWarningTriangleFilled","UiHourglass","UiCircleOutline"],"sources":["../../../src/data/test-runner/matrix-marks.ts"],"sourcesContent":["// The glyph vocabulary of a bulk test matrix (plans × environments): plan\n// cells, sub-steps, the environment header's verdict row and the legend all\n// read MATRIX_MARKS, in this order. The live web matrix and its printed report\n// draw from this one table. MatrixMarks.tsx renders it; this file holds only\n// the tokens and pure helpers (react/only-export-components).\n\nimport {\n UiCircleOutline,\n UiCircleX,\n UiHourglass,\n UiLoader,\n UiPass,\n UiProhibit,\n UiSkipForward,\n UiWarningCircle,\n UiWarningTriangleFilled,\n type IconComponent,\n} from \"../../icons\";\n\nexport type MatrixMarkToken = {\n icon: IconComponent;\n tone: string;\n /** Progress-bar fill; absent for states a bar leaves as its track. */\n bar?: string;\n label: string;\n legend?: string;\n /** Whether a sub-step spells the outcome out beside its glyph. */\n attention: boolean;\n spin?: boolean;\n};\n\nexport const MATRIX_MARKS = {\n passed: { icon: UiPass, tone: \"text-emerald-600 dark:text-emerald-400\", bar: \"bg-emerald-500\", label: \"passed\", attention: false },\n warned: { icon: UiWarningCircle, tone: \"text-amber-600 dark:text-amber-400\", bar: \"bg-amber-400\", label: \"warned\", attention: true },\n skipped: { icon: UiSkipForward, tone: \"text-slate-400 dark:text-slate-500\", bar: \"bg-slate-300 dark:bg-slate-600\", label: \"skipped\", attention: false },\n running: { icon: UiLoader, tone: \"text-blue-600 dark:text-blue-400\", bar: \"bg-blue-500 animate-pulse\", label: \"running\", attention: true, spin: true },\n aborted: { icon: UiProhibit, tone: \"text-red-400 dark:text-red-300\", bar: \"bg-red-300 dark:bg-red-800\", label: \"aborted\", attention: true },\n failed: { icon: UiCircleX, tone: \"text-red-600 dark:text-red-400\", bar: \"bg-red-600\", label: \"failed\", attention: true },\n error: { icon: UiWarningTriangleFilled, tone: \"text-red-600 dark:text-red-400\", bar: \"bg-red-800\", label: \"error\", legend: \"error · never started\", attention: true },\n pending: { icon: UiHourglass, tone: \"text-muted-foreground\", label: \"pending\", legend: \"pending · queued\", attention: false },\n absent: { icon: UiCircleOutline, tone: \"text-muted-foreground/60\", label: \"not in this run\", attention: false },\n} satisfies Record<string, MatrixMarkToken>;\n\nexport type MatrixMarkKey = keyof typeof MATRIX_MARKS;\nexport type MatrixCounts = Partial<Record<MatrixMarkKey, number>>;\n\nexport const MATRIX_ORDER = Object.keys(MATRIX_MARKS) as readonly MatrixMarkKey[];\n\n/** The plan outcomes an environment header's progress bar fills, left to right. */\nexport const MATRIX_PLAN_BAR = [\"passed\", \"warned\", \"failed\", \"error\", \"running\"] as const;\n\n/** One environment's plan totals, as its matrix column header reads them. */\nexport type MatrixEnvCounts = {\n passed: number;\n warned: number;\n failed: number;\n error: number;\n running: number;\n queued: number;\n total: number;\n};\n\nexport function matrixMarkKey(outcome: string): MatrixMarkKey {\n if (!Object.hasOwn(MATRIX_MARKS, outcome)) throw new Error(`matrix has no mark for outcome \"${outcome}\"`);\n return outcome as MatrixMarkKey;\n}\n\n/**\n * Whether a mix holds only the outcome a mark beside it already shows, so\n * drawing both would repeat the glyph.\n */\nexport function repeatsMark(counts: MatrixCounts, mark: MatrixMarkKey): boolean {\n const nonZero = MATRIX_ORDER.filter((key) => (counts[key] ?? 0) > 0);\n return nonZero.length === 1 && nonZero[0] === mark;\n}\n\n/** A mark's progress-bar fill; static (print) drops its animate-* classes. */\nexport function matrixBarClass(mark: (typeof MATRIX_PLAN_BAR)[number], isStatic: boolean): string {\n const bar: string = MATRIX_MARKS[mark].bar;\n return isStatic ? bar.split(\" \").filter((c) => !c.startsWith(\"animate-\")).join(\" \") : bar;\n}\n\n/** The matrix's one duration format: 42s, 4m 12s, 1h 03m. */\nexport function formatMatrixDuration(ms: number): string {\n const s = Math.round(ms / 1000);\n if (s < 60) return `${s}s`;\n const m = Math.floor(s / 60);\n if (m < 60) return `${m}m ${String(s % 60).padStart(2, \"0\")}s`;\n return `${Math.floor(m / 60)}h ${String(m % 60).padStart(2, \"0\")}m`;\n}\n"],"mappings":";;;;;;;;;;AA+BA,IAAa,eAAe;CAC1B,QAAQ;EAAE,MAAMA,eAAAA;EAAQ,MAAM;EAA0C,KAAK;EAAkB,OAAO;EAAU,WAAW;CAAM;CACjI,QAAQ;EAAE,MAAMC,wBAAAA;EAAiB,MAAM;EAAsC,KAAK;EAAgB,OAAO;EAAU,WAAW;CAAK;CACnI,SAAS;EAAE,MAAMC,sBAAAA;EAAe,MAAM;EAAsC,KAAK;EAAkC,OAAO;EAAW,WAAW;CAAM;CACtJ,SAAS;EAAE,MAAMC,iBAAAA;EAAU,MAAM;EAAoC,KAAK;EAA6B,OAAO;EAAW,WAAW;EAAM,MAAM;CAAK;CACrJ,SAAS;EAAE,MAAMC,mBAAAA;EAAY,MAAM;EAAkC,KAAK;EAA8B,OAAO;EAAW,WAAW;CAAK;CAC1I,QAAQ;EAAE,MAAMC,kBAAAA;EAAW,MAAM;EAAkC,KAAK;EAAc,OAAO;EAAU,WAAW;CAAK;CACvH,OAAO;EAAE,MAAMC,0BAAAA;EAAyB,MAAM;EAAkC,KAAK;EAAc,OAAO;EAAS,QAAQ;EAAyB,WAAW;CAAK;CACpK,SAAS;EAAE,MAAMC,oBAAAA;EAAa,MAAM;EAAyB,OAAO;EAAW,QAAQ;EAAoB,WAAW;CAAM;CAC5H,QAAQ;EAAE,MAAMC,wBAAAA;EAAiB,MAAM;EAA4B,OAAO;EAAmB,WAAW;CAAM;AAChH;AAKA,IAAa,eAAe,OAAO,KAAK,YAAY;;AAGpD,IAAa,kBAAkB;CAAC;CAAU;CAAU;CAAU;CAAS;AAAS;AAahF,SAAgB,cAAc,SAAgC;CAC5D,IAAI,CAAC,OAAO,OAAO,cAAc,OAAO,GAAG,MAAM,IAAI,MAAM,mCAAmC,QAAQ,EAAE;CACxG,OAAO;AACT;;;;;AAMA,SAAgB,YAAY,QAAsB,MAA8B;CAC9E,MAAM,UAAU,aAAa,QAAQ,SAAS,OAAO,QAAQ,KAAK,CAAC;CACnE,OAAO,QAAQ,WAAW,KAAK,QAAQ,OAAO;AAChD;;AAGA,SAAgB,eAAe,MAAwC,UAA2B;CAChG,MAAM,MAAc,aAAa,KAAK,CAAC;CACvC,OAAO,WAAW,IAAI,MAAM,GAAG,CAAC,CAAC,QAAQ,MAAM,CAAC,EAAE,WAAW,UAAU,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI;AACxF;;AAGA,SAAgB,qBAAqB,IAAoB;CACvD,MAAM,IAAI,KAAK,MAAM,KAAK,GAAI;CAC9B,IAAI,IAAI,IAAI,OAAO,GAAG,EAAE;CACxB,MAAM,IAAI,KAAK,MAAM,IAAI,EAAE;CAC3B,IAAI,IAAI,IAAI,OAAO,GAAG,EAAE,IAAI,OAAO,IAAI,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,EAAE;CAC5D,OAAO,GAAG,KAAK,MAAM,IAAI,EAAE,EAAE,IAAI,OAAO,IAAI,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,EAAE;AACnE"}