niceeval 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (428) hide show
  1. package/INDEX.md +31 -33
  2. package/dist/agents/types.d.ts +20 -17
  3. package/dist/context/types.d.ts +21 -21
  4. package/dist/i18n/en.d.ts +10 -2
  5. package/dist/i18n/zh-CN.d.ts +15 -7
  6. package/dist/o11y/execution-tree.d.ts +103 -0
  7. package/dist/o11y/otlp/select.d.ts +22 -0
  8. package/dist/report/{react → assets}/colors.d.ts +8 -0
  9. package/dist/report/{react → assets}/colors.js +23 -0
  10. package/dist/report/built-in/index.d.ts +2 -2
  11. package/dist/report/built-in/index.js +2 -2
  12. package/dist/report/built-in/standard.d.ts +8 -1
  13. package/dist/report/built-in/standard.js +17 -7
  14. package/dist/report/components/attempt-detail/AttemptAssertions.d.ts +6 -0
  15. package/dist/report/components/attempt-detail/AttemptAssertions.js +17 -0
  16. package/dist/report/components/attempt-detail/AttemptConversation.d.ts +6 -0
  17. package/dist/report/components/attempt-detail/AttemptConversation.js +34 -0
  18. package/dist/report/components/attempt-detail/AttemptDiagnostics.d.ts +6 -0
  19. package/dist/report/components/attempt-detail/AttemptDiagnostics.js +7 -0
  20. package/dist/report/components/attempt-detail/AttemptDiff.d.ts +6 -0
  21. package/dist/report/components/attempt-detail/AttemptDiff.js +11 -0
  22. package/dist/report/components/attempt-detail/AttemptError.d.ts +6 -0
  23. package/dist/report/components/attempt-detail/AttemptError.js +16 -0
  24. package/dist/report/components/attempt-detail/AttemptFixPrompt.d.ts +6 -0
  25. package/dist/report/components/attempt-detail/AttemptFixPrompt.js +7 -0
  26. package/dist/report/components/attempt-detail/AttemptSource.d.ts +6 -0
  27. package/dist/report/components/attempt-detail/AttemptSource.js +27 -0
  28. package/dist/report/components/attempt-detail/AttemptSummary.d.ts +8 -0
  29. package/dist/report/components/attempt-detail/AttemptSummary.js +20 -0
  30. package/dist/report/components/attempt-detail/AttemptTimeline.d.ts +6 -0
  31. package/dist/report/components/attempt-detail/AttemptTimeline.js +28 -0
  32. package/dist/report/components/attempt-detail/AttemptTrace.d.ts +6 -0
  33. package/dist/report/components/attempt-detail/AttemptTrace.js +29 -0
  34. package/dist/report/components/attempt-detail/AttemptUsage.d.ts +6 -0
  35. package/dist/report/components/attempt-detail/AttemptUsage.js +20 -0
  36. package/dist/report/components/attempt-detail/compute.d.ts +24 -0
  37. package/dist/report/components/attempt-detail/compute.js +254 -0
  38. package/dist/report/components/attempt-detail/faces.d.ts +14 -0
  39. package/dist/report/components/attempt-detail/faces.js +235 -0
  40. package/dist/report/components/attempt-detail/index.d.ts +38 -0
  41. package/dist/report/components/attempt-detail/index.js +527 -0
  42. package/dist/report/{react → components}/cell.d.ts +5 -3
  43. package/dist/report/{react → components}/cell.js +3 -3
  44. package/dist/report/{react → components/entity-lists}/AttemptList.d.ts +8 -4
  45. package/dist/report/{react → components/entity-lists}/AttemptList.js +16 -10
  46. package/dist/report/{react → components/entity-lists}/EvalList.d.ts +3 -3
  47. package/dist/report/{react → components/entity-lists}/EvalList.js +0 -0
  48. package/dist/report/{react → components/entity-lists}/ExperimentList.d.ts +3 -3
  49. package/dist/report/{react → components/entity-lists}/ExperimentList.js +10 -10
  50. package/dist/report/components/entity-lists/compute.d.ts +23 -0
  51. package/dist/report/components/entity-lists/compute.js +171 -0
  52. package/dist/report/components/entity-lists/faces.d.ts +5 -0
  53. package/dist/report/components/entity-lists/faces.js +174 -0
  54. package/dist/report/components/entity-lists/index.d.ts +51 -0
  55. package/dist/report/components/entity-lists/index.js +165 -0
  56. package/dist/report/{react → components}/fixtures.d.ts +2 -2
  57. package/dist/report/{react → components}/fixtures.js +1 -1
  58. package/dist/report/{react → components/metric-views}/DeltaTable.d.ts +2 -2
  59. package/dist/report/{react → components/metric-views}/DeltaTable.js +4 -4
  60. package/dist/report/{react → components/metric-views}/MetricBars.d.ts +3 -3
  61. package/dist/report/{react → components/metric-views}/MetricBars.js +3 -3
  62. package/dist/report/{react → components/metric-views}/MetricLine.d.ts +2 -2
  63. package/dist/report/{react → components/metric-views}/MetricLine.js +8 -5
  64. package/dist/report/{react → components/metric-views}/MetricMatrix.d.ts +3 -3
  65. package/dist/report/{react → components/metric-views}/MetricMatrix.js +4 -4
  66. package/dist/report/components/metric-views/MetricScatter.d.ts +11 -0
  67. package/dist/report/{react → components/metric-views}/MetricScatter.js +26 -23
  68. package/dist/report/{react → components/metric-views}/MetricTable.d.ts +3 -3
  69. package/dist/report/{react → components/metric-views}/MetricTable.js +4 -4
  70. package/dist/report/{react → components/metric-views}/Scoreboard.d.ts +2 -2
  71. package/dist/report/{react → components/metric-views}/Scoreboard.js +3 -3
  72. package/dist/report/components/metric-views/compute.d.ts +89 -0
  73. package/dist/report/{compute.js → components/metric-views/compute.js} +32 -415
  74. package/dist/report/components/metric-views/faces.d.ts +13 -0
  75. package/dist/report/components/metric-views/faces.js +381 -0
  76. package/dist/report/components/metric-views/index.d.ts +50 -0
  77. package/dist/report/components/metric-views/index.js +273 -0
  78. package/dist/report/{text → components/metric-views}/plot.js +1 -1
  79. package/dist/report/components/shared-compute.d.ts +22 -0
  80. package/dist/report/components/shared-compute.js +47 -0
  81. package/dist/report/components/shared-faces.d.ts +9 -0
  82. package/dist/report/components/shared-faces.js +26 -0
  83. package/dist/report/components/shared.d.ts +68 -0
  84. package/dist/report/components/shared.js +125 -0
  85. package/dist/report/{react → components/site-components}/CopyFixPrompt.d.ts +2 -2
  86. package/dist/report/{react → components/site-components}/CopyFixPrompt.js +2 -2
  87. package/dist/report/{react → components/site-components}/HeroCard.d.ts +2 -2
  88. package/dist/report/{react → components/site-components}/HeroCard.js +2 -2
  89. package/dist/report/{react → components/site-components}/ScopeWarnings.d.ts +2 -2
  90. package/dist/report/{react → components/site-components}/ScopeWarnings.js +3 -3
  91. package/dist/report/{react → components/site-components}/TraceWaterfall.d.ts +3 -3
  92. package/dist/report/{react → components/site-components}/TraceWaterfall.js +5 -5
  93. package/dist/report/components/site-components/compute.d.ts +28 -0
  94. package/dist/report/components/site-components/compute.js +132 -0
  95. package/dist/report/components/site-components/faces.d.ts +21 -0
  96. package/dist/report/components/site-components/faces.js +75 -0
  97. package/dist/report/components/site-components/index.d.ts +74 -0
  98. package/dist/report/components/site-components/index.js +220 -0
  99. package/dist/report/{scope-warnings.d.ts → components/site-components/scope-warnings.d.ts} +2 -2
  100. package/dist/report/{scope-warnings.js → components/site-components/scope-warnings.js} +2 -2
  101. package/dist/report/{react → components/summaries}/ScopeSummary.d.ts +2 -2
  102. package/dist/report/{react → components/summaries}/ScopeSummary.js +17 -8
  103. package/dist/report/components/summaries/compute.d.ts +7 -0
  104. package/dist/report/components/summaries/compute.js +51 -0
  105. package/dist/report/components/summaries/faces.d.ts +7 -0
  106. package/dist/report/components/summaries/faces.js +38 -0
  107. package/dist/report/components/summaries/index.d.ts +27 -0
  108. package/dist/report/components/summaries/index.js +78 -0
  109. package/dist/report/definition/grid-layout.d.ts +50 -0
  110. package/dist/report/definition/grid-layout.js +89 -0
  111. package/dist/report/{primitives.d.ts → definition/primitives.d.ts} +34 -4
  112. package/dist/report/{primitives.js → definition/primitives.js} +101 -10
  113. package/dist/report/{report.d.ts → definition/report.d.ts} +36 -65
  114. package/dist/report/{report.js → definition/report.js} +58 -90
  115. package/dist/report/{text/table.d.ts → definition/table-text.d.ts} +2 -2
  116. package/dist/report/{text/table.js → definition/table-text.js} +2 -2
  117. package/dist/report/{tree.d.ts → definition/tree.d.ts} +44 -12
  118. package/dist/report/{tree.js → definition/tree.js} +10 -12
  119. package/dist/report/index.d.ts +33 -18
  120. package/dist/report/index.js +23 -14
  121. package/dist/report/{aggregate.d.ts → model/aggregate.d.ts} +12 -4
  122. package/dist/report/{aggregate.js → model/aggregate.js} +28 -8
  123. package/dist/report/{flag.d.ts → model/flag.d.ts} +14 -1
  124. package/dist/report/{flag.js → model/flag.js} +35 -0
  125. package/dist/report/{format.d.ts → model/format.d.ts} +11 -5
  126. package/dist/report/{format.js → model/format.js} +48 -4
  127. package/dist/report/{locale.d.ts → model/locale.d.ts} +17 -17
  128. package/dist/report/{locale.js → model/locale.js} +31 -33
  129. package/dist/report/{metrics.d.ts → model/metrics.d.ts} +2 -2
  130. package/dist/report/{metrics.js → model/metrics.js} +2 -2
  131. package/dist/report/{types.d.ts → model/types.d.ts} +140 -20
  132. package/dist/report/react/index.d.ts +32 -21
  133. package/dist/report/react/index.js +33 -21
  134. package/dist/report/{load.d.ts → runtime/load.d.ts} +1 -1
  135. package/dist/report/{load.js → runtime/load.js} +1 -1
  136. package/dist/report/runtime/text.d.ts +67 -0
  137. package/dist/report/runtime/text.js +114 -0
  138. package/dist/report/runtime/web.d.ts +35 -0
  139. package/dist/report/runtime/web.js +70 -0
  140. package/dist/results/annotated-source.d.ts +87 -0
  141. package/dist/results/attempt-evidence.d.ts +74 -0
  142. package/dist/results/attempt-source.d.ts +15 -0
  143. package/dist/results/select.d.ts +12 -2
  144. package/dist/results/select.js +27 -5
  145. package/dist/runner/eval-selection.d.ts +26 -0
  146. package/dist/runner/feedback/sink.d.ts +26 -1
  147. package/dist/runner/types.d.ts +149 -23
  148. package/dist/sandbox/docker.d.ts +6 -0
  149. package/dist/sandbox/types.d.ts +23 -21
  150. package/dist/scoring/display.d.ts +7 -2
  151. package/dist/scoring/display.js +42 -4
  152. package/dist/scoring/types.d.ts +3 -3
  153. package/dist/shared/aggregate.d.ts +15 -1
  154. package/dist/shared/aggregate.js +32 -1
  155. package/dist/shared/types.d.ts +6 -1
  156. package/docs-site/images/logo-dark.svg +7 -0
  157. package/docs-site/images/logo.svg +6 -5
  158. package/docs-site/zh/README.md +8 -7
  159. package/docs-site/zh/examples/ai-agent-application.mdx +5 -5
  160. package/docs-site/zh/examples/coding-agent-extensions.mdx +4 -4
  161. package/docs-site/zh/examples/index.mdx +3 -3
  162. package/docs-site/zh/examples/integrations/ai-sdk-v7.mdx +1 -1
  163. package/docs-site/zh/examples/integrations/claude-sdk.mdx +3 -3
  164. package/docs-site/zh/examples/integrations/codex-sdk.mdx +3 -3
  165. package/docs-site/zh/examples/integrations/langgraph.mdx +3 -3
  166. package/docs-site/zh/examples/integrations/pi-sdk.mdx +2 -2
  167. package/docs-site/zh/explanation/adapter.mdx +19 -19
  168. package/docs-site/zh/explanation/assert.mdx +9 -9
  169. package/docs-site/zh/explanation/drive.mdx +4 -4
  170. package/docs-site/zh/explanation/evals.mdx +8 -8
  171. package/docs-site/zh/explanation/experiment.mdx +8 -8
  172. package/docs-site/zh/explanation/hitl.mdx +13 -13
  173. package/docs-site/zh/explanation/judge.mdx +3 -3
  174. package/docs-site/zh/explanation/overview.mdx +7 -7
  175. package/docs-site/zh/explanation/runner.mdx +11 -11
  176. package/docs-site/zh/explanation/tier.mdx +6 -6
  177. package/docs-site/zh/index.mdx +14 -14
  178. package/docs-site/zh/introduction.mdx +13 -16
  179. package/docs-site/zh/reference/builtin-agents.mdx +61 -28
  180. package/docs-site/zh/reference/capabilities.mdx +7 -27
  181. package/docs-site/zh/reference/cli.mdx +35 -32
  182. package/docs-site/zh/reference/define-agent.mdx +32 -31
  183. package/docs-site/zh/reference/define-config.mdx +6 -6
  184. package/docs-site/zh/reference/define-eval.mdx +33 -20
  185. package/docs-site/zh/reference/events.mdx +4 -4
  186. package/docs-site/zh/reference/expect.mdx +3 -3
  187. package/docs-site/zh/reference/official-adapters.mdx +17 -17
  188. package/docs-site/zh/reference/report-components.mdx +193 -121
  189. package/docs-site/zh/reference/results-data.mdx +13 -13
  190. package/docs-site/zh/troubleshooting/debug-sandbox.mdx +11 -11
  191. package/docs-site/zh/troubleshooting/debugging.mdx +43 -19
  192. package/docs-site/zh/{how-to → tutorials}/agent-feedback-loop.mdx +38 -38
  193. package/docs-site/zh/tutorials/agent-onboarding.mdx +100 -0
  194. package/docs-site/zh/tutorials/authoring.mdx +211 -0
  195. package/docs-site/zh/{how-to → tutorials}/ci-integration.mdx +8 -8
  196. package/docs-site/zh/{how-to → tutorials}/connect-otel.mdx +22 -22
  197. package/docs-site/zh/{how-to → tutorials}/connect-your-agent.mdx +58 -63
  198. package/docs-site/zh/tutorials/custom-reports.mdx +453 -0
  199. package/docs-site/zh/{how-to → tutorials}/dataset-fanout.mdx +4 -4
  200. package/docs-site/zh/tutorials/experiments.mdx +103 -0
  201. package/docs-site/zh/{how-to → tutorials}/fixtures.mdx +9 -9
  202. package/docs-site/zh/{how-to → tutorials}/publish-report.mdx +11 -5
  203. package/docs-site/zh/tutorials/quickstart.mdx +18 -18
  204. package/docs-site/zh/{how-to → tutorials}/reporters.mdx +6 -6
  205. package/docs-site/zh/{how-to → tutorials}/sandbox-agent.mdx +6 -6
  206. package/docs-site/zh/{how-to → tutorials}/sandbox-providers.mdx +41 -19
  207. package/docs-site/zh/{how-to → tutorials}/scoring-guide.mdx +4 -4
  208. package/docs-site/zh/{how-to → tutorials}/viewing-results.mdx +59 -50
  209. package/docs-site/zh/tutorials/write-experiment.mdx +355 -0
  210. package/docs-site/zh/{how-to → tutorials}/write-send.mdx +40 -40
  211. package/package.json +8 -4
  212. package/src/agents/bub.ts +21 -8
  213. package/src/agents/claude-code.ts +23 -10
  214. package/src/agents/codex.test.ts +22 -7
  215. package/src/agents/codex.ts +22 -9
  216. package/src/agents/index.ts +2 -2
  217. package/src/agents/openai-compat.ts +1 -1
  218. package/src/agents/post-setup.ts +45 -22
  219. package/src/agents/streaming.ts +1 -1
  220. package/src/agents/types.ts +20 -17
  221. package/src/agents/ui-message-stream.test.ts +10 -0
  222. package/src/agents/ui-message-stream.ts +12 -1
  223. package/src/cli.ts +77 -64
  224. package/src/context/types.ts +21 -21
  225. package/src/define.ts +13 -0
  226. package/src/i18n/en.ts +30 -15
  227. package/src/i18n/zh-CN.ts +30 -15
  228. package/src/index.ts +2 -0
  229. package/src/report/{react → assets}/colors.ts +22 -0
  230. package/src/report/{react → assets}/enhance.js +1 -33
  231. package/src/report/{react → assets}/styles.css +136 -112
  232. package/src/report/built-in/index.tsx +2 -2
  233. package/src/report/built-in/standard.tsx +18 -6
  234. package/src/report/components/attempt-detail/AttemptAssertions.tsx +67 -0
  235. package/src/report/components/attempt-detail/AttemptConversation.tsx +110 -0
  236. package/src/report/components/attempt-detail/AttemptDiagnostics.tsx +38 -0
  237. package/src/report/components/attempt-detail/AttemptDiff.tsx +35 -0
  238. package/src/report/components/attempt-detail/AttemptError.tsx +31 -0
  239. package/src/report/components/attempt-detail/AttemptFixPrompt.tsx +26 -0
  240. package/src/report/components/attempt-detail/AttemptSource.tsx +80 -0
  241. package/src/report/components/attempt-detail/AttemptSummary.tsx +74 -0
  242. package/src/report/components/attempt-detail/AttemptTimeline.tsx +108 -0
  243. package/src/report/components/attempt-detail/AttemptTrace.tsx +63 -0
  244. package/src/report/components/attempt-detail/AttemptUsage.tsx +29 -0
  245. package/src/report/components/attempt-detail/attempt-components.test.tsx +680 -0
  246. package/src/report/components/attempt-detail/compute.ts +287 -0
  247. package/src/report/components/attempt-detail/faces.ts +257 -0
  248. package/src/report/components/attempt-detail/index.tsx +583 -0
  249. package/src/report/components/attempt-detail/validate.test.ts +235 -0
  250. package/src/report/{react → components}/cell.tsx +6 -3
  251. package/src/report/{report.test.ts → components/compute.test.ts} +316 -46
  252. package/src/report/{react → components/entity-lists}/AttemptList.tsx +24 -16
  253. package/src/report/{react → components/entity-lists}/EvalList.tsx +0 -0
  254. package/src/report/{react → components/entity-lists}/ExperimentList.tsx +23 -20
  255. package/src/report/components/entity-lists/compute.ts +196 -0
  256. package/src/report/components/entity-lists/faces.ts +207 -0
  257. package/src/report/components/entity-lists/index.tsx +241 -0
  258. package/src/report/components/entity-lists/validate.test.ts +114 -0
  259. package/src/report/{react → components}/fixtures.ts +2 -2
  260. package/src/report/{react → components/metric-views}/DeltaTable.tsx +5 -5
  261. package/src/report/{react → components/metric-views}/MetricBars.tsx +5 -5
  262. package/src/report/{react → components/metric-views}/MetricLine.tsx +9 -6
  263. package/src/report/{react → components/metric-views}/MetricMatrix.tsx +6 -6
  264. package/src/report/{react → components/metric-views}/MetricScatter.tsx +41 -33
  265. package/src/report/{react → components/metric-views}/MetricTable.tsx +6 -6
  266. package/src/report/{react → components/metric-views}/Scoreboard.tsx +4 -4
  267. package/src/report/{compute.ts → components/metric-views/compute.ts} +51 -477
  268. package/src/report/components/metric-views/faces.ts +423 -0
  269. package/src/report/components/metric-views/index.tsx +352 -0
  270. package/src/report/{text → components/metric-views}/plot.ts +1 -1
  271. package/src/report/components/metric-views/validate.test.ts +211 -0
  272. package/src/report/{react → components}/render.test.tsx +47 -17
  273. package/src/report/components/shared-compute.ts +59 -0
  274. package/src/report/components/shared-faces.ts +30 -0
  275. package/src/report/components/shared.ts +192 -0
  276. package/src/report/{react → components/site-components}/CopyFixPrompt.tsx +3 -3
  277. package/src/report/{react → components/site-components}/HeroCard.tsx +3 -3
  278. package/src/report/{react → components/site-components}/ScopeWarnings.tsx +4 -4
  279. package/src/report/{react → components/site-components}/TraceWaterfall.tsx +13 -10
  280. package/src/report/components/site-components/compute.ts +144 -0
  281. package/src/report/components/site-components/faces.ts +80 -0
  282. package/src/report/components/site-components/index.tsx +265 -0
  283. package/src/report/{scope-warnings.ts → components/site-components/scope-warnings.ts} +3 -3
  284. package/src/report/{site-components.test.tsx → components/site-components/site-components.test.tsx} +23 -21
  285. package/src/report/components/site-components/validate.test.ts +89 -0
  286. package/src/report/{react → components/summaries}/ScopeSummary.tsx +26 -11
  287. package/src/report/components/summaries/compute.ts +56 -0
  288. package/src/report/components/summaries/faces.ts +48 -0
  289. package/src/report/components/summaries/index.tsx +127 -0
  290. package/src/report/components/summaries/validate.test.ts +49 -0
  291. package/src/report/definition/grid-layout.test.ts +124 -0
  292. package/src/report/definition/grid-layout.ts +146 -0
  293. package/src/report/{primitives.tsx → definition/primitives.tsx} +168 -18
  294. package/src/report/{report.ts → definition/report.ts} +98 -165
  295. package/src/report/{shell-head.test.ts → definition/shell-head.test.ts} +2 -2
  296. package/src/report/{text/table.ts → definition/table-text.ts} +4 -4
  297. package/src/report/{tree.ts → definition/tree.ts} +58 -22
  298. package/src/report/index.ts +126 -66
  299. package/src/report/{aggregate.ts → model/aggregate.ts} +31 -9
  300. package/src/report/{flag.ts → model/flag.ts} +39 -1
  301. package/src/report/{format.ts → model/format.ts} +59 -5
  302. package/src/report/{locale.ts → model/locale.ts} +34 -36
  303. package/src/report/{metrics.ts → model/metrics.ts} +3 -3
  304. package/src/report/{types.ts → model/types.ts} +143 -22
  305. package/src/report/react/index.tsx +52 -26
  306. package/src/report/{dual-render.test.tsx → runtime/dual-render.test.tsx} +564 -49
  307. package/src/report/runtime/host.test.ts +44 -0
  308. package/src/report/runtime/host.ts +138 -0
  309. package/src/report/{load.ts → runtime/load.ts} +1 -1
  310. package/src/report/runtime/text.ts +192 -0
  311. package/src/report/runtime/web.ts +106 -0
  312. package/src/results/attempt-evidence.ts +5 -1
  313. package/src/results/host-equivalence.test.ts +54 -3
  314. package/src/results/select.ts +30 -6
  315. package/src/runner/attempt.test.ts +48 -1
  316. package/src/runner/attempt.ts +62 -42
  317. package/src/runner/cleanup-timeout.test.ts +16 -0
  318. package/src/runner/cleanup-timeout.ts +23 -0
  319. package/src/runner/discover.ts +1 -2
  320. package/src/runner/eval-selection.test.ts +187 -0
  321. package/src/runner/eval-selection.ts +109 -0
  322. package/src/runner/experiment-cleanup-registry.test.ts +89 -0
  323. package/src/runner/experiment-cleanup-registry.ts +39 -0
  324. package/src/runner/experiment-labels.test.ts +71 -0
  325. package/src/runner/feedback/agent.test.ts +33 -1
  326. package/src/runner/feedback/agent.ts +12 -16
  327. package/src/runner/feedback/ci.test.ts +35 -1
  328. package/src/runner/feedback/ci.ts +16 -18
  329. package/src/runner/feedback/coordinator.ts +28 -0
  330. package/src/runner/feedback/human.test.ts +108 -1
  331. package/src/runner/feedback/human.ts +71 -21
  332. package/src/runner/feedback/reducer.test.ts +64 -0
  333. package/src/runner/feedback/reducer.ts +26 -0
  334. package/src/runner/feedback/sink.ts +40 -1
  335. package/src/runner/fingerprint.ts +2 -1
  336. package/src/runner/report.test.ts +14 -0
  337. package/src/runner/report.ts +3 -1
  338. package/src/runner/run.test.ts +390 -12
  339. package/src/runner/run.ts +259 -26
  340. package/src/runner/sandbox-selection.test.ts +13 -3
  341. package/src/runner/sandbox-selection.ts +4 -2
  342. package/src/runner/types.ts +158 -23
  343. package/src/sandbox/docker.ts +7 -0
  344. package/src/sandbox/e2b.ts +12 -13
  345. package/src/sandbox/types.ts +23 -21
  346. package/src/scoring/display.test.ts +28 -2
  347. package/src/scoring/display.ts +38 -5
  348. package/src/scoring/types.ts +3 -3
  349. package/src/shared/aggregate.test.ts +52 -0
  350. package/src/shared/aggregate.ts +30 -1
  351. package/src/shared/types.ts +6 -1
  352. package/src/show/command.test.ts +34 -0
  353. package/src/show/command.ts +16 -0
  354. package/src/show/compose.ts +1 -1
  355. package/src/show/index.ts +58 -22
  356. package/src/show/render.ts +16 -240
  357. package/src/show/show.test.ts +128 -35
  358. package/src/view/app/App.test.tsx +8 -7
  359. package/src/view/app/App.tsx +135 -51
  360. package/src/view/app/i18n.ts +7 -147
  361. package/src/view/app/lib/attempt-dialog.test.ts +65 -0
  362. package/src/view/app/lib/attempt-dialog.ts +69 -0
  363. package/src/view/app/main.tsx +0 -1
  364. package/src/view/app/types.ts +3 -86
  365. package/src/view/artifact-serving.test.ts +22 -38
  366. package/src/view/client-dist/app.css +1 -1
  367. package/src/view/client-dist/app.js +15 -22
  368. package/src/view/data.test.ts +48 -36
  369. package/src/view/data.ts +122 -89
  370. package/src/view/index.ts +3 -13
  371. package/src/view/server.ts +36 -6
  372. package/src/view/shared/types.ts +6 -31
  373. package/src/view/site-parity.test.ts +24 -1
  374. package/src/view/site.ts +144 -17
  375. package/src/view/styles.css +18 -806
  376. package/src/view/view-report.test.ts +182 -34
  377. package/dist/report/components.d.ts +0 -179
  378. package/dist/report/components.js +0 -544
  379. package/dist/report/compute.d.ts +0 -139
  380. package/dist/report/react/ExperimentComparison.d.ts +0 -10
  381. package/dist/report/react/ExperimentComparison.js +0 -12
  382. package/dist/report/react/MetricScatter.d.ts +0 -9
  383. package/dist/report/react/format.d.ts +0 -3
  384. package/dist/report/react/format.js +0 -7
  385. package/dist/report/text/faces.d.ts +0 -45
  386. package/dist/report/text/faces.js +0 -671
  387. package/dist/report/web.d.ts +0 -32
  388. package/dist/report/web.js +0 -48
  389. package/docs-site/zh/how-to/authoring.mdx +0 -162
  390. package/docs-site/zh/how-to/custom-reports.mdx +0 -414
  391. package/docs-site/zh/how-to/experiments.mdx +0 -86
  392. package/docs-site/zh/how-to/write-experiment.mdx +0 -164
  393. package/src/report/components.tsx +0 -925
  394. package/src/report/react/ExperimentComparison.tsx +0 -73
  395. package/src/report/react/format.ts +0 -9
  396. package/src/report/text/faces.ts +0 -767
  397. package/src/report/web.ts +0 -77
  398. package/src/show/report-host.test.ts +0 -205
  399. package/src/show/report-host.ts +0 -389
  400. package/src/view/app/components/AttemptModal.tsx +0 -496
  401. package/src/view/app/components/CodeView.test.tsx +0 -142
  402. package/src/view/app/components/CodeView.tsx +0 -310
  403. package/src/view/app/components/CopyControls.tsx +0 -106
  404. package/src/view/app/components/Trace.tsx +0 -100
  405. package/src/view/app/components/Transcript.tsx +0 -157
  406. package/src/view/app/components/ui/badge.tsx +0 -21
  407. package/src/view/app/lib/artifact-url.ts +0 -17
  408. package/src/view/app/lib/attempt-route.test.ts +0 -80
  409. package/src/view/app/lib/attempt-route.ts +0 -52
  410. package/src/view/app/lib/format.ts +0 -70
  411. package/src/view/app/lib/guards.test.ts +0 -108
  412. package/src/view/app/lib/guards.ts +0 -71
  413. package/src/view/app/lib/rows.ts +0 -22
  414. package/src/view/app/lib/transcript-data.tsx +0 -151
  415. package/src/view/app/lib/verdict.ts +0 -23
  416. package/src/view/app/shared.ts +0 -8
  417. /package/dist/report/{react → components/metric-views}/chart-math.d.ts +0 -0
  418. /package/dist/report/{react → components/metric-views}/chart-math.js +0 -0
  419. /package/dist/report/{text → components/metric-views}/plot.d.ts +0 -0
  420. /package/dist/report/{react → components/site-components}/PoweredBy.d.ts +0 -0
  421. /package/dist/report/{react → components/site-components}/PoweredBy.js +0 -0
  422. /package/dist/report/{text/layout.d.ts → model/text-layout.d.ts} +0 -0
  423. /package/dist/report/{text/layout.js → model/text-layout.js} +0 -0
  424. /package/dist/report/{types.js → model/types.js} +0 -0
  425. /package/src/report/{react → components/metric-views}/chart-math.test.ts +0 -0
  426. /package/src/report/{react → components/metric-views}/chart-math.ts +0 -0
  427. /package/src/report/{react → components/site-components}/PoweredBy.tsx +0 -0
  428. /package/src/report/{text/layout.ts → model/text-layout.ts} +0 -0
