niceeval 0.5.4 → 0.6.1

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 (337) hide show
  1. package/INDEX.md +55 -0
  2. package/dist/agents/types.d.ts +321 -0
  3. package/dist/context/types.d.ts +335 -0
  4. package/dist/i18n/core.d.ts +11 -0
  5. package/dist/i18n/en.d.ts +181 -0
  6. package/dist/i18n/index.d.ts +5 -0
  7. package/dist/i18n/zh-CN.d.ts +183 -0
  8. package/dist/o11y/otlp/parse.d.ts +3 -0
  9. package/dist/o11y/otlp/receiver.d.ts +21 -0
  10. package/dist/o11y/otlp/turn-otel.d.ts +43 -0
  11. package/dist/o11y/types.d.ts +190 -0
  12. package/dist/report/aggregate.d.ts +57 -0
  13. package/dist/report/aggregate.js +212 -0
  14. package/dist/report/built-ins/experiment-comparison.d.ts +1 -0
  15. package/dist/report/built-ins/experiment-comparison.js +13 -0
  16. package/dist/report/built-ins/index.d.ts +1 -0
  17. package/dist/report/built-ins/index.js +2 -0
  18. package/dist/report/components.d.ts +180 -0
  19. package/dist/report/components.js +132 -0
  20. package/dist/report/compute.d.ts +106 -0
  21. package/dist/report/compute.js +600 -0
  22. package/dist/report/flag.d.ts +6 -0
  23. package/dist/report/flag.js +8 -0
  24. package/dist/report/format.d.ts +26 -0
  25. package/dist/report/format.js +109 -0
  26. package/dist/report/index.d.ts +19 -0
  27. package/dist/report/index.js +30 -0
  28. package/dist/report/load.d.ts +12 -0
  29. package/dist/report/load.js +56 -0
  30. package/dist/report/locale.d.ts +94 -0
  31. package/dist/report/locale.js +183 -0
  32. package/dist/report/metrics.d.ts +21 -0
  33. package/dist/report/metrics.js +115 -0
  34. package/dist/report/primitives.d.ts +64 -0
  35. package/dist/report/primitives.js +113 -0
  36. package/dist/report/react/AttemptList.d.ts +23 -0
  37. package/dist/report/react/AttemptList.js +19 -0
  38. package/dist/report/react/DeltaTable.d.ts +8 -0
  39. package/dist/report/react/DeltaTable.js +24 -0
  40. package/dist/report/react/EvalList.d.ts +10 -0
  41. package/dist/report/react/EvalList.js +0 -0
  42. package/dist/report/react/ExperimentList.d.ts +11 -0
  43. package/dist/report/react/ExperimentList.js +67 -0
  44. package/dist/report/react/GroupSummary.d.ts +8 -0
  45. package/dist/report/react/GroupSummary.js +8 -0
  46. package/dist/report/react/MetricBars.d.ts +10 -0
  47. package/dist/report/react/MetricBars.js +48 -0
  48. package/dist/report/react/MetricLine.d.ts +9 -0
  49. package/dist/report/react/MetricLine.js +68 -0
  50. package/dist/report/react/MetricMatrix.d.ts +10 -0
  51. package/dist/report/react/MetricMatrix.js +23 -0
  52. package/dist/report/react/MetricScatter.d.ts +9 -0
  53. package/dist/report/react/MetricScatter.js +96 -0
  54. package/dist/report/react/MetricTable.d.ts +11 -0
  55. package/dist/report/react/MetricTable.js +26 -0
  56. package/dist/report/react/RunOverview.d.ts +8 -0
  57. package/dist/report/react/RunOverview.js +12 -0
  58. package/dist/report/react/Scoreboard.d.ts +8 -0
  59. package/dist/report/react/Scoreboard.js +26 -0
  60. package/dist/report/react/cell.d.ts +9 -0
  61. package/dist/report/react/cell.js +9 -0
  62. package/dist/report/react/chart-math.d.ts +14 -0
  63. package/dist/report/react/chart-math.js +68 -0
  64. package/dist/report/react/colors.d.ts +14 -0
  65. package/dist/report/react/colors.js +45 -0
  66. package/dist/report/react/fixtures.d.ts +19 -0
  67. package/dist/report/react/fixtures.js +398 -0
  68. package/dist/report/react/format.d.ts +3 -0
  69. package/dist/report/react/format.js +7 -0
  70. package/dist/report/react/index.d.ts +6 -0
  71. package/dist/report/react/index.js +14 -0
  72. package/dist/report/report.d.ts +24 -0
  73. package/dist/report/report.js +49 -0
  74. package/dist/report/text/faces.d.ts +29 -0
  75. package/dist/report/text/faces.js +541 -0
  76. package/dist/report/text/layout.d.ts +75 -0
  77. package/dist/report/text/layout.js +173 -0
  78. package/dist/report/text/plot.d.ts +37 -0
  79. package/dist/report/text/plot.js +81 -0
  80. package/dist/report/text/table.d.ts +8 -0
  81. package/dist/report/text/table.js +86 -0
  82. package/dist/report/tree.d.ts +99 -0
  83. package/dist/report/tree.js +228 -0
  84. package/dist/report/types.d.ts +408 -0
  85. package/dist/report/types.js +4 -0
  86. package/dist/report/web.d.ts +15 -0
  87. package/dist/report/web.js +37 -0
  88. package/dist/results/locator.d.ts +75 -0
  89. package/dist/results/locator.js +0 -0
  90. package/dist/results/select.d.ts +45 -0
  91. package/dist/results/select.js +230 -0
  92. package/dist/results/source-hash.d.ts +9 -0
  93. package/dist/results/types.d.ts +204 -0
  94. package/dist/runner/eval-source.d.ts +22 -0
  95. package/dist/runner/fingerprint.d.ts +23 -0
  96. package/dist/runner/types.d.ts +332 -0
  97. package/dist/sandbox/docker-stream.d.ts +24 -0
  98. package/dist/sandbox/docker.d.ts +84 -0
  99. package/dist/sandbox/e2b.d.ts +31 -0
  100. package/dist/sandbox/errors.d.ts +4 -0
  101. package/dist/sandbox/local-files.d.ts +6 -0
  102. package/dist/sandbox/paths.d.ts +4 -0
  103. package/dist/sandbox/registry.d.ts +13 -0
  104. package/dist/sandbox/resolve.d.ts +40 -0
  105. package/dist/sandbox/retry.d.ts +10 -0
  106. package/dist/sandbox/shell.d.ts +11 -0
  107. package/dist/sandbox/source-files.d.ts +13 -0
  108. package/dist/sandbox/types.d.ts +203 -0
  109. package/dist/sandbox/vercel.d.ts +37 -0
  110. package/dist/scoring/types.d.ts +70 -0
  111. package/dist/shared/aggregate.d.ts +29 -0
  112. package/dist/shared/aggregate.js +55 -0
  113. package/dist/shared/types.d.ts +27 -0
  114. package/dist/shared/verdict.d.ts +27 -0
  115. package/dist/shared/verdict.js +34 -0
  116. package/dist/tty-line.d.ts +6 -0
  117. package/dist/types.d.ts +7 -0
  118. package/dist/util.d.ts +22 -0
  119. package/docs-site/zh/concepts/adapter.mdx +7 -7
  120. package/docs-site/zh/concepts/assert.mdx +19 -18
  121. package/docs-site/zh/concepts/evals.mdx +12 -11
  122. package/docs-site/zh/concepts/experiment.mdx +2 -2
  123. package/docs-site/zh/concepts/judge.mdx +9 -9
  124. package/docs-site/zh/concepts/overview.mdx +3 -3
  125. package/docs-site/zh/concepts/tier.mdx +6 -6
  126. package/docs-site/zh/example/claude-code-codex-plugin.mdx +9 -5
  127. package/docs-site/zh/example/claude-code-codex-skill.mdx +12 -7
  128. package/docs-site/zh/guides/agent-feedback-loop.mdx +237 -78
  129. package/docs-site/zh/guides/connect-otel.mdx +3 -3
  130. package/docs-site/zh/guides/connect-your-agent.mdx +2 -2
  131. package/docs-site/zh/guides/custom-reports.mdx +196 -36
  132. package/docs-site/zh/guides/experiments.mdx +7 -1
  133. package/docs-site/zh/guides/fixtures.mdx +4 -2
  134. package/docs-site/zh/guides/official-adapters.mdx +31 -9
  135. package/docs-site/zh/guides/publish-report.mdx +4 -4
  136. package/docs-site/zh/guides/report-components.mdx +194 -55
  137. package/docs-site/zh/guides/reporters.mdx +2 -2
  138. package/docs-site/zh/guides/results-data.mdx +61 -34
  139. package/docs-site/zh/guides/runner.mdx +2 -2
  140. package/docs-site/zh/guides/sandbox-agent.mdx +3 -2
  141. package/docs-site/zh/guides/{sandbox-backends.mdx → sandbox-providers.mdx} +12 -12
  142. package/docs-site/zh/guides/viewing-results.mdx +200 -79
  143. package/docs-site/zh/guides/write-experiment.mdx +9 -1
  144. package/docs-site/zh/guides/write-send.mdx +4 -3
  145. package/docs-site/zh/index.mdx +4 -3
  146. package/docs-site/zh/introduction.mdx +2 -1
  147. package/docs-site/zh/quickstart.mdx +5 -3
  148. package/docs-site/zh/reference/builtin-agents.mdx +38 -13
  149. package/docs-site/zh/reference/capabilities.mdx +1 -1
  150. package/docs-site/zh/reference/cli.mdx +36 -13
  151. package/docs-site/zh/reference/define-agent.mdx +15 -13
  152. package/docs-site/zh/reference/define-config.mdx +1 -1
  153. package/docs-site/zh/reference/define-eval.mdx +3 -3
  154. package/docs-site/zh/reference/events.mdx +9 -3
  155. package/docs-site/zh/reference/expect.mdx +13 -8
  156. package/package.json +10 -6
  157. package/src/agents/bub.ts +129 -42
  158. package/src/agents/claude-code.test.ts +152 -0
  159. package/src/agents/claude-code.ts +127 -13
  160. package/src/agents/codex.test.ts +192 -0
  161. package/src/agents/codex.ts +151 -12
  162. package/src/agents/index.ts +9 -3
  163. package/src/agents/manifest.ts +41 -0
  164. package/src/agents/plugin-config.test.ts +94 -0
  165. package/src/agents/shared.ts +33 -2
  166. package/src/agents/skills.test.ts +214 -0
  167. package/src/agents/skills.ts +304 -0
  168. package/src/agents/streaming.ts +5 -5
  169. package/src/agents/types.ts +59 -3
  170. package/src/cli.ts +74 -47
  171. package/src/context/context.test.ts +20 -2
  172. package/src/context/control-flow.ts +1 -1
  173. package/src/context/session.test.ts +92 -1
  174. package/src/context/session.ts +23 -1
  175. package/src/context/types.ts +4 -4
  176. package/src/define.test.ts +8 -8
  177. package/src/define.ts +11 -11
  178. package/src/expect/index.ts +5 -5
  179. package/src/i18n/en.ts +33 -15
  180. package/src/i18n/zh-CN.ts +37 -21
  181. package/src/index.ts +1 -1
  182. package/src/o11y/execution-tree.test.ts +452 -0
  183. package/src/o11y/execution-tree.ts +367 -0
  184. package/src/o11y/otlp/receiver.ts +1 -1
  185. package/src/o11y/otlp/select.ts +7 -3
  186. package/src/o11y/parsers/claude-code.test.ts +175 -0
  187. package/src/o11y/parsers/claude-code.ts +32 -0
  188. package/src/o11y/types.ts +6 -1
  189. package/src/report/aggregate.ts +26 -20
  190. package/src/report/built-in-user-parity.test.tsx +640 -0
  191. package/src/report/built-ins/experiment-comparison.tsx +19 -0
  192. package/src/report/built-ins/index.ts +2 -0
  193. package/src/report/components.tsx +225 -50
  194. package/src/report/compute.ts +335 -54
  195. package/src/report/dual-render.test.tsx +1129 -0
  196. package/src/report/flag.ts +1 -1
  197. package/src/report/format.ts +35 -2
  198. package/src/report/index.ts +47 -14
  199. package/src/report/load.ts +1 -1
  200. package/src/report/locale.ts +229 -0
  201. package/src/report/metrics.ts +39 -15
  202. package/src/report/primitives.tsx +100 -2
  203. package/src/report/react/AttemptList.tsx +113 -0
  204. package/src/report/react/DeltaTable.tsx +9 -6
  205. package/src/report/react/EvalList.tsx +0 -0
  206. package/src/report/react/ExperimentList.tsx +220 -0
  207. package/src/report/react/GroupSummary.tsx +66 -0
  208. package/src/report/react/MetricBars.tsx +24 -13
  209. package/src/report/react/MetricLine.tsx +21 -21
  210. package/src/report/react/MetricMatrix.tsx +8 -4
  211. package/src/report/react/MetricScatter.tsx +126 -96
  212. package/src/report/react/MetricTable.tsx +115 -15
  213. package/src/report/react/RunOverview.tsx +48 -26
  214. package/src/report/react/Scoreboard.tsx +15 -7
  215. package/src/report/react/cell.tsx +18 -9
  216. package/src/{view/app/lib/chart.ts → report/react/chart-math.ts} +2 -6
  217. package/src/report/react/colors.ts +24 -15
  218. package/src/report/react/enhance.js +168 -0
  219. package/src/report/react/fixtures.ts +169 -34
  220. package/src/report/react/format.ts +1 -1
  221. package/src/report/react/index.tsx +21 -6
  222. package/src/report/react/render.test.tsx +180 -31
  223. package/src/report/react/styles.css +432 -213
  224. package/src/report/report.test.ts +398 -94
  225. package/src/report/report.ts +29 -11
  226. package/src/report/text/faces.ts +344 -96
  227. package/src/report/text/layout.ts +76 -11
  228. package/src/report/text/table.ts +98 -0
  229. package/src/report/tree.ts +103 -20
  230. package/src/report/types.ts +184 -25
  231. package/src/report/web.ts +46 -19
  232. package/src/results/annotated-source.test.ts +146 -0
  233. package/src/results/annotated-source.ts +128 -0
  234. package/src/results/attempt-evidence.test.ts +158 -0
  235. package/src/results/attempt-evidence.ts +158 -0
  236. package/src/results/attempt-source.ts +48 -0
  237. package/src/results/copy.ts +154 -139
  238. package/src/results/format.ts +54 -47
  239. package/src/results/host-equivalence.test.ts +527 -0
  240. package/src/results/index.ts +56 -14
  241. package/src/results/locator.test.ts +198 -0
  242. package/src/results/locator.ts +0 -0
  243. package/src/results/open.ts +392 -175
  244. package/src/results/results.test.ts +943 -494
  245. package/src/results/select.ts +129 -23
  246. package/src/results/skipped-notice.ts +0 -0
  247. package/src/results/source-hash.ts +28 -0
  248. package/src/results/types.ts +98 -62
  249. package/src/results/writer.ts +304 -176
  250. package/src/runner/attempt.test.ts +170 -0
  251. package/src/runner/attempt.ts +48 -13
  252. package/src/runner/discover.test.ts +65 -0
  253. package/src/runner/discover.ts +6 -2
  254. package/src/runner/eval-source.test.ts +121 -0
  255. package/src/runner/eval-source.ts +45 -0
  256. package/src/runner/fingerprint.ts +54 -1
  257. package/src/runner/remote-sandbox.ts +1 -1
  258. package/src/runner/report.test.ts +6 -8
  259. package/src/runner/report.ts +5 -4
  260. package/src/runner/reporters/artifacts.ts +27 -41
  261. package/src/runner/reporters/braintrust.test.ts +3 -3
  262. package/src/runner/reporters/braintrust.ts +1 -1
  263. package/src/runner/reporters/console.ts +6 -6
  264. package/src/runner/reporters/json.ts +4 -4
  265. package/src/runner/reporters/live.test.ts +56 -0
  266. package/src/runner/reporters/live.ts +36 -10
  267. package/src/runner/reporters/quiet.test.ts +66 -0
  268. package/src/runner/reporters/quiet.ts +49 -0
  269. package/src/runner/reporters/shared.ts +5 -5
  270. package/src/runner/reporters/table.ts +17 -17
  271. package/src/runner/run.ts +61 -102
  272. package/src/runner/types.ts +43 -32
  273. package/src/sandbox/checkpoint.ts +1 -1
  274. package/src/sandbox/docker.ts +15 -2
  275. package/src/sandbox/e2b.ts +11 -5
  276. package/src/sandbox/errors.ts +13 -0
  277. package/src/sandbox/index.ts +3 -3
  278. package/src/sandbox/local-files.ts +1 -1
  279. package/src/sandbox/registry.ts +6 -5
  280. package/src/sandbox/resolve.ts +38 -23
  281. package/src/sandbox/retry.test.ts +68 -0
  282. package/src/sandbox/retry.ts +44 -0
  283. package/src/sandbox/types.ts +24 -24
  284. package/src/sandbox/vercel.ts +17 -3
  285. package/src/scoring/collector.ts +3 -3
  286. package/src/scoring/judge.ts +2 -2
  287. package/src/scoring/scoped.ts +13 -1
  288. package/src/scoring/types.ts +7 -7
  289. package/src/scoring/verdict.ts +4 -4
  290. package/src/shared/aggregate.ts +23 -3
  291. package/src/shared/types.ts +2 -2
  292. package/src/shared/{outcome.ts → verdict.ts} +15 -15
  293. package/src/show/compose.ts +12 -132
  294. package/src/show/index.ts +119 -95
  295. package/src/show/render.ts +462 -159
  296. package/src/show/show.test.ts +471 -253
  297. package/src/source-loc.ts +1 -1
  298. package/src/tty-line.ts +32 -0
  299. package/src/types.ts +2 -2
  300. package/src/util.test.ts +21 -1
  301. package/src/util.ts +5 -1
  302. package/src/view/app/App.tsx +33 -198
  303. package/src/view/app/components/AttemptModal.tsx +6 -6
  304. package/src/view/app/components/CodeView.tsx +2 -2
  305. package/src/view/app/components/CopyControls.tsx +32 -36
  306. package/src/view/app/components/SkippedRunsBanner.tsx +140 -0
  307. package/src/view/app/components/ui/badge.tsx +1 -1
  308. package/src/view/app/components/ui/dialog.tsx +2 -2
  309. package/src/view/app/i18n.ts +40 -31
  310. package/src/view/app/lib/artifact-url.ts +1 -1
  311. package/src/view/app/lib/attempt-route.test.ts +37 -47
  312. package/src/view/app/lib/attempt-route.ts +26 -26
  313. package/src/view/app/lib/rows.ts +9 -112
  314. package/src/view/app/lib/verdict.ts +25 -0
  315. package/src/view/app/main.tsx +9 -12
  316. package/src/view/app/pages/RunsPage.tsx +4 -4
  317. package/src/view/app/pages/TracesPage.tsx +3 -3
  318. package/src/view/app/shared.ts +0 -1
  319. package/src/view/app/types.ts +6 -51
  320. package/src/view/artifact-serving.test.ts +136 -0
  321. package/src/view/client-dist/app.css +1 -1
  322. package/src/view/client-dist/app.js +21 -21
  323. package/src/view/data.test.ts +214 -143
  324. package/src/view/data.ts +166 -120
  325. package/src/view/index.ts +23 -11
  326. package/src/view/server.ts +52 -17
  327. package/src/view/shared/types.ts +22 -31
  328. package/src/view/styles.css +117 -327
  329. package/src/view/view-report.test.ts +146 -98
  330. package/src/report/default-report.tsx +0 -222
  331. package/src/report/dual-face.test.tsx +0 -527
  332. package/src/report/react/CaseList.tsx +0 -70
  333. package/src/view/app/components/CostScoreChart.tsx +0 -147
  334. package/src/view/app/components/ExperimentTable.tsx +0 -284
  335. package/src/view/app/components/GroupSelector.tsx +0 -61
  336. package/src/view/app/components/primitives.tsx +0 -43
  337. package/src/view/app/lib/outcome.ts +0 -82
