@flanksource/clicky-ui 0.3.47 → 0.3.48

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 (169) hide show
  1. package/README.md +49 -0
  2. package/dist/comments/CommentSidePanel.cjs +10 -4
  3. package/dist/comments/CommentSidePanel.cjs.map +1 -1
  4. package/dist/comments/CommentSidePanel.d.ts +2 -0
  5. package/dist/comments/CommentSidePanel.d.ts.map +1 -1
  6. package/dist/comments/CommentSidePanel.js +10 -4
  7. package/dist/comments/CommentSidePanel.js.map +1 -1
  8. package/dist/comments/CommentThread.cjs +2 -0
  9. package/dist/comments/CommentThread.cjs.map +1 -1
  10. package/dist/comments/CommentThread.d.ts +2 -1
  11. package/dist/comments/CommentThread.d.ts.map +1 -1
  12. package/dist/comments/CommentThread.js +2 -0
  13. package/dist/comments/CommentThread.js.map +1 -1
  14. package/dist/comments/CommentThreadList.cjs +3 -0
  15. package/dist/comments/CommentThreadList.cjs.map +1 -1
  16. package/dist/comments/CommentThreadList.d.ts +3 -1
  17. package/dist/comments/CommentThreadList.d.ts.map +1 -1
  18. package/dist/comments/CommentThreadList.js +3 -0
  19. package/dist/comments/CommentThreadList.js.map +1 -1
  20. package/dist/data/CodeLine.cjs +4 -2
  21. package/dist/data/CodeLine.cjs.map +1 -1
  22. package/dist/data/CodeLine.d.ts.map +1 -1
  23. package/dist/data/CodeLine.js +4 -2
  24. package/dist/data/CodeLine.js.map +1 -1
  25. package/dist/data/SymbolBadge.cjs +61 -0
  26. package/dist/data/SymbolBadge.cjs.map +1 -0
  27. package/dist/data/SymbolBadge.d.ts +21 -0
  28. package/dist/data/SymbolBadge.d.ts.map +1 -0
  29. package/dist/data/SymbolBadge.js +61 -0
  30. package/dist/data/SymbolBadge.js.map +1 -0
  31. package/dist/data/test-runner/TestDetailPanel.cjs +26 -12
  32. package/dist/data/test-runner/TestDetailPanel.cjs.map +1 -1
  33. package/dist/data/test-runner/TestDetailPanel.d.ts.map +1 -1
  34. package/dist/data/test-runner/TestDetailPanel.js +27 -13
  35. package/dist/data/test-runner/TestDetailPanel.js.map +1 -1
  36. package/dist/data/test-runner/TestRunner.cjs +6 -0
  37. package/dist/data/test-runner/TestRunner.cjs.map +1 -1
  38. package/dist/data/test-runner/TestRunner.d.ts +3 -1
  39. package/dist/data/test-runner/TestRunner.d.ts.map +1 -1
  40. package/dist/data/test-runner/TestRunner.js +6 -0
  41. package/dist/data/test-runner/TestRunner.js.map +1 -1
  42. package/dist/data/test-runner/TestTreeNode.cjs +2 -1
  43. package/dist/data/test-runner/TestTreeNode.cjs.map +1 -1
  44. package/dist/data/test-runner/TestTreeNode.d.ts.map +1 -1
  45. package/dist/data/test-runner/TestTreeNode.js +2 -1
  46. package/dist/data/test-runner/TestTreeNode.js.map +1 -1
  47. package/dist/data/test-runner/adapter.cjs +4 -0
  48. package/dist/data/test-runner/adapter.cjs.map +1 -1
  49. package/dist/data/test-runner/adapter.d.ts +9 -0
  50. package/dist/data/test-runner/adapter.d.ts.map +1 -1
  51. package/dist/data/test-runner/adapter.js +4 -0
  52. package/dist/data/test-runner/adapter.js.map +1 -1
  53. package/dist/data/test-runner/context.cjs.map +1 -1
  54. package/dist/data/test-runner/context.d.ts +2 -0
  55. package/dist/data/test-runner/context.d.ts.map +1 -1
  56. package/dist/data/test-runner/context.js.map +1 -1
  57. package/dist/data/test-runner/index.d.ts +2 -2
  58. package/dist/data/test-runner/index.d.ts.map +1 -1
  59. package/dist/data/test-runner/marks.cjs +42 -1
  60. package/dist/data/test-runner/marks.cjs.map +1 -1
  61. package/dist/data/test-runner/marks.d.ts +10 -0
  62. package/dist/data/test-runner/marks.d.ts.map +1 -1
  63. package/dist/data/test-runner/marks.js +42 -1
  64. package/dist/data/test-runner/marks.js.map +1 -1
  65. package/dist/data/traces/TerminalCastPlayer.d.ts +21 -0
  66. package/dist/data/traces/TerminalCastPlayer.d.ts.map +1 -0
  67. package/dist/data/traces/TraceHarView.d.ts +13 -0
  68. package/dist/data/traces/TraceHarView.d.ts.map +1 -0
  69. package/dist/data/traces/cast.d.ts +9 -0
  70. package/dist/data/traces/cast.d.ts.map +1 -0
  71. package/dist/data/traces/fixtures.d.ts +8 -0
  72. package/dist/data/traces/fixtures.d.ts.map +1 -0
  73. package/dist/data/traces/har-adapter.d.ts +13 -0
  74. package/dist/data/traces/har-adapter.d.ts.map +1 -0
  75. package/dist/data/traces/terminal-screen.d.ts +45 -0
  76. package/dist/data/traces/terminal-screen.d.ts.map +1 -0
  77. package/dist/data/traces/terminal-timeline.d.ts +26 -0
  78. package/dist/data/traces/terminal-timeline.d.ts.map +1 -0
  79. package/dist/data/traces/types.d.ts +141 -0
  80. package/dist/data/traces/types.d.ts.map +1 -0
  81. package/dist/data/verification/VerificationResults.cjs +4 -0
  82. package/dist/data/verification/VerificationResults.cjs.map +1 -1
  83. package/dist/data/verification/VerificationResults.d.ts +3 -1
  84. package/dist/data/verification/VerificationResults.d.ts.map +1 -1
  85. package/dist/data/verification/VerificationResults.js +4 -0
  86. package/dist/data/verification/VerificationResults.js.map +1 -1
  87. package/dist/data/verification/adapterViews.cjs +12 -3
  88. package/dist/data/verification/adapterViews.cjs.map +1 -1
  89. package/dist/data/verification/adapterViews.d.ts +4 -1
  90. package/dist/data/verification/adapterViews.d.ts.map +1 -1
  91. package/dist/data/verification/adapterViews.js +12 -3
  92. package/dist/data/verification/adapterViews.js.map +1 -1
  93. package/dist/data/verification/adapters.cjs +53 -10
  94. package/dist/data/verification/adapters.cjs.map +1 -1
  95. package/dist/data/verification/adapters.d.ts +7 -1
  96. package/dist/data/verification/adapters.d.ts.map +1 -1
  97. package/dist/data/verification/adapters.js +53 -10
  98. package/dist/data/verification/adapters.js.map +1 -1
  99. package/dist/data/verification/index.d.ts +4 -2
  100. package/dist/data/verification/index.d.ts.map +1 -1
  101. package/dist/data/verification/stepKind.cjs +217 -0
  102. package/dist/data/verification/stepKind.cjs.map +1 -0
  103. package/dist/data/verification/stepKind.d.ts +87 -0
  104. package/dist/data/verification/stepKind.d.ts.map +1 -0
  105. package/dist/data/verification/stepKind.js +217 -0
  106. package/dist/data/verification/stepKind.js.map +1 -0
  107. package/dist/data/verification/stepKindViews.cjs +111 -0
  108. package/dist/data/verification/stepKindViews.cjs.map +1 -0
  109. package/dist/data/verification/stepKindViews.d.ts +18 -0
  110. package/dist/data/verification/stepKindViews.d.ts.map +1 -0
  111. package/dist/data/verification/stepKindViews.js +111 -0
  112. package/dist/data/verification/stepKindViews.js.map +1 -0
  113. package/dist/data/verification/verifyReportTests.cjs +1 -0
  114. package/dist/data/verification/verifyReportTests.cjs.map +1 -1
  115. package/dist/data/verification/verifyReportTests.d.ts +2 -1
  116. package/dist/data/verification/verifyReportTests.d.ts.map +1 -1
  117. package/dist/data/verification/verifyReportTests.js +1 -0
  118. package/dist/data/verification/verifyReportTests.js.map +1 -1
  119. package/dist/data/version-info.cjs +3 -3
  120. package/dist/data/version-info.js +3 -3
  121. package/dist/data.cjs +14 -0
  122. package/dist/data.cjs.map +1 -1
  123. package/dist/data.d.ts +1 -0
  124. package/dist/data.d.ts.map +1 -1
  125. package/dist/data.js +16 -2
  126. package/dist/data.js.map +1 -1
  127. package/dist/index.cjs +16 -0
  128. package/dist/index.cjs.map +1 -1
  129. package/dist/index.js +18 -2
  130. package/dist/index.js.map +1 -1
  131. package/dist/rpc/FilterForm.cjs +9 -0
  132. package/dist/rpc/FilterForm.cjs.map +1 -1
  133. package/dist/rpc/FilterForm.d.ts +5 -1
  134. package/dist/rpc/FilterForm.d.ts.map +1 -1
  135. package/dist/rpc/FilterForm.js +9 -0
  136. package/dist/rpc/FilterForm.js.map +1 -1
  137. package/dist/rpc/OperationArgsForm.cjs +153 -0
  138. package/dist/rpc/OperationArgsForm.cjs.map +1 -0
  139. package/dist/rpc/OperationArgsForm.d.ts +16 -0
  140. package/dist/rpc/OperationArgsForm.d.ts.map +1 -0
  141. package/dist/rpc/OperationArgsForm.js +153 -0
  142. package/dist/rpc/OperationArgsForm.js.map +1 -0
  143. package/dist/rpc/OperationScheduleDialog.cjs +61 -17
  144. package/dist/rpc/OperationScheduleDialog.cjs.map +1 -1
  145. package/dist/rpc/OperationScheduleDialog.d.ts +3 -0
  146. package/dist/rpc/OperationScheduleDialog.d.ts.map +1 -1
  147. package/dist/rpc/OperationScheduleDialog.js +62 -18
  148. package/dist/rpc/OperationScheduleDialog.js.map +1 -1
  149. package/dist/rpc/OperationSchedules.cjs +6 -1
  150. package/dist/rpc/OperationSchedules.cjs.map +1 -1
  151. package/dist/rpc/OperationSchedules.d.ts.map +1 -1
  152. package/dist/rpc/OperationSchedules.js +6 -1
  153. package/dist/rpc/OperationSchedules.js.map +1 -1
  154. package/dist/rpc/operation-schedule-model.cjs +0 -7
  155. package/dist/rpc/operation-schedule-model.cjs.map +1 -1
  156. package/dist/rpc/operation-schedule-model.d.ts +0 -2
  157. package/dist/rpc/operation-schedule-model.d.ts.map +1 -1
  158. package/dist/rpc/operation-schedule-model.js +0 -7
  159. package/dist/rpc/operation-schedule-model.js.map +1 -1
  160. package/dist/rpc/operation-schedule-types.d.ts +6 -1
  161. package/dist/rpc/operation-schedule-types.d.ts.map +1 -1
  162. package/dist/rpc.cjs +2 -0
  163. package/dist/rpc.cjs.map +1 -1
  164. package/dist/rpc.d.ts +1 -0
  165. package/dist/rpc.d.ts.map +1 -1
  166. package/dist/rpc.js +2 -0
  167. package/dist/rpc.js.map +1 -1
  168. package/dist/styles.css +1 -1
  169. package/package.json +1 -1
@@ -5,6 +5,7 @@ import { IconButton } from "../../components/IconButton.js";
5
5
  import { cn } from "../../lib/utils.js";
6
6
  import { useTestRunner } from "./context.js";
7
7
  import { testStatus, outcomeFor, totalDuration, humanizeName, formatTestDuration, sum } from "./status.js";
8
+ import { toolKeyFor } from "./marks.js";
8
9
  import { OutcomeIcon, ToolIcon } from "./MarkIcons.js";
9
10
  import { UiClass } from "../../icons/components/UiClass.js";
10
11
  import { UiStop } from "../../icons/components/UiStop.js";