@@ -0,0 +1,273 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { arrayProblem, cellProblem, isLocalizedText, isObject, makeDataComponent, hrefOf, } from "../shared.js";
3
+ import { deltaTableData, metricLineData, metricMatrixData, metricScatterData, metricTableData, scoreboardData, } from "./compute.js";
4
+ import { barsText, deltaText, lineText, matrixText, scatterText, scoreboardText, tableText } from "./faces.js";
5
+ import { MetricTable as MetricTableWeb } from "./MetricTable.js";
6
+ import { MetricMatrix as MetricMatrixWeb } from "./MetricMatrix.js";
7
+ import { MetricBars as MetricBarsWeb } from "./MetricBars.js";
8
+ import { Scoreboard as ScoreboardWeb } from "./Scoreboard.js";
9
+ import { MetricScatter as MetricScatterWeb } from "./MetricScatter.js";
10
+ import { MetricLine as MetricLineWeb } from "./MetricLine.js";
11
+ import { DeltaTable as DeltaTableWeb } from "./DeltaTable.js";
12
+ /** columns / metric / x / y 共用的 MetricColumn 形状(src/report/model/types.ts)。 */
13
+ function metricColumnProblem(value, path) {
14
+ if (!isObject(value))
15
+ return `"${path}" must be a MetricColumn { key, label }`;
16
+ if (typeof value.key !== "string")
17
+ return `"${path}.key" must be a string`;
18
+ if (!isLocalizedText(value.label))
19
+ return `"${path}.label" must be a LocalizedText`;
20
+ return null;
21
+ }
22
+ export const validateTableData = (data) => {
23
+ if (!isObject(data))
24
+ return "expected an object";
25
+ if (typeof data.rowDimension !== "string")
26
+ return 'missing "rowDimension" (string)';
27
+ const columnsProblem = arrayProblem(data.columns, "columns", metricColumnProblem);
28
+ if (columnsProblem !== null)
29
+ return columnsProblem;
30
+ return arrayProblem(data.rows, "rows", (row, path) => {
31
+ if (!isObject(row))
32
+ return `"${path}" must be an object`;
33
+ if (typeof row.key !== "string")
34
+ return `"${path}.key" must be a string`;
35
+ if (!isObject(row.cells))
36
+ return `"${path}.cells" must be an object`;
37
+ for (const [metricKey, cell] of Object.entries(row.cells)) {
38
+ const problem = cellProblem(cell, `${path}.cells.${metricKey}`);
39
+ if (problem !== null)
40
+ return problem;
41
+ }
42
+ return null;
43
+ });
44
+ };
45
+ export const validateMatrixData = (data) => {
46
+ if (!isObject(data))
47
+ return "expected an object";
48
+ if (typeof data.rowDimension !== "string" || typeof data.columnDimension !== "string") {
49
+ return 'missing "rowDimension" / "columnDimension" (string)';
50
+ }
51
+ const metricProblem = metricColumnProblem(data.metric, "metric");
52
+ if (metricProblem !== null)
53
+ return metricProblem;
54
+ return arrayProblem(data.cells, "cells", (item, path) => {
55
+ if (!isObject(item))
56
+ return `"${path}" must be an object`;
57
+ if (typeof item.row !== "string" || typeof item.column !== "string") {
58
+ return `"${path}.row" / "${path}.column" must be strings`;
59
+ }
60
+ return cellProblem(item.cell, `${path}.cell`);
61
+ });
62
+ };
63
+ export const validateScatterData = (data) => {
64
+ if (!isObject(data))
65
+ return "expected an object";
66
+ if (typeof data.pointDimension !== "string")
67
+ return 'missing "pointDimension" (string)';
68
+ const xColumnProblem = metricColumnProblem(data.x, "x");
69
+ if (xColumnProblem !== null)
70
+ return xColumnProblem;
71
+ const yColumnProblem = metricColumnProblem(data.y, "y");
72
+ if (yColumnProblem !== null)
73
+ return yColumnProblem;
74
+ return arrayProblem(data.rows, "rows", (row, path) => {
75
+ if (!isObject(row) || typeof row.key !== "string")
76
+ return `"${path}" must be an object with a string "key"`;
77
+ const xProblem = cellProblem(row.x, `${path}.x`);
78
+ if (xProblem !== null)
79
+ return xProblem;
80
+ return cellProblem(row.y, `${path}.y`);
81
+ });
82
+ };
83
+ export const validateLineData = (data) => {
84
+ if (!isObject(data))
85
+ return "expected an object";
86
+ if (!isObject(data.x) || typeof data.x.key !== "string" || !isLocalizedText(data.x.label)) {
87
+ return '"x" must be an axis descriptor { key, label }';
88
+ }
89
+ const yColumnProblem = metricColumnProblem(data.y, "y");
90
+ if (yColumnProblem !== null)
91
+ return yColumnProblem;
92
+ return arrayProblem(data.rows, "rows", (row, path) => {
93
+ if (!isObject(row) || typeof row.key !== "string")
94
+ return `"${path}" must be an object with a string "key"`;
95
+ if (!(row.x === null || typeof row.x === "number"))
96
+ return `"${path}.x" must be a number or null`;
97
+ if (!isLocalizedText(row.xDisplay))
98
+ return `"${path}.xDisplay" must be a LocalizedText`;
99
+ return cellProblem(row.y, `${path}.y`);
100
+ });
101
+ };
102
+ function scoreTotalProblem(value, path) {
103
+ if (!isObject(value))
104
+ return `"${path}" must be an object { value, display, notRun, unscorable, refs }`;
105
+ if (typeof value.value !== "number")
106
+ return `"${path}.value" must be a number`;
107
+ if (!isLocalizedText(value.display))
108
+ return `"${path}.display" must be a LocalizedText`;
109
+ if (typeof value.notRun !== "number")
110
+ return `"${path}.notRun" must be a number`;
111
+ if (typeof value.unscorable !== "number")
112
+ return `"${path}.unscorable" must be a number`;
113
+ if (!Array.isArray(value.refs))
114
+ return `"${path}.refs" must be an array`;
115
+ return null;
116
+ }
117
+ export const validateScoreboardData = (data) => {
118
+ if (!isObject(data))
119
+ return "expected an object";
120
+ if (typeof data.rowDimension !== "string")
121
+ return 'missing "rowDimension" (string)';
122
+ if (!Array.isArray(data.questions))
123
+ return 'missing "questions" (array)';
124
+ if (typeof data.fullMarks !== "number")
125
+ return 'missing "fullMarks" (number)';
126
+ if (typeof data.ignoredEvals !== "number")
127
+ return 'missing "ignoredEvals" (number)';
128
+ return arrayProblem(data.rows, "rows", (row, path) => {
129
+ if (!isObject(row) || typeof row.key !== "string")
130
+ return `"${path}" must be an object with a string "key"`;
131
+ const totalProblem = scoreTotalProblem(row.total, `${path}.total`);
132
+ if (totalProblem !== null)
133
+ return totalProblem;
134
+ return arrayProblem(row.subjects, `${path}.subjects`, (subject, subjectPath) => {
135
+ if (!isObject(subject) || typeof subject.key !== "string") {
136
+ return `"${subjectPath}" must be an object with a string "key"`;
137
+ }
138
+ if (typeof subject.earned !== "number")
139
+ return `"${subjectPath}.earned" must be a number`;
140
+ if (typeof subject.possible !== "number")
141
+ return `"${subjectPath}.possible" must be a number`;
142
+ if (typeof subject.questions !== "number")
143
+ return `"${subjectPath}.questions" must be a number`;
144
+ if (typeof subject.notRun !== "number")
145
+ return `"${subjectPath}.notRun" must be a number`;
146
+ if (typeof subject.unscorable !== "number")
147
+ return `"${subjectPath}.unscorable" must be a number`;
148
+ if (!isLocalizedText(subject.display))
149
+ return `"${subjectPath}.display" must be a LocalizedText`;
150
+ if (!Array.isArray(subject.refs))
151
+ return `"${subjectPath}.refs" must be an array`;
152
+ return null;
153
+ });
154
+ });
155
+ };
156
+ const DELTA_OUTCOMES = ["improved", "regressed", "unchanged", "unavailable"];
157
+ export const validateDeltaData = (data) => {
158
+ if (!isObject(data))
159
+ return "expected an object";
160
+ if (typeof data.byDimension !== "string")
161
+ return 'missing "byDimension" (string)';
162
+ const columnsProblem = arrayProblem(data.columns, "columns", metricColumnProblem);
163
+ if (columnsProblem !== null)
164
+ return columnsProblem;
165
+ return arrayProblem(data.rows, "rows", (row, path) => {
166
+ if (!isObject(row) || typeof row.key !== "string")
167
+ return `"${path}" must be an object with a string "key"`;
168
+ if (!isLocalizedText(row.label))
169
+ return `"${path}.label" must be a LocalizedText`;
170
+ if (!isObject(row.a) || typeof row.a.key !== "string")
171
+ return `"${path}.a" must be an object with a string "key"`;
172
+ if (!isObject(row.b) || typeof row.b.key !== "string")
173
+ return `"${path}.b" must be an object with a string "key"`;
174
+ if (!isObject(row.cells))
175
+ return `"${path}.cells" must be an object`;
176
+ for (const [metricKey, cell] of Object.entries(row.cells)) {
177
+ const cellPath = `${path}.cells.${metricKey}`;
178
+ if (!isObject(cell))
179
+ return `"${cellPath}" must be an object { a, b, delta, display, outcome }`;
180
+ const aProblem = cellProblem(cell.a, `${cellPath}.a`);
181
+ if (aProblem !== null)
182
+ return aProblem;
183
+ const bProblem = cellProblem(cell.b, `${cellPath}.b`);
184
+ if (bProblem !== null)
185
+ return bProblem;
186
+ if (!(cell.delta === null || typeof cell.delta === "number"))
187
+ return `"${cellPath}.delta" must be a number or null`;
188
+ if (!isLocalizedText(cell.display))
189
+ return `"${cellPath}.display" must be a LocalizedText`;
190
+ if (typeof cell.outcome !== "string" || !DELTA_OUTCOMES.includes(cell.outcome)) {
191
+ return `"${cellPath}.outcome" must be one of ${JSON.stringify(DELTA_OUTCOMES)}`;
192
+ }
193
+ }
194
+ return null;
195
+ });
196
+ };
197
+ /** 榜单:一行一个维度值、一列一个指标,回答「谁整体更好」。 */
198
+ export const MetricTable = makeDataComponent({
199
+ name: "MetricTable",
200
+ dataFnName: "metricTableData",
201
+ shapeName: "TableData",
202
+ dataFn: metricTableData,
203
+ specKeys: ["rows", "columns", "sort", "evals"],
204
+ validate: validateTableData,
205
+ web: (props, ctx) => (_jsx(MetricTableWeb, { data: props.data, filter: props.filter, locale: props.locale ?? ctx.locale, attemptHref: hrefOf(props, ctx), className: props.className })),
206
+ text: (props, ctx) => tableText(props.data, ctx),
207
+ });
208
+ /** 逐题格子:行 × 列两个维度、格子里一个指标,回答「哪道题谁挂了」。 */
209
+ export const MetricMatrix = makeDataComponent({
210
+ name: "MetricMatrix",
211
+ dataFnName: "metricMatrixData",
212
+ shapeName: "MatrixData",
213
+ dataFn: metricMatrixData,
214
+ specKeys: ["rows", "columns", "cell", "evals"],
215
+ validate: validateMatrixData,
216
+ web: (props, ctx) => (_jsx(MetricMatrixWeb, { data: props.data, locale: props.locale ?? ctx.locale, attemptHref: hrefOf(props, ctx), className: props.className })),
217
+ text: (props, ctx) => matrixText(props.data, ctx),
218
+ });
219
+ /** 分组条形:同一份矩阵数据的另一种摆法;与 MetricMatrix 写同一份 spec 时只计算一次。 */
220
+ export const MetricBars = makeDataComponent({
221
+ name: "MetricBars",
222
+ dataFnName: "metricMatrixData",
223
+ shapeName: "MatrixData",
224
+ dataFn: metricMatrixData,
225
+ specKeys: ["rows", "columns", "cell", "evals"],
226
+ validate: validateMatrixData,
227
+ web: (props, ctx) => (_jsx(MetricBarsWeb, { data: props.data, locale: props.locale ?? ctx.locale, attemptHref: hrefOf(props, ctx), className: props.className })),
228
+ text: (props, ctx) => barsText(props.data, ctx),
229
+ });
230
+ /** 考试成绩单:总分 + 分科小计,固定分母、notRun / unscorable 分开如实报。 */
231
+ export const Scoreboard = makeDataComponent({
232
+ name: "Scoreboard",
233
+ dataFnName: "scoreboardData",
234
+ shapeName: "ScoreboardData",
235
+ dataFn: scoreboardData,
236
+ specKeys: ["rows", "questions", "subject", "weights", "fullMarks", "score"],
237
+ validate: validateScoreboardData,
238
+ web: (props, ctx) => _jsx(ScoreboardWeb, { data: props.data, locale: props.locale ?? ctx.locale, className: props.className }),
239
+ text: (props, ctx) => scoreboardText(props.data, ctx),
240
+ });
241
+ /** 两个指标之间的取舍:每个点一个维度值,x / y 各一个指标,series 决定颜色和图例归类。 */
242
+ export const MetricScatter = makeDataComponent({
243
+ name: "MetricScatter",
244
+ dataFnName: "metricScatterData",
245
+ shapeName: "ScatterData",
246
+ dataFn: metricScatterData,
247
+ specKeys: ["points", "series", "x", "y", "evals"],
248
+ validate: validateScatterData,
249
+ web: (props, ctx) => (_jsx(MetricScatterWeb, { data: props.data, connect: props.connect, pointHref: props.pointHref, locale: props.locale ?? ctx.locale, className: props.className })),
250
+ text: (props, ctx) => scatterText(props.data, ctx, { connect: props.connect }),
251
+ });
252
+ /** 趋势线:x 是 NumericAxis(参数轴),点身份 = (series, x)。 */
253
+ export const MetricLine = makeDataComponent({
254
+ name: "MetricLine",
255
+ dataFnName: "metricLineData",
256
+ shapeName: "LineData",
257
+ dataFn: metricLineData,
258
+ specKeys: ["x", "series", "y", "evals"],
259
+ validate: validateLineData,
260
+ web: (props, ctx) => (_jsx(MetricLineWeb, { data: props.data, pointHref: props.pointHref, locale: props.locale ?? ctx.locale, className: props.className })),
261
+ text: (props, ctx) => lineText(props.data, ctx),
262
+ });
263
+ /** 成对对比:每行一对(字面声明或 pairsByFlag 派生),格子里 A、B、Δ 三个值。 */
264
+ export const DeltaTable = makeDataComponent({
265
+ name: "DeltaTable",
266
+ dataFnName: "deltaTableData",
267
+ shapeName: "DeltaData",
268
+ dataFn: deltaTableData,
269
+ specKeys: ["by", "pairs", "metrics", "evals"],
270
+ validate: validateDeltaData,
271
+ web: (props, ctx) => _jsx(DeltaTableWeb, { data: props.data, locale: props.locale ?? ctx.locale, className: props.className }),
272
+ text: (props, ctx) => deltaText(props.data, ctx),
273
+ });
@@ -1,7 +1,7 @@
1
1
  // text 面的字符坐标图:MetricScatter / MetricLine 共用。
