niceeval 0.6.2 → 0.7.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 (218) hide show
  1. package/INDEX.md +23 -23
  2. package/README.zh.md +6 -6
  3. package/dist/agents/types.d.ts +2 -2
  4. package/dist/i18n/zh-CN.d.ts +3 -3
  5. package/dist/report/aggregate.d.ts +32 -26
  6. package/dist/report/aggregate.js +157 -76
  7. package/dist/report/built-in/index.d.ts +2 -0
  8. package/dist/report/built-in/index.js +8 -0
  9. package/dist/report/components.d.ts +91 -164
  10. package/dist/report/components.js +377 -114
  11. package/dist/report/compute.d.ts +86 -73
  12. package/dist/report/compute.js +592 -432
  13. package/dist/report/flag.d.ts +28 -17
  14. package/dist/report/flag.js +86 -16
  15. package/dist/report/format.d.ts +11 -11
  16. package/dist/report/format.js +17 -15
  17. package/dist/report/index.d.ts +16 -17
  18. package/dist/report/index.js +20 -22
  19. package/dist/report/load.js +3 -2
  20. package/dist/report/locale.d.ts +49 -34
  21. package/dist/report/locale.js +106 -58
  22. package/dist/report/metrics.d.ts +10 -3
  23. package/dist/report/metrics.js +46 -12
  24. package/dist/report/primitives.d.ts +42 -15
  25. package/dist/report/primitives.js +135 -26
  26. package/dist/report/react/AttemptList.d.ts +10 -8
  27. package/dist/report/react/AttemptList.js +18 -10
  28. package/dist/report/react/DeltaTable.js +19 -18
  29. package/dist/report/react/EvalList.d.ts +3 -3
  30. package/dist/report/react/EvalList.js +0 -0
  31. package/dist/report/react/ExperimentComparison.d.ts +4 -2
  32. package/dist/report/react/ExperimentComparison.js +5 -4
  33. package/dist/report/react/ExperimentList.d.ts +3 -3
  34. package/dist/report/react/ExperimentList.js +16 -15
  35. package/dist/report/react/MetricBars.js +5 -4
  36. package/dist/report/react/MetricLine.js +12 -5
  37. package/dist/report/react/MetricMatrix.js +1 -1
  38. package/dist/report/react/MetricScatter.js +54 -17
  39. package/dist/report/react/MetricTable.js +2 -12
  40. package/dist/report/react/ScopeSummary.d.ts +10 -0
  41. package/dist/report/react/ScopeSummary.js +17 -0
  42. package/dist/report/react/Scoreboard.js +6 -6
  43. package/dist/report/react/cell.js +2 -2
  44. package/dist/report/react/fixtures.d.ts +5 -9
  45. package/dist/report/react/fixtures.js +105 -149
  46. package/dist/report/react/index.d.ts +15 -5
  47. package/dist/report/react/index.js +18 -7
  48. package/dist/report/report.d.ts +137 -20
  49. package/dist/report/report.js +261 -34
  50. package/dist/report/text/faces.d.ts +17 -19
  51. package/dist/report/text/faces.js +225 -157
  52. package/dist/report/text/plot.js +1 -1
  53. package/dist/report/text/table.js +2 -2
  54. package/dist/report/tree.d.ts +90 -40
  55. package/dist/report/tree.js +252 -94
  56. package/dist/report/types.d.ts +245 -300
  57. package/dist/report/types.js +4 -3
  58. package/dist/report/web.d.ts +21 -5
  59. package/dist/report/web.js +42 -16
  60. package/dist/results/select.d.ts +38 -16
  61. package/dist/results/select.js +73 -25
  62. package/dist/results/types.d.ts +38 -14
  63. package/dist/shared/aggregate.d.ts +3 -2
  64. package/dist/shared/aggregate.js +5 -4
  65. package/docs-site/zh/README.md +44 -0
  66. package/docs-site/zh/examples/ai-agent-application.mdx +63 -0
  67. package/docs-site/zh/examples/coding-agent-extensions.mdx +57 -0
  68. package/docs-site/zh/examples/index.mdx +50 -0
  69. package/docs-site/zh/{concepts → explanation}/adapter.mdx +11 -11
  70. package/docs-site/zh/{concepts → explanation}/assert.mdx +7 -7
  71. package/docs-site/zh/{concepts → explanation}/drive.mdx +8 -8
  72. package/docs-site/zh/{concepts → explanation}/evals.mdx +4 -4
  73. package/docs-site/zh/{concepts → explanation}/experiment.mdx +8 -8
  74. package/docs-site/zh/{concepts → explanation}/hitl.mdx +8 -8
  75. package/docs-site/zh/{concepts → explanation}/judge.mdx +5 -5
  76. package/docs-site/zh/{concepts → explanation}/overview.mdx +5 -5
  77. package/docs-site/zh/{guides → explanation}/runner.mdx +1 -1
  78. package/docs-site/zh/{concepts → explanation}/tier.mdx +6 -6
  79. package/docs-site/zh/{guides → how-to}/agent-feedback-loop.mdx +7 -7
  80. package/docs-site/zh/{guides → how-to}/authoring.mdx +2 -2
  81. package/docs-site/zh/{guides → how-to}/connect-otel.mdx +6 -6
  82. package/docs-site/zh/{guides → how-to}/connect-your-agent.mdx +18 -18
  83. package/docs-site/zh/{guides → how-to}/custom-reports.mdx +6 -6
  84. package/docs-site/zh/{guides → how-to}/experiments.mdx +3 -3
  85. package/docs-site/zh/{guides → how-to}/publish-report.mdx +2 -2
  86. package/docs-site/zh/{guides → how-to}/sandbox-agent.mdx +2 -2
  87. package/docs-site/zh/{guides → how-to}/sandbox-providers.mdx +1 -1
  88. package/docs-site/zh/{guides → how-to}/viewing-results.mdx +6 -6
  89. package/docs-site/zh/{guides → how-to}/write-experiment.mdx +3 -3
  90. package/docs-site/zh/{guides → how-to}/write-send.mdx +13 -13
  91. package/docs-site/zh/index.mdx +23 -25
  92. package/docs-site/zh/introduction.mdx +8 -8
  93. package/docs-site/zh/reference/builtin-agents.mdx +5 -5
  94. package/docs-site/zh/reference/capabilities.mdx +6 -6
  95. package/docs-site/zh/reference/cli.mdx +9 -7
  96. package/docs-site/zh/reference/define-agent.mdx +1 -1
  97. package/docs-site/zh/reference/events.mdx +3 -3
  98. package/docs-site/zh/{guides → reference}/official-adapters.mdx +7 -7
  99. package/docs-site/zh/{guides → reference}/report-components.mdx +5 -5
  100. package/docs-site/zh/{guides → reference}/results-data.mdx +5 -5
  101. package/docs-site/zh/{guides → troubleshooting}/debug-sandbox.mdx +2 -2
  102. package/docs-site/zh/{guides → troubleshooting}/debugging.mdx +4 -2
  103. package/docs-site/zh/{quickstart.mdx → tutorials/quickstart.mdx} +5 -17
  104. package/package.json +6 -2
  105. package/src/agents/index.ts +2 -2
  106. package/src/agents/openai-compat.ts +1 -1
  107. package/src/agents/streaming.ts +2 -2
  108. package/src/agents/types.ts +3 -3
  109. package/src/cli.ts +42 -23
  110. package/src/context/context.ts +1 -1
  111. package/src/context/session.test.ts +1 -1
  112. package/src/context/session.ts +1 -1
  113. package/src/i18n/en.ts +18 -16
  114. package/src/i18n/zh-CN.ts +16 -15
  115. package/src/report/aggregate.ts +175 -87
  116. package/src/report/built-in/index.tsx +9 -0
  117. package/src/report/components.tsx +625 -285
  118. package/src/report/compute.ts +717 -515
  119. package/src/report/dual-render.test.tsx +738 -1148
  120. package/src/report/flag.ts +97 -33
  121. package/src/report/format.ts +18 -22
  122. package/src/report/index.ts +113 -58
  123. package/src/report/load.ts +3 -2
  124. package/src/report/locale.ts +120 -69
  125. package/src/report/metrics.ts +42 -12
  126. package/src/report/primitives.tsx +190 -45
  127. package/src/report/react/AttemptList.tsx +32 -20
  128. package/src/report/react/DeltaTable.tsx +63 -45
  129. package/src/report/react/EvalList.tsx +0 -0
  130. package/src/report/react/ExperimentComparison.tsx +12 -7
  131. package/src/report/react/ExperimentList.tsx +38 -26
  132. package/src/report/react/MetricBars.tsx +5 -4
  133. package/src/report/react/MetricLine.tsx +13 -8
  134. package/src/report/react/MetricMatrix.tsx +2 -2
  135. package/src/report/react/MetricScatter.tsx +74 -20
  136. package/src/report/react/MetricTable.tsx +4 -76
  137. package/src/report/react/ScopeSummary.tsx +86 -0
  138. package/src/report/react/Scoreboard.tsx +28 -10
  139. package/src/report/react/cell.tsx +2 -2
  140. package/src/report/react/enhance.js +57 -5
  141. package/src/report/react/fixtures.ts +109 -156
  142. package/src/report/react/index.tsx +24 -39
  143. package/src/report/react/render.test.tsx +139 -104
  144. package/src/report/react/styles.css +181 -91
  145. package/src/report/report.test.ts +761 -1031
  146. package/src/report/report.ts +425 -47
  147. package/src/report/text/faces.ts +257 -164
  148. package/src/report/text/plot.ts +1 -1
  149. package/src/report/text/table.ts +2 -2
  150. package/src/report/tree.ts +362 -104
  151. package/src/report/types.ts +257 -287
  152. package/src/report/web.ts +63 -20
  153. package/src/results/attempt-evidence.test.ts +4 -4
  154. package/src/results/attempt-evidence.ts +5 -5
  155. package/src/results/copy.ts +6 -6
  156. package/src/results/host-equivalence.test.ts +26 -14
  157. package/src/results/index.ts +10 -4
  158. package/src/results/open.ts +8 -4
  159. package/src/results/results.test.ts +4 -3
  160. package/src/results/select.ts +104 -34
  161. package/src/results/types.ts +36 -14
  162. package/src/runner/feedback/human.test.ts +1 -1
  163. package/src/runner/run.ts +1 -1
  164. package/src/sandbox/cli-commands.ts +2 -2
  165. package/src/scoring/judge.test.ts +1 -1
  166. package/src/shared/aggregate.ts +5 -4
  167. package/src/show/compose.ts +50 -67
  168. package/src/show/index.ts +107 -56
  169. package/src/show/render.ts +43 -27
  170. package/src/show/report-host.test.ts +188 -0
  171. package/src/show/report-host.ts +375 -0
  172. package/src/show/show.test.ts +86 -36
  173. package/src/view/app/App.test.tsx +69 -0
  174. package/src/view/app/App.tsx +144 -48
  175. package/src/view/app/components/AttemptModal.tsx +324 -63
  176. package/src/view/app/components/CodeView.tsx +10 -4
  177. package/src/view/app/i18n.ts +31 -17
  178. package/src/view/app/lib/artifact-url.ts +14 -3
  179. package/src/view/app/main.tsx +13 -8
  180. package/src/view/app/pages/{RunsPage.tsx → AttemptsPage.tsx} +6 -6
  181. package/src/view/app/types.ts +4 -1
  182. package/src/view/artifact-serving.test.ts +21 -1
  183. package/src/view/client-dist/app.css +1 -1
  184. package/src/view/client-dist/app.js +6 -6
  185. package/src/view/data.test.ts +9 -3
  186. package/src/view/data.ts +145 -49
  187. package/src/view/index.ts +48 -44
  188. package/src/view/server.ts +35 -15
  189. package/src/view/shared/types.ts +34 -5
  190. package/src/view/styles.css +224 -0
  191. package/src/view/view-report.test.ts +161 -57
  192. package/dist/report/built-ins/experiment-comparison.d.ts +0 -39
  193. package/dist/report/built-ins/experiment-comparison.js +0 -119
  194. package/dist/report/built-ins/index.d.ts +0 -2
  195. package/dist/report/built-ins/index.js +0 -2
  196. package/dist/report/react/GroupSummary.d.ts +0 -8
  197. package/dist/report/react/GroupSummary.js +0 -8
  198. package/dist/report/react/RunOverview.d.ts +0 -8
  199. package/dist/report/react/RunOverview.js +0 -12
  200. package/docs-site/zh/example/ai-agent-application.mdx +0 -152
  201. package/docs-site/zh/example/claude-code-codex-plugin.mdx +0 -167
  202. package/docs-site/zh/example/claude-code-codex-skill.mdx +0 -152
  203. package/docs-site/zh/example/showcase.mdx +0 -39
  204. package/src/report/built-in-user-parity.test.tsx +0 -597
  205. package/src/report/built-ins/experiment-comparison.tsx +0 -179
  206. package/src/report/built-ins/index.ts +0 -7
  207. package/src/report/react/GroupSummary.tsx +0 -66
  208. package/src/report/react/RunOverview.tsx +0 -109
  209. /package/docs-site/zh/{example/tier1-ai-sdk-v7.mdx → examples/integrations/ai-sdk-v7.mdx} +0 -0
  210. /package/docs-site/zh/{example/tier1-claude-sdk.mdx → examples/integrations/claude-sdk.mdx} +0 -0
  211. /package/docs-site/zh/{example/tier1-codex-sdk.mdx → examples/integrations/codex-sdk.mdx} +0 -0
  212. /package/docs-site/zh/{example/tier1-langgraph.mdx → examples/integrations/langgraph.mdx} +0 -0
  213. /package/docs-site/zh/{example/tier1-pi-sdk.mdx → examples/integrations/pi-sdk.mdx} +0 -0
  214. /package/docs-site/zh/{guides → how-to}/ci-integration.mdx +0 -0
  215. /package/docs-site/zh/{guides → how-to}/dataset-fanout.mdx +0 -0
  216. /package/docs-site/zh/{guides → how-to}/fixtures.mdx +0 -0
  217. /package/docs-site/zh/{guides → how-to}/reporters.mdx +0 -0
  218. /package/docs-site/zh/{guides → how-to}/scoring-guide.mdx +0 -0