@@ -8,29 +8,35 @@ import { dirname, join } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
9
  import { renderToStaticMarkup } from "react-dom/server";
10
10
  import { describe, expect, it } from "vitest";
11
+ import type { AttemptLocator } from "../../results/locator.ts";
11
12
 
12
13
  import {
13
- CaseList,
14
+ AttemptList,
14
15
  DeltaTable,
16
+ EvalList,
17
+ ExperimentList,
15
18
  MetricMatrix,
16
19
  MetricScatter,
17
20
  MetricTable,
18
21
  RunOverview,
19
22
  Scoreboard,
20
23
  } from "./index.tsx";
21
- import { NRE_PALETTE, colorClassForKey, colorIndexForKey } from "./colors.ts";
24
+ import { colorClassForKey, seriesClassForKey } from "./colors.ts";
22
25
  import {
23
- caseListData,
26
+ attemptListItems,
24
27
  deltaData,
28
+ evalListItems,
29
+ experimentListItems,
25
30
  matrixData,
26
31
  overviewData,
27
32
  overviewWithCost,
28
33
  scatterData,
29
34
  scoreboardData,
30
35
  tableData,
36
+ tableDataWithMeta,
31
37
  } from "./fixtures.ts";
32
38
 
33
- const attemptHref = (ref: { run: string; result: number }) => `/attempts/${ref.run}/${ref.result}`;
39
+ const attemptHref = (locator: AttemptLocator) => `/attempts/${locator}`;
34
40
 