2
2
  // 形态照 docs-site/zh/reference/report-components.mdx 的示例块:y 轴刻度 + │ 边框,
3
3
  // 点用字母标注、图例列在图下;lower-better 的轴反向,「好」的角落恒在右上。
4
- import { padDisplay, padStartDisplay, stringWidth } from "./layout.js";
4
+ import { padDisplay, padStartDisplay, stringWidth } from "../../model/text-layout.js";
5
5
  function makeScale(values, cells, invert) {
6
6
  const lo = Math.min(...values);
7
7
  const hi = Math.max(...values);
@@ -0,0 +1,22 @@
1
+ import type { Snapshot } from "../../results/types.ts";
2
+ import { type Item } from "../model/aggregate.ts";
3
+ import type { VerdictTally } from "../model/types.ts";
4
+ /**
5
+ * 每个 experiment 只保留自己 `selectedEvalIds` 内的 eval/attempt——两个 experiment 声明不同
6
+ * eval 集时各自只统计自己选中的那部分,未选择的 eval(即使恰好在同一次运行里跑过)不进
7
+ * 分母、不污染另一个 experiment。第三方快照缺该字段时 `selectedEvalIdsOf` 退化为其实际
8
+ * evals,过滤天然是 no-op。宿主注入的 current() Scope 在合成时已按这条规则收窄,这里对
9
+ * 真实 Scope 是幂等的;只对作者手工拼的 Snapshot[] 真正生效。`experimentListData` /
10
+ * `scopeSummaryData` / `metricScatterData` 共用同一条规则,保证经 `ExperimentComparison`
11
+ * 展开后收到的 spec 与直接调用同一份 input 深相等。
12
+ */
13
+ export declare function selectedEvalsOnly(snapshots: readonly Snapshot[]): readonly Snapshot[];
14
+ export declare function tallyOf(): VerdictTally;
15
+ /** 一批 Item 的组级统计(experimentListData / scopeSummaryData 共用)。 */
16
+ export declare function summarizeItems(items: Item[]): {
17
+ experiments: number;
18
+ evals: number;
19
+ attempts: number;
20
+ verdicts: VerdictTally;
21
+ lastRunAt: string | undefined;
22
+ };
@@ -0,0 +1,47 @@
1
+ // 跨组件族共用的计算辅助:summaries(scopeSummaryData)、entity-lists(experimentListData)、
2
+ // metric-views(metricScatterData)三族的 *Data 函数共用同一条 selectedEvalIds 投影规则与
3
+ // 同一套组级统计折叠,住在这里而不是任一族自己的 compute.ts,避免三份重复实现分叉。
4
+ import { selectedEvalIdsOf } from "../../results/select.js";
5
+ import { evalLevelStats } from "../../shared/verdict.js";
6
+ import { experimentIdOf, fullEvalKey } from "../model/aggregate.js";
7
+ /**
8
+ * 每个 experiment 只保留自己 `selectedEvalIds` 内的 eval/attempt——两个 experiment 声明不同
9
+ * eval 集时各自只统计自己选中的那部分,未选择的 eval(即使恰好在同一次运行里跑过)不进
10
+ * 分母、不污染另一个 experiment。第三方快照缺该字段时 `selectedEvalIdsOf` 退化为其实际
11
+ * evals,过滤天然是 no-op。宿主注入的 current() Scope 在合成时已按这条规则收窄,这里对
12
+ * 真实 Scope 是幂等的;只对作者手工拼的 Snapshot[] 真正生效。`experimentListData` /
13
+ * `scopeSummaryData` / `metricScatterData` 共用同一条规则,保证经 `ExperimentComparison`
14
+ * 展开后收到的 spec 与直接调用同一份 input 深相等。
15
+ */
16
+ export function selectedEvalsOnly(snapshots) {
17
+ return snapshots.map((snapshot) => {
18
+ const selected = new Set(selectedEvalIdsOf(snapshot));
19
+ const evals = snapshot.evals.filter((ev) => selected.has(ev.id));
20
+ if (evals.length === snapshot.evals.length)
21
+ return snapshot;
22
+ return { ...snapshot, evals, attempts: evals.flatMap((ev) => ev.attempts) };
23
+ });
24
+ }
25
+ export function tallyOf() {
26
+ return { passed: 0, failed: 0, errored: 0, skipped: 0 };
27
+ }
28
+ /** 一批 Item 的组级统计(experimentListData / scopeSummaryData 共用)。 */
29
+ export function summarizeItems(items) {
30
+ const experimentIds = new Set();
31
+ for (const item of items)
32
+ experimentIds.add(experimentIdOf(item));
33
+ const stats = evalLevelStats(items.map((item) => ({ verdict: item.attempt.result.verdict, key: fullEvalKey(item) })), (r) => r.key);
34
+ let lastRunAt;
35
+ for (const item of items) {
36
+ const startedAt = item.snapshot.startedAt;
37
+ if (lastRunAt === undefined || startedAt > lastRunAt)
38
+ lastRunAt = startedAt;
39
+ }
40
+ return {
41
+ experiments: experimentIds.size,
42
+ evals: stats.evals,
43
+ attempts: items.length,
44
+ verdicts: { passed: stats.passed, failed: stats.failed, errored: stats.errored, skipped: stats.skipped },
45
+ lastRunAt,
46
+ };
47
+ }
@@ -0,0 +1,9 @@
1
+ import type { MetricCell, VerdictTally } from "../model/types.ts";
2
+ import { type ReportLocale } from "../model/locale.ts";
3
+ export declare const MISSING_MARK = "\u2014";
4
+ /** 缺数据文案随 locale(en = "no data")。 */
5
+ export declare function missingText(locale: ReportLocale): string;
6
+ /** 格子的文本形态:缺数据 —,覆盖不全带 samples/total 角标;display 按 locale 解析。 */
7
+ export declare function cellText(cell: MetricCell, locale: ReportLocale): string;
8
+ /** verdict 计票的紧凑文案("3 passed · 1 failed"):非零判定逐个列,全部为零如实 —。 */
9
+ export declare function verdictTallyText(tally: VerdictTally, locale: ReportLocale): string;
@@ -0,0 +1,26 @@
1
+ // 跨组件族共用的 text 面辅助:MetricCell / VerdictTally 的文本渲染在 summaries、
2
+ // entity-lists、metric-views 三族的 text 面里都要用到,住在这里而不是任一族自己的
3
+ // faces.ts,避免三份重复实现分叉(与 ../cell.tsx 的 MetricCellView 是同一份契约的
4
+ // text/web 两面)。
5
+ import { localeText, resolveLocalizedText } from "../model/locale.js";
6
+ export const MISSING_MARK = "—";
7
+ /** 缺数据文案随 locale(en = "no data")。 */
8
+ export function missingText(locale) {
9
+ return localeText(locale, "cell.missing");
10
+ }
11
+ /** 格子的文本形态:缺数据 —,覆盖不全带 samples/total 角标;display 按 locale 解析。 */
12
+ export function cellText(cell, locale) {
13
+ if (cell.value === null)
14
+ return MISSING_MARK;
15
+ const display = resolveLocalizedText(cell.display, locale);
16
+ return cell.samples < cell.total ? `${display} ${cell.samples}/${cell.total}` : display;
17
+ }
18
+ /** verdict 计票的紧凑文案("3 passed · 1 failed"):非零判定逐个列,全部为零如实 —。 */
19
+ export function verdictTallyText(tally, locale) {
20
+ const parts = [];
21
+ for (const kind of ["passed", "failed", "errored", "skipped"]) {
22
+ if (tally[kind] > 0)
23
+ parts.push(`${tally[kind]} ${localeText(locale, `verdict.${kind}`)}`);
24
+ }
25
+ return parts.length > 0 ? parts.join(" · ") : MISSING_MARK;
26
+ }
@@ -0,0 +1,68 @@
1
+ import type { ReactNode } from "react";
2
+ import { type ReportComponent, type TextContext, type WebContext } from "../definition/tree.ts";
3
+ import type { ReportInput } from "../model/types.ts";
4
+ import type { ReportLocale } from "../model/locale.ts";
5
+ import type { AttemptLocator } from "../../results/locator.ts";
6
+ /** 拼 class 名:过滤空值,末尾接使用者透传的 className。 */
7
+ export declare function cx(...parts: (string | undefined | false)[]): string;
8
+ type Never<T> = {
9
+ [K in keyof T]?: never;
10
+ };
11
+ /**
12
+ * 官方数据组件的统一 props 组合(docs/feature/reports/library/metric-views.md):
13
+ * data 形态(接收配套 *Data 的产物)或 spec 形态(Options 平铺 + 可选 input)。
14
+ */
15
+ export type DataProps<Data, Options, Presentation> = ({
16
+ data: Data;
17
+ input?: never;
18
+ } & Never<Options> & Presentation) | ({
19
+ data?: never;
20
+ input?: ReportInput;
21
+ } & Options & Presentation);
22
+ export declare function isObject(value: unknown): value is Record<string, unknown>;
23
+ /** LocalizedText = string | Record<string, string>(src/shared/types.ts)。 */
24
+ export declare function isLocalizedText(value: unknown): boolean;
25
+ /**
26
+ * 字段路径前缀的结构校验原语:通过为 `null`,否则给出带完整字段路径的具体问题
27
+ * (如 `"rows[2].cells.costUSD.samples" must be a number`)。每个族的 validate*Data
28
+ * 用这些原语递归拼自己的形状,不重新发明逐字段判断。
29
+ */
30
+ export declare function cellProblem(value: unknown, path: string): string | null;
31
+ export declare function isCell(value: unknown): boolean;
32
+ /** 四态 tally { passed, failed, errored, skipped } 的字段路径前缀校验。 */
33
+ export declare function tallyProblem(value: unknown, path: string): string | null;
34
+ export declare function isTally(value: unknown): boolean;
35
+ /** 数组的逐项校验:每项跑 `itemCheck(item, "path[i]")`,第一个非 null 问题即返回。 */
36
+ export declare function arrayProblem(value: unknown, path: string, itemCheck: (item: unknown, itemPath: string) => string | null): string | null;
37
+ export type Validator = (data: unknown) => string | null;
38
+ export declare function dataShapeError(component: string, dataFnName: string, shape: string, problem: string): Error;
39
+ export interface DataComponentDef<Data, Options, Presentation> {
40
+ name: string;
41
+ dataFnName: string;
42
+ shapeName: string;
43
+ dataFn: (input: ReportInput, options: Options) => Promise<Data>;
44
+ /** spec 形态的计算选项 prop 名(不含 input);未列出的 props 视为呈现选项原样保留。 */
45
+ specKeys: readonly string[];
46
+ validate: Validator;
47
+ web(props: {
48
+ data: Data;
49
+ } & Presentation, ctx: WebContext): ReactNode;
50
+ text(props: {
51
+ data: Data;
52
+ } & Presentation, ctx: TextContext): string;
53
+ }
54
+ export declare function makeDataComponent<Data, Options, Presentation>(def: DataComponentDef<Data, Options, Presentation>): ReportComponent<DataProps<Data, Options, Presentation>>;
55
+ /**
56
+ * 缺省接证据室,显式 prop 覆盖。`ctx.attemptHref` 本身已经是「有没有」的完整信号——
57
+ * 宿主外直接渲染、或宿主内但当前 definition 没有 attempt-input page 时它就是 undefined,
58
+ * 不需要再判断是否在宿主里。
59
+ */
60
+ export declare function hrefOf(props: {
61
+ attemptHref?: (locator: AttemptLocator) => string;
62
+ }, ctx: WebContext): ((locator: AttemptLocator) => string) | undefined;
63
+ export interface ChromeProps {
64
+ /** chrome 文案 locale;省略时随宿主上下文(宿主外默认 "en")。 */
65
+ locale?: ReportLocale;
66
+ className?: string;
67
+ }
68
+ export {};
@@ -0,0 +1,125 @@
1
+ // 官方双面组件的共用装配机制:spec / data 双形态判别(DataProps)、data 结构校验的通用
2
+ // 原语(isObject / isLocalizedText / isCell / isTally / cellProblem / tallyProblem /
3
+ // arrayProblem / dataShapeError)、`makeDataComponent` 装配器与 `hrefOf` 证据室深链解析、
4
+ // `ChromeProps` 呈现选项基类、`cx` classname 拼接——每个组件族在自己的 index.tsx 里用这些
5
+ // 原语递归拼自己的 validate*Data(字段路径要覆盖到嵌套 MetricCell/Tally,不只顶层哨兵),
6
+ // 具体的 validate*Data 与组件导出留在各族。
7
+ import { defineComponent, memoFetchOf, } from "../definition/tree.js";
8
+ /** 拼 class 名:过滤空值,末尾接使用者透传的 className。 */
9
+ export function cx(...parts) {
10
+ return parts.filter(Boolean).join(" ");
11
+ }
12
+ // ───────────────────────── data 结构校验(版本漂移防线)─────────────────────────
13
+ export function isObject(value) {
14
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15
+ }
16
+ /** LocalizedText = string | Record<string, string>(src/shared/types.ts)。 */
17
+ export function isLocalizedText(value) {
18
+ if (typeof value === "string")
19
+ return true;
20
+ return isObject(value) && Object.values(value).every((entry) => typeof entry === "string");
21
+ }
22
+ /**
23
+ * 字段路径前缀的结构校验原语:通过为 `null`,否则给出带完整字段路径的具体问题
24
+ * (如 `"rows[2].cells.costUSD.samples" must be a number`)。每个族的 validate*Data
25
+ * 用这些原语递归拼自己的形状,不重新发明逐字段判断。
26
+ */
27
+ export function cellProblem(value, path) {
28
+ if (!isObject(value))
29
+ return `"${path}" must be a MetricCell { value, display, samples, total, refs }`;
30
+ if (!(value.value === null || typeof value.value === "number"))
31
+ return `"${path}.value" must be a number or null`;
32
+ if (!isLocalizedText(value.display))
33
+ return `"${path}.display" must be a LocalizedText`;
34
+ if (typeof value.samples !== "number")
35
+ return `"${path}.samples" must be a number`;
36
+ if (typeof value.total !== "number")
37
+ return `"${path}.total" must be a number`;
38
+ if (!Array.isArray(value.refs) || !value.refs.every((ref) => typeof ref === "string")) {
39
+ return `"${path}.refs" must be an array of locator strings`;
40
+ }
41
+ return null;
42
+ }
43
+ export function isCell(value) {
44
+ return cellProblem(value, "cell") === null;
45
+ }
46
+ /** 四态 tally { passed, failed, errored, skipped } 的字段路径前缀校验。 */
47
+ export function tallyProblem(value, path) {
48
+ if (!isObject(value))
49
+ return `"${path}" must be a tally { passed, failed, errored, skipped }`;
50
+ for (const key of ["passed", "failed", "errored", "skipped"]) {
51
+ if (typeof value[key] !== "number")
52
+ return `"${path}.${key}" must be a number`;
53
+ }
54
+ return null;
55
+ }
56
+ export function isTally(value) {
57
+ return tallyProblem(value, "tally") === null;
58
+ }
59
+ /** 数组的逐项校验:每项跑 `itemCheck(item, "path[i]")`,第一个非 null 问题即返回。 */
60
+ export function arrayProblem(value, path, itemCheck) {
61
+ if (!Array.isArray(value))
62
+ return `"${path}" must be an array`;
63
+ for (let i = 0; i < value.length; i++) {
64
+ const problem = itemCheck(value[i], `${path}[${i}]`);
65
+ if (problem !== null)
66
+ return problem;
67
+ }
68
+ return null;
69
+ }
70
+ export function dataShapeError(component, dataFnName, shape, problem) {
71
+ return new Error(`<${component}> received data that does not match the current ${shape} shape: ${problem}. ` +
72
+ `It may have been computed by a different niceeval version (component data carries no schemaVersion; the support window is same-version write and read). ` +
73
+ `Recompute it with ${dataFnName}() from this niceeval version, then re-render.`);
74
+ }
75
+ export function makeDataComponent(def) {
76
+ const assertData = (data) => {
77
+ const problem = def.validate(data);
78
+ if (problem !== null)
79
+ throw dataShapeError(def.name, def.dataFnName, def.shapeName, problem);
80
+ return data;
81
+ };
82
+ const resolve = async (props, ctx) => {
83
+ const givenSpec = def.specKeys.filter((key) => props[key] !== undefined);
84
+ if (props.data !== undefined) {
85
+ if (givenSpec.length > 0 || props.input !== undefined) {
86
+ const extras = [...givenSpec, ...(props.input !== undefined ? ["input"] : [])];
87
+ throw new Error(`<${def.name}> got both \`data\` and spec field${extras.length > 1 ? "s" : ""} (${extras.join(", ")}) — the two data sources are exclusive and niceeval will not silently pick one. ` +
88
+ `Keep \`data\` (precomputed with ${def.dataFnName}()) and drop the spec fields, or drop \`data\` and let the pipeline compute from the spec.`);
89
+ }
90
+ assertData(props.data);
91
+ return props;
92
+ }
93
+ const options = {};
94
+ for (const key of givenSpec)
95
+ options[key] = props[key];
96
+ const input = props.input ?? ctx.input;
97
+ const data = await memoFetchOf(ctx)(def.dataFn, input, options, () => def.dataFn(input, options));
98
+ const rest = { ...props };
99
+ delete rest.input;
100
+ for (const key of def.specKeys)
101
+ delete rest[key];
102
+ return { ...rest, data };
103
+ };
104
+ const component = defineComponent({
105
+ resolve,
106
+ web: (props, ctx) => {
107
+ assertData(props.data);
108
+ return def.web(props, ctx);
109
+ },
110
+ text: (props, ctx) => {
111
+ assertData(props.data);
112
+ return def.text(props, ctx);
113
+ },
114
+ });
115
+ component.displayName = def.name;
116
+ return component;
117
+ }
118
+ /**
119
+ * 缺省接证据室,显式 prop 覆盖。`ctx.attemptHref` 本身已经是「有没有」的完整信号——
120
+ * 宿主外直接渲染、或宿主内但当前 definition 没有 attempt-input page 时它就是 undefined,
121
+ * 不需要再判断是否在宿主里。
122
+ */
123
+ export function hrefOf(props, ctx) {
124
+ return props.attemptHref ?? ctx.attemptHref;
125
+ }
@@ -1,6 +1,6 @@
1
1
  import type { ReactElement } from "react";
2
- import type { CopyFixPromptData } from "../types.ts";
3
- import { type ReportLocale } from "../locale.ts";
2
+ import type { CopyFixPromptData } from "../../model/types.ts";
3
+ import { type ReportLocale } from "../../model/locale.ts";
4
4
  /**
5
5
  * 批量修复 prompt(纯 web 渲染面):折叠块内完整 prompt 文本 + 复制按钮(增强层)。
6
6
  * 嵌入自有 React 页面时配合 `copyFixPromptData()` 使用;failures 为 0 返回 null。
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { DEFAULT_REPORT_LOCALE, countText, localeText } from "../locale.js";
3
- import { cx } from "./format.js";
2
+ import { DEFAULT_REPORT_LOCALE, countText, localeText } from "../../model/locale.js";
3
+ import { cx } from "../shared.js";
4
4
  /**
5
5
  * 批量修复 prompt(纯 web 渲染面):折叠块内完整 prompt 文本 + 复制按钮(增强层)。
6
6
  * 嵌入自有 React 页面时配合 `copyFixPromptData()` 使用;failures 为 0 返回 null。
@@ -1,6 +1,6 @@
1
1
  import type { ReactElement } from "react";
2
- import type { HeroData } from "../types.ts";
3
- import { type LocalizedText, type ReportLocale } from "../locale.ts";
2
+ import type { HeroData } from "../../model/types.ts";
3
+ import { type LocalizedText, type ReportLocale } from "../../model/locale.ts";
4
4
  /**
5
5
  * 站点标题区(纯 web 渲染面):`<h1>` 标题 + meta 行 + 品牌行。
6
6
  * 嵌入自有 React 页面时配合 `heroData()` 使用。
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { DEFAULT_REPORT_LOCALE, localeText, resolveLocalizedText } from "../locale.js";
3
- import { cx } from "./format.js";
2
+ import { DEFAULT_REPORT_LOCALE, localeText, resolveLocalizedText } from "../../model/locale.js";
3
+ import { cx } from "../shared.js";
4
4
  import { PoweredBy } from "./PoweredBy.js";
5
5
  /** ISO 时间 → 按 locale 的「最后运行」显示(年月日 时:分);不可解析原样返回。 */
6
6
  function formatLastRun(iso, locale) {
@@ -1,6 +1,6 @@
1
1
  import type { ReactElement } from "react";
2
- import type { ScopeWarning } from "../types.ts";
3
- import { type ReportLocale } from "../locale.ts";
2
+ import type { ScopeWarning } from "../../model/types.ts";
3
+ import { type ReportLocale } from "../../model/locale.ts";
4
4
  /**
5
5
  * 选择警告区(纯 web 渲染面):按动作聚合的警告组。嵌入自有 React 页面时传
6
6
  * `data={scope.warnings}`;空集返回 null,不渲染空容器。
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { DEFAULT_REPORT_LOCALE } from "../locale.js";
3
- import { groupScopeWarnings, warningDetailsLabel } from "../scope-warnings.js";
4
- import { cx } from "./format.js";
2
+ import { DEFAULT_REPORT_LOCALE } from "../../model/locale.js";
3
+ import { groupScopeWarnings, warningDetailsLabel } from "./scope-warnings.js";
4
+ import { cx } from "../shared.js";
5
5
  /** 命令的可复制块:无 JS 时点击全选(user-select: all),enhance.js 在场时点击复制。 */
6
6
  function CommandBlock({ command }) {
7
7
  return (_jsx("code", { className: "nre-warning-command", "data-nre-copy": command, children: command }));