niceeval 0.3.0 → 0.4.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 (101) hide show
  1. package/README.zh.md +11 -0
  2. package/docs/README.md +2 -1
  3. package/docs/adapters/collection.md +1 -1
  4. package/docs/adapters/contract.md +6 -5
  5. package/docs/adapters/reference/claude-code-otel-telemetry.md +1 -1
  6. package/docs/assertions.md +0 -1
  7. package/docs/capabilities-by-construction.md +2 -2
  8. package/docs/cli.md +2 -2
  9. package/docs/concepts.md +2 -2
  10. package/docs/e2e-ci.md +135 -98
  11. package/docs/eval-authoring.md +3 -3
  12. package/docs/experiments.md +7 -4
  13. package/docs/observability.md +9 -5
  14. package/docs/origin-integration.md +18 -18
  15. package/docs/references.md +1 -1
  16. package/docs/reports.md +551 -0
  17. package/docs/results-format.md +3 -3
  18. package/docs/results-lib.md +191 -0
  19. package/docs/runner.md +1 -1
  20. package/docs/sandbox.md +1 -1
  21. package/docs/source-map.md +22 -2
  22. package/docs/tier-sync.md +74 -58
  23. package/docs/view.md +39 -5
  24. package/package.json +29 -3
  25. package/src/agents/ai-sdk.ts +3 -0
  26. package/src/agents/claude-code.ts +18 -1
  27. package/src/agents/codex.ts +3 -2
  28. package/src/agents/index.ts +16 -0
  29. package/src/agents/openai-compat.test.ts +56 -0
  30. package/src/agents/openai-compat.ts +151 -0
  31. package/src/agents/types.ts +3 -1
  32. package/src/cli.ts +3 -2
  33. package/src/context/context.test.ts +78 -1
  34. package/src/context/context.ts +19 -11
  35. package/src/context/session.ts +2 -0
  36. package/src/context/types.ts +11 -5
  37. package/src/i18n/en.ts +3 -4
  38. package/src/i18n/zh-CN.ts +2 -3
  39. package/src/o11y/cost.test.ts +40 -0
  40. package/src/o11y/cost.ts +27 -5
  41. package/src/o11y/derive.ts +2 -2
  42. package/src/o11y/otlp/mappers/claude-code.test.ts +31 -0
  43. package/src/o11y/otlp/mappers/claude-code.ts +24 -0
  44. package/src/o11y/otlp/mappers/index.ts +1 -0
  45. package/src/o11y/otlp/parse.test.ts +127 -0
  46. package/src/o11y/prices.json +176 -119
  47. package/src/o11y/types.ts +2 -1
  48. package/src/report/aggregate.ts +215 -0
  49. package/src/report/compute.ts +405 -0
  50. package/src/report/format.ts +47 -0
  51. package/src/report/index.ts +40 -0
  52. package/src/report/metrics.ts +96 -0
  53. package/src/report/react/CaseList.tsx +70 -0
  54. package/src/report/react/DeltaTable.tsx +79 -0
  55. package/src/report/react/MetricMatrix.tsx +75 -0
  56. package/src/report/react/MetricScatter.tsx +217 -0
  57. package/src/report/react/MetricTable.tsx +71 -0
  58. package/src/report/react/RunOverview.tsx +87 -0
  59. package/src/report/react/Scoreboard.tsx +87 -0
  60. package/src/report/react/cell.tsx +49 -0
  61. package/src/report/react/colors.ts +42 -0
  62. package/src/report/react/data.ts +17 -0
  63. package/src/report/react/fixtures.ts +236 -0
  64. package/src/report/react/format.ts +34 -0
  65. package/src/report/react/index.tsx +34 -0
  66. package/src/report/react/render.test.tsx +303 -0
  67. package/src/report/react/styles.css +302 -0
  68. package/src/report/report.test.ts +667 -0
  69. package/src/report/types.ts +210 -0
  70. package/src/results/copy.ts +200 -0
  71. package/src/results/format.ts +75 -0
  72. package/src/results/index.ts +27 -0
  73. package/src/results/open.ts +207 -0
  74. package/src/results/results.test.ts +359 -0
  75. package/src/results/select.ts +101 -0
  76. package/src/results/types.ts +98 -0
  77. package/src/runner/attempt.ts +3 -1
  78. package/src/runner/report.test.ts +111 -0
  79. package/src/runner/report.ts +52 -1
  80. package/src/runner/reporters/artifacts.ts +1 -1
  81. package/src/runner/reporters/braintrust.test.ts +106 -0
  82. package/src/runner/reporters/braintrust.ts +197 -0
  83. package/src/runner/reporters/index.ts +3 -1
  84. package/src/runner/run.ts +30 -19
  85. package/src/runner/types.ts +17 -0
  86. package/src/sandbox/types.ts +3 -3
  87. package/src/view/app/App.tsx +82 -17
  88. package/src/view/app/components/CostScoreChart.tsx +127 -0
  89. package/src/view/app/i18n.ts +10 -1
  90. package/src/view/app/lib/attempt-route.test.ts +89 -0
  91. package/src/view/app/lib/attempt-route.ts +52 -0
  92. package/src/view/app/lib/chart.ts +72 -0
  93. package/src/view/app/lib/outcome.ts +1 -1
  94. package/src/view/app/types.ts +4 -5
  95. package/src/view/client-dist/app.css +1 -1
  96. package/src/view/client-dist/app.js +18 -18
  97. package/src/view/index.ts +12 -18
  98. package/src/view/loader.ts +14 -7
  99. package/src/view/shared/types.ts +14 -1
  100. package/src/view/styles.css +43 -0
  101. package/src/view/template.html +0 -1