35
41
  describe("RunOverview", () => {
36
42
  const html = renderToStaticMarkup(<RunOverview data={overviewData} />);
@@ -39,7 +45,11 @@ describe("RunOverview", () => {
39
45
  expect(html).toContain("nre-overview");
40
46
  expect(html).toContain("<dd>12</dd>"); // 题目
41
47
  expect(html).toContain("<dd>48</dd>"); // attempts
42
- expect(html).toContain("78%"); // 36 / (36+8+2),skipped 不进分母
48
+ // totals.passRate.display 原样渲染(两级聚合 70%),不是从 passed/failed/errored
49
+ // 现场重算的 78%(36/(36+8+2))—— 两个数刻意不同,证明组件没有偷懒重算
50
+ expect(html).toContain("70%");
51
+ expect(html).not.toContain("78%");
52
+ expect(html).toContain("46/48"); // samples < total:覆盖率角标(2 个 skipped attempt 不进桶)
43
53
  expect(html).toContain("4m 21s"); // 261000ms
44
54
  });
45
55
 
@@ -88,7 +98,54 @@ describe("MetricTable", () => {
88
98
  });
89
99
 
90
100
  it("refs + attemptHref 出普通 <a>", () => {
91
- expect(html).toContain('href="/attempts/run-a/0"');
101
+ expect(html).toContain('href="/attempts/@1a0a0a0"');
102
+ });
103
+
104
+ it("渐进增强的 data 属性:所有表头 data-nre-sort、格子 data-sort-value(无 JS 时纯属性,内容完整)", () => {
105
+ expect(html.match(/data-nre-sort/g)).toHaveLength(3); // 维度列 + 2 指标列
106
+ expect(html).toContain('data-sort-value="codex"');
107
+ expect(html).toContain('data-sort-value="0.87"');
108
+ // 缺数据格子的排序值为空串:enhance.js 排序时恒沉底
109
+ expect(html).toContain('data-sort-value=""');
110
+ });
111
+ });
112
+
113
+ describe("MetricTable:meta 榜单 parity 与 filter", () => {
114
+ const html = renderToStaticMarkup(<MetricTable data={tableDataWithMeta} />);
115
+
116
+ it("meta 在场补 Model / Agent 列,列序 experiment、model、agent、指标、verdicts", () => {
117
+ expect(html).toContain(">Model</th>");
118
+ expect(html).toContain(">Agent</th>");
119
+ expect(html).toContain(">Verdicts</th>");
120
+ expect(html.indexOf(">Model</th>")).toBeLessThan(html.indexOf(">Agent</th>"));
121
+ expect(html.indexOf(">Agent</th>")).toBeLessThan(html.indexOf("pass rate"));
122
+ expect(html).toContain("gpt-5.4");
123
+ });
124
+
125
+ it("verdict 计票列:eval 级折叠口径的 pill,零计数不出 pill", () => {
126
+ expect(html).toContain("nre-verdict-pill");
127
+ expect(html).toContain("1 passed");
128
+ expect(html).toContain("1 failed");
129
+ expect(html).toContain("2 passed");
130
+ expect(html).not.toContain("0 errored");
131
+ });
132
+
133
+ it("filter 开:表格前渲染 data-nre-filter 输入框(无 JS 静默无功能),仍无 <script>", () => {
134
+ const filtered = renderToStaticMarkup(<MetricTable data={tableDataWithMeta} filter />);
135
+ expect(filtered).toContain("data-nre-filter");
136
+ expect(filtered).toContain('class="nre-filter"');
137
+ expect(filtered.indexOf("nre-filter")).toBeLessThan(filtered.indexOf("<table"));
138
+ expect(filtered).toContain('placeholder="Filter rows…"');
139
+ expect(filtered).not.toContain("<script");
140
+ });
141
+
142
+ it("locale=zh-CN:chrome 文案与 meta 列头走字典;display 不本地化", () => {
143
+ const zh = renderToStaticMarkup(<MetricTable data={tableDataWithMeta} filter locale="zh-CN" />);
144
+ expect(zh).toContain(">模型</th>");
145
+ expect(zh).toContain(">结果</th>");
146
+ expect(zh).toContain("1 通过");
147
+ expect(zh).toContain('placeholder="筛选行…"');
148
+ expect(zh).toContain("50%"); // display 是 format 产物,不本地化
92
149
  });
93
150
  });