@@ -1,40 +1,25 @@
1
- // niceeval/report/react —— 零件复用的导出点:把某一块指标表嵌进已有内部面板时从这里
2
- // import。导出的组件与 niceeval/report 是同一批双面组件(web 面即 React 渲染,零 IO、
3
- // 可进 "use client",不 hydrate 也完整),都带自己的 data 计算函数;那是零件的复用,
4
- // 不是另一套报告系统 —— 报告的家在官方宿主(--report)。
1
+ // niceeval/report/react —— 纯 web 渲染面的导出点:把某一块指标表嵌进已有 React 页面时
2
+ // 从这里 import。组件只收算好的可序列化 `data`(data 形态),不含任何读盘 / artifact
3
+ // 计算代码;计算函数、spec 形态与组合组件只住在 niceeval/report。
5
4
  //
6
5
  // 契约:
7
6
  // - 组件只认「算好的可序列化数据」:零 hooks、零数据操作;
8
7
  // - 样式随包发布:配套 ./styles.css(nre-* 稳定类名),使用者在其后加载覆盖即可;
8
+ // - 渐进增强脚本 ./enhance.js 可选加载,初始静态 HTML 无 JS 完整可读;
9
9
  // - 跨块配色一致:维度键 → 稳定散列 → 固定调色板下标(colors.ts)。