@@ -47,7 +48,7 @@ function TestTreeNode({ node, selected }) {
47
48
  const message = status && MESSAGE_STATUSES.has(status) ? (node.message ?? "").trim() : "";
48
49
  return /* @__PURE__ */ jsxs(Fragment, { children: [
49
50
  outcome ? /* @__PURE__ */ jsx(OutcomeIcon, { outcome }) : /* @__PURE__ */ jsx(Icon, { icon: UiClass, className: "shrink-0 text-muted-foreground" }),
50
- fwNode != null ? /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center text-sm", children: fwNode }) : /* @__PURE__ */ jsx(ToolIcon, { tool: node.framework, className: "text-sm" }),
51
+ fwNode != null ? /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center text-sm", children: fwNode }) : /* @__PURE__ */ jsx(ToolIcon, { tool: toolKeyFor(node), className: "text-sm" }),
51
52
  (_c = leadingAdapter == null ? void 0 : leadingAdapter.renderRowLeading) == null ? void 0 : _c.call(leadingAdapter, { node, runner }),
52
53
  /* @__PURE__ */ jsx(
53
54
  "span",
@@ -1 +1 @@
1
- {"version":3,"file":"TestTreeNode.js","sources":["../../../src/data/test-runner/TestTreeNode.tsx"],"sourcesContent":["import { Icon } from \"../Icon\";\nimport { Badge } from \"../Badge\";\nimport { IconButton } from \"../../components/IconButton\";\nimport { UiClass, UiRefresh, UiStop } from \"../../icons\";\nimport { cn } from \"../../lib/utils\";\nimport type { Test } from \"./types\";\nimport { useTestRunner } from \"./context\";\nimport {\n formatTestDuration,\n humanizeName,\n outcomeFor,\n sum,\n testStatus,\n totalDuration,\n} from \"./status\";\nimport { OutcomeIcon, ToolIcon } from \"./MarkIcons\";\n\nconst NAME_TONE: Record<string, string> = {\n failed: \"text-red-600 dark:text-red-400\",\n timedout: \"text-amber-600 dark:text-amber-400\",\n warned: \"text-amber-600 dark:text-amber-400\",\n running: \"text-blue-600 dark:text-blue-400\",\n skipped: \"text-yellow-700 dark:text-yellow-500\",\n pending: \"text-muted-foreground\",\n passed: \"text-foreground\",\n};\n\nconst MESSAGE_STATUSES = new Set([\"failed\", \"timedout\", \"warned\", \"skipped\"]);\n\n/** Per-status roll-up count badges shown on a container row. */\nfunction CountBadges({ node }: { node: Test }) {\n const s = sum(node);\n if (s.total === 0) return null;\n return (\n <span className=\"flex shrink-0 items-center gap-1\">\n {s.passed > 0 && <Badge tone=\"success\" variant=\"solid\" size=\"xxs\" count={s.passed} />}\n {s.failed > 0 && <Badge tone=\"danger\" variant=\"solid\" size=\"xxs\" count={s.failed} />}\n {s.warned > 0 && <Badge tone=\"warning\" variant=\"outline\" size=\"xxs\" count={s.warned} />}\n {s.timedout > 0 && <Badge tone=\"warning\" variant=\"solid\" size=\"xxs\" count={s.timedout} />}\n {s.skipped > 0 && <Badge tone=\"warning\" variant=\"soft\" size=\"xxs\" count={s.skipped} />}\n {s.running > 0 && <Badge tone=\"info\" variant=\"solid\" size=\"xxs\" count={s.running} />}\n {s.pending > 0 && <Badge tone=\"neutral\" variant=\"soft\" size=\"xxs\" count={s.pending} />}\n </span>\n );\n}\n\n/**\n * Renders one test tree row: status icon, optional adapter decoration + framework\n * icon, status-colored name, duration, per-status count badges, and (when the\n * runner exposes the handlers) rerun/stop buttons. Designed as the `renderRow`\n * for the clicky `Tree`.\n */\nexport function TestTreeNode({ node, selected }: { node: Test; selected: boolean }) {\n const runner = useTestRunner();\n const hasChildren = (node.children?.length ?? 0) > 0;\n const status = testStatus(node);\n const outcome = outcomeFor(node);\n const fwAdapter = runner.adapters.resolveFrameworkIcon(node, runner);\n const fwNode = fwAdapter?.renderFrameworkIcon?.({ node, runner });\n const leadingAdapter = runner.adapters.resolveRowLeading(node, runner);\n const dur = node.duration || (hasChildren ? totalDuration(node) : 0);\n const canRerun = !!runner.onRerun && node.framework !== \"task\";\n const canStop = !!runner.onStop && !!node.can_stop && !!node.task_id;\n // A verdict line under the name for rows that need explaining; green,\n // running, and queued rows stay single-line.\n const message = status && MESSAGE_STATUSES.has(status) ? (node.message ?? \"\").trim() : \"\";\n\n return (\n <>\n {outcome ? (\n <OutcomeIcon outcome={outcome} />\n ) : (\n <Icon icon={UiClass} className=\"shrink-0 text-muted-foreground\" />\n )}\n\n {fwNode != null ? (\n <span className=\"flex shrink-0 items-center text-sm\">{fwNode}</span>\n ) : (\n <ToolIcon tool={node.framework} className=\"text-sm\" />\n )}\n\n {leadingAdapter?.renderRowLeading?.({ node, runner })}\n\n <span\n className={cn(\n \"min-w-0 truncate\",\n selected ? \"font-semibold\" : \"font-medium\",\n (status && NAME_TONE[status]) || \"text-foreground\",\n )}\n title={message || undefined}\n >\n {humanizeName(node.name, node.framework)}\n </span>\n {/* The verdict trails the name on the same line and gives way first. */}\n {message ? (\n <span\n className=\"min-w-0 flex-1 shrink-[100] truncate text-xs text-muted-foreground\"\n title={message}\n data-row-message=\"\"\n >\n {message}\n </span>\n ) : (\n <span className=\"flex-1\" />\n )}\n\n {node.progress && node.progress.total > 0 && !node.passed && !node.failed && (\n <span className=\"flex shrink-0 items-center gap-1 text-xs text-blue-600 dark:text-blue-400\">\n {node.progress.phase && <span>{node.progress.phase}</span>}\n <span className=\"tabular-nums\">\n {node.progress.done}/{node.progress.total}\n </span>\n </span>\n )}\n\n {dur > 0 && (\n <span className=\"shrink-0 text-xs text-muted-foreground\">{formatTestDuration(dur)}</span>\n )}\n\n {hasChildren && <CountBadges node={node} />}\n\n {canStop && (\n <IconButton\n icon={UiStop}\n label=\"Stop\"\n disabled={runner.busy.stop}\n onClick={(e) => {\n e.stopPropagation();\n runner.onStop!(node);\n }}\n />\n )}\n\n {canRerun && (\n <IconButton\n icon={UiRefresh}\n label=\"Rerun\"\n disabled={runner.busy.rerun}\n onClick={(e) => {\n e.stopPropagation();\n runner.onRerun!(node);\n }}\n />\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TestTreeNode.js","sources":["../../../src/data/test-runner/TestTreeNode.tsx"],"sourcesContent":["import { Icon } from \"../Icon\";\nimport { Badge } from \"../Badge\";\nimport { IconButton } from \"../../components/IconButton\";\nimport { UiClass, UiRefresh, UiStop } from \"../../icons\";\nimport { cn } from \"../../lib/utils\";\nimport type { Test } from \"./types\";\nimport { useTestRunner } from \"./context\";\nimport {\n formatTestDuration,\n humanizeName,\n outcomeFor,\n sum,\n testStatus,\n totalDuration,\n} from \"./status\";\nimport { toolKeyFor } from \"./marks\";\nimport { OutcomeIcon, ToolIcon } from \"./MarkIcons\";\n\nconst NAME_TONE: Record<string, string> = {\n failed: \"text-red-600 dark:text-red-400\",\n timedout: \"text-amber-600 dark:text-amber-400\",\n warned: \"text-amber-600 dark:text-amber-400\",\n running: \"text-blue-600 dark:text-blue-400\",\n skipped: \"text-yellow-700 dark:text-yellow-500\",\n pending: \"text-muted-foreground\",\n passed: \"text-foreground\",\n};\n\nconst MESSAGE_STATUSES = new Set([\"failed\", \"timedout\", \"warned\", \"skipped\"]);\n\n/** Per-status roll-up count badges shown on a container row. */\nfunction CountBadges({ node }: { node: Test }) {\n const s = sum(node);\n if (s.total === 0) return null;\n return (\n <span className=\"flex shrink-0 items-center gap-1\">\n {s.passed > 0 && <Badge tone=\"success\" variant=\"solid\" size=\"xxs\" count={s.passed} />}\n {s.failed > 0 && <Badge tone=\"danger\" variant=\"solid\" size=\"xxs\" count={s.failed} />}\n {s.warned > 0 && <Badge tone=\"warning\" variant=\"outline\" size=\"xxs\" count={s.warned} />}\n {s.timedout > 0 && <Badge tone=\"warning\" variant=\"solid\" size=\"xxs\" count={s.timedout} />}\n {s.skipped > 0 && <Badge tone=\"warning\" variant=\"soft\" size=\"xxs\" count={s.skipped} />}\n {s.running > 0 && <Badge tone=\"info\" variant=\"solid\" size=\"xxs\" count={s.running} />}\n {s.pending > 0 && <Badge tone=\"neutral\" variant=\"soft\" size=\"xxs\" count={s.pending} />}\n </span>\n );\n}\n\n/**\n * Renders one test tree row: status icon, optional adapter decoration + framework\n * icon, status-colored name, duration, per-status count badges, and (when the\n * runner exposes the handlers) rerun/stop buttons. Designed as the `renderRow`\n * for the clicky `Tree`.\n */\nexport function TestTreeNode({ node, selected }: { node: Test; selected: boolean }) {\n const runner = useTestRunner();\n const hasChildren = (node.children?.length ?? 0) > 0;\n const status = testStatus(node);\n const outcome = outcomeFor(node);\n const fwAdapter = runner.adapters.resolveFrameworkIcon(node, runner);\n const fwNode = fwAdapter?.renderFrameworkIcon?.({ node, runner });\n const leadingAdapter = runner.adapters.resolveRowLeading(node, runner);\n const dur = node.duration || (hasChildren ? totalDuration(node) : 0);\n const canRerun = !!runner.onRerun && node.framework !== \"task\";\n const canStop = !!runner.onStop && !!node.can_stop && !!node.task_id;\n // A verdict line under the name for rows that need explaining; green,\n // running, and queued rows stay single-line.\n const message = status && MESSAGE_STATUSES.has(status) ? (node.message ?? \"\").trim() : \"\";\n\n return (\n <>\n {outcome ? (\n <OutcomeIcon outcome={outcome} />\n ) : (\n <Icon icon={UiClass} className=\"shrink-0 text-muted-foreground\" />\n )}\n\n {fwNode != null ? (\n <span className=\"flex shrink-0 items-center text-sm\">{fwNode}</span>\n ) : (\n <ToolIcon tool={toolKeyFor(node)} className=\"text-sm\" />\n )}\n\n {leadingAdapter?.renderRowLeading?.({ node, runner })}\n\n <span\n className={cn(\n \"min-w-0 truncate\",\n selected ? \"font-semibold\" : \"font-medium\",\n (status && NAME_TONE[status]) || \"text-foreground\",\n )}\n title={message || undefined}\n >\n {humanizeName(node.name, node.framework)}\n </span>\n {/* The verdict trails the name on the same line and gives way first. */}\n {message ? (\n <span\n className=\"min-w-0 flex-1 shrink-[100] truncate text-xs text-muted-foreground\"\n title={message}\n data-row-message=\"\"\n >\n {message}\n </span>\n ) : (\n <span className=\"flex-1\" />\n )}\n\n {node.progress && node.progress.total > 0 && !node.passed && !node.failed && (\n <span className=\"flex shrink-0 items-center gap-1 text-xs text-blue-600 dark:text-blue-400\">\n {node.progress.phase && <span>{node.progress.phase}</span>}\n <span className=\"tabular-nums\">\n {node.progress.done}/{node.progress.total}\n </span>\n </span>\n )}\n\n {dur > 0 && (\n <span className=\"shrink-0 text-xs text-muted-foreground\">{formatTestDuration(dur)}</span>\n )}\n\n {hasChildren && <CountBadges node={node} />}\n\n {canStop && (\n <IconButton\n icon={UiStop}\n label=\"Stop\"\n disabled={runner.busy.stop}\n onClick={(e) => {\n e.stopPropagation();\n runner.onStop!(node);\n }}\n />\n )}\n\n {canRerun && (\n <IconButton\n icon={UiRefresh}\n label=\"Rerun\"\n disabled={runner.busy.rerun}\n onClick={(e) => {\n e.stopPropagation();\n runner.onRerun!(node);\n }}\n />\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -28,6 +28,10 @@ class TestNodeAdapterRegistry {
28
28
  resolveActions(node, runner) {
29
29
  return this.firstWith(node, runner, (a) => !!a.nodeActions);
30
30
  }
31
+ resolveAttempts(node, runner) {
32
+ var _a, _b;
33
+ return ((_b = (_a = this.firstWith(node, runner, (a) => !!a.detailAttempts)) == null ? void 0 : _a.detailAttempts) == null ? void 0 : _b.call(_a, { node, runner })) ?? [];
34
+ }
31
35
  /** Merge `detailTabs` from every matching adapter, de-duplicated by tab id. */
32
36
  resolveTabs(node, runner) {
33
37
  const ctx = { node, runner };
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.cjs","sources":["../../../src/data/test-runner/adapter.ts"],"sourcesContent":["// Node adapters let a host plug domain-specific rendering into the test runner\n// without forking it. An adapter claims nodes via `match` and contributes how\n// they render in each surface (tree row, detail body, detail tabs, header\n// actions). The registry resolves the first matching adapter per surface;\n// surfaces with no match fall back to the runner's built-in default rendering.\n//\n// This formalizes the wrapper-with-an-if-chain pattern hosts use today (e.g.\n// a ResultDetailPane fall-through over domain panels + tab providers + per-step\n// actions) into a registered, composable contract.\n\nimport type { ReactNode } from \"react\";\nimport type { Test } from \"./types\";\nimport type { TestRunnerContext } from \"./context\";\nimport { mergeCopySections, type CopySections } from \"./copy\";\n\nexport type AdapterContext = {\n node: Test;\n runner: TestRunnerContext;\n};\n\nexport type DetailTab = {\n id: string;\n label: string;\n render: () => ReactNode;\n};\n\nexport type TestNodeAdapter = {\n /** Stable identifier, used as a React key and for debugging. */\n id: string;\n /** Claim the node. The first registered adapter to return true wins per surface. */\n match: (node: Test, runner: TestRunnerContext) => boolean;\n /**\n * Replace the framework icon for the node — return any node, or null to fall\n * back to the built-in tool mark (`TEST_TOOLS`). For a tool the catalog\n * lacks, prefer adding it to `TEST_TOOLS` so every surface shows it.\n */\n renderFrameworkIcon?: (c: AdapterContext) => ReactNode;\n /** Extra decoration rendered between the framework icon and the node name. */\n renderRowLeading?: (c: AdapterContext) => ReactNode;\n /** Primary detail body for the node, replacing the default panel body. */\n renderDetail?: (c: AdapterContext) => ReactNode;\n /** Extra detail tabs, merged (in registration order) across all matching adapters. */\n detailTabs?: (c: AdapterContext) => DetailTab[];\n /** Node-scoped action buttons rendered in the detail header. */\n nodeActions?: (c: AdapterContext) => ReactNode;\n /**\n * Domain facts, brief sections and menu items for the header's \"Copy details\"\n * button, merged (in registration order) across all matching adapters on top\n * of the generic sections every node gets.\n */\n copy?: (c: AdapterContext) => CopySections;\n /**\n * Whether `renderDetail` owns its own full-height scroll container. When\n * false (the default for custom bodies), the panel wraps it in a scroll\n * region. The built-in default body owns its scroll.\n */\n ownsScroll?: (c: AdapterContext) => boolean;\n};\n\n/**\n * Ordered set of node adapters. First match wins per surface; `detailTabs` and\n * `copy` are the exceptions — they merge across every matching adapter so\n * independent adapters can each contribute a tab or a copy section.\n */\nexport class TestNodeAdapterRegistry {\n readonly adapters: TestNodeAdapter[];\n\n constructor(adapters: TestNodeAdapter[] = []) {\n this.adapters = adapters;\n }\n\n private firstWith(\n node: Test,\n runner: TestRunnerContext,\n has: (a: TestNodeAdapter) => boolean,\n ): TestNodeAdapter | null {\n for (const a of this.adapters) {\n if (has(a) && a.match(node, runner)) return a;\n }\n return null;\n }\n\n /** Adapter whose `renderDetail` should drive the body, or null for the default. */\n resolveDetail(node: Test, runner: TestRunnerContext): TestNodeAdapter | null {\n return this.firstWith(node, runner, (a) => !!a.renderDetail);\n }\n\n resolveRowLeading(node: Test, runner: TestRunnerContext): TestNodeAdapter | null {\n return this.firstWith(node, runner, (a) => !!a.renderRowLeading);\n }\n\n resolveFrameworkIcon(node: Test, runner: TestRunnerContext): TestNodeAdapter | null {\n return this.firstWith(node, runner, (a) => !!a.renderFrameworkIcon);\n }\n\n resolveActions(node: Test, runner: TestRunnerContext): TestNodeAdapter | null {\n return this.firstWith(node, runner, (a) => !!a.nodeActions);\n }\n\n /** Merge `detailTabs` from every matching adapter, de-duplicated by tab id. */\n resolveTabs(node: Test, runner: TestRunnerContext): DetailTab[] {\n const ctx: AdapterContext = { node, runner };\n const seen = new Set<string>();\n const tabs: DetailTab[] = [];\n for (const a of this.adapters) {\n if (!a.detailTabs || !a.match(node, runner)) continue;\n for (const tab of a.detailTabs(ctx)) {\n if (seen.has(tab.id)) continue;\n seen.add(tab.id);\n tabs.push(tab);\n }\n }\n return tabs;\n }\n\n /**\n * Merge `copy` sections from every matching adapter, de-duplicated by fact\n * label, section title and item label — the first contributor wins.\n */\n resolveCopy(node: Test, runner: TestRunnerContext): Required<CopySections> {\n const ctx: AdapterContext = { node, runner };\n return mergeCopySections(\n this.adapters.flatMap((a) => (a.copy && a.match(node, runner) ? [a.copy(ctx)] : [])),\n );\n }\n\n /** True when the resolved detail adapter owns its own scroll region. */\n ownsScroll(node: Test, runner: TestRunnerContext): boolean {\n const a = this.resolveDetail(node, runner);\n if (!a?.ownsScroll) return false;\n return a.ownsScroll({ node, runner });\n }\n}\n\nexport function createTestRunnerRegistry(\n adapters: TestNodeAdapter[] = [],\n): TestNodeAdapterRegistry {\n return new TestNodeAdapterRegistry(adapters);\n}\n"],"names":["mergeCopySections"],"mappings":";;;;;;AAgEO,MAAM,wBAAwB;AAAA,EAGnC,YAAY,WAA8B,IAAI;AAFrC;AAGP,SAAK,WAAW;AAAA,EAClB;AAAA,EAEQ,UACN,MACA,QACA,KACwB;AACxB,eAAW,KAAK,KAAK,UAAU;AAC7B,UAAI,IAAI,CAAC,KAAK,EAAE,MAAM,MAAM,MAAM,EAAG,QAAO;AAAA,IAC9C;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,cAAc,MAAY,QAAmD;AAC3E,WAAO,KAAK,UAAU,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,YAAY;AAAA,EAC7D;AAAA,EAEA,kBAAkB,MAAY,QAAmD;AAC/E,WAAO,KAAK,UAAU,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,gBAAgB;AAAA,EACjE;AAAA,EAEA,qBAAqB,MAAY,QAAmD;AAClF,WAAO,KAAK,UAAU,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,mBAAmB;AAAA,EACpE;AAAA,EAEA,eAAe,MAAY,QAAmD;AAC5E,WAAO,KAAK,UAAU,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW;AAAA,EAC5D;AAAA;AAAA,EAGA,YAAY,MAAY,QAAwC;AAC9D,UAAM,MAAsB,EAAE,MAAM,OAAA;AACpC,UAAM,2BAAW,IAAA;AACjB,UAAM,OAAoB,CAAA;AAC1B,eAAW,KAAK,KAAK,UAAU;AAC7B,UAAI,CAAC,EAAE,cAAc,CAAC,EAAE,MAAM,MAAM,MAAM,EAAG;AAC7C,iBAAW,OAAO,EAAE,WAAW,GAAG,GAAG;AACnC,YAAI,KAAK,IAAI,IAAI,EAAE,EAAG;AACtB,aAAK,IAAI,IAAI,EAAE;AACf,aAAK,KAAK,GAAG;AAAA,MACf;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,MAAY,QAAmD;AACzE,UAAM,MAAsB,EAAE,MAAM,OAAA;AACpC,WAAOA,KAAAA;AAAAA,MACL,KAAK,SAAS,QAAQ,CAAC,MAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,MAAM,IAAI,CAAC,EAAE,KAAK,GAAG,CAAC,IAAI,CAAA,CAAG;AAAA,IAAA;AAAA,EAEvF;AAAA;AAAA,EAGA,WAAW,MAAY,QAAoC;AACzD,UAAM,IAAI,KAAK,cAAc,MAAM,MAAM;AACzC,QAAI,EAAC,uBAAG,YAAY,QAAO;AAC3B,WAAO,EAAE,WAAW,EAAE,MAAM,QAAQ;AAAA,EACtC;AACF;AAEO,SAAS,yBACd,WAA8B,IACL;AACzB,SAAO,IAAI,wBAAwB,QAAQ;AAC7C;;;"}
1
+ {"version":3,"file":"adapter.cjs","sources":["../../../src/data/test-runner/adapter.ts"],"sourcesContent":["// Node adapters let a host plug domain-specific rendering into the test runner\n// without forking it. An adapter claims nodes via `match` and contributes how\n// they render in each surface (tree row, detail body, detail tabs, header\n// actions). The registry resolves the first matching adapter per surface;\n// surfaces with no match fall back to the runner's built-in default rendering.\n//\n// This formalizes the wrapper-with-an-if-chain pattern hosts use today (e.g.\n// a ResultDetailPane fall-through over domain panels + tab providers + per-step\n// actions) into a registered, composable contract.\n\nimport type { ReactNode } from \"react\";\nimport type { Test } from \"./types\";\nimport type { TestRunnerContext } from \"./context\";\nimport { mergeCopySections, type CopySections } from \"./copy\";\n\nexport type AdapterContext = {\n node: Test;\n runner: TestRunnerContext;\n};\n\nexport type DetailTab = {\n id: string;\n label: string;\n render: () => ReactNode;\n};\n\nexport type DetailAttempt = { id: number; status: string; durationMs?: number; node: Test };\n\nexport type TestNodeAdapter = {\n /** Stable identifier, used as a React key and for debugging. */\n id: string;\n /** Claim the node. The first registered adapter to return true wins per surface. */\n match: (node: Test, runner: TestRunnerContext) => boolean;\n /**\n * Replace the framework icon for the node — return any node, or null to fall\n * back to the built-in tool mark (`TEST_TOOLS`). For a tool the catalog\n * lacks, prefer adding it to `TEST_TOOLS` so every surface shows it.\n */\n renderFrameworkIcon?: (c: AdapterContext) => ReactNode;\n /** Extra decoration rendered between the framework icon and the node name. */\n renderRowLeading?: (c: AdapterContext) => ReactNode;\n /** Primary detail body for the node, replacing the default panel body. */\n renderDetail?: (c: AdapterContext) => ReactNode;\n /** Extra detail tabs, merged (in registration order) across all matching adapters. */\n detailTabs?: (c: AdapterContext) => DetailTab[];\n /** Stored attempt views for the header chooser; tree and run totals remain step-level. */\n detailAttempts?: (c: AdapterContext) => DetailAttempt[];\n /** Node-scoped action buttons rendered in the detail header. */\n nodeActions?: (c: AdapterContext) => ReactNode;\n /**\n * Domain facts, brief sections and menu items for the header's \"Copy details\"\n * button, merged (in registration order) across all matching adapters on top\n * of the generic sections every node gets.\n */\n copy?: (c: AdapterContext) => CopySections;\n /**\n * Whether `renderDetail` owns its own full-height scroll container. When\n * false (the default for custom bodies), the panel wraps it in a scroll\n * region. The built-in default body owns its scroll.\n */\n ownsScroll?: (c: AdapterContext) => boolean;\n};\n\n/**\n * Ordered set of node adapters. First match wins per surface; `detailTabs` and\n * `copy` are the exceptions — they merge across every matching adapter so\n * independent adapters can each contribute a tab or a copy section.\n */\nexport class TestNodeAdapterRegistry {\n readonly adapters: TestNodeAdapter[];\n\n constructor(adapters: TestNodeAdapter[] = []) {\n this.adapters = adapters;\n }\n\n private firstWith(\n node: Test,\n runner: TestRunnerContext,\n has: (a: TestNodeAdapter) => boolean,\n ): TestNodeAdapter | null {\n for (const a of this.adapters) {\n if (has(a) && a.match(node, runner)) return a;\n }\n return null;\n }\n\n /** Adapter whose `renderDetail` should drive the body, or null for the default. */\n resolveDetail(node: Test, runner: TestRunnerContext): TestNodeAdapter | null {\n return this.firstWith(node, runner, (a) => !!a.renderDetail);\n }\n\n resolveRowLeading(node: Test, runner: TestRunnerContext): TestNodeAdapter | null {\n return this.firstWith(node, runner, (a) => !!a.renderRowLeading);\n }\n\n resolveFrameworkIcon(node: Test, runner: TestRunnerContext): TestNodeAdapter | null {\n return this.firstWith(node, runner, (a) => !!a.renderFrameworkIcon);\n }\n\n resolveActions(node: Test, runner: TestRunnerContext): TestNodeAdapter | null {\n return this.firstWith(node, runner, (a) => !!a.nodeActions);\n }\n\n resolveAttempts(node: Test, runner: TestRunnerContext): DetailAttempt[] {\n return this.firstWith(node, runner, (a) => !!a.detailAttempts)?.detailAttempts?.({ node, runner }) ?? [];\n }\n\n /** Merge `detailTabs` from every matching adapter, de-duplicated by tab id. */\n resolveTabs(node: Test, runner: TestRunnerContext): DetailTab[] {\n const ctx: AdapterContext = { node, runner };\n const seen = new Set<string>();\n const tabs: DetailTab[] = [];\n for (const a of this.adapters) {\n if (!a.detailTabs || !a.match(node, runner)) continue;\n for (const tab of a.detailTabs(ctx)) {\n if (seen.has(tab.id)) continue;\n seen.add(tab.id);\n tabs.push(tab);\n }\n }\n return tabs;\n }\n\n /**\n * Merge `copy` sections from every matching adapter, de-duplicated by fact\n * label, section title and item label — the first contributor wins.\n */\n resolveCopy(node: Test, runner: TestRunnerContext): Required<CopySections> {\n const ctx: AdapterContext = { node, runner };\n return mergeCopySections(\n this.adapters.flatMap((a) => (a.copy && a.match(node, runner) ? [a.copy(ctx)] : [])),\n );\n }\n\n /** True when the resolved detail adapter owns its own scroll region. */\n ownsScroll(node: Test, runner: TestRunnerContext): boolean {\n const a = this.resolveDetail(node, runner);\n if (!a?.ownsScroll) return false;\n return a.ownsScroll({ node, runner });\n }\n}\n\nexport function createTestRunnerRegistry(\n adapters: TestNodeAdapter[] = [],\n): TestNodeAdapterRegistry {\n return new TestNodeAdapterRegistry(adapters);\n}\n"],"names":["mergeCopySections"],"mappings":";;;;;;AAoEO,MAAM,wBAAwB;AAAA,EAGnC,YAAY,WAA8B,IAAI;AAFrC;AAGP,SAAK,WAAW;AAAA,EAClB;AAAA,EAEQ,UACN,MACA,QACA,KACwB;AACxB,eAAW,KAAK,KAAK,UAAU;AAC7B,UAAI,IAAI,CAAC,KAAK,EAAE,MAAM,MAAM,MAAM,EAAG,QAAO;AAAA,IAC9C;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,cAAc,MAAY,QAAmD;AAC3E,WAAO,KAAK,UAAU,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,YAAY;AAAA,EAC7D;AAAA,EAEA,kBAAkB,MAAY,QAAmD;AAC/E,WAAO,KAAK,UAAU,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,gBAAgB;AAAA,EACjE;AAAA,EAEA,qBAAqB,MAAY,QAAmD;AAClF,WAAO,KAAK,UAAU,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,mBAAmB;AAAA,EACpE;AAAA,EAEA,eAAe,MAAY,QAAmD;AAC5E,WAAO,KAAK,UAAU,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW;AAAA,EAC5D;AAAA,EAEA,gBAAgB,MAAY,QAA4C;;AACtE,aAAO,gBAAK,UAAU,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,cAAc,MAAtD,mBAAyD,mBAAzD,4BAA0E,EAAE,MAAM,OAAA,OAAa,CAAA;AAAA,EACxG;AAAA;AAAA,EAGA,YAAY,MAAY,QAAwC;AAC9D,UAAM,MAAsB,EAAE,MAAM,OAAA;AACpC,UAAM,2BAAW,IAAA;AACjB,UAAM,OAAoB,CAAA;AAC1B,eAAW,KAAK,KAAK,UAAU;AAC7B,UAAI,CAAC,EAAE,cAAc,CAAC,EAAE,MAAM,MAAM,MAAM,EAAG;AAC7C,iBAAW,OAAO,EAAE,WAAW,GAAG,GAAG;AACnC,YAAI,KAAK,IAAI,IAAI,EAAE,EAAG;AACtB,aAAK,IAAI,IAAI,EAAE;AACf,aAAK,KAAK,GAAG;AAAA,MACf;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,MAAY,QAAmD;AACzE,UAAM,MAAsB,EAAE,MAAM,OAAA;AACpC,WAAOA,KAAAA;AAAAA,MACL,KAAK,SAAS,QAAQ,CAAC,MAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,MAAM,IAAI,CAAC,EAAE,KAAK,GAAG,CAAC,IAAI,CAAA,CAAG;AAAA,IAAA;AAAA,EAEvF;AAAA;AAAA,EAGA,WAAW,MAAY,QAAoC;AACzD,UAAM,IAAI,KAAK,cAAc,MAAM,MAAM;AACzC,QAAI,EAAC,uBAAG,YAAY,QAAO;AAC3B,WAAO,EAAE,WAAW,EAAE,MAAM,QAAQ;AAAA,EACtC;AACF;AAEO,SAAS,yBACd,WAA8B,IACL;AACzB,SAAO,IAAI,wBAAwB,QAAQ;AAC7C;;;"}
@@ -11,6 +11,12 @@ export type DetailTab = {
11
11
  label: string;
12
12
  render: () => ReactNode;
13
13
  };
14
+ export type DetailAttempt = {
15
+ id: number;
16
+ status: string;
17
+ durationMs?: number;
18
+ node: Test;
19
+ };
14
20
  export type TestNodeAdapter = {
15
21
  /** Stable identifier, used as a React key and for debugging. */
16
22
  id: string;
@@ -28,6 +34,8 @@ export type TestNodeAdapter = {
28
34
  renderDetail?: (c: AdapterContext) => ReactNode;
29
35
  /** Extra detail tabs, merged (in registration order) across all matching adapters. */
30
36
  detailTabs?: (c: AdapterContext) => DetailTab[];
37
+ /** Stored attempt views for the header chooser; tree and run totals remain step-level. */
38
+ detailAttempts?: (c: AdapterContext) => DetailAttempt[];
31
39
  /** Node-scoped action buttons rendered in the detail header. */
32
40
  nodeActions?: (c: AdapterContext) => ReactNode;
33
41
  /**
@@ -57,6 +65,7 @@ export declare class TestNodeAdapterRegistry {
57
65
  resolveRowLeading(node: Test, runner: TestRunnerContext): TestNodeAdapter | null;
58
66
  resolveFrameworkIcon(node: Test, runner: TestRunnerContext): TestNodeAdapter | null;
59
67
  resolveActions(node: Test, runner: TestRunnerContext): TestNodeAdapter | null;
68
+ resolveAttempts(node: Test, runner: TestRunnerContext): DetailAttempt[];
60
69
  /** Merge `detailTabs` from every matching adapter, de-duplicated by tab id. */
61
70
  resolveTabs(node: Test, runner: TestRunnerContext): DetailTab[];
62
71
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/adapter.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,QAAQ,CAAC;AAE9D,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,iBAAiB,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,SAAS,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,gEAAgE;IAChE,EAAE,EAAE,MAAM,CAAC;IACX,oFAAoF;IACpF,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,KAAK,OAAO,CAAC;IAC1D;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,SAAS,CAAC;IACvD,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,SAAS,CAAC;IACpD,0EAA0E;IAC1E,YAAY,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,SAAS,CAAC;IAChD,sFAAsF;IACtF,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,SAAS,EAAE,CAAC;IAChD,gEAAgE;IAChE,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,SAAS,CAAC;IAC/C;;;;OAIG;IACH,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,YAAY,CAAC;IAC3C;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,OAAO,CAAC;CAC7C,CAAC;AAEF;;;;GAIG;AACH,qBAAa,uBAAuB;IAClC,QAAQ,CAAC,QAAQ,EAAE,eAAe,EAAE,CAAC;gBAEzB,QAAQ,GAAE,eAAe,EAAO;IAI5C,OAAO,CAAC,SAAS;IAWjB,mFAAmF;IACnF,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,GAAG,eAAe,GAAG,IAAI;IAI5E,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,GAAG,eAAe,GAAG,IAAI;IAIhF,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,GAAG,eAAe,GAAG,IAAI;IAInF,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,GAAG,eAAe,GAAG,IAAI;IAI7E,+EAA+E;IAC/E,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,GAAG,SAAS,EAAE;IAe/D;;;OAGG;IACH,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,GAAG,QAAQ,CAAC,YAAY,CAAC;IAO1E,wEAAwE;IACxE,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO;CAK3D;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,GAAE,eAAe,EAAO,GAC/B,uBAAuB,CAEzB"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/adapter.ts"],"names":[],"mappings":"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"}
@@ -26,6 +26,10 @@ class TestNodeAdapterRegistry {
26
26
  resolveActions(node, runner) {
27
27
  return this.firstWith(node, runner, (a) => !!a.nodeActions);
28
28
  }
29
+ resolveAttempts(node, runner) {
30
+ var _a, _b;
31
+ return ((_b = (_a = this.firstWith(node, runner, (a) => !!a.detailAttempts)) == null ? void 0 : _a.detailAttempts) == null ? void 0 : _b.call(_a, { node, runner })) ?? [];
32
+ }
29
33
  /** Merge `detailTabs` from every matching adapter, de-duplicated by tab id. */
30
34
  resolveTabs(node, runner) {
31
35
  const ctx = { node, runner };
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.js","sources":["../../../src/data/test-runner/adapter.ts"],"sourcesContent":["// Node adapters let a host plug domain-specific rendering into the test runner\n// without forking it. An adapter claims nodes via `match` and contributes how\n// they render in each surface (tree row, detail body, detail tabs, header\n// actions). The registry resolves the first matching adapter per surface;\n// surfaces with no match fall back to the runner's built-in default rendering.\n//\n// This formalizes the wrapper-with-an-if-chain pattern hosts use today (e.g.\n// a ResultDetailPane fall-through over domain panels + tab providers + per-step\n// actions) into a registered, composable contract.\n\nimport type { ReactNode } from \"react\";\nimport type { Test } from \"./types\";\nimport type { TestRunnerContext } from \"./context\";\nimport { mergeCopySections, type CopySections } from \"./copy\";\n\nexport type AdapterContext = {\n node: Test;\n runner: TestRunnerContext;\n};\n\nexport type DetailTab = {\n id: string;\n label: string;\n render: () => ReactNode;\n};\n\nexport type TestNodeAdapter = {\n /** Stable identifier, used as a React key and for debugging. */\n id: string;\n /** Claim the node. The first registered adapter to return true wins per surface. */\n match: (node: Test, runner: TestRunnerContext) => boolean;\n /**\n * Replace the framework icon for the node — return any node, or null to fall\n * back to the built-in tool mark (`TEST_TOOLS`). For a tool the catalog\n * lacks, prefer adding it to `TEST_TOOLS` so every surface shows it.\n */\n renderFrameworkIcon?: (c: AdapterContext) => ReactNode;\n /** Extra decoration rendered between the framework icon and the node name. */\n renderRowLeading?: (c: AdapterContext) => ReactNode;\n /** Primary detail body for the node, replacing the default panel body. */\n renderDetail?: (c: AdapterContext) => ReactNode;\n /** Extra detail tabs, merged (in registration order) across all matching adapters. */\n detailTabs?: (c: AdapterContext) => DetailTab[];\n /** Node-scoped action buttons rendered in the detail header. */\n nodeActions?: (c: AdapterContext) => ReactNode;\n /**\n * Domain facts, brief sections and menu items for the header's \"Copy details\"\n * button, merged (in registration order) across all matching adapters on top\n * of the generic sections every node gets.\n */\n copy?: (c: AdapterContext) => CopySections;\n /**\n * Whether `renderDetail` owns its own full-height scroll container. When\n * false (the default for custom bodies), the panel wraps it in a scroll\n * region. The built-in default body owns its scroll.\n */\n ownsScroll?: (c: AdapterContext) => boolean;\n};\n\n/**\n * Ordered set of node adapters. First match wins per surface; `detailTabs` and\n * `copy` are the exceptions — they merge across every matching adapter so\n * independent adapters can each contribute a tab or a copy section.\n */\nexport class TestNodeAdapterRegistry {\n readonly adapters: TestNodeAdapter[];\n\n constructor(adapters: TestNodeAdapter[] = []) {\n this.adapters = adapters;\n }\n\n private firstWith(\n node: Test,\n runner: TestRunnerContext,\n has: (a: TestNodeAdapter) => boolean,\n ): TestNodeAdapter | null {\n for (const a of this.adapters) {\n if (has(a) && a.match(node, runner)) return a;\n }\n return null;\n }\n\n /** Adapter whose `renderDetail` should drive the body, or null for the default. */\n resolveDetail(node: Test, runner: TestRunnerContext): TestNodeAdapter | null {\n return this.firstWith(node, runner, (a) => !!a.renderDetail);\n }\n\n resolveRowLeading(node: Test, runner: TestRunnerContext): TestNodeAdapter | null {\n return this.firstWith(node, runner, (a) => !!a.renderRowLeading);\n }\n\n resolveFrameworkIcon(node: Test, runner: TestRunnerContext): TestNodeAdapter | null {\n return this.firstWith(node, runner, (a) => !!a.renderFrameworkIcon);\n }\n\n resolveActions(node: Test, runner: TestRunnerContext): TestNodeAdapter | null {\n return this.firstWith(node, runner, (a) => !!a.nodeActions);\n }\n\n /** Merge `detailTabs` from every matching adapter, de-duplicated by tab id. */\n resolveTabs(node: Test, runner: TestRunnerContext): DetailTab[] {\n const ctx: AdapterContext = { node, runner };\n const seen = new Set<string>();\n const tabs: DetailTab[] = [];\n for (const a of this.adapters) {\n if (!a.detailTabs || !a.match(node, runner)) continue;\n for (const tab of a.detailTabs(ctx)) {\n if (seen.has(tab.id)) continue;\n seen.add(tab.id);\n tabs.push(tab);\n }\n }\n return tabs;\n }\n\n /**\n * Merge `copy` sections from every matching adapter, de-duplicated by fact\n * label, section title and item label — the first contributor wins.\n */\n resolveCopy(node: Test, runner: TestRunnerContext): Required<CopySections> {\n const ctx: AdapterContext = { node, runner };\n return mergeCopySections(\n this.adapters.flatMap((a) => (a.copy && a.match(node, runner) ? [a.copy(ctx)] : [])),\n );\n }\n\n /** True when the resolved detail adapter owns its own scroll region. */\n ownsScroll(node: Test, runner: TestRunnerContext): boolean {\n const a = this.resolveDetail(node, runner);\n if (!a?.ownsScroll) return false;\n return a.ownsScroll({ node, runner });\n }\n}\n\nexport function createTestRunnerRegistry(\n adapters: TestNodeAdapter[] = [],\n): TestNodeAdapterRegistry {\n return new TestNodeAdapterRegistry(adapters);\n}\n"],"names":[],"mappings":";;;;AAgEO,MAAM,wBAAwB;AAAA,EAGnC,YAAY,WAA8B,IAAI;AAFrC;AAGP,SAAK,WAAW;AAAA,EAClB;AAAA,EAEQ,UACN,MACA,QACA,KACwB;AACxB,eAAW,KAAK,KAAK,UAAU;AAC7B,UAAI,IAAI,CAAC,KAAK,EAAE,MAAM,MAAM,MAAM,EAAG,QAAO;AAAA,IAC9C;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,cAAc,MAAY,QAAmD;AAC3E,WAAO,KAAK,UAAU,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,YAAY;AAAA,EAC7D;AAAA,EAEA,kBAAkB,MAAY,QAAmD;AAC/E,WAAO,KAAK,UAAU,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,gBAAgB;AAAA,EACjE;AAAA,EAEA,qBAAqB,MAAY,QAAmD;AAClF,WAAO,KAAK,UAAU,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,mBAAmB;AAAA,EACpE;AAAA,EAEA,eAAe,MAAY,QAAmD;AAC5E,WAAO,KAAK,UAAU,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW;AAAA,EAC5D;AAAA;AAAA,EAGA,YAAY,MAAY,QAAwC;AAC9D,UAAM,MAAsB,EAAE,MAAM,OAAA;AACpC,UAAM,2BAAW,IAAA;AACjB,UAAM,OAAoB,CAAA;AAC1B,eAAW,KAAK,KAAK,UAAU;AAC7B,UAAI,CAAC,EAAE,cAAc,CAAC,EAAE,MAAM,MAAM,MAAM,EAAG;AAC7C,iBAAW,OAAO,EAAE,WAAW,GAAG,GAAG;AACnC,YAAI,KAAK,IAAI,IAAI,EAAE,EAAG;AACtB,aAAK,IAAI,IAAI,EAAE;AACf,aAAK,KAAK,GAAG;AAAA,MACf;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,MAAY,QAAmD;AACzE,UAAM,MAAsB,EAAE,MAAM,OAAA;AACpC,WAAO;AAAA,MACL,KAAK,SAAS,QAAQ,CAAC,MAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,MAAM,IAAI,CAAC,EAAE,KAAK,GAAG,CAAC,IAAI,CAAA,CAAG;AAAA,IAAA;AAAA,EAEvF;AAAA;AAAA,EAGA,WAAW,MAAY,QAAoC;AACzD,UAAM,IAAI,KAAK,cAAc,MAAM,MAAM;AACzC,QAAI,EAAC,uBAAG,YAAY,QAAO;AAC3B,WAAO,EAAE,WAAW,EAAE,MAAM,QAAQ;AAAA,EACtC;AACF;AAEO,SAAS,yBACd,WAA8B,IACL;AACzB,SAAO,IAAI,wBAAwB,QAAQ;AAC7C;"}
1
+ {"version":3,"file":"adapter.js","sources":["../../../src/data/test-runner/adapter.ts"],"sourcesContent":["// Node adapters let a host plug domain-specific rendering into the test runner\n// without forking it. An adapter claims nodes via `match` and contributes how\n// they render in each surface (tree row, detail body, detail tabs, header\n// actions). The registry resolves the first matching adapter per surface;\n// surfaces with no match fall back to the runner's built-in default rendering.\n//\n// This formalizes the wrapper-with-an-if-chain pattern hosts use today (e.g.\n// a ResultDetailPane fall-through over domain panels + tab providers + per-step\n// actions) into a registered, composable contract.\n\nimport type { ReactNode } from \"react\";\nimport type { Test } from \"./types\";\nimport type { TestRunnerContext } from \"./context\";\nimport { mergeCopySections, type CopySections } from \"./copy\";\n\nexport type AdapterContext = {\n node: Test;\n runner: TestRunnerContext;\n};\n\nexport type DetailTab = {\n id: string;\n label: string;\n render: () => ReactNode;\n};\n\nexport type DetailAttempt = { id: number; status: string; durationMs?: number; node: Test };\n\nexport type TestNodeAdapter = {\n /** Stable identifier, used as a React key and for debugging. */\n id: string;\n /** Claim the node. The first registered adapter to return true wins per surface. */\n match: (node: Test, runner: TestRunnerContext) => boolean;\n /**\n * Replace the framework icon for the node — return any node, or null to fall\n * back to the built-in tool mark (`TEST_TOOLS`). For a tool the catalog\n * lacks, prefer adding it to `TEST_TOOLS` so every surface shows it.\n */\n renderFrameworkIcon?: (c: AdapterContext) => ReactNode;\n /** Extra decoration rendered between the framework icon and the node name. */\n renderRowLeading?: (c: AdapterContext) => ReactNode;\n /** Primary detail body for the node, replacing the default panel body. */\n renderDetail?: (c: AdapterContext) => ReactNode;\n /** Extra detail tabs, merged (in registration order) across all matching adapters. */\n detailTabs?: (c: AdapterContext) => DetailTab[];\n /** Stored attempt views for the header chooser; tree and run totals remain step-level. */\n detailAttempts?: (c: AdapterContext) => DetailAttempt[];\n /** Node-scoped action buttons rendered in the detail header. */\n nodeActions?: (c: AdapterContext) => ReactNode;\n /**\n * Domain facts, brief sections and menu items for the header's \"Copy details\"\n * button, merged (in registration order) across all matching adapters on top\n * of the generic sections every node gets.\n */\n copy?: (c: AdapterContext) => CopySections;\n /**\n * Whether `renderDetail` owns its own full-height scroll container. When\n * false (the default for custom bodies), the panel wraps it in a scroll\n * region. The built-in default body owns its scroll.\n */\n ownsScroll?: (c: AdapterContext) => boolean;\n};\n\n/**\n * Ordered set of node adapters. First match wins per surface; `detailTabs` and\n * `copy` are the exceptions — they merge across every matching adapter so\n * independent adapters can each contribute a tab or a copy section.\n */\nexport class TestNodeAdapterRegistry {\n readonly adapters: TestNodeAdapter[];\n\n constructor(adapters: TestNodeAdapter[] = []) {\n this.adapters = adapters;\n }\n\n private firstWith(\n node: Test,\n runner: TestRunnerContext,\n has: (a: TestNodeAdapter) => boolean,\n ): TestNodeAdapter | null {\n for (const a of this.adapters) {\n if (has(a) && a.match(node, runner)) return a;\n }\n return null;\n }\n\n /** Adapter whose `renderDetail` should drive the body, or null for the default. */\n resolveDetail(node: Test, runner: TestRunnerContext): TestNodeAdapter | null {\n return this.firstWith(node, runner, (a) => !!a.renderDetail);\n }\n\n resolveRowLeading(node: Test, runner: TestRunnerContext): TestNodeAdapter | null {\n return this.firstWith(node, runner, (a) => !!a.renderRowLeading);\n }\n\n resolveFrameworkIcon(node: Test, runner: TestRunnerContext): TestNodeAdapter | null {\n return this.firstWith(node, runner, (a) => !!a.renderFrameworkIcon);\n }\n\n resolveActions(node: Test, runner: TestRunnerContext): TestNodeAdapter | null {\n return this.firstWith(node, runner, (a) => !!a.nodeActions);\n }\n\n resolveAttempts(node: Test, runner: TestRunnerContext): DetailAttempt[] {\n return this.firstWith(node, runner, (a) => !!a.detailAttempts)?.detailAttempts?.({ node, runner }) ?? [];\n }\n\n /** Merge `detailTabs` from every matching adapter, de-duplicated by tab id. */\n resolveTabs(node: Test, runner: TestRunnerContext): DetailTab[] {\n const ctx: AdapterContext = { node, runner };\n const seen = new Set<string>();\n const tabs: DetailTab[] = [];\n for (const a of this.adapters) {\n if (!a.detailTabs || !a.match(node, runner)) continue;\n for (const tab of a.detailTabs(ctx)) {\n if (seen.has(tab.id)) continue;\n seen.add(tab.id);\n tabs.push(tab);\n }\n }\n return tabs;\n }\n\n /**\n * Merge `copy` sections from every matching adapter, de-duplicated by fact\n * label, section title and item label — the first contributor wins.\n */\n resolveCopy(node: Test, runner: TestRunnerContext): Required<CopySections> {\n const ctx: AdapterContext = { node, runner };\n return mergeCopySections(\n this.adapters.flatMap((a) => (a.copy && a.match(node, runner) ? [a.copy(ctx)] : [])),\n );\n }\n\n /** True when the resolved detail adapter owns its own scroll region. */\n ownsScroll(node: Test, runner: TestRunnerContext): boolean {\n const a = this.resolveDetail(node, runner);\n if (!a?.ownsScroll) return false;\n return a.ownsScroll({ node, runner });\n }\n}\n\nexport function createTestRunnerRegistry(\n adapters: TestNodeAdapter[] = [],\n): TestNodeAdapterRegistry {\n return new TestNodeAdapterRegistry(adapters);\n}\n"],"names":[],"mappings":";;;;AAoEO,MAAM,wBAAwB;AAAA,EAGnC,YAAY,WAA8B,IAAI;AAFrC;AAGP,SAAK,WAAW;AAAA,EAClB;AAAA,EAEQ,UACN,MACA,QACA,KACwB;AACxB,eAAW,KAAK,KAAK,UAAU;AAC7B,UAAI,IAAI,CAAC,KAAK,EAAE,MAAM,MAAM,MAAM,EAAG,QAAO;AAAA,IAC9C;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,cAAc,MAAY,QAAmD;AAC3E,WAAO,KAAK,UAAU,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,YAAY;AAAA,EAC7D;AAAA,EAEA,kBAAkB,MAAY,QAAmD;AAC/E,WAAO,KAAK,UAAU,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,gBAAgB;AAAA,EACjE;AAAA,EAEA,qBAAqB,MAAY,QAAmD;AAClF,WAAO,KAAK,UAAU,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,mBAAmB;AAAA,EACpE;AAAA,EAEA,eAAe,MAAY,QAAmD;AAC5E,WAAO,KAAK,UAAU,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW;AAAA,EAC5D;AAAA,EAEA,gBAAgB,MAAY,QAA4C;;AACtE,aAAO,gBAAK,UAAU,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,cAAc,MAAtD,mBAAyD,mBAAzD,4BAA0E,EAAE,MAAM,OAAA,OAAa,CAAA;AAAA,EACxG;AAAA;AAAA,EAGA,YAAY,MAAY,QAAwC;AAC9D,UAAM,MAAsB,EAAE,MAAM,OAAA;AACpC,UAAM,2BAAW,IAAA;AACjB,UAAM,OAAoB,CAAA;AAC1B,eAAW,KAAK,KAAK,UAAU;AAC7B,UAAI,CAAC,EAAE,cAAc,CAAC,EAAE,MAAM,MAAM,MAAM,EAAG;AAC7C,iBAAW,OAAO,EAAE,WAAW,GAAG,GAAG;AACnC,YAAI,KAAK,IAAI,IAAI,EAAE,EAAG;AACtB,aAAK,IAAI,IAAI,EAAE;AACf,aAAK,KAAK,GAAG;AAAA,MACf;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,MAAY,QAAmD;AACzE,UAAM,MAAsB,EAAE,MAAM,OAAA;AACpC,WAAO;AAAA,MACL,KAAK,SAAS,QAAQ,CAAC,MAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,MAAM,IAAI,CAAC,EAAE,KAAK,GAAG,CAAC,IAAI,CAAA,CAAG;AAAA,IAAA;AAAA,EAEvF;AAAA;AAAA,EAGA,WAAW,MAAY,QAAoC;AACzD,UAAM,IAAI,KAAK,cAAc,MAAM,MAAM;AACzC,QAAI,EAAC,uBAAG,YAAY,QAAO;AAC3B,WAAO,EAAE,WAAW,EAAE,MAAM,QAAQ;AAAA,EACtC;AACF;AAEO,SAAS,yBACd,WAA8B,IACL;AACzB,SAAO,IAAI,wBAAwB,QAAQ;AAC7C;"}
@@ -1 +1 @@
1
- {"version":3,"file":"context.cjs","sources":["../../../src/data/test-runner/context.ts"],"sourcesContent":["// A single context object carries all runner state + handlers, provided once\n// and read by every sub-component via useTestRunner() — instead of threading\n// onRerun/onStop/selected/runMeta/... through each component's props. Matches\n// clicky's existing Provider idiom (ClickyRuntimeProvider / TagActionsProvider).\n\nimport { createContext, useContext } from \"react\";\nimport type { RunMeta, SnapshotStatus, Test } from \"./types\";\nimport type { TestFilters } from \"./filterState\";\nimport type { TestNodeAdapterRegistry } from \"./adapter\";\n\nexport type TestRunnerContext = {\n /** The (already filtered, host-owned) test forest to render. */\n tests: Test[];\n /** Currently selected node, or null. */\n selected: Test | null;\n runMeta?: RunMeta | undefined;\n /** Run status flags (data only — no transport lives here). */\n status?: SnapshotStatus | undefined;\n /** Whether the run has finished. */\n done: boolean;\n /**\n * Injected wall-clock epoch ms for elapsed-time display. Kept as data so the\n * summary stays pure and testable; hosts pass `Date.now()`.\n */\n now?: number | undefined;\n /** Active status/framework filters. */\n filters: TestFilters;\n /** Tri-state expand-all: true (all open), false (all closed), null (default). */\n expandAll: boolean | null;\n /** In-flight action flags driving button disabled/spinner states. */\n busy: { rerun?: boolean; stop?: boolean };\n\n /**\n * Active detail-tab id. When provided, TestDetailPanel renders this tab\n * (controlled); when omitted it keeps its own internal tab state.\n */\n activeTab?: string | undefined;\n /** Called with the picked tab id when the detail tab is controlled. */\n onTabChange?: ((tabId: string) => void) | undefined;\n\n onSelect: (node: Test | null) => void;\n onFiltersChange: (next: TestFilters) => void;\n onExpandAllChange: (next: boolean | null) => void;\n /** Optional — when absent, the rerun affordance is hidden. */\n onRerun?: ((node: Test) => void) | undefined;\n /** Optional — when absent, the stop affordance is hidden. */\n onStop?: ((node: Test) => void) | undefined;\n\n /** Node adapters driving domain-specific rendering. */\n adapters: TestNodeAdapterRegistry;\n};\n\nconst Context = createContext<TestRunnerContext | null>(null);\n\nexport const TestRunnerProvider = Context.Provider;\n\nexport function useTestRunner(): TestRunnerContext {\n const ctx = useContext(Context);\n if (!ctx) {\n throw new Error(\"useTestRunner must be used within a <TestRunner> / TestRunnerProvider\");\n }\n return ctx;\n}\n"],"names":["createContext","useContext"],"mappings":";;;AAoDA,MAAM,UAAUA,MAAAA,cAAwC,IAAI;AAErD,MAAM,qBAAqB,QAAQ;AAEnC,SAAS,gBAAmC;AACjD,QAAM,MAAMC,MAAAA,WAAW,OAAO;AAC9B,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,uEAAuE;AAAA,EACzF;AACA,SAAO;AACT;;;"}
1
+ {"version":3,"file":"context.cjs","sources":["../../../src/data/test-runner/context.ts"],"sourcesContent":["// A single context object carries all runner state + handlers, provided once\n// and read by every sub-component via useTestRunner() — instead of threading\n// onRerun/onStop/selected/runMeta/... through each component's props. Matches\n// clicky's existing Provider idiom (ClickyRuntimeProvider / TagActionsProvider).\n\nimport { createContext, useContext } from \"react\";\nimport type { RunMeta, SnapshotStatus, Test } from \"./types\";\nimport type { TestFilters } from \"./filterState\";\nimport type { TestNodeAdapterRegistry } from \"./adapter\";\n\nexport type TestRunnerContext = {\n /** The (already filtered, host-owned) test forest to render. */\n tests: Test[];\n /** Currently selected node, or null. */\n selected: Test | null;\n runMeta?: RunMeta | undefined;\n /** Run status flags (data only — no transport lives here). */\n status?: SnapshotStatus | undefined;\n /** Whether the run has finished. */\n done: boolean;\n /**\n * Injected wall-clock epoch ms for elapsed-time display. Kept as data so the\n * summary stays pure and testable; hosts pass `Date.now()`.\n */\n now?: number | undefined;\n /** Active status/framework filters. */\n filters: TestFilters;\n /** Tri-state expand-all: true (all open), false (all closed), null (default). */\n expandAll: boolean | null;\n /** In-flight action flags driving button disabled/spinner states. */\n busy: { rerun?: boolean; stop?: boolean };\n\n /**\n * Active detail-tab id. When provided, TestDetailPanel renders this tab\n * (controlled); when omitted it keeps its own internal tab state.\n */\n activeTab?: string | undefined;\n /** Called with the picked tab id when the detail tab is controlled. */\n onTabChange?: ((tabId: string) => void) | undefined;\n activeAttempt?: number | undefined;\n onAttemptChange?: ((attempt: number) => void) | undefined;\n\n onSelect: (node: Test | null) => void;\n onFiltersChange: (next: TestFilters) => void;\n onExpandAllChange: (next: boolean | null) => void;\n /** Optional — when absent, the rerun affordance is hidden. */\n onRerun?: ((node: Test) => void) | undefined;\n /** Optional — when absent, the stop affordance is hidden. */\n onStop?: ((node: Test) => void) | undefined;\n\n /** Node adapters driving domain-specific rendering. */\n adapters: TestNodeAdapterRegistry;\n};\n\nconst Context = createContext<TestRunnerContext | null>(null);\n\nexport const TestRunnerProvider = Context.Provider;\n\nexport function useTestRunner(): TestRunnerContext {\n const ctx = useContext(Context);\n if (!ctx) {\n throw new Error(\"useTestRunner must be used within a <TestRunner> / TestRunnerProvider\");\n }\n return ctx;\n}\n"],"names":["createContext","useContext"],"mappings":";;;AAsDA,MAAM,UAAUA,MAAAA,cAAwC,IAAI;AAErD,MAAM,qBAAqB,QAAQ;AAEnC,SAAS,gBAAmC;AACjD,QAAM,MAAMC,MAAAA,WAAW,OAAO;AAC9B,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,uEAAuE;AAAA,EACzF;AACA,SAAO;AACT;;;"}
@@ -32,6 +32,8 @@ export type TestRunnerContext = {
32
32
  activeTab?: string | undefined;
33
33
  /** Called with the picked tab id when the detail tab is controlled. */
34
34
  onTabChange?: ((tabId: string) => void) | undefined;
35
+ activeAttempt?: number | undefined;
36
+ onAttemptChange?: ((attempt: number) => void) | undefined;
35
37
  onSelect: (node: Test | null) => void;
36
38
  onFiltersChange: (next: TestFilters) => void;
37
39
  onExpandAllChange: (next: boolean | null) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/context.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAC7D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC;AAEzD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,gEAAgE;IAChE,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,wCAAwC;IACxC,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,8DAA8D;IAC9D,MAAM,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACpC,oCAAoC;IACpC,IAAI,EAAE,OAAO,CAAC;IACd;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,uCAAuC;IACvC,OAAO,EAAE,WAAW,CAAC;IACrB,iFAAiF;IACjF,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;IAC1B,qEAAqE;IACrE,IAAI,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAE1C;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,uEAAuE;IACvE,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAEpD,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,CAAC;IACtC,eAAe,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC;IAC7C,iBAAiB,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,KAAK,IAAI,CAAC;IAClD,8DAA8D;IAC9D,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAC7C,6DAA6D;IAC7D,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAE5C,uDAAuD;IACvD,QAAQ,EAAE,uBAAuB,CAAC;CACnC,CAAC;AAIF,eAAO,MAAM,kBAAkB,oDAAmB,CAAC;AAEnD,wBAAgB,aAAa,IAAI,iBAAiB,CAMjD"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/context.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAC7D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC;AAEzD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,gEAAgE;IAChE,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,wCAAwC;IACxC,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,8DAA8D;IAC9D,MAAM,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACpC,oCAAoC;IACpC,IAAI,EAAE,OAAO,CAAC;IACd;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,uCAAuC;IACvC,OAAO,EAAE,WAAW,CAAC;IACrB,iFAAiF;IACjF,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;IAC1B,qEAAqE;IACrE,IAAI,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAE1C;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,uEAAuE;IACvE,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACpD,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,eAAe,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAE1D,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,CAAC;IACtC,eAAe,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC;IAC7C,iBAAiB,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,KAAK,IAAI,CAAC;IAClD,8DAA8D;IAC9D,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAC7C,6DAA6D;IAC7D,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAE5C,uDAAuD;IACvD,QAAQ,EAAE,uBAAuB,CAAC;CACnC,CAAC;AAIF,eAAO,MAAM,kBAAkB,oDAAmB,CAAC;AAEnD,wBAAgB,aAAa,IAAI,iBAAiB,CAMjD"}
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sources":["../../../src/data/test-runner/context.ts"],"sourcesContent":["// A single context object carries all runner state + handlers, provided once\n// and read by every sub-component via useTestRunner() — instead of threading\n// onRerun/onStop/selected/runMeta/... through each component's props. Matches\n// clicky's existing Provider idiom (ClickyRuntimeProvider / TagActionsProvider).\n\nimport { createContext, useContext } from \"react\";\nimport type { RunMeta, SnapshotStatus, Test } from \"./types\";\nimport type { TestFilters } from \"./filterState\";\nimport type { TestNodeAdapterRegistry } from \"./adapter\";\n\nexport type TestRunnerContext = {\n /** The (already filtered, host-owned) test forest to render. */\n tests: Test[];\n /** Currently selected node, or null. */\n selected: Test | null;\n runMeta?: RunMeta | undefined;\n /** Run status flags (data only — no transport lives here). */\n status?: SnapshotStatus | undefined;\n /** Whether the run has finished. */\n done: boolean;\n /**\n * Injected wall-clock epoch ms for elapsed-time display. Kept as data so the\n * summary stays pure and testable; hosts pass `Date.now()`.\n */\n now?: number | undefined;\n /** Active status/framework filters. */\n filters: TestFilters;\n /** Tri-state expand-all: true (all open), false (all closed), null (default). */\n expandAll: boolean | null;\n /** In-flight action flags driving button disabled/spinner states. */\n busy: { rerun?: boolean; stop?: boolean };\n\n /**\n * Active detail-tab id. When provided, TestDetailPanel renders this tab\n * (controlled); when omitted it keeps its own internal tab state.\n */\n activeTab?: string | undefined;\n /** Called with the picked tab id when the detail tab is controlled. */\n onTabChange?: ((tabId: string) => void) | undefined;\n\n onSelect: (node: Test | null) => void;\n onFiltersChange: (next: TestFilters) => void;\n onExpandAllChange: (next: boolean | null) => void;\n /** Optional — when absent, the rerun affordance is hidden. */\n onRerun?: ((node: Test) => void) | undefined;\n /** Optional — when absent, the stop affordance is hidden. */\n onStop?: ((node: Test) => void) | undefined;\n\n /** Node adapters driving domain-specific rendering. */\n adapters: TestNodeAdapterRegistry;\n};\n\nconst Context = createContext<TestRunnerContext | null>(null);\n\nexport const TestRunnerProvider = Context.Provider;\n\nexport function useTestRunner(): TestRunnerContext {\n const ctx = useContext(Context);\n if (!ctx) {\n throw new Error(\"useTestRunner must be used within a <TestRunner> / TestRunnerProvider\");\n }\n return ctx;\n}\n"],"names":[],"mappings":";AAoDA,MAAM,UAAU,cAAwC,IAAI;AAErD,MAAM,qBAAqB,QAAQ;AAEnC,SAAS,gBAAmC;AACjD,QAAM,MAAM,WAAW,OAAO;AAC9B,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,uEAAuE;AAAA,EACzF;AACA,SAAO;AACT;"}
1
+ {"version":3,"file":"context.js","sources":["../../../src/data/test-runner/context.ts"],"sourcesContent":["// A single context object carries all runner state + handlers, provided once\n// and read by every sub-component via useTestRunner() — instead of threading\n// onRerun/onStop/selected/runMeta/... through each component's props. Matches\n// clicky's existing Provider idiom (ClickyRuntimeProvider / TagActionsProvider).\n\nimport { createContext, useContext } from \"react\";\nimport type { RunMeta, SnapshotStatus, Test } from \"./types\";\nimport type { TestFilters } from \"./filterState\";\nimport type { TestNodeAdapterRegistry } from \"./adapter\";\n\nexport type TestRunnerContext = {\n /** The (already filtered, host-owned) test forest to render. */\n tests: Test[];\n /** Currently selected node, or null. */\n selected: Test | null;\n runMeta?: RunMeta | undefined;\n /** Run status flags (data only — no transport lives here). */\n status?: SnapshotStatus | undefined;\n /** Whether the run has finished. */\n done: boolean;\n /**\n * Injected wall-clock epoch ms for elapsed-time display. Kept as data so the\n * summary stays pure and testable; hosts pass `Date.now()`.\n */\n now?: number | undefined;\n /** Active status/framework filters. */\n filters: TestFilters;\n /** Tri-state expand-all: true (all open), false (all closed), null (default). */\n expandAll: boolean | null;\n /** In-flight action flags driving button disabled/spinner states. */\n busy: { rerun?: boolean; stop?: boolean };\n\n /**\n * Active detail-tab id. When provided, TestDetailPanel renders this tab\n * (controlled); when omitted it keeps its own internal tab state.\n */\n activeTab?: string | undefined;\n /** Called with the picked tab id when the detail tab is controlled. */\n onTabChange?: ((tabId: string) => void) | undefined;\n activeAttempt?: number | undefined;\n onAttemptChange?: ((attempt: number) => void) | undefined;\n\n onSelect: (node: Test | null) => void;\n onFiltersChange: (next: TestFilters) => void;\n onExpandAllChange: (next: boolean | null) => void;\n /** Optional — when absent, the rerun affordance is hidden. */\n onRerun?: ((node: Test) => void) | undefined;\n /** Optional — when absent, the stop affordance is hidden. */\n onStop?: ((node: Test) => void) | undefined;\n\n /** Node adapters driving domain-specific rendering. */\n adapters: TestNodeAdapterRegistry;\n};\n\nconst Context = createContext<TestRunnerContext | null>(null);\n\nexport const TestRunnerProvider = Context.Provider;\n\nexport function useTestRunner(): TestRunnerContext {\n const ctx = useContext(Context);\n if (!ctx) {\n throw new Error(\"useTestRunner must be used within a <TestRunner> / TestRunnerProvider\");\n }\n return ctx;\n}\n"],"names":[],"mappings":";AAsDA,MAAM,UAAU,cAAwC,IAAI;AAErD,MAAM,qBAAqB,QAAQ;AAEnC,SAAS,gBAAmC;AACjD,QAAM,MAAM,WAAW,OAAO;AAC9B,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,uEAAuE;AAAA,EACzF;AACA,SAAO;AACT;"}
@@ -7,10 +7,10 @@ export { Expectation, type ExpectationProps } from './Expectation.js';
7
7
  export { TestRunSummary, type TestRunSummaryProps } from './TestRunSummary.js';
8
8
  export { TestFilterBar, type TestFilterBarProps } from './TestFilterBar.js';
9
9
  export { TestRunnerProvider, useTestRunner, type TestRunnerContext, } from './context.js';
10
- export { TestNodeAdapterRegistry, createTestRunnerRegistry, type TestNodeAdapter, type AdapterContext, type DetailTab, } from './adapter.js';
10
+ export { TestNodeAdapterRegistry, createTestRunnerRegistry, type TestNodeAdapter, type AdapterContext, type DetailTab, type DetailAttempt, } from './adapter.js';
11
11
  export { genericCopySections, mergeCopySections, buildBrief, buildNodeBrief, buildNodeJson, type BriefFact, type BriefSection, type CopyItem, type CopySections, } from './copy.js';
12
12
  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';
13
- export { TEST_OUTCOMES, LINT_SEVERITIES, TEST_TOOLS, toolMark, type ResultMark, type LintSeverity, type ToolKind, type ToolMark, } from './marks.js';
13
+ export { TEST_OUTCOMES, LINT_SEVERITIES, TEST_TOOLS, toolMark, toolKeyFor, type ResultMark, type LintSeverity, type ToolKind, type ToolMark, } from './marks.js';
14
14
  export { OutcomeIcon, LintSeverityIcon, ToolIcon } from './MarkIcons.js';
15
15
  export { slugify, annotateRoutePaths, findNodeByRoutePath } from './routePath.js';
16
16
  export { type FilterMode, type FilterState, type TestFilters, emptyTestFilters, decodeFilterState, encodeFilterState, cycleFilterState, matchesFilterState, filterStateToRecord, recordToFilterState, } from './filterState.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,EACL,iBAAiB,EACjB,UAAU,EACV,YAAY,GACb,MAAM,qBAAqB,CAAC;AAC7B,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,EACL,uBAAuB,EACvB,wBAAwB,EACxB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,SAAS,GACf,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,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":"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,EACL,iBAAiB,EACjB,UAAU,EACV,YAAY,GACb,MAAM,qBAAqB,CAAC;AAC7B,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,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"}
@@ -21,12 +21,18 @@ const UiLogoOxc = require("../../icons/components/UiLogoOxc.cjs");
21
21
  const UiLogoEslint = require("../../icons/components/UiLogoEslint.cjs");
22
22
  const UiLogoGo = require("../../icons/components/UiLogoGo.cjs");
23
23
  const UiSealCheck = require("../../icons/components/UiSealCheck.cjs");
24
+ const UiCursorClick = require("../../icons/components/UiCursorClick.cjs");
25
+ const UiLogoPlaywright = require("../../icons/components/UiLogoPlaywright.cjs");
26
+ const UiGlobe = require("../../icons/components/UiGlobe.cjs");
27
+ const UiRobotAi = require("../../icons/components/UiRobotAi.cjs");
28
+ const UiChatDots = require("../../icons/components/UiChatDots.cjs");
29
+ const UiClipboardText = require("../../icons/components/UiClipboardText.cjs");
30
+ const UiServer = require("../../icons/components/UiServer.cjs");
24
31
  const UiDatabase = require("../../icons/components/UiDatabase.cjs");
25
32
  const UiSparkles = require("../../icons/components/UiSparkles.cjs");
26
33
  const UiListChecks = require("../../icons/components/UiListChecks.cjs");
27
34
  const UiBeaker = require("../../icons/components/UiBeaker.cjs");
28
35
  const UiTerminal = require("../../icons/components/UiTerminal.cjs");
29
- const UiLogoPlaywright = require("../../icons/components/UiLogoPlaywright.cjs");
30
36
  const UiLogoJest = require("../../icons/components/UiLogoJest.cjs");
31
37
  const UiLogoVitest = require("../../icons/components/UiLogoVitest.cjs");
32
38
  const LABEL = Object.fromEntries(status.STATUS_LABELS.map((s) => [s.key, s.label]));
@@ -88,6 +94,31 @@ const TEST_TOOLS = {
88
94
  className: "text-violet-600 dark:text-violet-400"
89
95
  },
90
96
  query: { icon: UiDatabase.UiDatabase, label: "Query", kind: "fixture", className: MUTED },
97
+ // Typed step kinds (gavel fixtures/kind registry): the node's `framework`
98
+ // is the kind name. `ai` is the runner behind `verify` but is a prompt/CEL
99
+ // round-trip rather than an acceptance-criteria review, so it takes the
100
+ // same violet family with its own glyph and label.
101
+ services: { icon: UiServer.UiServer, label: "Services", kind: "fixture", className: MUTED },
102
+ todo: { icon: UiClipboardText.UiClipboardText, label: "TODO step", kind: "fixture", className: MUTED },
103
+ todo_prompt: { icon: UiChatDots.UiChatDots, label: "TODO prompt run", kind: "fixture", className: MUTED },
104
+ ai: {
105
+ icon: UiRobotAi.UiRobotAi,
106
+ label: "AI step",
107
+ kind: "fixture",
108
+ className: "text-violet-600 dark:text-violet-400"
109
+ },
110
+ browser: { icon: UiGlobe.UiGlobe, label: "Browser", kind: "fixture", className: MUTED },
111
+ // The fixture-step twin of the `playwright` test framework above; both
112
+ // arrive as framework "playwright", so toolKeyFor() picks this key when the
113
+ // node carries `detail.step`. Muted like every other fixture step, so the
114
+ // brand green stays on the spec results the framework reports.
115
+ "playwright-step": {
116
+ icon: UiLogoPlaywright.UiLogoPlaywright,
117
+ label: "Playwright step",
118
+ kind: "fixture",
119
+ className: MUTED
120
+ },
121
+ "agent-browser": { icon: UiCursorClick.UiCursorClick, label: "Agent browser", kind: "fixture", className: MUTED },
91
122
  criterion: { icon: UiSealCheck.UiSealCheck, label: "Acceptance criterion", kind: "fixture", className: MUTED },
92
123
  checklist: { icon: UiSealCheck.UiSealCheck, label: "Acceptance criteria", kind: "fixture", className: MUTED },
93
124
  "golangci-lint": { icon: UiLogoGo.UiLogoGo, label: "golangci-lint", kind: "lint", className: GO },
@@ -136,8 +167,18 @@ const TEST_TOOLS = {
136
167
  function toolMark(tool) {
137
168
  return tool && TEST_TOOLS[tool] || null;
138
169
  }
170
+ function hasStepInfo(detail) {
171
+ if (detail === null || typeof detail !== "object" || Array.isArray(detail)) return false;
172
+ const step = detail.step;
173
+ return step !== null && typeof step === "object" && !Array.isArray(step);
174
+ }
175
+ function toolKeyFor(node) {
176
+ if (node.framework === "playwright" && hasStepInfo(node.detail)) return "playwright-step";
177
+ return node.framework;
178
+ }
139
179
  exports.LINT_SEVERITIES = LINT_SEVERITIES;
140
180
  exports.TEST_OUTCOMES = TEST_OUTCOMES;
141
181
  exports.TEST_TOOLS = TEST_TOOLS;
182
+ exports.toolKeyFor = toolKeyFor;
142
183
  exports.toolMark = toolMark;
143
184
  //# sourceMappingURL=marks.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"marks.cjs","sources":["../../../src/data/test-runner/marks.ts"],"sourcesContent":["// The shared iconography for test and lint results: what happened (outcome),\n// how bad a finding is (lint severity), and what produced it (tool).\n//\n// Every outcome has its own outline glyph, so a result never reads by colour\n// alone.\n// Tool marks are bundled Simple Icons brand glyphs (offline, no runtime icon\n// names), coloured per theme here because one brand hex can't hold up on both\n// light and dark surfaces.\n\nimport type { StaticIconComponent } from \"../Icon\";\nimport {\n UiCircleOutline,\n UiCircleX,\n UiBeaker,\n UiCopy,\n UiDatabase,\n UiInfo,\n UiKey,\n UiListChecks,\n UiLoader,\n UiLogoEslint,\n UiLogoGo,\n UiLogoJest,\n UiLogoMarkdown,\n UiLogoOxc,\n UiLogoPlaywright,\n UiLogoPython,\n UiLogoReact,\n UiLogoRuff,\n UiLogoTypescript,\n UiLogoVitest,\n UiPass,\n UiProhibit,\n UiSealCheck,\n UiSkipForward,\n UiSparkles,\n UiTerminal,\n UiTimer,\n UiWarningTriangle,\n} from \"../../icons\";\nimport { STATUS_LABELS, type TestOutcome } from \"./status\";\n\nexport type { TestOutcome } from \"./status\";\n\nexport type ResultMark = {\n icon: StaticIconComponent;\n label: string;\n /** Text colour classes for light and dark themes. */\n className: string;\n spin?: boolean;\n};\n\nconst LABEL = Object.fromEntries(STATUS_LABELS.map((s) => [s.key, s.label]));\n\nconst RED = \"text-red-600 dark:text-red-400\";\nconst AMBER = \"text-amber-600 dark:text-amber-400\";\n\nexport const TEST_OUTCOMES: Record<TestOutcome, ResultMark> = {\n failed: { icon: UiCircleX, label: LABEL.failed!, className: RED },\n errored: { icon: UiProhibit, label: \"Could not run\", className: RED },\n timedout: { icon: UiTimer, label: LABEL.timedout!, className: AMBER },\n warned: { icon: UiWarningTriangle, label: LABEL.warned!, className: AMBER },\n passed: {\n icon: UiPass,\n label: LABEL.passed!,\n className: \"text-emerald-600 dark:text-emerald-400\",\n },\n skipped: { icon: UiSkipForward, label: LABEL.skipped!, className: \"text-slate-500\" },\n pending: {\n icon: UiCircleOutline,\n label: LABEL.pending!,\n className: \"text-slate-400 dark:text-slate-500\",\n },\n running: {\n icon: UiLoader,\n label: LABEL.running!,\n className: \"text-blue-600 dark:text-blue-400\",\n spin: true,\n },\n};\n\nexport type LintSeverity = \"error\" | \"warning\" | \"info\";\n\n/** An error-severity finding fails the verdict and a warning warns it, so they share those outcomes' marks. */\nexport const LINT_SEVERITIES: Record<LintSeverity, ResultMark> = {\n error: { ...TEST_OUTCOMES.failed, label: \"Error\" },\n warning: { ...TEST_OUTCOMES.warned, label: \"Warning\" },\n info: { icon: UiInfo, label: \"Info\", className: \"text-sky-600 dark:text-sky-400\" },\n};\n\nexport type ToolKind = \"test\" | \"lint\" | \"fixture\" | \"runner\";\n\nexport type ToolMark = {\n icon: StaticIconComponent;\n label: string;\n kind: ToolKind;\n className: string;\n};\n\nconst GO = \"text-[#00add8]\";\nconst MARKDOWN = \"text-foreground\";\nconst MUTED = \"text-muted-foreground\";\n\n/**\n * Keyed by the identifiers gavel reports: testrunner/parsers Framework values\n * and each linter's Name(). A tool without a mark of its own takes its\n * language's (golangci-lint → Go, pyright → Python).\n */\nexport const TEST_TOOLS: Record<string, ToolMark> = {\n \"go test\": { icon: UiLogoGo, label: \"go test\", kind: \"test\", className: GO },\n ginkgo: { icon: UiLogoGo, label: \"Ginkgo\", kind: \"test\", className: GO },\n vitest: { icon: UiLogoVitest, label: \"Vitest\", kind: \"test\", className: \"text-[#6e9f18]\" },\n jest: { icon: UiLogoJest, label: \"Jest\", kind: \"test\", className: \"text-[#c21325] dark:text-[#e5576a]\" },\n playwright: {\n icon: UiLogoPlaywright,\n label: \"Playwright\",\n kind: \"test\",\n className: \"text-[#2ead33]\",\n },\n fixture: { icon: UiLogoMarkdown, label: \"Markdown fixture\", kind: \"test\", className: MARKDOWN },\n task: { icon: UiTerminal, label: \"Task\", kind: \"runner\", className: MUTED },\n // Fixture step types (gavel fixtures FixtureResult.Type) and the\n // \"checklist\" node verifyReportTests synthesizes for acceptance criteria.\n exec: { icon: UiTerminal, label: \"Command\", kind: \"fixture\", className: MUTED },\n test: { icon: UiBeaker, label: \"Test step\", kind: \"fixture\", className: MUTED },\n lint: { icon: UiListChecks, label: \"Lint step\", kind: \"fixture\", className: MUTED },\n verify: {\n icon: UiSparkles,\n label: \"AI review\",\n kind: \"fixture\",\n className: \"text-violet-600 dark:text-violet-400\",\n },\n query: { icon: UiDatabase, label: \"Query\", kind: \"fixture\", className: MUTED },\n criterion: { icon: UiSealCheck, label: \"Acceptance criterion\", kind: \"fixture\", className: MUTED },\n checklist: { icon: UiSealCheck, label: \"Acceptance criteria\", kind: \"fixture\", className: MUTED },\n \"golangci-lint\": { icon: UiLogoGo, label: \"golangci-lint\", kind: \"lint\", className: GO },\n eslint: {\n icon: UiLogoEslint,\n label: \"ESLint\",\n kind: \"lint\",\n className: \"text-[#4b32c3] dark:text-[#8080f2]\",\n },\n oxlint: {\n icon: UiLogoOxc,\n label: \"oxlint\",\n kind: \"lint\",\n className: \"text-[#0891b2] dark:text-[#00f7f1]\",\n },\n tsc: {\n icon: UiLogoTypescript,\n label: \"TypeScript compiler\",\n kind: \"lint\",\n className: \"text-[#3178c6]\",\n },\n markdownlint: { icon: UiLogoMarkdown, label: \"markdownlint\", kind: \"lint\", className: MARKDOWN },\n vale: { icon: UiLogoMarkdown, label: \"Vale (prose)\", kind: \"lint\", className: MARKDOWN },\n pyright: {\n icon: UiLogoPython,\n label: \"Pyright\",\n kind: \"lint\",\n className: \"text-[#3776ab] dark:text-[#ffd43b]\",\n },\n ruff: {\n icon: UiLogoRuff,\n label: \"Ruff\",\n kind: \"lint\",\n className: \"text-[#261230] dark:text-[#d7ff64]\",\n },\n // A secret scanner's finding is about a credential, not a language.\n betterleaks: { icon: UiKey, label: \"betterleaks (secrets)\", kind: \"lint\", className: MUTED },\n jscpd: { icon: UiCopy, label: \"jscpd (duplicate code)\", kind: \"lint\", className: MUTED },\n reactdoctor: {\n icon: UiLogoReact,\n label: \"React Doctor\",\n kind: \"lint\",\n className: \"text-[#0e7490] dark:text-[#61dafb]\",\n },\n};\n\n/** The tool's mark, or null for a tool the catalog doesn't know (render its name alone). */\nexport function toolMark(tool: string | undefined): ToolMark | null {\n return (tool && TEST_TOOLS[tool]) || null;\n}\n\n"],"names":["STATUS_LABELS","UiCircleX","UiProhibit","UiTimer","UiWarningTriangle","UiPass","UiSkipForward","UiCircleOutline","UiLoader","UiInfo","UiLogoGo","UiLogoVitest","UiLogoJest","UiLogoPlaywright","UiLogoMarkdown","UiTerminal","UiBeaker","UiListChecks","UiSparkles","UiDatabase","UiSealCheck","UiLogoEslint","UiLogoOxc","UiLogoTypescript","UiLogoPython","UiLogoRuff","UiKey","UiCopy","UiLogoReact"],"mappings":"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"}
1
+ {"version":3,"file":"marks.cjs","sources":["../../../src/data/test-runner/marks.ts"],"sourcesContent":["// The shared iconography for test and lint results: what happened (outcome),\n// how bad a finding is (lint severity), and what produced it (tool).\n//\n// Every outcome has its own outline glyph, so a result never reads by colour\n// alone.\n// Tool marks are bundled Simple Icons brand glyphs (offline, no runtime icon\n// names), coloured per theme here because one brand hex can't hold up on both\n// light and dark surfaces.\n\nimport type { StaticIconComponent } from \"../Icon\";\nimport {\n UiCircleOutline,\n UiCircleX,\n UiBeaker,\n UiChatDots,\n UiClipboardText,\n UiCopy,\n UiCursorClick,\n UiDatabase,\n UiGlobe,\n UiInfo,\n UiKey,\n UiListChecks,\n UiLoader,\n UiLogoEslint,\n UiLogoGo,\n UiLogoJest,\n UiLogoMarkdown,\n UiLogoOxc,\n UiLogoPlaywright,\n UiLogoPython,\n UiLogoReact,\n UiLogoRuff,\n UiLogoTypescript,\n UiLogoVitest,\n UiPass,\n UiProhibit,\n UiRobotAi,\n UiSealCheck,\n UiServer,\n UiSkipForward,\n UiSparkles,\n UiTerminal,\n UiTimer,\n UiWarningTriangle,\n} from \"../../icons\";\nimport { STATUS_LABELS, type TestOutcome } from \"./status\";\n\nexport type { TestOutcome } from \"./status\";\n\nexport type ResultMark = {\n icon: StaticIconComponent;\n label: string;\n /** Text colour classes for light and dark themes. */\n className: string;\n spin?: boolean;\n};\n\nconst LABEL = Object.fromEntries(STATUS_LABELS.map((s) => [s.key, s.label]));\n\nconst RED = \"text-red-600 dark:text-red-400\";\nconst AMBER = \"text-amber-600 dark:text-amber-400\";\n\nexport const TEST_OUTCOMES: Record<TestOutcome, ResultMark> = {\n failed: { icon: UiCircleX, label: LABEL.failed!, className: RED },\n errored: { icon: UiProhibit, label: \"Could not run\", className: RED },\n timedout: { icon: UiTimer, label: LABEL.timedout!, className: AMBER },\n warned: { icon: UiWarningTriangle, label: LABEL.warned!, className: AMBER },\n passed: {\n icon: UiPass,\n label: LABEL.passed!,\n className: \"text-emerald-600 dark:text-emerald-400\",\n },\n skipped: { icon: UiSkipForward, label: LABEL.skipped!, className: \"text-slate-500\" },\n pending: {\n icon: UiCircleOutline,\n label: LABEL.pending!,\n className: \"text-slate-400 dark:text-slate-500\",\n },\n running: {\n icon: UiLoader,\n label: LABEL.running!,\n className: \"text-blue-600 dark:text-blue-400\",\n spin: true,\n },\n};\n\nexport type LintSeverity = \"error\" | \"warning\" | \"info\";\n\n/** An error-severity finding fails the verdict and a warning warns it, so they share those outcomes' marks. */\nexport const LINT_SEVERITIES: Record<LintSeverity, ResultMark> = {\n error: { ...TEST_OUTCOMES.failed, label: \"Error\" },\n warning: { ...TEST_OUTCOMES.warned, label: \"Warning\" },\n info: { icon: UiInfo, label: \"Info\", className: \"text-sky-600 dark:text-sky-400\" },\n};\n\nexport type ToolKind = \"test\" | \"lint\" | \"fixture\" | \"runner\";\n\nexport type ToolMark = {\n icon: StaticIconComponent;\n label: string;\n kind: ToolKind;\n className: string;\n};\n\nconst GO = \"text-[#00add8]\";\nconst MARKDOWN = \"text-foreground\";\nconst MUTED = \"text-muted-foreground\";\n\n/**\n * Keyed by the identifiers gavel reports: testrunner/parsers Framework values\n * and each linter's Name(). A tool without a mark of its own takes its\n * language's (golangci-lint → Go, pyright → Python).\n */\nexport const TEST_TOOLS: Record<string, ToolMark> = {\n \"go test\": { icon: UiLogoGo, label: \"go test\", kind: \"test\", className: GO },\n ginkgo: { icon: UiLogoGo, label: \"Ginkgo\", kind: \"test\", className: GO },\n vitest: { icon: UiLogoVitest, label: \"Vitest\", kind: \"test\", className: \"text-[#6e9f18]\" },\n jest: { icon: UiLogoJest, label: \"Jest\", kind: \"test\", className: \"text-[#c21325] dark:text-[#e5576a]\" },\n playwright: {\n icon: UiLogoPlaywright,\n label: \"Playwright\",\n kind: \"test\",\n className: \"text-[#2ead33]\",\n },\n fixture: { icon: UiLogoMarkdown, label: \"Markdown fixture\", kind: \"test\", className: MARKDOWN },\n task: { icon: UiTerminal, label: \"Task\", kind: \"runner\", className: MUTED },\n // Fixture step types (gavel fixtures FixtureResult.Type) and the\n // \"checklist\" node verifyReportTests synthesizes for acceptance criteria.\n exec: { icon: UiTerminal, label: \"Command\", kind: \"fixture\", className: MUTED },\n test: { icon: UiBeaker, label: \"Test step\", kind: \"fixture\", className: MUTED },\n lint: { icon: UiListChecks, label: \"Lint step\", kind: \"fixture\", className: MUTED },\n verify: {\n icon: UiSparkles,\n label: \"AI review\",\n kind: \"fixture\",\n className: \"text-violet-600 dark:text-violet-400\",\n },\n query: { icon: UiDatabase, label: \"Query\", kind: \"fixture\", className: MUTED },\n // Typed step kinds (gavel fixtures/kind registry): the node's `framework`\n // is the kind name. `ai` is the runner behind `verify` but is a prompt/CEL\n // round-trip rather than an acceptance-criteria review, so it takes the\n // same violet family with its own glyph and label.\n services: { icon: UiServer, label: \"Services\", kind: \"fixture\", className: MUTED },\n todo: { icon: UiClipboardText, label: \"TODO step\", kind: \"fixture\", className: MUTED },\n todo_prompt: { icon: UiChatDots, label: \"TODO prompt run\", kind: \"fixture\", className: MUTED },\n ai: {\n icon: UiRobotAi,\n label: \"AI step\",\n kind: \"fixture\",\n className: \"text-violet-600 dark:text-violet-400\",\n },\n browser: { icon: UiGlobe, label: \"Browser\", kind: \"fixture\", className: MUTED },\n // The fixture-step twin of the `playwright` test framework above; both\n // arrive as framework \"playwright\", so toolKeyFor() picks this key when the\n // node carries `detail.step`. Muted like every other fixture step, so the\n // brand green stays on the spec results the framework reports.\n \"playwright-step\": {\n icon: UiLogoPlaywright,\n label: \"Playwright step\",\n kind: \"fixture\",\n className: MUTED,\n },\n \"agent-browser\": { icon: UiCursorClick, label: \"Agent browser\", kind: \"fixture\", className: MUTED },\n criterion: { icon: UiSealCheck, label: \"Acceptance criterion\", kind: \"fixture\", className: MUTED },\n checklist: { icon: UiSealCheck, label: \"Acceptance criteria\", kind: \"fixture\", className: MUTED },\n \"golangci-lint\": { icon: UiLogoGo, label: \"golangci-lint\", kind: \"lint\", className: GO },\n eslint: {\n icon: UiLogoEslint,\n label: \"ESLint\",\n kind: \"lint\",\n className: \"text-[#4b32c3] dark:text-[#8080f2]\",\n },\n oxlint: {\n icon: UiLogoOxc,\n label: \"oxlint\",\n kind: \"lint\",\n className: \"text-[#0891b2] dark:text-[#00f7f1]\",\n },\n tsc: {\n icon: UiLogoTypescript,\n label: \"TypeScript compiler\",\n kind: \"lint\",\n className: \"text-[#3178c6]\",\n },\n markdownlint: { icon: UiLogoMarkdown, label: \"markdownlint\", kind: \"lint\", className: MARKDOWN },\n vale: { icon: UiLogoMarkdown, label: \"Vale (prose)\", kind: \"lint\", className: MARKDOWN },\n pyright: {\n icon: UiLogoPython,\n label: \"Pyright\",\n kind: \"lint\",\n className: \"text-[#3776ab] dark:text-[#ffd43b]\",\n },\n ruff: {\n icon: UiLogoRuff,\n label: \"Ruff\",\n kind: \"lint\",\n className: \"text-[#261230] dark:text-[#d7ff64]\",\n },\n // A secret scanner's finding is about a credential, not a language.\n betterleaks: { icon: UiKey, label: \"betterleaks (secrets)\", kind: \"lint\", className: MUTED },\n jscpd: { icon: UiCopy, label: \"jscpd (duplicate code)\", kind: \"lint\", className: MUTED },\n reactdoctor: {\n icon: UiLogoReact,\n label: \"React Doctor\",\n kind: \"lint\",\n className: \"text-[#0e7490] dark:text-[#61dafb]\",\n },\n};\n\n/** The tool's mark, or null for a tool the catalog doesn't know (render its name alone). */\nexport function toolMark(tool: string | undefined): ToolMark | null {\n return (tool && TEST_TOOLS[tool]) || null;\n}\n\nfunction hasStepInfo(detail: unknown): boolean {\n if (detail === null || typeof detail !== \"object\" || Array.isArray(detail)) return false;\n const step = (detail as Record<string, unknown>).step;\n return step !== null && typeof step === \"object\" && !Array.isArray(step);\n}\n\n/**\n * The TEST_TOOLS key for a node. A node's `framework` is the key, except that\n * \"playwright\" names both the test framework and the `playwright` fixture-step\n * kind: a node carrying `detail.step` (the fixture verifier's step id and\n * kind) is the step.\n */\nexport function toolKeyFor(node: { framework?: string | undefined; detail?: unknown }): string | undefined {\n if (node.framework === \"playwright\" && hasStepInfo(node.detail)) return \"playwright-step\";\n return node.framework;\n}\n\n"],"names":["STATUS_LABELS","UiCircleX","UiProhibit","UiTimer","UiWarningTriangle","UiPass","UiSkipForward","UiCircleOutline","UiLoader","UiInfo","UiLogoGo","UiLogoVitest","UiLogoJest","UiLogoPlaywright","UiLogoMarkdown","UiTerminal","UiBeaker","UiListChecks","UiSparkles","UiDatabase","UiServer","UiClipboardText","UiChatDots","UiRobotAi","UiGlobe","UiCursorClick","UiSealCheck","UiLogoEslint","UiLogoOxc","UiLogoTypescript","UiLogoPython","UiLogoRuff","UiKey","UiCopy","UiLogoReact"],"mappings":"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"}
@@ -27,4 +27,14 @@ export type ToolMark = {
27
27
  export declare const TEST_TOOLS: Record<string, ToolMark>;
28
28
  /** The tool's mark, or null for a tool the catalog doesn't know (render its name alone). */
29
29
  export declare function toolMark(tool: string | undefined): ToolMark | null;
30
+ /**
31
+ * The TEST_TOOLS key for a node. A node's `framework` is the key, except that
32
+ * "playwright" names both the test framework and the `playwright` fixture-step
33
+ * kind: a node carrying `detail.step` (the fixture verifier's step id and
34
+ * kind) is the step.
35
+ */
36
+ export declare function toolKeyFor(node: {
37
+ framework?: string | undefined;
38
+ detail?: unknown;
39
+ }): string | undefined;
30
40
  //# sourceMappingURL=marks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"marks.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/marks.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AA+BnD,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAE3D,YAAY,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,mBAAmB,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAOF,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,WAAW,EAAE,UAAU,CAsBzD,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAExD,+GAA+G;AAC/G,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,YAAY,EAAE,UAAU,CAI5D,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE9D,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,mBAAmB,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAMF;;;;GAIG;AACH,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAqE/C,CAAC;AAEF,4FAA4F;AAC5F,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,IAAI,CAElE"}
1
+ {"version":3,"file":"marks.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/marks.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAqCnD,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAE3D,YAAY,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,mBAAmB,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAOF,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,WAAW,EAAE,UAAU,CAsBzD,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAExD,+GAA+G;AAC/G,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,YAAY,EAAE,UAAU,CAI5D,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE9D,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,mBAAmB,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAMF;;;;GAIG;AACH,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CA8F/C,CAAC;AAEF,4FAA4F;AAC5F,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,IAAI,CAElE;AAQD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,GAAG,SAAS,CAGzG"}
@@ -19,12 +19,18 @@ import { UiLogoOxc } from "../../icons/components/UiLogoOxc.js";
19
19
  import { UiLogoEslint } from "../../icons/components/UiLogoEslint.js";
20
20
  import { UiLogoGo } from "../../icons/components/UiLogoGo.js";
21
21
  import { UiSealCheck } from "../../icons/components/UiSealCheck.js";
22
+ import { UiCursorClick } from "../../icons/components/UiCursorClick.js";
23
+ import { UiLogoPlaywright } from "../../icons/components/UiLogoPlaywright.js";
24
+ import { UiGlobe } from "../../icons/components/UiGlobe.js";
25
+ import { UiRobotAi } from "../../icons/components/UiRobotAi.js";
26
+ import { UiChatDots } from "../../icons/components/UiChatDots.js";
27
+ import { UiClipboardText } from "../../icons/components/UiClipboardText.js";
28
+ import { UiServer } from "../../icons/components/UiServer.js";
22
29
  import { UiDatabase } from "../../icons/components/UiDatabase.js";
23
30
  import { UiSparkles } from "../../icons/components/UiSparkles.js";
24
31
  import { UiListChecks } from "../../icons/components/UiListChecks.js";
25
32
  import { UiBeaker } from "../../icons/components/UiBeaker.js";
26
33
  import { UiTerminal } from "../../icons/components/UiTerminal.js";
27
- import { UiLogoPlaywright } from "../../icons/components/UiLogoPlaywright.js";
28
34
  import { UiLogoJest } from "../../icons/components/UiLogoJest.js";
29
35
  import { UiLogoVitest } from "../../icons/components/UiLogoVitest.js";
30
36
  const LABEL = Object.fromEntries(STATUS_LABELS.map((s) => [s.key, s.label]));
@@ -86,6 +92,31 @@ const TEST_TOOLS = {
86
92
  className: "text-violet-600 dark:text-violet-400"
87
93
  },
88
94
  query: { icon: UiDatabase, label: "Query", kind: "fixture", className: MUTED },
95
+ // Typed step kinds (gavel fixtures/kind registry): the node's `framework`
96
+ // is the kind name. `ai` is the runner behind `verify` but is a prompt/CEL
97
+ // round-trip rather than an acceptance-criteria review, so it takes the
98
+ // same violet family with its own glyph and label.
99
+ services: { icon: UiServer, label: "Services", kind: "fixture", className: MUTED },
100
+ todo: { icon: UiClipboardText, label: "TODO step", kind: "fixture", className: MUTED },
101
+ todo_prompt: { icon: UiChatDots, label: "TODO prompt run", kind: "fixture", className: MUTED },
102
+ ai: {
103
+ icon: UiRobotAi,
104
+ label: "AI step",
105
+ kind: "fixture",
106
+ className: "text-violet-600 dark:text-violet-400"
107
+ },
108
+ browser: { icon: UiGlobe, label: "Browser", kind: "fixture", className: MUTED },
109
+ // The fixture-step twin of the `playwright` test framework above; both
110
+ // arrive as framework "playwright", so toolKeyFor() picks this key when the
111
+ // node carries `detail.step`. Muted like every other fixture step, so the
112
+ // brand green stays on the spec results the framework reports.
113
+ "playwright-step": {
114
+ icon: UiLogoPlaywright,
115
+ label: "Playwright step",
116
+ kind: "fixture",
117
+ className: MUTED
118
+ },
119
+ "agent-browser": { icon: UiCursorClick, label: "Agent browser", kind: "fixture", className: MUTED },
89
120
  criterion: { icon: UiSealCheck, label: "Acceptance criterion", kind: "fixture", className: MUTED },
90
121
  checklist: { icon: UiSealCheck, label: "Acceptance criteria", kind: "fixture", className: MUTED },
91
122
  "golangci-lint": { icon: UiLogoGo, label: "golangci-lint", kind: "lint", className: GO },
@@ -134,10 +165,20 @@ const TEST_TOOLS = {
134
165
  function toolMark(tool) {
135
166
  return tool && TEST_TOOLS[tool] || null;
136
167
  }
168
+ function hasStepInfo(detail) {
169
+ if (detail === null || typeof detail !== "object" || Array.isArray(detail)) return false;
170
+ const step = detail.step;
171
+ return step !== null && typeof step === "object" && !Array.isArray(step);
172
+ }
173
+ function toolKeyFor(node) {
174
+ if (node.framework === "playwright" && hasStepInfo(node.detail)) return "playwright-step";
175
+ return node.framework;
176
+ }
137
177
  export {
138
178
  LINT_SEVERITIES,
139
179
  TEST_OUTCOMES,
140
180
  TEST_TOOLS,
181
+ toolKeyFor,
141
182
  toolMark
142
183
  };
143
184
  //# sourceMappingURL=marks.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"marks.js","sources":["../../../src/data/test-runner/marks.ts"],"sourcesContent":["// The shared iconography for test and lint results: what happened (outcome),\n// how bad a finding is (lint severity), and what produced it (tool).\n//\n// Every outcome has its own outline glyph, so a result never reads by colour\n// alone.\n// Tool marks are bundled Simple Icons brand glyphs (offline, no runtime icon\n// names), coloured per theme here because one brand hex can't hold up on both\n// light and dark surfaces.\n\nimport type { StaticIconComponent } from \"../Icon\";\nimport {\n UiCircleOutline,\n UiCircleX,\n UiBeaker,\n UiCopy,\n UiDatabase,\n UiInfo,\n UiKey,\n UiListChecks,\n UiLoader,\n UiLogoEslint,\n UiLogoGo,\n UiLogoJest,\n UiLogoMarkdown,\n UiLogoOxc,\n UiLogoPlaywright,\n UiLogoPython,\n UiLogoReact,\n UiLogoRuff,\n UiLogoTypescript,\n UiLogoVitest,\n UiPass,\n UiProhibit,\n UiSealCheck,\n UiSkipForward,\n UiSparkles,\n UiTerminal,\n UiTimer,\n UiWarningTriangle,\n} from \"../../icons\";\nimport { STATUS_LABELS, type TestOutcome } from \"./status\";\n\nexport type { TestOutcome } from \"./status\";\n\nexport type ResultMark = {\n icon: StaticIconComponent;\n label: string;\n /** Text colour classes for light and dark themes. */\n className: string;\n spin?: boolean;\n};\n\nconst LABEL = Object.fromEntries(STATUS_LABELS.map((s) => [s.key, s.label]));\n\nconst RED = \"text-red-600 dark:text-red-400\";\nconst AMBER = \"text-amber-600 dark:text-amber-400\";\n\nexport const TEST_OUTCOMES: Record<TestOutcome, ResultMark> = {\n failed: { icon: UiCircleX, label: LABEL.failed!, className: RED },\n errored: { icon: UiProhibit, label: \"Could not run\", className: RED },\n timedout: { icon: UiTimer, label: LABEL.timedout!, className: AMBER },\n warned: { icon: UiWarningTriangle, label: LABEL.warned!, className: AMBER },\n passed: {\n icon: UiPass,\n label: LABEL.passed!,\n className: \"text-emerald-600 dark:text-emerald-400\",\n },\n skipped: { icon: UiSkipForward, label: LABEL.skipped!, className: \"text-slate-500\" },\n pending: {\n icon: UiCircleOutline,\n label: LABEL.pending!,\n className: \"text-slate-400 dark:text-slate-500\",\n },\n running: {\n icon: UiLoader,\n label: LABEL.running!,\n className: \"text-blue-600 dark:text-blue-400\",\n spin: true,\n },\n};\n\nexport type LintSeverity = \"error\" | \"warning\" | \"info\";\n\n/** An error-severity finding fails the verdict and a warning warns it, so they share those outcomes' marks. */\nexport const LINT_SEVERITIES: Record<LintSeverity, ResultMark> = {\n error: { ...TEST_OUTCOMES.failed, label: \"Error\" },\n warning: { ...TEST_OUTCOMES.warned, label: \"Warning\" },\n info: { icon: UiInfo, label: \"Info\", className: \"text-sky-600 dark:text-sky-400\" },\n};\n\nexport type ToolKind = \"test\" | \"lint\" | \"fixture\" | \"runner\";\n\nexport type ToolMark = {\n icon: StaticIconComponent;\n label: string;\n kind: ToolKind;\n className: string;\n};\n\nconst GO = \"text-[#00add8]\";\nconst MARKDOWN = \"text-foreground\";\nconst MUTED = \"text-muted-foreground\";\n\n/**\n * Keyed by the identifiers gavel reports: testrunner/parsers Framework values\n * and each linter's Name(). A tool without a mark of its own takes its\n * language's (golangci-lint → Go, pyright → Python).\n */\nexport const TEST_TOOLS: Record<string, ToolMark> = {\n \"go test\": { icon: UiLogoGo, label: \"go test\", kind: \"test\", className: GO },\n ginkgo: { icon: UiLogoGo, label: \"Ginkgo\", kind: \"test\", className: GO },\n vitest: { icon: UiLogoVitest, label: \"Vitest\", kind: \"test\", className: \"text-[#6e9f18]\" },\n jest: { icon: UiLogoJest, label: \"Jest\", kind: \"test\", className: \"text-[#c21325] dark:text-[#e5576a]\" },\n playwright: {\n icon: UiLogoPlaywright,\n label: \"Playwright\",\n kind: \"test\",\n className: \"text-[#2ead33]\",\n },\n fixture: { icon: UiLogoMarkdown, label: \"Markdown fixture\", kind: \"test\", className: MARKDOWN },\n task: { icon: UiTerminal, label: \"Task\", kind: \"runner\", className: MUTED },\n // Fixture step types (gavel fixtures FixtureResult.Type) and the\n // \"checklist\" node verifyReportTests synthesizes for acceptance criteria.\n exec: { icon: UiTerminal, label: \"Command\", kind: \"fixture\", className: MUTED },\n test: { icon: UiBeaker, label: \"Test step\", kind: \"fixture\", className: MUTED },\n lint: { icon: UiListChecks, label: \"Lint step\", kind: \"fixture\", className: MUTED },\n verify: {\n icon: UiSparkles,\n label: \"AI review\",\n kind: \"fixture\",\n className: \"text-violet-600 dark:text-violet-400\",\n },\n query: { icon: UiDatabase, label: \"Query\", kind: \"fixture\", className: MUTED },\n criterion: { icon: UiSealCheck, label: \"Acceptance criterion\", kind: \"fixture\", className: MUTED },\n checklist: { icon: UiSealCheck, label: \"Acceptance criteria\", kind: \"fixture\", className: MUTED },\n \"golangci-lint\": { icon: UiLogoGo, label: \"golangci-lint\", kind: \"lint\", className: GO },\n eslint: {\n icon: UiLogoEslint,\n label: \"ESLint\",\n kind: \"lint\",\n className: \"text-[#4b32c3] dark:text-[#8080f2]\",\n },\n oxlint: {\n icon: UiLogoOxc,\n label: \"oxlint\",\n kind: \"lint\",\n className: \"text-[#0891b2] dark:text-[#00f7f1]\",\n },\n tsc: {\n icon: UiLogoTypescript,\n label: \"TypeScript compiler\",\n kind: \"lint\",\n className: \"text-[#3178c6]\",\n },\n markdownlint: { icon: UiLogoMarkdown, label: \"markdownlint\", kind: \"lint\", className: MARKDOWN },\n vale: { icon: UiLogoMarkdown, label: \"Vale (prose)\", kind: \"lint\", className: MARKDOWN },\n pyright: {\n icon: UiLogoPython,\n label: \"Pyright\",\n kind: \"lint\",\n className: \"text-[#3776ab] dark:text-[#ffd43b]\",\n },\n ruff: {\n icon: UiLogoRuff,\n label: \"Ruff\",\n kind: \"lint\",\n className: \"text-[#261230] dark:text-[#d7ff64]\",\n },\n // A secret scanner's finding is about a credential, not a language.\n betterleaks: { icon: UiKey, label: \"betterleaks (secrets)\", kind: \"lint\", className: MUTED },\n jscpd: { icon: UiCopy, label: \"jscpd (duplicate code)\", kind: \"lint\", className: MUTED },\n reactdoctor: {\n icon: UiLogoReact,\n label: \"React Doctor\",\n kind: \"lint\",\n className: \"text-[#0e7490] dark:text-[#61dafb]\",\n },\n};\n\n/** The tool's mark, or null for a tool the catalog doesn't know (render its name alone). */\nexport function toolMark(tool: string | undefined): ToolMark | null {\n return (tool && TEST_TOOLS[tool]) || null;\n}\n\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"marks.js","sources":["../../../src/data/test-runner/marks.ts"],"sourcesContent":["// The shared iconography for test and lint results: what happened (outcome),\n// how bad a finding is (lint severity), and what produced it (tool).\n//\n// Every outcome has its own outline glyph, so a result never reads by colour\n// alone.\n// Tool marks are bundled Simple Icons brand glyphs (offline, no runtime icon\n// names), coloured per theme here because one brand hex can't hold up on both\n// light and dark surfaces.\n\nimport type { StaticIconComponent } from \"../Icon\";\nimport {\n UiCircleOutline,\n UiCircleX,\n UiBeaker,\n UiChatDots,\n UiClipboardText,\n UiCopy,\n UiCursorClick,\n UiDatabase,\n UiGlobe,\n UiInfo,\n UiKey,\n UiListChecks,\n UiLoader,\n UiLogoEslint,\n UiLogoGo,\n UiLogoJest,\n UiLogoMarkdown,\n UiLogoOxc,\n UiLogoPlaywright,\n UiLogoPython,\n UiLogoReact,\n UiLogoRuff,\n UiLogoTypescript,\n UiLogoVitest,\n UiPass,\n UiProhibit,\n UiRobotAi,\n UiSealCheck,\n UiServer,\n UiSkipForward,\n UiSparkles,\n UiTerminal,\n UiTimer,\n UiWarningTriangle,\n} from \"../../icons\";\nimport { STATUS_LABELS, type TestOutcome } from \"./status\";\n\nexport type { TestOutcome } from \"./status\";\n\nexport type ResultMark = {\n icon: StaticIconComponent;\n label: string;\n /** Text colour classes for light and dark themes. */\n className: string;\n spin?: boolean;\n};\n\nconst LABEL = Object.fromEntries(STATUS_LABELS.map((s) => [s.key, s.label]));\n\nconst RED = \"text-red-600 dark:text-red-400\";\nconst AMBER = \"text-amber-600 dark:text-amber-400\";\n\nexport const TEST_OUTCOMES: Record<TestOutcome, ResultMark> = {\n failed: { icon: UiCircleX, label: LABEL.failed!, className: RED },\n errored: { icon: UiProhibit, label: \"Could not run\", className: RED },\n timedout: { icon: UiTimer, label: LABEL.timedout!, className: AMBER },\n warned: { icon: UiWarningTriangle, label: LABEL.warned!, className: AMBER },\n passed: {\n icon: UiPass,\n label: LABEL.passed!,\n className: \"text-emerald-600 dark:text-emerald-400\",\n },\n skipped: { icon: UiSkipForward, label: LABEL.skipped!, className: \"text-slate-500\" },\n pending: {\n icon: UiCircleOutline,\n label: LABEL.pending!,\n className: \"text-slate-400 dark:text-slate-500\",\n },\n running: {\n icon: UiLoader,\n label: LABEL.running!,\n className: \"text-blue-600 dark:text-blue-400\",\n spin: true,\n },\n};\n\nexport type LintSeverity = \"error\" | \"warning\" | \"info\";\n\n/** An error-severity finding fails the verdict and a warning warns it, so they share those outcomes' marks. */\nexport const LINT_SEVERITIES: Record<LintSeverity, ResultMark> = {\n error: { ...TEST_OUTCOMES.failed, label: \"Error\" },\n warning: { ...TEST_OUTCOMES.warned, label: \"Warning\" },\n info: { icon: UiInfo, label: \"Info\", className: \"text-sky-600 dark:text-sky-400\" },\n};\n\nexport type ToolKind = \"test\" | \"lint\" | \"fixture\" | \"runner\";\n\nexport type ToolMark = {\n icon: StaticIconComponent;\n label: string;\n kind: ToolKind;\n className: string;\n};\n\nconst GO = \"text-[#00add8]\";\nconst MARKDOWN = \"text-foreground\";\nconst MUTED = \"text-muted-foreground\";\n\n/**\n * Keyed by the identifiers gavel reports: testrunner/parsers Framework values\n * and each linter's Name(). A tool without a mark of its own takes its\n * language's (golangci-lint → Go, pyright → Python).\n */\nexport const TEST_TOOLS: Record<string, ToolMark> = {\n \"go test\": { icon: UiLogoGo, label: \"go test\", kind: \"test\", className: GO },\n ginkgo: { icon: UiLogoGo, label: \"Ginkgo\", kind: \"test\", className: GO },\n vitest: { icon: UiLogoVitest, label: \"Vitest\", kind: \"test\", className: \"text-[#6e9f18]\" },\n jest: { icon: UiLogoJest, label: \"Jest\", kind: \"test\", className: \"text-[#c21325] dark:text-[#e5576a]\" },\n playwright: {\n icon: UiLogoPlaywright,\n label: \"Playwright\",\n kind: \"test\",\n className: \"text-[#2ead33]\",\n },\n fixture: { icon: UiLogoMarkdown, label: \"Markdown fixture\", kind: \"test\", className: MARKDOWN },\n task: { icon: UiTerminal, label: \"Task\", kind: \"runner\", className: MUTED },\n // Fixture step types (gavel fixtures FixtureResult.Type) and the\n // \"checklist\" node verifyReportTests synthesizes for acceptance criteria.\n exec: { icon: UiTerminal, label: \"Command\", kind: \"fixture\", className: MUTED },\n test: { icon: UiBeaker, label: \"Test step\", kind: \"fixture\", className: MUTED },\n lint: { icon: UiListChecks, label: \"Lint step\", kind: \"fixture\", className: MUTED },\n verify: {\n icon: UiSparkles,\n label: \"AI review\",\n kind: \"fixture\",\n className: \"text-violet-600 dark:text-violet-400\",\n },\n query: { icon: UiDatabase, label: \"Query\", kind: \"fixture\", className: MUTED },\n // Typed step kinds (gavel fixtures/kind registry): the node's `framework`\n // is the kind name. `ai` is the runner behind `verify` but is a prompt/CEL\n // round-trip rather than an acceptance-criteria review, so it takes the\n // same violet family with its own glyph and label.\n services: { icon: UiServer, label: \"Services\", kind: \"fixture\", className: MUTED },\n todo: { icon: UiClipboardText, label: \"TODO step\", kind: \"fixture\", className: MUTED },\n todo_prompt: { icon: UiChatDots, label: \"TODO prompt run\", kind: \"fixture\", className: MUTED },\n ai: {\n icon: UiRobotAi,\n label: \"AI step\",\n kind: \"fixture\",\n className: \"text-violet-600 dark:text-violet-400\",\n },\n browser: { icon: UiGlobe, label: \"Browser\", kind: \"fixture\", className: MUTED },\n // The fixture-step twin of the `playwright` test framework above; both\n // arrive as framework \"playwright\", so toolKeyFor() picks this key when the\n // node carries `detail.step`. Muted like every other fixture step, so the\n // brand green stays on the spec results the framework reports.\n \"playwright-step\": {\n icon: UiLogoPlaywright,\n label: \"Playwright step\",\n kind: \"fixture\",\n className: MUTED,\n },\n \"agent-browser\": { icon: UiCursorClick, label: \"Agent browser\", kind: \"fixture\", className: MUTED },\n criterion: { icon: UiSealCheck, label: \"Acceptance criterion\", kind: \"fixture\", className: MUTED },\n checklist: { icon: UiSealCheck, label: \"Acceptance criteria\", kind: \"fixture\", className: MUTED },\n \"golangci-lint\": { icon: UiLogoGo, label: \"golangci-lint\", kind: \"lint\", className: GO },\n eslint: {\n icon: UiLogoEslint,\n label: \"ESLint\",\n kind: \"lint\",\n className: \"text-[#4b32c3] dark:text-[#8080f2]\",\n },\n oxlint: {\n icon: UiLogoOxc,\n label: \"oxlint\",\n kind: \"lint\",\n className: \"text-[#0891b2] dark:text-[#00f7f1]\",\n },\n tsc: {\n icon: UiLogoTypescript,\n label: \"TypeScript compiler\",\n kind: \"lint\",\n className: \"text-[#3178c6]\",\n },\n markdownlint: { icon: UiLogoMarkdown, label: \"markdownlint\", kind: \"lint\", className: MARKDOWN },\n vale: { icon: UiLogoMarkdown, label: \"Vale (prose)\", kind: \"lint\", className: MARKDOWN },\n pyright: {\n icon: UiLogoPython,\n label: \"Pyright\",\n kind: \"lint\",\n className: \"text-[#3776ab] dark:text-[#ffd43b]\",\n },\n ruff: {\n icon: UiLogoRuff,\n label: \"Ruff\",\n kind: \"lint\",\n className: \"text-[#261230] dark:text-[#d7ff64]\",\n },\n // A secret scanner's finding is about a credential, not a language.\n betterleaks: { icon: UiKey, label: \"betterleaks (secrets)\", kind: \"lint\", className: MUTED },\n jscpd: { icon: UiCopy, label: \"jscpd (duplicate code)\", kind: \"lint\", className: MUTED },\n reactdoctor: {\n icon: UiLogoReact,\n label: \"React Doctor\",\n kind: \"lint\",\n className: \"text-[#0e7490] dark:text-[#61dafb]\",\n },\n};\n\n/** The tool's mark, or null for a tool the catalog doesn't know (render its name alone). */\nexport function toolMark(tool: string | undefined): ToolMark | null {\n return (tool && TEST_TOOLS[tool]) || null;\n}\n\nfunction hasStepInfo(detail: unknown): boolean {\n if (detail === null || typeof detail !== \"object\" || Array.isArray(detail)) return false;\n const step = (detail as Record<string, unknown>).step;\n return step !== null && typeof step === \"object\" && !Array.isArray(step);\n}\n\n/**\n * The TEST_TOOLS key for a node. A node's `framework` is the key, except that\n * \"playwright\" names both the test framework and the `playwright` fixture-step\n * kind: a node carrying `detail.step` (the fixture verifier's step id and\n * kind) is the step.\n */\nexport function toolKeyFor(node: { framework?: string | undefined; detail?: unknown }): string | undefined {\n if (node.framework === \"playwright\" && hasStepInfo(node.detail)) return \"playwright-step\";\n return node.framework;\n}\n\n"],"names":[],"mappings":"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"}