94
151
 
@@ -107,8 +164,8 @@ describe("MetricMatrix", () => {
107
164
  it("格子数字与 refs 下钻链接", () => {
108
165
  expect(html).toContain("100%");
109
166
  expect(html).toContain("0%");
110
- expect(html).toContain('href="/attempts/run-b/3"');
111
- expect(html).toContain('href="/attempts/run-b/7"');
167
+ expect(html).toContain('href="/attempts/@1b3b3b3"');
168
+ expect(html).toContain('href="/attempts/@1b7b7b7"');
112
169
  });
113
170
 
114
171
  it("列头(维度键)带稳定散列配色 class", () => {
@@ -145,13 +202,14 @@ describe("Scoreboard", () => {
145
202
  describe("MetricScatter", () => {
146
203
  const html = renderToStaticMarkup(<MetricScatter data={scatterData} pointHref={(row) => `/exp/${row.key}`} />);
147
204
 
205
+ // data-key 在点的 <g> 上,坐标在其内第一个 circle 上(非贪婪跨标签匹配)
148
206
  const cxOf = (key: string): number => {
149
- const m = html.match(new RegExp(`data-key="${key}"[^>]*\\bcx="([\\d.]+)"`));
207
+ const m = html.match(new RegExp(`data-key="${key}"[\\s\\S]*?\\bcx="([\\d.]+)"`));
150
208
  expect(m, `circle for ${key}`).toBeTruthy();
151
209
  return Number(m![1]);
152
210
  };
153
211
  const cyOf = (key: string): number => {
154
- const m = html.match(new RegExp(`data-key="${key}"[^>]*\\bcy="([\\d.]+)"`));
212
+ const m = html.match(new RegExp(`data-key="${key}"[\\s\\S]*?\\bcy="([\\d.]+)"`));
155
213
  expect(m, `circle for ${key}`).toBeTruthy();
156
214
  return Number(m![1]);
157
215
  };
@@ -168,12 +226,21 @@ describe("MetricScatter", () => {
168
226
  expect(cyOf("compare/bub-high")).toBeLessThan(cyOf("compare/bub-low"));
169
227
  });
170
228
 
171
- it("同系列点连线并标系列名,线色来自稳定散列调色板", () => {
229
+ it("同系列点连线,系列色走类名(nre-series-cN,CSS 上色跟随深浅主题),图例列系列名", () => {
172
230
  expect(html).toContain("<polyline");
173
- expect(html).toContain(">bub</text>");
174
- expect(html).toContain(NRE_PALETTE[colorIndexForKey("bub")]);
175
- // codex 只有 1 个可画点:不出线,但系列名仍标出
176
- expect(html).toContain(">codex</text>");
231
+ expect(html).toContain(`nre-scatter-line ${seriesClassForKey("bub")}`);
232
+ // 渲染面零内联 hex:深色主题下由 CSS 变量切换
233
+ expect(html).not.toMatch(/#[0-9a-f]{6}/i);
234
+ // 图例:codex 只有 1 个可画点、不出线,但系列名仍在图例里
235
+ expect(html).toContain(">bub</span>");
236
+ expect(html).toContain(">codex</span>");
237
+ });
238
+
239
+ it("niceTicks 网格与每点直接标签(id 末段)", () => {
240
+ expect(html).toContain("nre-scatter-grid");
241
+ expect(html.match(/nre-scatter-tick/g)!.length).toBeGreaterThanOrEqual(6);
242
+ expect(html).toContain(">bub-low</text>");
243
+ expect(html).toContain(">codex-mid</text>");
177
244
  });
178
245
 
179
246
  it("null 点不画,底部注脚如实报数", () => {
@@ -190,7 +257,7 @@ describe("MetricScatter", () => {
190
257
  expect(html).toContain('href="/exp/compare/bub-low"');
191
258
  });
192
259
 
193
- it("全部点都缺数据时不画空坐标系", () => {
260
+ it("全部点都缺数据时不画空坐标系:显式说明缺哪两个指标", () => {
194
261
  const empty = renderToStaticMarkup(
195
262
  <MetricScatter
196
263
  data={{
@@ -200,9 +267,23 @@ describe("MetricScatter", () => {
200
267
  />,
201
268
  );
202
269
  expect(empty).not.toContain("<svg");
203
- expect(empty).toContain("no data");
270
+ expect(empty).toContain("No data to plot"); // 0 可画点:命名 x/y 指标,不画空图
204
271
  expect(empty).toContain("4 points missing data");
205
272
  });
273
+
274
+ it("恰好 1 个可画点:照常画出单点", () => {
275
+ const single = renderToStaticMarkup(
276
+ <MetricScatter
277
+ data={{
278
+ ...scatterData,
279
+ // 只留第一个点可画,其余 x 置空
280
+ rows: scatterData.rows.map((r, i) => (i === 0 ? r : { ...r, x: { ...r.x, value: null } })),
281
+ }}
282
+ />,
283
+ );
284
+ expect(single).toContain("<svg");
285
+ expect(single).toContain("nre-scatter-point");
286
+ });
206
287
  });
207
288
 
208
289
  describe("DeltaTable", () => {
@@ -233,12 +314,16 @@ describe("DeltaTable", () => {
233
314
  });
234
315
  });
235
316
 
236
- describe("CaseList", () => {
237
- const html = renderToStaticMarkup(<CaseList data={caseListData} attemptHref={attemptHref} />);
317
+ // AttemptList / EvalList / ExperimentList 的公开(报告组件)Props 没有 attemptHref 覆盖口子——
318
+ // 证据室深链恒经 ctx.attemptHref,宿主外直接嵌进 React 应用时退化为默认 `#/attempt/<locator>`
319
+ // (tree.ts 的 DEFAULT_WEB_CONTEXT),不是纯展示、也不发明另一套断链(与 MetricTable 等
320
+ // 可选 attemptHref 的组件不同,这三个组件的下钻不是可选行为)。
321
+
322
+ describe("AttemptList", () => {
323
+ const html = renderToStaticMarkup(<AttemptList items={attemptListItems} />);
238
324
 
239
- it("逐条失败断言:name、score、detail、evidence", () => {
325
+ it("逐条断言:name、score、detail、evidence", () => {
240
326
  expect(html).toContain("roots-correct");
241
- expect(html).toContain("score 0");
242
327
  expect(html).toContain("expected x=2, got x=3");
243
328
  expect(html).toContain("judge: sign flipped when substituting into the quadratic formula");
244
329
  });
@@ -247,21 +332,83 @@ describe("CaseList", () => {
247
332
  expect(html).toContain("TypeError: cannot read properties of undefined");
248
333
  });
249
334
 
250
- it("truncated 如实报「还有 n 条没列」", () => {
251
- expect(html).toContain("and 2 more not shown");
335
+ it("total > items.length 时如实报「还有 n 条没列」", () => {
336
+ const html2 = renderToStaticMarkup(<AttemptList items={attemptListItems} total={attemptListItems.length + 2} />);
337
+ expect(html2).toContain("and 2 more not shown");
338
+ // 不传 total(或 total === items.length)不产出截断文案
339
+ expect(html).not.toContain("more not shown");
252
340
  });
253
341
 
254
- it("每条案例带 attemptHref 下钻链接", () => {
255
- expect(html).toContain('href="/attempts/run-a/4"');
256
- expect(html).toContain('href="/attempts/run-c/1"');
342
+ it("每条 attempt 带 locator + 默认证据室深链,不追加字母缩写", () => {
343
+ expect(html).toContain('href="#/attempt/@1a4a4a4"');
344
+ expect(html).toContain('href="#/attempt/@1c1c1c1"');
345
+ expect(html).not.toMatch(/\[[EXD⏱,]+\]/);
257
346
  });
258
347
 
259
- it("长文本收进 <details>,零 JS 可展开", () => {
348
+ it("长文本(evidence)收进 <details>,零 JS 可展开", () => {
260
349
  expect(html).toContain("<details");
261
350
  expect(html).toContain("<summary>");
262
351
  });
263
352
  });
264
353
 
354
+ describe("EvalList", () => {
355
+ const html = renderToStaticMarkup(<EvalList items={evalListItems} />);
356
+
357
+ it("每项一个 experimentId + evalId,判定与分数在场", () => {
358
+ expect(html).toContain("algebra/quadratic");
359
+ expect(html).toContain("geometry/angles");
360
+ expect(html).toContain("compare/bub");
361
+ expect(html).toContain("compare/codex");
362
+ });
363
+
364
+ it("展开到这道题的 Attempt(与 AttemptList 同一套 AttemptRow 渲染)", () => {
365
+ expect(html).toContain('href="#/attempt/@1a4a4a4"');
366
+ expect(html).toContain("roots-correct");
367
+ });
368
+
369
+ it("零 JS 靠原生 <details>,静态 HTML 内容已完整", () => {
370
+ expect(html).toContain("<details");
371
+ expect(html).not.toContain("<script");
372
+ });
373
+ });
374
+
375
+ describe("ExperimentList", () => {
376
+ const html = renderToStaticMarkup(<ExperimentList items={experimentListItems} filter />);
377
+
378
+ it("web 面是带过滤框与八个固定列头的 experiment 比较表", () => {
379
+ expect(html).toContain('data-nre-experiment-filter=""');
380
+ for (const header of ["Experiment", "Model", "Agent", "Avg duration", "Pass rate", "Tokens", "Est. cost", "Result"]) {
381
+ expect(html).toContain(`>${header}</button>`);
382
+ }
383
+ expect(html).toContain('data-nre-experiment-sort="4" class="nre-sort-desc"');
384
+ });
385
+
386
+ it("主行:身份、agent/model、官方两级聚合指标与判定构成", () => {
387
+ expect(html).toContain("compare/bub");
388
+ expect(html).toContain("compare/codex");
389
+ expect(html).toContain("gpt-5.4");
390
+ expect(html).toContain("50%"); // passRate.display
391
+ expect(html).toContain("1 passed / 1 failed");
392
+ expect(html).toContain("memory=true");
393
+ });
394
+
395
+ it("展开到 Eval:同一道题的全部 Attempt 逐条显示", () => {
396
+ expect(html).toContain("algebra/quadratic");
397
+ expect(html).toContain('href="#/attempt/@1a4a4a4"');
398
+ expect(html).toContain('href="#/attempt/@1b5b5b5"');
399
+ expect(html.match(/algebra\/quadratic/g)).toHaveLength(1);
400
+ expect(html).toContain("nre-experiment-eval-header");
401
+ expect(html.match(/nre-experiment-attempt-row/g)).toHaveLength(3);
402
+ expect(html).toContain("├─");
403
+ expect(html).toContain("└─");
404
+ });
405
+
406
+ it("零 JS 靠原生 <details>,无 <script>", () => {
407
+ expect(html).toContain("<details");
408
+ expect(html).not.toContain("<script");
409
+ });
410
+ });
411
+
265
412
  describe("跨组件契约", () => {
266
413
  it("同一维度键在所有块里同色(稳定散列,与渲染顺序无关)", () => {
267
414
  const cls = colorClassForKey("bub");
@@ -269,12 +416,12 @@ describe("跨组件契约", () => {
269
416
  const matrix = renderToStaticMarkup(<MetricMatrix data={matrixData} />);
270
417
  const board = renderToStaticMarkup(<Scoreboard data={scoreboardData} />);
271
418
  const delta = renderToStaticMarkup(<DeltaTable data={deltaData} />);
272
- const cases = renderToStaticMarkup(<CaseList data={caseListData} />);
273
- for (const html of [table, matrix, board, delta, cases]) {
419
+ const attempts = renderToStaticMarkup(<AttemptList items={attemptListItems} />);
420
+ for (const html of [table, matrix, board, delta, attempts]) {
274
421
  expect(html).toContain(cls);
275
422
  }
276
423
  const scatter = renderToStaticMarkup(<MetricScatter data={scatterData} />);
277
- expect(scatter).toContain(NRE_PALETTE[colorIndexForKey("bub")]);
424
+ expect(scatter).toContain(seriesClassForKey("bub"));
278
425
  });
279
426
 
280
427
  it("静态输出不含 <script>:交互只靠 <a>/<details>/CSS", () => {
@@ -285,7 +432,9 @@ describe("跨组件契约", () => {
285
432
  renderToStaticMarkup(<Scoreboard data={scoreboardData} />),
286
433
  renderToStaticMarkup(<MetricScatter data={scatterData} />),
287
434
  renderToStaticMarkup(<DeltaTable data={deltaData} />),
288
- renderToStaticMarkup(<CaseList data={caseListData} />),
435
+ renderToStaticMarkup(<AttemptList items={attemptListItems} />),
436
+ renderToStaticMarkup(<EvalList items={evalListItems} />),
437
+ renderToStaticMarkup(<ExperimentList items={experimentListItems} />),
289
438
  ].join("");
290
439
  expect(all).not.toContain("<script");
291
440
  });