10
10
 
11
- export {
12
- AttemptList,
13
- DeltaTable,
14
- EvalList,
15
- ExperimentList,
16
- GroupSummary,
17
- MetricBars,
18
- MetricLine,
19
- MetricMatrix,
20
- MetricScatter,
21
- MetricTable,
22
- RunOverview,
23
- Scoreboard,
24
- } from "../components.tsx";
25
- export type {
26
- AttemptListProps,
27
- DeltaTableProps,
28
- EvalListProps,
29
- ExperimentListProps,
30
- GroupSummaryProps,
31
- MetricLineProps,
32
- MetricMatrixProps,
33
- MetricScatterProps,
34
- MetricTableProps,
35
- RunOverviewProps,
36
- ScoreboardProps,
37
- } from "../components.tsx";
11
+ export { AttemptList } from "./AttemptList.tsx";
12
+ export { EvalList } from "./EvalList.tsx";
13
+ export { ExperimentList } from "./ExperimentList.tsx";
14
+ export { ScopeSummary } from "./ScopeSummary.tsx";
15
+ export { ExperimentComparisonView as ExperimentComparison } from "./ExperimentComparison.tsx";
16
+ export { MetricTable } from "./MetricTable.tsx";
17
+ export { MetricMatrix } from "./MetricMatrix.tsx";
18
+ export { MetricBars } from "./MetricBars.tsx";
19
+ export { MetricScatter } from "./MetricScatter.tsx";
20
+ export { MetricLine } from "./MetricLine.tsx";
21
+ export { DeltaTable } from "./DeltaTable.tsx";
22
+ export { Scoreboard } from "./Scoreboard.tsx";
38
23
 