@@ -0,0 +1,303 @@
1
+ // 「不 hydrate 也完整」的验收测试:每个组件过 renderToStaticMarkup,
2
+ // 断言纯静态 HTML 里就有全部关键内容——数字、覆盖率角标、缺数据文案、
3
+ // 散点的 SVG 与系列名、truncated 行、attemptHref 链接。
4
+ // 另外锁两条契约:源码零 hooks;维度键跨组件同色。
5
+
6
+ import { readFileSync, readdirSync } from "node:fs";
7
+ import { dirname, join } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ import { renderToStaticMarkup } from "react-dom/server";
10
+ import { describe, expect, it } from "vitest";
11
+
12
+ import {
13
+ CaseList,
14
+ DeltaTable,
15
+ MetricMatrix,
16
+ MetricScatter,
17
+ MetricTable,
18
+ RunOverview,
19
+ Scoreboard,
20
+ } from "./index.tsx";
21
+ import { NRE_PALETTE, colorClassForKey, colorIndexForKey } from "./colors.ts";
22
+ import {
23
+ caseListData,
24
+ deltaData,
25
+ matrixData,
26
+ overviewData,
27
+ overviewWithCost,
28
+ scatterData,
29
+ scoreboardData,
30
+ tableData,
31
+ } from "./fixtures.ts";
32
+
33
+ const attemptHref = (ref: { run: string; result: number }) => `/attempts/${ref.run}/${ref.result}`;
34
+
35
+ describe("RunOverview", () => {
36
+ const html = renderToStaticMarkup(<RunOverview data={overviewData} />);
37
+
38
+ it("KPI 条:快照数、题数、attempts、通过率、耗时", () => {
39
+ expect(html).toContain("nre-overview");
40
+ expect(html).toContain("<dd>12</dd>"); // 题目
41
+ expect(html).toContain("<dd>48</dd>"); // attempts
42
+ expect(html).toContain("78%"); // 36 / (36+8+2),skipped 不进分母
43
+ expect(html).toContain("4m 21s"); // 261000ms
44
+ });
45
+
46
+ it("costUSD 全缺 = null:显示缺数据,不编 $0", () => {
47
+ expect(html).toContain("no data");
48
+ expect(html).not.toContain("$0");
49
+ const withCost = renderToStaticMarkup(<RunOverview data={overviewWithCost} />);
50
+ expect(withCost).toContain("$1.23");
51
+ });
52
+
53
+ it("数据来源与 warnings 直接渲染在条内", () => {
54
+ expect(html).toContain("2 snapshots");
55
+ expect(html).toContain("compare/bub");
56
+ expect(html).toContain("2026-07-01T10:00:00Z");
57
+ expect(html).toContain("compare/bub 快照缺 3 个 eval 的结果");
58
+ expect(html).toContain("nre-warnings");
59
+ });
60
+ });
61
+
62
+ describe("MetricTable", () => {
63
+ const html = renderToStaticMarkup(<MetricTable data={tableData} attemptHref={attemptHref} />);
64
+
65
+ it("按传入顺序渲染行,不重排(排序在数据侧)", () => {
66
+ // fixture 里 codex(50%)在 bub(87%)前面,输出必须保持
67
+ expect(html.indexOf(">codex<")).toBeGreaterThan(-1);
68
+ expect(html.indexOf(">codex<")).toBeLessThan(html.indexOf(">bub<"));
69
+ });
70
+
71
+ it("列头带 label、unit 与 better 方向", () => {
72
+ expect(html).toContain("通过率");
73
+ expect(html).toContain("(%)");
74
+ expect(html).toContain("代码行数");
75
+ expect(html).toContain("↑");
76
+ expect(html).toContain("↓");
77
+ });
78
+
79
+ it("数字与覆盖率角标:samples < total 如实标出", () => {
80
+ expect(html).toContain("87%");
81
+ expect(html).toContain("120 lines");
82
+ expect(html).toMatch(/<sup class="nre-coverage"[^>]*>5\/6<\/sup>/);
83
+ });
84
+
85
+ it("全 null 渲染缺数据文案,绝不画 0", () => {
86
+ expect(html).toContain("no data");
87
+ expect(html).toContain("nre-cell-missing");
88
+ });
89
+
90
+ it("refs + attemptHref 出普通 <a>", () => {
91
+ expect(html).toContain('href="/attempts/run-a/0"');
92
+ });
93
+ });
94
+
95
+ describe("MetricMatrix", () => {
96
+ const html = renderToStaticMarkup(<MetricMatrix data={matrixData} attemptHref={attemptHref} />);
97
+
98
+ it("caption 标出指标与行列维度", () => {
99
+ expect(html).toContain("通过率");
100
+ expect(html).toContain("eval × agent");
101
+ });
102
+
103
+ it("稀疏格子:没有样本的格子空着(恰好一个)", () => {
104
+ expect(html.match(/nre-td-empty/g)).toHaveLength(1);
105
+ });
106
+
107
+ it("格子数字与 refs 下钻链接", () => {
108
+ expect(html).toContain("100%");
109
+ expect(html).toContain("0%");
110
+ expect(html).toContain('href="/attempts/run-b/3"');
111
+ expect(html).toContain('href="/attempts/run-b/7"');
112
+ });
113
+
114
+ it("列头(维度键)带稳定散列配色 class", () => {
115
+ expect(html).toContain(colorClassForKey("bub"));
116
+ expect(html).toContain(colorClassForKey("codex"));
117
+ });
118
+ });
119
+
120
+ describe("Scoreboard", () => {
121
+ const html = renderToStaticMarkup(<Scoreboard data={scoreboardData} />);
122
+
123
+ it("总分 + 满分口径", () => {
124
+ expect(html).toContain("78.5");
125
+ expect(html).toContain("52");
126
+ expect(html).toContain("/ 100");
127
+ });
128
+
129
+ it("分科小计 earned/possible", () => {
130
+ expect(html).toContain("14/16");
131
+ expect(html).toContain("3/4");
132
+ });
133
+
134
+ it("missing 注脚:没跑按 0 计的题数如实展示", () => {
135
+ expect(html).toContain("1 eval missing, scored 0");
136
+ expect(html).toContain("2 evals missing, scored 0");
137
+ });
138
+
139
+ it("实际生效的权重表可审计", () => {
140
+ expect(html).toContain("algebra/ ×2");
141
+ expect(html).toContain("others ×1");
142
+ });
143
+ });
144
+
145
+ describe("MetricScatter", () => {
146
+ const html = renderToStaticMarkup(<MetricScatter data={scatterData} pointHref={(row) => `/exp/${row.key}`} />);
147
+
148
+ const cxOf = (key: string): number => {
149
+ const m = html.match(new RegExp(`data-key="${key}"[^>]*\\bcx="([\\d.]+)"`));
150
+ expect(m, `circle for ${key}`).toBeTruthy();
151
+ return Number(m![1]);
152
+ };
153
+ const cyOf = (key: string): number => {
154
+ const m = html.match(new RegExp(`data-key="${key}"[^>]*\\bcy="([\\d.]+)"`));
155
+ expect(m, `circle for ${key}`).toBeTruthy();
156
+ return Number(m![1]);
157
+ };
158
+
159
+ it("内联 SVG + 轴标签", () => {
160
+ expect(html).toContain("<svg");
161
+ expect(html).toContain("成本($)");
162
+ expect(html).toContain("通过率(%)");
163
+ });
164
+
165
+ it("better:lower 的 x 轴反向:便宜($5)在贵($10)右边,好的角落恒在右上", () => {
166
+ expect(cxOf("compare/bub-low")).toBeGreaterThan(cxOf("compare/bub-high"));
167
+ // y 轴 better:higher:通过率高(90%)在低(50%)上方(SVG y 向下增长)
168
+ expect(cyOf("compare/bub-high")).toBeLessThan(cyOf("compare/bub-low"));
169
+ });
170
+
171
+ it("同系列点连线并标系列名,线色来自稳定散列调色板", () => {
172
+ expect(html).toContain("<polyline");
173
+ expect(html).toContain(">bub</text>");
174
+ expect(html).toContain(NRE_PALETTE[colorIndexForKey("bub")]);
175
+ // codex 只有 1 个可画点:不出线,但系列名仍标出
176
+ expect(html).toContain(">codex</text>");
177
+ });
178
+
179
+ it("null 点不画,底部注脚如实报数", () => {
180
+ expect(html).not.toContain('data-key="compare/codex-broken"');
181
+ expect(html).toContain("1 point missing data");
182
+ });
183
+
184
+ it("hover 退化为 <title>:display 与 samples/total", () => {
185
+ expect(html).toContain("<title>");
186
+ expect(html).toContain("50%(6/6)");
187
+ });
188
+
189
+ it("pointHref:点包普通 <a>", () => {
190
+ expect(html).toContain('href="/exp/compare/bub-low"');
191
+ });
192
+
193
+ it("全部点都缺数据时不画空坐标系", () => {
194
+ const empty = renderToStaticMarkup(
195
+ <MetricScatter
196
+ data={{
197
+ ...scatterData,
198
+ rows: scatterData.rows.map((r) => ({ ...r, x: { ...r.x, value: null } })),
199
+ }}
200
+ />,
201
+ );
202
+ expect(empty).not.toContain("<svg");
203
+ expect(empty).toContain("no data");
204
+ expect(empty).toContain("4 points missing data");
205
+ });
206
+ });
207
+
208
+ describe("DeltaTable", () => {
209
+ const html = renderToStaticMarkup(<DeltaTable data={deltaData} />);
210
+
211
+ it("每格 A/B/Δ 三值", () => {
212
+ expect(html).toContain("50%");
213
+ expect(html).toContain("62%");
214
+ expect(html).toContain("+12pp");
215
+ expect(html).toContain(">A</span>");
216
+ expect(html).toContain(">B</span>");
217
+ expect(html).toContain(">Δ</span>");
218
+ });
219
+
220
+ it("涨跌好坏按 better 配色:通过率涨=好,成本涨=坏,0=平", () => {
221
+ expect(html).toContain("nre-delta-good");
222
+ expect(html).toContain("nre-delta-bad");
223
+ expect(html).toContain("nre-delta-flat");
224
+ });
225
+
226
+ it("任一侧缺数据:Δ 显示为缺,不硬算", () => {
227
+ expect(html).toContain("nre-delta-missing");
228
+ expect(html).toContain("no data");
229
+ });
230
+
231
+ it("每行标出 A → B 的 experimentId", () => {
232
+ expect(html).toContain("compare/bub → compare/bub--agents-md");
233
+ });
234
+ });
235
+
236
+ describe("CaseList", () => {
237
+ const html = renderToStaticMarkup(<CaseList data={caseListData} attemptHref={attemptHref} />);
238
+
239
+ it("逐条失败断言:name、score、detail、evidence", () => {
240
+ expect(html).toContain("roots-correct");
241
+ expect(html).toContain("score 0");
242
+ expect(html).toContain("期望 x=2,得到 x=3");
243
+ expect(html).toContain("judge: 求根公式代入时符号写反");
244
+ });
245
+
246
+ it("errored 的 error 摘要", () => {
247
+ expect(html).toContain("TypeError: cannot read properties of undefined");
248
+ });
249
+
250
+ it("truncated 如实报「还有 n 条没列」", () => {
251
+ expect(html).toContain("and 2 more not shown");
252
+ });
253
+
254
+ it("每条案例带 attemptHref 下钻链接", () => {
255
+ expect(html).toContain('href="/attempts/run-a/4"');
256
+ expect(html).toContain('href="/attempts/run-c/1"');
257
+ });
258
+
259
+ it("长文本收进 <details>,零 JS 可展开", () => {
260
+ expect(html).toContain("<details");
261
+ expect(html).toContain("<summary>");
262
+ });
263
+ });
264
+
265
+ describe("跨组件契约", () => {
266
+ it("同一维度键在所有块里同色(稳定散列,与渲染顺序无关)", () => {
267
+ const cls = colorClassForKey("bub");
268
+ const table = renderToStaticMarkup(<MetricTable data={tableData} />);
269
+ const matrix = renderToStaticMarkup(<MetricMatrix data={matrixData} />);
270
+ const board = renderToStaticMarkup(<Scoreboard data={scoreboardData} />);
271
+ const delta = renderToStaticMarkup(<DeltaTable data={deltaData} />);
272
+ const cases = renderToStaticMarkup(<CaseList data={caseListData} />);
273
+ for (const html of [table, matrix, board, delta, cases]) {
274
+ expect(html).toContain(cls);
275
+ }
276
+ const scatter = renderToStaticMarkup(<MetricScatter data={scatterData} />);
277
+ expect(scatter).toContain(NRE_PALETTE[colorIndexForKey("bub")]);
278
+ });
279
+
280
+ it("静态输出不含 <script>:交互只靠 <a>/<details>/CSS", () => {
281
+ const all = [
282
+ renderToStaticMarkup(<RunOverview data={overviewData} />),
283
+ renderToStaticMarkup(<MetricTable data={tableData} attemptHref={attemptHref} />),
284
+ renderToStaticMarkup(<MetricMatrix data={matrixData} attemptHref={attemptHref} />),
285
+ renderToStaticMarkup(<Scoreboard data={scoreboardData} />),
286
+ renderToStaticMarkup(<MetricScatter data={scatterData} />),
287
+ renderToStaticMarkup(<DeltaTable data={deltaData} />),
288
+ renderToStaticMarkup(<CaseList data={caseListData} />),
289
+ ].join("");
290
+ expect(all).not.toContain("<script");
291
+ });
292
+
293
+ it("组件源码零 hooks(本实验的「不 hydrate 也完整」用最笨的方式保证)", () => {
294
+ const dir = dirname(fileURLToPath(import.meta.url));
295
+ const sources = readdirSync(dir)
296
+ .filter((f) => (f.endsWith(".tsx") || f.endsWith(".ts")) && !f.includes(".test."))
297
+ .map((f) => readFileSync(join(dir, f), "utf8"));
298
+ expect(sources.length).toBeGreaterThanOrEqual(12);
299
+ for (const src of sources) {
300
+ expect(src).not.toMatch(/\buse[A-Z][A-Za-z]*\s*\(/);
301
+ }
302
+ });
303
+ });
@@ -0,0 +1,302 @@
1
+ /*
2
+ * niceeval/report/react/styles.css —— 随包发布的默认样式。
3
+ * 契约(docs/reports.md「四条跨组件契约」):
4
+ * - 所有节点都有稳定的 nre-* 类名;这份 CSS + 类名 = 静态页零依赖成立;
5
+ * - 要定制,在本文件之后加载自己的覆盖即可,不需要构建工具;
6
+ * - 调色板 --nre-c0..c7 的值与 src/report/react/colors.ts 的 NRE_PALETTE
7
+ * 逐个对应(SVG 内联 hex 用那份),改色时两边一起改。
8
+ * 视觉刻意极简:结构与诚实细节优先,品牌化交给使用者的覆盖样式。
9
+ */
10
+
11
+ .nre {
12
+ /* 调色板:键 → 稳定散列下标 → 同键同色(colors.ts) */
13
+ --nre-c0: #2563eb;
14
+ --nre-c1: #db2777;
15
+ --nre-c2: #059669;
16
+ --nre-c3: #d97706;
17
+ --nre-c4: #7c3aed;
18
+ --nre-c5: #0891b2;
19
+ --nre-c6: #dc2626;
20
+ --nre-c7: #65a30d;
21
+
22
+ font-family: system-ui, -apple-system, sans-serif;
23
+ font-size: 14px;
24
+ line-height: 1.5;
25
+ color: #171717;
26
+ margin: 1rem 0;
27
+ }
28
+
29
+ /* ---- 维度键上色:同一个 key 在任何块里同色 ---- */
30
+ .nre .nre-c0 { color: var(--nre-c0); }
31
+ .nre .nre-c1 { color: var(--nre-c1); }
32
+ .nre .nre-c2 { color: var(--nre-c2); }
33
+ .nre .nre-c3 { color: var(--nre-c3); }
34
+ .nre .nre-c4 { color: var(--nre-c4); }
35
+ .nre .nre-c5 { color: var(--nre-c5); }
36
+ .nre .nre-c6 { color: var(--nre-c6); }
37
+ .nre .nre-c7 { color: var(--nre-c7); }
38
+ .nre .nre-key { font-weight: 600; }
39
+
40
+ /* ---- 表格通用 ---- */
41
+ table.nre,
42
+ .nre table {
43
+ border-collapse: collapse;
44
+ width: 100%;
45
+ }
46
+ .nre th,
47
+ .nre td {
48
+ border: 1px solid #e5e5e5;
49
+ padding: 0.4rem 0.6rem;
50
+ text-align: left;
51
+ vertical-align: top;
52
+ }
53
+ .nre thead th {
54
+ background: #fafafa;
55
+ font-weight: 600;
56
+ }
57
+ .nre .nre-dimension {
58
+ color: #737373;
59
+ font-weight: 500;
60
+ }
61
+ .nre .nre-unit {
62
+ color: #a3a3a3;
63
+ font-weight: 400;
64
+ margin-left: 0.25em;
65
+ }
66
+ .nre .nre-better {
67
+ color: #a3a3a3;
68
+ margin-left: 0.25em;
69
+ }
70
+
71
+ /* ---- MetricCell:缺数据 / 覆盖率角标 / 证据链接 ---- */
72
+ .nre .nre-missing {
73
+ color: #a3a3a3;
74
+ font-style: italic;
75
+ }
76
+ .nre .nre-coverage {
77
+ color: #b45309;
78
+ font-size: 0.72em;
79
+ margin-left: 0.3em;
80
+ }
81
+ .nre .nre-refs {
82
+ margin-left: 0.4em;
83
+ }
84
+ .nre .nre-ref {
85
+ color: #2563eb;
86
+ font-size: 0.85em;
87
+ text-decoration: none;
88
+ margin-right: 0.25em;
89
+ }
90
+ .nre .nre-ref:hover {
91
+ text-decoration: underline;
92
+ }
93
+ .nre .nre-td-empty {
94
+ background: #fafafa;
95
+ }
96
+
97
+ /* ---- RunOverview ---- */
98
+ .nre-overview .nre-kpis {
99
+ display: flex;
100
+ flex-wrap: wrap;
101
+ gap: 1.5rem;
102
+ margin: 0 0 0.5rem;
103
+ }
104
+ .nre-overview .nre-kpi dt {
105
+ color: #737373;
106
+ font-size: 0.85em;
107
+ }
108
+ .nre-overview .nre-kpi dd {
109
+ margin: 0;
110
+ font-size: 1.4em;
111
+ font-weight: 600;
112
+ }
113
+ .nre-overview .nre-outcomes {
114
+ margin: 0.25rem 0;
115
+ color: #525252;
116
+ }
117
+ .nre-overview .nre-outcome {
118
+ margin-right: 1em;
119
+ }
120
+ .nre .nre-outcome-passed { color: #059669; }
121
+ .nre .nre-outcome-failed { color: #dc2626; }
122
+ .nre .nre-outcome-errored { color: #b45309; }
123
+ .nre .nre-outcome-skipped { color: #a3a3a3; }
124
+ .nre-overview .nre-source {
125
+ color: #737373;
126
+ font-size: 0.85em;
127
+ margin: 0.25rem 0;
128
+ }
129
+ .nre-overview .nre-source-snapshot {
130
+ margin-left: 0.75em;
131
+ }
132
+ .nre .nre-warnings {
133
+ list-style: none;
134
+ margin: 0.5rem 0 0;
135
+ padding: 0.5rem 0.75rem;
136
+ background: #fffbeb;
137
+ border: 1px solid #fde68a;
138
+ color: #92400e;
139
+ }
140
+
141
+ /* ---- MetricMatrix ---- */
142
+ .nre-metric-matrix .nre-matrix-caption {
143
+ caption-side: top;
144
+ text-align: left;
145
+ font-weight: 600;
146
+ padding-bottom: 0.4rem;
147
+ }
148
+ .nre-metric-matrix .nre-matrix-axes {
149
+ color: #a3a3a3;
150
+ font-weight: 400;
151
+ margin-left: 0.75em;
152
+ }
153
+
154
+ /* ---- Scoreboard ---- */
155
+ .nre-scoreboard .nre-total {
156
+ font-weight: 700;
157
+ }
158
+ .nre-scoreboard .nre-full-marks {
159
+ color: #a3a3a3;
160
+ font-weight: 400;
161
+ margin-left: 0.25em;
162
+ }
163
+ .nre-scoreboard .nre-subject-missing {
164
+ display: block;
165
+ color: #b45309;
166
+ font-size: 0.8em;
167
+ }
168
+ .nre-scoreboard .nre-weights {
169
+ color: #737373;
170
+ font-size: 0.85em;
171
+ margin: 0.4rem 0 0;
172
+ }
173
+ .nre-scoreboard .nre-weight,
174
+ .nre-scoreboard .nre-weight-rest {
175
+ margin-left: 0.5em;
176
+ }
177
+
178
+ /* ---- MetricScatter ---- */
179
+ .nre-metric-scatter {
180
+ margin: 1rem 0;
181
+ }
182
+ .nre-metric-scatter .nre-scatter-svg {
183
+ max-width: 640px;
184
+ width: 100%;
185
+ height: auto;
186
+ display: block;
187
+ }
188
+ .nre-metric-scatter .nre-scatter-missing {
189
+ color: #b45309;
190
+ font-size: 0.85em;
191
+ margin: 0.25rem 0 0;
192
+ }
193
+ .nre-metric-scatter .nre-scatter-point-link circle:hover {
194
+ stroke: #171717;
195
+ stroke-width: 1.5;
196
+ }
197
+
198
+ /* ---- DeltaTable ---- */
199
+ .nre-delta-table .nre-pair-ids {
200
+ display: block;
201
+ color: #a3a3a3;
202
+ font-size: 0.8em;
203
+ font-weight: 400;
204
+ }
205
+ .nre-delta-table .nre-delta-side {
206
+ display: block;
207
+ white-space: nowrap;
208
+ }
209
+ .nre-delta-table .nre-delta-tag {
210
+ display: inline-block;
211
+ width: 1.2em;
212
+ color: #a3a3a3;
213
+ font-size: 0.8em;
214
+ }
215
+ .nre .nre-delta-good { color: #059669; }
216
+ .nre .nre-delta-bad { color: #dc2626; }
217
+ .nre .nre-delta-flat { color: #737373; }
218
+ .nre .nre-delta-neutral { color: #171717; }
219
+ .nre .nre-delta-missing { color: #a3a3a3; }
220
+
221
+ /* ---- CaseList ---- */
222
+ .nre-case-list .nre-cases {
223
+ list-style: none;
224
+ margin: 0;
225
+ padding: 0;
226
+ }
227
+ .nre-case-list .nre-case {
228
+ border: 1px solid #e5e5e5;
229
+ border-left-width: 3px;
230
+ padding: 0.6rem 0.75rem;
231
+ margin-bottom: 0.5rem;
232
+ }
233
+ .nre-case-list .nre-case-failed { border-left-color: #dc2626; }
234
+ .nre-case-list .nre-case-errored { border-left-color: #b45309; }
235
+ .nre-case-list .nre-case-head {
236
+ display: flex;
237
+ flex-wrap: wrap;
238
+ gap: 0.75em;
239
+ align-items: baseline;
240
+ }
241
+ .nre-case-list .nre-case-outcome {
242
+ font-weight: 700;
243
+ text-transform: uppercase;
244
+ font-size: 0.8em;
245
+ }
246
+ .nre-case-list .nre-case-eval {
247
+ font-weight: 600;
248
+ }
249
+ .nre-case-list .nre-case-experiment,
250
+ .nre-case-list .nre-case-duration,
251
+ .nre-case-list .nre-case-cost {
252
+ color: #737373;
253
+ font-size: 0.9em;
254
+ }
255
+ .nre-case-list .nre-case-link {
256
+ margin-left: auto;
257
+ color: #2563eb;
258
+ }
259
+ .nre-case-list .nre-case-error {
260
+ margin: 0.4rem 0 0;
261
+ padding: 0.3rem 0.5rem;
262
+ background: #fef2f2;
263
+ color: #991b1b;
264
+ font-family: ui-monospace, monospace;
265
+ font-size: 0.85em;
266
+ white-space: pre-wrap;
267
+ }
268
+ .nre-case-list .nre-assertions {
269
+ margin: 0.4rem 0 0;
270
+ padding-left: 1.25rem;
271
+ }
272
+ .nre-case-list .nre-assertion-name {
273
+ font-family: ui-monospace, monospace;
274
+ }
275
+ .nre-case-list .nre-assertion-score {
276
+ color: #737373;
277
+ margin-left: 0.75em;
278
+ font-size: 0.9em;
279
+ }
280
+ .nre-case-list .nre-assertion-more summary {
281
+ cursor: pointer;
282
+ color: #737373;
283
+ font-size: 0.85em;
284
+ }
285
+ .nre-case-list .nre-assertion-detail {
286
+ margin: 0.25rem 0;
287
+ white-space: pre-wrap;
288
+ }
289
+ .nre-case-list .nre-assertion-evidence {
290
+ margin: 0.25rem 0 0.25rem 0.75rem;
291
+ padding-left: 0.5rem;
292
+ border-left: 2px solid #d4d4d4;
293
+ color: #525252;
294
+ white-space: pre-wrap;
295
+ }
296
+ .nre-case-list .nre-truncated {
297
+ color: #b45309;
298
+ font-size: 0.9em;
299
+ }
300
+ .nre-case-list .nre-case-empty {
301
+ color: #737373;
302
+ }