39
24
  // 数据契约类型(家在 ../types.ts,「算」与「画」两侧共用同一份)
40
25
  export type {
@@ -42,25 +27,25 @@ export type {
42
27
  AttemptLocator,
43
28
  DeltaData,
44
29
  EvalListItem,
30
+ ExperimentComparisonData,
31
+ ExperimentComparisonGroupData,
45
32
  ExperimentListEvalRow,
46
33
  ExperimentListItem,
47
- GroupSummaryData,
48
- LineAxis,
49
34
  LineData,
50
35
  MatrixData,
51
36
  MetricCell,
52
37
  MetricColumn,
53
- OverviewData,
54
38
  ScatterData,
39
+ ScopeSummaryData,
40
+ ScopeWarning,
55
41
  ScoreboardData,
56
- SelectionWarning,
57
42
  TableData,
58
- TableRowMeta,
43
+ VerdictTally,
59
44
  } from "../types.ts";
60
45
 
61
- // locale(官方组件 chrome 文案;指标 label 的按 locale 字典也用它解析)
62
- export { DEFAULT_REPORT_LOCALE, resolveMetricLabel } from "../locale.ts";
63
- export type { LocalizedLabel, ReportLocale } from "../locale.ts";
46
+ // locale(官方组件 chrome 文案;LocalizedText 的按 locale 解析也用它)
47
+ export { DEFAULT_REPORT_LOCALE, resolveLocalizedText, resolveMetricLabel } from "../locale.ts";
48
+ export type { LocalizedText, ReportLocale } from "../locale.ts";
64
49
 
65
50
  // 稳定配色(自定义组件想与官方组件同键同色时用;seriesClassForKey 配 CSS 的 --nre-series)
66
51
  export { NRE_PALETTE, colorClassForKey, colorHexForKey, colorIndexForKey, seriesClassForKey } from "./colors.ts";
@@ -16,8 +16,8 @@ import {
16
16
  MetricMatrix,
17
17
  MetricScatter,
18
18
  MetricTable,
19
- RunOverview,
20
19
  Scoreboard,
20
+ ScopeSummary,
21
21
  } from "./index.tsx";
22
22
  import { colorClassForKey, seriesClassForKey } from "./colors.ts";
23
23
  import {
@@ -26,56 +26,53 @@ import {
26
26
  evalListItems,
27
27
  experimentListItems,
28
28
  matrixData,
29
- overviewData,
30
- overviewWithCost,
31
29
  scatterData,
30
+ scopeSummaryData,
32
31
  scoreboardData,
33
32
  tableData,
34
- tableDataWithMeta,
35
33
  } from "./fixtures.ts";
36
34
 
37
35
  const attemptHref = (locator: AttemptLocator) => `/attempts/${locator}`;
38
36
 
39
- describe("RunOverview", () => {
40
- const html = renderToStaticMarkup(<RunOverview data={overviewData} />);
37
+ describe("ScopeSummary", () => {
38
+ const html = renderToStaticMarkup(<ScopeSummary data={scopeSummaryData} />);
41
39
 
42
- it("KPI 条:快照数、题数、attempts、通过率、耗时", () => {
43
- expect(html).toContain("nre-overview");
44
- expect(html).toContain("<dd>12</dd>"); // 题目
45
- expect(html).toContain("<dd>48</dd>"); // attempts
46
- // totals.passRate.display 原样渲染(两级聚合 70%),不是从 passed/failed/errored
47
- // 现场重算的 78%(36/(36+8+2))—— 两个数刻意不同,证明组件没有偷懒重算
48
- expect(html).toContain("70%");
49
- expect(html).not.toContain("78%");
50
- expect(html).toContain("46/48"); // samples < total:覆盖率角标(2 个 skipped attempt 不进桶)
51
- expect(html).toContain("4m 21s"); // 261000ms
40
+ it("KPI:experiment/eval/attempt 数、成功率与总成本原样渲染(不从计票重算)", () => {
41
+ expect(html).toContain("nre-scope-summary");
42
+ expect(html).toContain("<dd>2</dd>"); // experiments
43
+ expect(html).toContain("<dd>6</dd>"); // evals
44
+ expect(html).toContain("<dd>9</dd>"); // attempts
45
+ // endToEndPassRate.display 原样渲染(两级聚合 60%),不是 eval 计票 3/5 = 60% 巧合之外的重算;
46
+ // attempt 计票 4/9 ≈ 44% 也不该出现
47
+ expect(html).toContain("60%");
48
+ expect(html).not.toContain("44%");
49
+ expect(html).toContain("$1.50");
52
50
  });
53
51
 
54
- it("costUSD 全缺 = null:显示缺数据,不编 $0", () => {
55
- expect(html).toContain("no data");
56
- expect(html).not.toContain("$0");
57
- const withCost = renderToStaticMarkup(<RunOverview data={overviewWithCost} />);
58
- expect(withCost).toContain("$1.23");
52
+ it("votes 默认 eval 级计票;votes='attempt' 切换显示但 data 不变", () => {
53
+ expect(html).toContain("3 passed");
54
+ expect(html).toContain('data-votes="eval"');
55
+ const attempts = renderToStaticMarkup(<ScopeSummary data={scopeSummaryData} votes="attempt" />);
56
+ expect(attempts).toContain("4 passed");
57
+ expect(attempts).toContain('data-votes="attempt"');
59
58
  });
60
59
 
61
- it("数据来源与 warnings 直接渲染在条内", () => {
62
- expect(html).toContain("2 snapshots");
63
- expect(html).toContain("compare/bub");
60
+ it("时间窗来自 range,不编造当前时间", () => {
64
61
  expect(html).toContain("2026-07-01T10:00:00Z");
65
- expect(html).toContain("snapshot covers 9 of 12 evals seen in history");
66
- expect(html).toContain("nre-warnings");
62
+ expect(html).toContain("2026-07-01T11:30:00Z");
67
63
  });
68
64
  });
69
65
 
70
66
  describe("MetricTable", () => {
71
67
  const html = renderToStaticMarkup(<MetricTable data={tableData} attemptHref={attemptHref} />);
72
68
 
73
- it("列头带 label、unit 与 better 方向", () => {
69
+ it("列头带 label、unit 与 better 方向,首列是 rowDimension", () => {
74
70
  expect(html).toContain("pass rate");
75
71
  expect(html).toContain("(%)");
76
72
  expect(html).toContain("code lines");
77
73
  expect(html).toContain("↑");
78
74
  expect(html).toContain("↓");
75
+ expect(html).toContain(">agent</th>");
79
76
  });
80
77
 
81
78
  it("数字与覆盖率角标:samples < total 如实标出", () => {
@@ -90,7 +87,7 @@ describe("MetricTable", () => {
90
87
  });
91
88
 
92
89
  it("refs + attemptHref 出普通 <a>", () => {
93
- expect(html).toContain('href="/attempts/@1a0a0a0"');
90
+ expect(html).toContain('href="/attempts/@1a0a0a0a"');
94
91
  });
95
92
 
96
93
  it("渐进增强的 data 属性:所有表头 data-nre-sort、格子 data-sort-value(无 JS 时纯属性,内容完整)", () => {
@@ -100,30 +97,9 @@ describe("MetricTable", () => {
100
97
  // 缺数据格子的排序值为空串:enhance.js 排序时恒沉底
101
98
  expect(html).toContain('data-sort-value=""');
102
99
  });
103
- });
104
-
105
- describe("MetricTable:meta 榜单 parity 与 filter", () => {
106
- const html = renderToStaticMarkup(<MetricTable data={tableDataWithMeta} />);
107
-
108
- it("meta 在场补 Model / Agent 列,列序 experiment、model、agent、指标、verdicts", () => {
109
- expect(html).toContain(">Model</th>");
110
- expect(html).toContain(">Agent</th>");
111
- expect(html).toContain(">Verdicts</th>");
112
- expect(html.indexOf(">Model</th>")).toBeLessThan(html.indexOf(">Agent</th>"));
113
- expect(html.indexOf(">Agent</th>")).toBeLessThan(html.indexOf("pass rate"));
114
- expect(html).toContain("gpt-5.4");
115
- });
116
-
117
- it("verdict 计票列:eval 级折叠口径的 pill,零计数不出 pill", () => {
118
- expect(html).toContain("nre-verdict-pill");
119
- expect(html).toContain("1 passed");
120
- expect(html).toContain("1 failed");
121
- expect(html).toContain("2 passed");
122
- expect(html).not.toContain("0 errored");
123
- });
124
100
 
125
101
  it("filter 开:表格前渲染 data-nre-filter 输入框(无 JS 静默无功能),仍无 <script>", () => {
126
- const filtered = renderToStaticMarkup(<MetricTable data={tableDataWithMeta} filter />);
102
+ const filtered = renderToStaticMarkup(<MetricTable data={tableData} filter />);
127
103
  expect(filtered).toContain("data-nre-filter");
128
104
  expect(filtered).toContain('class="nre-filter"');
129
105
  expect(filtered.indexOf("nre-filter")).toBeLessThan(filtered.indexOf("<table"));
@@ -131,11 +107,8 @@ describe("MetricTable:meta 榜单 parity 与 filter", () => {
131
107
  expect(filtered).not.toContain("<script");
132
108
  });
133
109
 
134
- it("locale=zh-CN:chrome 文案与 meta 列头走字典;display 不本地化", () => {
135
- const zh = renderToStaticMarkup(<MetricTable data={tableDataWithMeta} filter locale="zh-CN" />);
136
- expect(zh).toContain(">模型</th>");
137
- expect(zh).toContain(">结果</th>");
138
- expect(zh).toContain("1 通过");
110
+ it("locale=zh-CN:chrome 文案走字典;display 不本地化", () => {
111
+ const zh = renderToStaticMarkup(<MetricTable data={tableData} filter locale="zh-CN" />);
139
112
  expect(zh).toContain('placeholder="筛选行…"');
140
113
  expect(zh).toContain("50%"); // display 是 format 产物,不本地化
141
114
  });
@@ -144,16 +117,17 @@ describe("MetricTable:meta 榜单 parity 与 filter", () => {
144
117
  describe("MetricMatrix", () => {
145
118
  const html = renderToStaticMarkup(<MetricMatrix data={matrixData} attemptHref={attemptHref} />);
146
119
 
147
- it("caption 标出指标与行列维度", () => {
120
+ it("caption 标出指标与行列维度(rowDimension × columnDimension)", () => {
148
121
  expect(html).toContain("pass rate");
149
122
  expect(html).toContain("eval × agent");
150
123
  });
151
124
 
152
- it("格子数字与 refs 下钻链接", () => {
125
+ it("格子数字与 refs 下钻链接;稀疏格子空着不补 0", () => {
153
126
  expect(html).toContain("100%");
154
127
  expect(html).toContain("0%");
155
- expect(html).toContain('href="/attempts/@1b3b3b3"');
156
- expect(html).toContain('href="/attempts/@1b7b7b7"');
128
+ expect(html).toContain('href="/attempts/@1b3b3b3b"');
129
+ expect(html).toContain('href="/attempts/@1b7b7b7b"');
130
+ expect(html).toContain("nre-td-empty");
157
131
  });
158
132
  });
159
133
 
@@ -166,19 +140,20 @@ describe("Scoreboard", () => {
166
140
  expect(html).toContain("/ 100");
167
141
  });
168
142
 
169
- it("分科小计 earned/possible", () => {
170
- expect(html).toContain("14/16");
171
- expect(html).toContain("3/4");
143
+ it("分科小计 display(earned/possible 与同尺度百分比)", () => {
144
+ expect(html).toContain("14/16 (87.5%)");
145
+ expect(html).toContain("3/4 (75%)");
172
146
  });
173
147
 
174
- it("missing 注脚:没跑按 0 计的题数如实展示", () => {
175
- expect(html).toContain("1 eval missing, scored 0");
176
- expect(html).toContain("2 evals missing, scored 0");
148
+ it("notRun unscorable 分开注脚,不合并成一个笼统的缺失数", () => {
149
+ expect(html).toContain("1 eval not run, scored 0");
150
+ expect(html).toContain("2 evals unscorable, scored 0");
177
151
  });
178
152
 
179
- it("实际生效的权重表可审计", () => {
153
+ it("实际生效的权重表可审计;题集外被忽略的 eval 如实报数", () => {
180
154
  expect(html).toContain("algebra/ ×2");
181
155
  expect(html).toContain("others ×1");
156
+ expect(html).toContain("1 eval outside the question set ignored");
182
157
  });
183
158
  });
184
159
 
@@ -203,10 +178,37 @@ describe("MetricScatter", () => {
203
178
  expect(html).toContain("pass rate(%)");
204
179
  });
205
180
 
206
- it("better:lower 的 x 轴反向:便宜($5)在贵($10)右边,好的角落恒在右上", () => {
181
+ it("轴方向跟随 better:成本轴(lower)反向,便宜($5)在贵($10)右边;提示恒为「越靠右上越好」", () => {
207
182
  expect(cxOf("compare/bub-low")).toBeGreaterThan(cxOf("compare/bub-high"));
208
- // y 轴 better:higher:通过率高(90%)在低(50%)上方(SVG y 向下增长)
183
+ // y 轴(higher)正向:通过率高(90%)在低(50%)上方(SVG y 向下增长)
209
184
  expect(cyOf("compare/bub-high")).toBeLessThan(cyOf("compare/bub-low"));
185
+ expect(html).toContain("better → upper right");
186
+ });
187
+
188
+ it("反向轴的刻度显示真实值:x 刻度从左到右数值递减(值大在左),数字不变", () => {
189
+ // x 轴刻度 <text class="nre-scatter-tick" x="…">$N</text>:抓出 (x, 值) 对,按 x 升序后值应递减。
190
+ const ticks = [...html.matchAll(/nre-scatter-tick" x="([\d.]+)" y="[\d.]+" text-anchor="middle">\$([\d.]+)/g)].map(
191
+ (m) => ({ px: Number(m[1]), value: Number(m[2]) }),
192
+ );
193
+ expect(ticks.length).toBeGreaterThanOrEqual(2);
194
+ const byPx = [...ticks].sort((a, b) => a.px - b.px);
195
+ for (let i = 1; i < byPx.length; i++) {
196
+ expect(byPx[i]!.value).toBeLessThan(byPx[i - 1]!.value);
197
+ }
198
+ });
199
+
200
+ it("任一轴未声明 better:该轴正向渲染且整图无方向提示(组件不猜「更好」朝哪边)", () => {
201
+ const noBetter = renderToStaticMarkup(
202
+ <MetricScatter data={{ ...scatterData, x: { key: "cost", label: "cost", unit: "$" } }} />,
203
+ );
204
+ expect(noBetter).not.toContain("better →");
205
+ // x 正向:便宜($5)回到贵($10)左边。
206
+ const cx = (key: string): number => {
207
+ const m = noBetter.match(new RegExp(`data-key="${key}"[\\s\\S]*?\\bcx="([\\d.]+)"`));
208
+ expect(m, `circle for ${key}`).toBeTruthy();
209
+ return Number(m![1]);
210
+ };
211
+ expect(cx("compare/bub-low")).toBeLessThan(cx("compare/bub-high"));
210
212
  });
211
213
 
212
214
  it("同系列点连线,系列色走类名(nre-series-cN,CSS 上色跟随深浅主题),图例列系列名", () => {
@@ -214,18 +216,33 @@ describe("MetricScatter", () => {
214
216
  expect(html).toContain(`nre-scatter-line ${seriesClassForKey("bub")}`);
215
217
  // 渲染面零内联 hex:深色主题下由 CSS 变量切换
216
218
  expect(html).not.toMatch(/#[0-9a-f]{6}/i);
217
- // 图例:codex 只有 1 个可画点、不出线,但系列名仍在图例里
218
219
  expect(html).toContain(">bub</span>");
219
220
  expect(html).toContain(">codex</span>");
220
221
  });
221
222
 
222
- it("niceTicks 网格与每点直接标签(id 末段)", () => {
223
+ it("niceTicks 网格与每点直接标签(末段唯一时缩成末段)", () => {
223
224
  expect(html).toContain("nre-scatter-grid");
224
225
  expect(html.match(/nre-scatter-tick/g)!.length).toBeGreaterThanOrEqual(6);
225
226
  expect(html).toContain(">bub-low</text>");
226
227
  expect(html).toContain(">codex-mid</text>");
227
228
  });
228
229
 
230
+ it("末段重名的点使用能区分它们的最短路径后缀", () => {
231
+ const dupes = renderToStaticMarkup(
232
+ <MetricScatter
233
+ data={{
234
+ ...scatterData,
235
+ rows: [
236
+ { ...scatterData.rows[0]!, key: "compare/a/run" },
237
+ { ...scatterData.rows[1]!, key: "compare/b/run" },
238
+ ],
239
+ }}
240
+ />,
241
+ );
242
+ expect(dupes).toContain(">a/run</text>");
243
+ expect(dupes).toContain(">b/run</text>");
244
+ });
245
+
229
246
  it("hover 退化为 <title>:display 与 samples/total", () => {
230
247
  expect(html).toContain("<title>");
231
248
  expect(html).toContain("50%(6/6)");
@@ -276,49 +293,57 @@ describe("DeltaTable", () => {
276
293
  expect(html).toContain(">Δ</span>");
277
294
  });
278
295
 
279
- it("涨跌好坏按 better 配色:通过率涨=好,成本涨=坏,0=平", () => {
296
+ it("涨跌好坏按数据侧算好的 outcome 配色:improved / regressed / unchanged / unavailable", () => {
280
297
  expect(html).toContain("nre-delta-good");
281
298
  expect(html).toContain("nre-delta-bad");
282
299
  expect(html).toContain("nre-delta-flat");
300
+ expect(html).toContain('data-outcome="unavailable"');
283
301
  });
284
302
 
285
- it("每行标出 A → B 的 experimentId", () => {
303
+ it("每行显示作者声明的 pair label 与 A → B 的维度键", () => {
304
+ expect(html).toContain(">bub</span>");
286
305
  expect(html).toContain("compare/bub → compare/bub--agents-md");
287
306
  });
288
- });
289
307
 
290
- // AttemptList / EvalList / ExperimentList 的公开(报告组件)Props 没有 attemptHref 覆盖口子——
291
- // 证据室深链恒经 ctx.attemptHref,宿主外直接嵌进 React 应用时退化为默认 `#/attempt/<locator>`
292
- // (tree.ts 的 DEFAULT_WEB_CONTEXT),不是纯展示、也不发明另一套断链(与 MetricTable 等
293
- // 可选 attemptHref 的组件不同,这三个组件的下钻不是可选行为)
308
+ it("0 对显示明确空态并报告配对域实验数", () => {
309
+ const empty = renderToStaticMarkup(<DeltaTable data={{ ...deltaData, experiments: 3, rows: [] }} />);
310
+ expect(empty).not.toContain("<table");
311
+ expect(empty).toContain("3 experiments, 0 comparable pairs");
312
+ });
313
+ });
294
314
 
295
315
  describe("AttemptList", () => {
296
- const html = renderToStaticMarkup(<AttemptList items={attemptListItems} />);
316
+ const html = renderToStaticMarkup(<AttemptList data={attemptListItems} />);
297
317
 
298
- it("只显示一条主失败摘要,不内联完整 evidence", () => {
299
- expect(html).toContain("roots-correct");
300
- expect(html).toContain("expected x=2");
301
- expect(html).toContain("received x=3");
302
- expect(html).not.toContain("judge: sign flipped when substituting into the quadratic formula");
318
+ it("显示算好的 failureSummary 与 +N more failures 计数,不重算摘要", () => {
319
+ expect(html).toContain("gate: roots-correct · expected x=2 · received x=3");
320
+ expect(html).toContain("+1 more failure");
321
+ // errored 的一层摘要(phase · code · message)
322
+ expect(html).toContain("eval.run · unexpected-error · TypeError");
303
323
  });
304
324
 
305
- it("total > items.length 时如实报「还有 n 条没列」", () => {
306
- const html2 = renderToStaticMarkup(<AttemptList items={attemptListItems} total={attemptListItems.length + 2} />);
325
+ it("total > data.length 时如实报「还有 n 条没列」", () => {
326
+ const html2 = renderToStaticMarkup(<AttemptList data={attemptListItems} total={attemptListItems.length + 2} />);
307
327
  expect(html2).toContain("and 2 more not shown");
308
- // 不传 total(或 total === items.length)不产出截断文案
328
+ // 不传 total(或 total === data.length)不产出截断文案
309
329
  expect(html).not.toContain("more not shown");
310
330
  });
311
331
 
332
+ it("costUSD 为 null 时不显示成本,也不伪造 $0", () => {
333
+ expect(html).toContain("$0.12");
334
+ expect(html.match(/nre-attempt-cost/g)).toHaveLength(1); // errored 那条 costUSD: null
335
+ });
336
+
312
337
  it("AttemptList 自身不展开断言详情", () => {
313
338
  expect(html).not.toContain("<details");
314
339
  });
315
340
  });
316
341
 
317
342
  describe("EvalList", () => {
318
- const html = renderToStaticMarkup(<EvalList items={evalListItems} />);
343
+ const html = renderToStaticMarkup(<EvalList data={evalListItems} />);
319
344
 
320
345
  it("展开到这道题的 Attempt(与 AttemptList 同一套 AttemptRow 渲染)", () => {
321
- expect(html).toContain('href="#/attempt/@1a4a4a4"');
346
+ expect(html).toContain('href="#/attempt/@1a4a4a4a"');
322
347
  expect(html).toContain("roots-correct");
323
348
  });
324
349
 
@@ -329,34 +354,45 @@ describe("EvalList", () => {
329
354
  });
330
355
 
331
356
  describe("ExperimentList", () => {
332
- const html = renderToStaticMarkup(<ExperimentList items={experimentListItems} filter />);
357
+ const html = renderToStaticMarkup(<ExperimentList data={experimentListItems} filter />);
333
358
 
334
- it("web 面是带过滤框与八个固定列头的 experiment 比较表", () => {
359
+ it("web 面是带过滤框与八个固定列头的 experiment 比较表(成本列头是 Cost)", () => {
335
360
  expect(html).toContain('data-nre-experiment-filter=""');
336
- for (const header of ["Experiment", "Model", "Agent", "Avg duration", "End-to-end pass rate", "Tokens", "Est. cost", "Result"]) {
361
+ for (const header of ["Experiment", "Model", "Agent", "Avg duration", "End-to-end pass rate", "Tokens", "Cost", "Result"]) {
337
362
  expect(html).toContain(`>${header}</button>`);
338
363
  }
364
+ expect(html).not.toContain("Est. cost");
339
365
  expect(html).toContain('data-nre-experiment-sort="4" class="nre-sort-desc"');
340
366
  });
341
367
 
342
- it("主行:身份、agent/model、官方两级聚合指标与判定构成", () => {
368
+ it("主行:身份、agent/model、官方两级聚合指标与 eval 级判定构成", () => {
343
369
  expect(html).toContain("compare/bub");
344
370
  expect(html).toContain("compare/codex");
345
371
  expect(html).toContain("gpt-5.4");
346
- expect(html).toContain("50%"); // passRate.display
372
+ expect(html).toContain("50%"); // endToEndPassRate.display
347
373
  expect(html).toContain("1 passed / 1 failed");
348
374
  expect(html).toContain("memory=true");
349
375
  });
350
376
 
351
- it("展开到 Eval:同一道题的全部 Attempt 逐条显示", () => {
377
+ it("展开到 Eval:父行只有折叠判定与题级聚合,失败摘要只在 Attempt 子行", () => {
352
378
  expect(html).toContain("algebra/quadratic");
353
- expect(html).toContain('href="#/attempt/@1a4a4a4"');
354
- expect(html).toContain('href="#/attempt/@1b5b5b5"');
379
+ expect(html).toContain('href="#/attempt/@1a4a4a4a"');
380
+ expect(html).toContain('href="#/attempt/@1b5b5b5b"');
355
381
  expect(html.match(/algebra\/quadratic/g)).toHaveLength(1);
356
382
  expect(html).toContain("nre-experiment-eval-header");
357
- expect(html.match(/nre-experiment-attempt-row/g)).toHaveLength(3);
383
+ expect(html.match(/nre-experiment-attempt-row/g)).toHaveLength(4);
358
384
  expect(html).toContain("├─");
359
385
  expect(html).toContain("└─");
386
+ // 主失败摘要只出现在 attempt 子行:两次(同一 eval 的两轮 attempt 各自的摘要),不在父行重复
387
+ expect(html.match(/gate: roots-correct/g)).toHaveLength(2);
388
+ });
389
+
390
+ it("relativeTo 去掉组前缀只显示末段;data-sort-value 仍是完整 id", () => {
391
+ const relative = renderToStaticMarkup(<ExperimentList data={experimentListItems} relativeTo="compare" />);
392
+ expect(relative).toContain(">bub</b>");
393
+ expect(relative).toContain('data-sort-value="compare/bub"');
394
+ // 不传 relativeTo 显示完整 id
395
+ expect(html).toContain(">compare/bub</b>");
360
396
  });
361
397
 
362
398
  it("零 JS 靠原生 <details>,无 <script>", () => {
@@ -371,9 +407,8 @@ describe("跨组件契约", () => {
371
407
  const table = renderToStaticMarkup(<MetricTable data={tableData} />);
372
408
  const matrix = renderToStaticMarkup(<MetricMatrix data={matrixData} />);
373
409
  const board = renderToStaticMarkup(<Scoreboard data={scoreboardData} />);
374
- const delta = renderToStaticMarkup(<DeltaTable data={deltaData} />);
375
- const attempts = renderToStaticMarkup(<AttemptList items={attemptListItems} />);
376
- for (const html of [table, matrix, board, delta, attempts]) {
410
+ const attempts = renderToStaticMarkup(<AttemptList data={attemptListItems} />);
411
+ for (const html of [table, matrix, board, attempts]) {
377
412
  expect(html).toContain(cls);
378
413
  }
379
414
  const scatter = renderToStaticMarkup(<MetricScatter data={scatterData} />);
@@ -382,15 +417,15 @@ describe("跨组件契约", () => {
382
417
 
383
418
  it("静态输出不含 <script>:交互只靠 <a>/<details>/CSS", () => {
384
419
  const all = [
385
- renderToStaticMarkup(<RunOverview data={overviewData} />),
420
+ renderToStaticMarkup(<ScopeSummary data={scopeSummaryData} />),
386
421
  renderToStaticMarkup(<MetricTable data={tableData} attemptHref={attemptHref} />),
387
422
  renderToStaticMarkup(<MetricMatrix data={matrixData} attemptHref={attemptHref} />),
388
423
  renderToStaticMarkup(<Scoreboard data={scoreboardData} />),
389
424
  renderToStaticMarkup(<MetricScatter data={scatterData} />),
390
425
  renderToStaticMarkup(<DeltaTable data={deltaData} />),
391
- renderToStaticMarkup(<AttemptList items={attemptListItems} />),
392
- renderToStaticMarkup(<EvalList items={evalListItems} />),
393
- renderToStaticMarkup(<ExperimentList items={experimentListItems} />),
426
+ renderToStaticMarkup(<AttemptList data={attemptListItems} />),
427
+ renderToStaticMarkup(<EvalList data={evalListItems} />),
428
+ renderToStaticMarkup(<ExperimentList data={experimentListItems} />),
394
429
  ].join("");
395
430
  expect(all).not.toContain("<script");
396
431
  });