niceeval 0.9.0 → 0.9.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 (39) hide show
  1. package/dist/i18n/zh-CN.d.ts +1 -1
  2. package/dist/report/report.js +2 -2
  3. package/dist/report/tree.d.ts +1 -1
  4. package/dist/report/tree.js +1 -1
  5. package/dist/results/select.d.ts +2 -2
  6. package/dist/results/select.js +1 -1
  7. package/dist/shared/aggregate.d.ts +1 -1
  8. package/dist/shared/aggregate.js +1 -1
  9. package/docs-site/zh/how-to/custom-reports.mdx +1 -1
  10. package/docs-site/zh/how-to/experiments.mdx +2 -2
  11. package/docs-site/zh/how-to/publish-report.mdx +6 -0
  12. package/docs-site/zh/how-to/viewing-results.mdx +14 -5
  13. package/docs-site/zh/reference/cli.mdx +2 -2
  14. package/docs-site/zh/troubleshooting/debugging.mdx +3 -3
  15. package/package.json +1 -1
  16. package/src/cli.ts +7 -8
  17. package/src/i18n/en.ts +3 -3
  18. package/src/i18n/zh-CN.ts +3 -3
  19. package/src/report/dual-render.test.tsx +2 -2
  20. package/src/report/react/styles.css +12 -6
  21. package/src/report/report.ts +2 -2
  22. package/src/report/tree.ts +2 -2
  23. package/src/results/host-equivalence.test.ts +1 -1
  24. package/src/results/select.ts +2 -2
  25. package/src/shared/aggregate.ts +1 -1
  26. package/src/show/index.ts +2 -2
  27. package/src/show/render.ts +1 -1
  28. package/src/show/report-host.test.ts +2 -2
  29. package/src/show/report-host.ts +1 -1
  30. package/src/show/show.test.ts +1 -1
  31. package/src/view/app/App.test.tsx +7 -6
  32. package/src/view/app/App.tsx +18 -6
  33. package/src/view/client-dist/app.css +1 -1
  34. package/src/view/client-dist/app.js +1 -1
  35. package/src/view/data.ts +20 -9
  36. package/src/view/index.ts +2 -12
  37. package/src/view/server.ts +4 -4
  38. package/src/view/styles.css +18 -1
  39. package/src/view/view-report.test.ts +44 -15
@@ -54,7 +54,7 @@ export declare const zhCN: {
54
54
  readonly "cli.show.noResults": "{{root}} 下没有结果。先 `niceeval exp` 跑一轮,再 `niceeval show`。\n";
55
55
  readonly "cli.show.runDirMissing": "Results directory not found: {{dir}}\n";
56
56
  readonly "cli.show.noEvalMatch": "No results matched: {{patterns}}. Evals with results: {{evals}}\n";
57
- readonly "cli.show.noExperimentMatch": "No experiment matched --experiment {{arg}}. Experiments with results: {{experiments}}\n";
57
+ readonly "cli.show.noExperimentMatch": "No experiment matched --exp {{arg}}. Experiments with results: {{experiments}}\n";
58
58
  readonly "cli.show.historyReportConflict": "`--history` and `--report` are mutually exclusive: both take over the main output. --history is the host's per-attempt execution timeline; for snapshot-level trends, compose exp.snapshots inside your report file instead.\n";
59
59
  readonly "cli.show.evidenceNeedsEval": "--source / --execution / --diff show one attempt's evidence, but the selection matched {{matched}} evals. Pick an attempt locator from the index below:\n{{index}}\n";
60
60
  readonly "cli.show.locatorMalformed": "{{message}}\n";
@@ -334,10 +334,10 @@ export function reportTitleText(definition, scope, locale) {
334
334
  function quoteArg(value) {
335
335
  return /^[A-Za-z0-9._/@-]+$/.test(value) ? value : `'${value.replaceAll("'", `'"'"'`)}'`;
336
336
  }
337
- /** 按上下文拼组索引的可复制命令:`niceeval show <patterns> --experiment <id> [--results/--report/--page]`。 */
337
+ /** 按上下文拼组索引的可复制命令:`niceeval show <patterns> --exp <id> [--results/--report/--page]`。 */
338
338
  function experimentCommandFor(ctx) {
339
339
  return (prefix) => {
340
- const parts = ["niceeval show", ...ctx.patterns.map(quoteArg), `--experiment ${quoteArg(prefix)}`];
340
+ const parts = ["niceeval show", ...ctx.patterns.map(quoteArg), `--exp ${quoteArg(prefix)}`];
341
341
  if (ctx.results !== undefined)
342
342
  parts.push(`--results ${quoteArg(ctx.results)}`);
343
343
  if (ctx.report !== undefined)
@@ -38,7 +38,7 @@ export interface TextContext {
38
38
  attemptCommand(locator: AttemptLocator): string;
39
39
  /**
40
40
  * 组索引一类「按实验收窄」命令的生成;宿主注入以携带完整上下文(--results / --report /
41
- * --page 与位置参数),默认 `niceeval show --experiment <id>`。非契约字段,官方组件内部用。
41
+ * --page 与位置参数),默认 `niceeval show --exp <id>`。非契约字段,官方组件内部用。
42
42
  */
43
43
  experimentCommand(experimentIdPrefix: string): string;
44
44
  }
@@ -351,7 +351,7 @@ export function createTextContext(options) {
351
351
  // 默认下钻命令:AttemptLocator 是 `@` 前缀的不透明短串,`niceeval show @<locator>` 是
352
352
  // show 已实现的真实 CLI 语法,不需要反查 eval id 再拼一条近似命令。
353
353
  const attemptCommand = options?.attemptCommand ?? ((locator) => `niceeval show ${locator}`);
354
- const experimentCommand = options?.experimentCommand ?? ((prefix) => `niceeval show --experiment ${shellQuote(prefix)}`);
354
+ const experimentCommand = options?.experimentCommand ?? ((prefix) => `niceeval show --exp ${shellQuote(prefix)}`);
355
355
  const make = (w) => ({
356
356
  width: w,
357
357
  locale,
@@ -11,7 +11,7 @@ export declare function selectLatest(results: Pick<Results, "experiments" | "ski
11
11
  }): Scope;
12
12
  /** selectCurrentResults 的范围输入:experiment id 前缀与 eval id 前缀,都可缺省。 */
13
13
  export interface ResultScope {
14
- /** experiment id 前缀(--experiment),分段匹配语义同 filterExperiments。 */
14
+ /** experiment id 前缀(--exp),分段匹配语义同 filterExperiments。 */
15
15
  experiment?: string | string[];
16
16
  /** eval id 前缀(位置参数),收窄 Scope 覆盖的 eval;覆盖警告分母同步收窄到范围内。 */
17
17
  patterns?: string[];
@@ -68,7 +68,7 @@ export declare function dedupeAttempts(attempts: AttemptHandle[]): {
68
68
  };
69
69
  /** 快照新旧比较:startedAt 优先,同刻按快照目录名(时间戳 + 随机后缀,字典序即时序)。 */
70
70
  export declare function isNewerSnapshot(a: Snapshot, b: Snapshot): boolean;
71
- /** experiment id 分段前缀过滤(--experiment / latest({ experiments }) 同一语义);包内使用,不进公共 barrel。 */
71
+ /** experiment id 分段前缀过滤(--exp / latest({ experiments }) 同一语义);包内使用,不进公共 barrel。 */
72
72
  export declare function filterExperiments(experiments: Experiment[], filter?: string | string[]): Experiment[];
73
73
  /**
74
74
  * stale 警告的人话时距:选粒度最大的单位,四舍五入。结构化形态是单源——message 的英文时距
@@ -312,7 +312,7 @@ export function isNewerSnapshot(a, b) {
312
312
  return byStart > 0;
313
313
  return a.dir.localeCompare(b.dir) > 0;
314
314
  }
315
- /** experiment id 分段前缀过滤(--experiment / latest({ experiments }) 同一语义);包内使用,不进公共 barrel。 */
315
+ /** experiment id 分段前缀过滤(--exp / latest({ experiments }) 同一语义);包内使用,不进公共 barrel。 */
316
316
  export function filterExperiments(experiments, filter) {
317
317
  if (filter === undefined)
318
318
  return experiments;
@@ -18,7 +18,7 @@ export declare function experimentGroupOf(experimentId: string): string | undefi
18
18
  /**
19
19
  * eval id 前缀过滤,同 CLI 位置参数语义(docs/feature/reports/show.md「打开与收窄」):
20
20
  * eval 位置参数是收窄过滤,按**裸前缀宽松匹配**——"algebra" 命中 "algebra"、"algebra/..."
21
- * 也命中 "algebra2",多命中正是它的用途(与 `--experiment` 的按路径段匹配有意不同)。
21
+ * 也命中 "algebra2",多命中正是它的用途(与 `--exp` 的按路径段匹配有意不同)。
22
22
  */
23
23
  export declare function evalPrefixPredicate(evals?: string | string[]): (id: string) => boolean;
24
24
  /** 无 experimentId 时的兜底标签。 */
@@ -38,7 +38,7 @@ export function experimentGroupOf(experimentId) {
38
38
  /**
39
39
  * eval id 前缀过滤,同 CLI 位置参数语义(docs/feature/reports/show.md「打开与收窄」):
40
40
  * eval 位置参数是收窄过滤,按**裸前缀宽松匹配**——"algebra" 命中 "algebra"、"algebra/..."
41
- * 也命中 "algebra2",多命中正是它的用途(与 `--experiment` 的按路径段匹配有意不同)。
41
+ * 也命中 "algebra2",多命中正是它的用途(与 `--exp` 的按路径段匹配有意不同)。
42
42
  */
43
43
  export function evalPrefixPredicate(evals) {
44
44
  if (evals === undefined)
@@ -81,7 +81,7 @@ niceeval view --report reports/exam.tsx # 网页:同一棵树走网页面
81
81
 
82
82
  挑选提醒(覆盖不全、快照过期、有没跑完的运行、读不出来的快照)由选择警告(`ScopeWarnings`)组件显示:提醒按实验归组,组头列出实验名、问题标签和一条可复制的重跑命令,每条提醒的原文收在组内可展开的折叠块里(提醒少时默认展开),读不出来的快照单独归成一组。默认报告每一页都摆了它;自己的报告想显示提醒,就在页首摆一个 `<ScopeWarnings />`——不摆就不显示,数字是否附带完整性提醒由你对读者负责。
83
83
 
84
- 命令行的范围先作用在挑选上,报告拿到的就是收窄到这个范围后的 `selection`:位置参数的 eval id 前缀收窄 Selection 覆盖的 eval(覆盖提醒的分母同样收窄到范围内),`--results` 把结果根换成指定目录,`--experiment` 让 Selection 只留该实验。`--history` 与 `--report` 互斥——趋势在报告里用 `exp.snapshots` 自己摆;证据切面(`--eval` / `--execution` / `--diff`)只看证据,不渲染报告。
84
+ 命令行的范围先作用在挑选上,报告拿到的就是收窄到这个范围后的 `selection`:位置参数的 eval id 前缀收窄 Selection 覆盖的 eval(覆盖提醒的分母同样收窄到范围内),`--results` 把结果根换成指定目录,`--exp` 让 Selection 只留该实验。`--history` 与 `--report` 互斥——趋势在报告里用 `exp.snapshots` 自己摆;证据切面(`--eval` / `--execution` / `--diff`)只看证据,不渲染报告。
85
85
 
86
86
  页面里的每个组件都是**双面**的:网页面是 React 渲染,终端面是字符渲染,两面吃同一份算好的数据。实体列表按 experiment → Eval → Attempt 展示事实;指标表、矩阵、条形图、成绩单、散点图、趋势图和差异表展示聚合值。完整清单见[报告组件](/zh/reference/report-components)。网页面的实体、格子和点深链到 Attempt 详情,终端面印出对应的 `niceeval show <eval id>` 下钻命令。
87
87
 
@@ -68,11 +68,11 @@ api-validation claude-code pass@3 = 1/3 (33%) mean 41s
68
68
  除了 pass rate,还应该看平均耗时、token、成本和失败类型。
69
69
 
70
70
  ```bash
71
- npx niceeval show --experiment compare-models
71
+ npx niceeval show --exp compare-models
72
72
  npx niceeval view
73
73
  ```
74
74
 
75
- `show --experiment` 按路径段匹配 id 前缀,所以组名会选中组内所有配置,但不会误中名字只是在字符串上相似的另一个组。`view` 默认加载完整结果,直接在页面里选组;`--experiment` 对它只是可选的启动时收窄。
75
+ `show --exp` 按路径段匹配 id 前缀,所以组名会选中组内所有配置,但不会误中名字只是在字符串上相似的另一个组。`view` 默认加载完整结果,直接在页面里选组;`--exp` 对它只是可选的启动时收窄。
76
76
 
77
77
  ## 设计 experiment 的建议
78
78
 
@@ -31,6 +31,12 @@ await copySnapshots(results.latest(), output, {
31
31
  npx niceeval view --results report-data --out site
32
32
  ```
33
33
 
34
+ 只发布一部分实验时,把收窄直接交给导出命令,页面和证据都只含收窄后的范围(见[查看结果 · 导出与静态托管](/zh/how-to/viewing-results#导出与静态托管)):
35
+
36
+ ```bash
37
+ npx niceeval view --results report-data --exp compare --out site
38
+ ```
39
+
34
40
  `view` 对零可读结果直接报错、非零退出,不会导出一张空报告——`report-data/` checkout 坏掉,或所有落盘与当前 niceeval 的 schemaVersion 不兼容被整批跳过时,构建失败,Vercel / GitHub Pages 保留上一次部署。错误逐条列出被跳过的快照目录与原因,schemaVersion 场景还给出能直接查看旧落盘的 `npx niceeval@<版本> view` 命令。
35
41
 
36
42
  ## 发布自定义报告
@@ -66,7 +66,7 @@ Sandbox 创建、setup 或 teardown 错误不依赖 trace。`result.json` 保存
66
66
 
67
67
  同一个实验多次跑会留下多份结果,不带 `@<locator>` 的默认视图只回答一个问题——「现在整体怎样」:每个 experiment × eval 取时间上最新的那份判定,同一个 experiment 跨多次运行拼出来。按前缀只重跑了部分 eval 时,其余 eval 的判定从更早的运行补齐,报告永远是全局最新,不会因为一次局部重跑变残缺。合成是有标注的:每份判定都带上它产生的时间,能看出报告是从哪几次运行拼出来的。合成结果可能混着不同版本的被测代码——所以收工判定以 `--force` 全量重跑为准,迭代途中的 `show` 负责快、收尾的全量跑负责真。
68
68
 
69
- 单个 eval 视图里,多 experiment、多 Attempt 时的断言明细块默认挑最新一次失败的 Attempt 展开;没有失败就挑最新一次。这只是一个默认展开的启发式,不是精确选择——需要精确看某一次 Attempt,复制那一行的 `@<locator>` 直接 `show` 它即可。`--experiment compare` 按路径段前缀把 Selection 收窄到整个 `compare` 组,`--experiment compare/bub` 只留一个 experiment;`--results <目录>` 换结果根,`--snapshot <snapshot.json>` 只看某一次落盘的快照,`--history` 看跨 run 趋势。`--report <文件>` 同时替换 `show` / `view` 的默认报告。位置前缀、`--results`、`--experiment` 对自定义报告同样生效;`--history` 与 `--report` 互斥。
69
+ 单个 eval 视图里,多 experiment、多 Attempt 时的断言明细块默认挑最新一次失败的 Attempt 展开;没有失败就挑最新一次。这只是一个默认展开的启发式,不是精确选择——需要精确看某一次 Attempt,复制那一行的 `@<locator>` 直接 `show` 它即可。`--exp compare` 按路径段前缀把 Selection 收窄到整个 `compare` 组,`--exp compare/bub` 只留一个 experiment;`--results <目录>` 换结果根,`--snapshot <snapshot.json>` 只看某一次落盘的快照,`--history` 看跨 run 趋势。`--report <文件>` 同时替换 `show` / `view` 的默认报告。位置前缀、`--results`、`--exp` 对自定义报告同样生效;`--history` 与 `--report` 互斥。
70
70
 
71
71
  `niceeval view` 的每个视图都有 CLI 对应物:
72
72
 
@@ -83,7 +83,7 @@ Sandbox 创建、setup 或 teardown 错误不依赖 trace。`result.json` 保存
83
83
 
84
84
  ### 默认分组比较报告
85
85
 
86
- 不带 flag 的 `niceeval show` 如果命中多个可比组,只列组索引和可直接执行的 `niceeval show --experiment <group>` 命令;Selection 只剩一个组时才输出该组的成本 × 端到端成功率图和实验列表。组的边界来自 experiment id 的完整父目录:`compare/*` 与 `dev-e2b/*` 不共享坐标系、连线、排序或汇总数字;顶层 experiment 各自形成单例组。
86
+ 不带 flag 的 `niceeval show` 如果命中多个可比组,只列组索引和可直接执行的 `niceeval show --exp <group>` 命令;Selection 只剩一个组时才输出该组的成本 × 端到端成功率图和实验列表。组的边界来自 experiment id 的完整父目录:`compare/*` 与 `dev-e2b/*` 不共享坐标系、连线、排序或汇总数字;顶层 experiment 各自形成单例组。
87
87
 
88
88
  组内实验列表先给固定列汇总,再按 experiment → Eval → Attempt 展开。locator 只保留 `@<id>`,失败原因优先显示期望值/实际值或命令退出码;完整断言和 evidence 留在 `niceeval show @<locator>`。某组只有一个 experiment 时散点仍显示单点,不出现“至少两个实验才能比较”的空态。快照未完成、过旧或覆盖不全的 warning 位于组索引前,同一条 warning 只显示一次。
89
89
 
@@ -329,7 +329,7 @@ compare/codex-gpt-5.4 · 5 runs · passed 2/5
329
329
  npx niceeval view
330
330
  ```
331
331
 
332
- 这会打开本地结果查看器。它的首页报告和 `niceeval show` 选结果的规则一模一样:对每个 experiment 的每道 eval,取时间上最新的那份判定,同一个 experiment 跨多次运行拼出来;收窄范围(eval ID 前缀、`--experiment`)时按同一条规则收窄。你可以浏览 eval、查看 agent 的对话与工具调用、读 diff、检查断言结果。数据不会上传到外部服务。
332
+ 这会打开本地结果查看器。它的首页报告和 `niceeval show` 选结果的规则一模一样:对每个 experiment 的每道 eval,取时间上最新的那份判定,同一个 experiment 跨多次运行拼出来;收窄范围(eval ID 前缀、`--exp`)时按同一条规则收窄。你可以浏览 eval、查看 agent 的对话与工具调用、读 diff、检查断言结果。数据不会上传到外部服务。
333
333
 
334
334
  <Tip>
335
335
  失败后立刻运行 `npx niceeval view`,可以直接打开刚刚那次运行的 artifacts。
@@ -349,7 +349,16 @@ npx niceeval view
349
349
  npx niceeval view --out site
350
350
  ```
351
351
 
352
- NiceEval 写入 `<dir>/index.html`,并把查看器要读取的 artifact(`sources.json`、`events.json`、`trace.json`)复制到 `<dir>/artifact/` 下。把整个目录交给任何静态托管(Vercel、GitHub Pages 或 `python3 -m http.server`),代码视图、transcript 和 trace 瀑布图都和本地 `niceeval view` 一致;表格排序、过滤和图表悬停也照常可用——所需脚本已内联进页面,浏览器禁用 JS 时页面仍完整可读,只是少这些浏览操作。
352
+ NiceEval 写入 `<dir>/index.html`,并把查看器要读取的 artifact(`sources.json`、`events.json`、`trace.json`,有 `diff.json` 也带上)复制到 `<dir>/artifact/` 下。把整个目录交给任何静态托管(Vercel、GitHub Pages 或 `python3 -m http.server`),代码视图、transcript 和 trace 瀑布图都和本地 `niceeval view` 一致;表格排序、过滤和图表悬停也照常可用——所需脚本已内联进页面,浏览器禁用 JS 时页面仍完整可读,只是少这些浏览操作。
353
+
354
+ 只想发布一部分结果时,把本地查看用的收窄直接交给导出:
355
+
356
+ ```bash
357
+ npx niceeval view --exp compare --out site # 只发布 compare 可比组
358
+ npx niceeval view weather --out site # 只发布 weather 开头的 eval
359
+ ```
360
+
361
+ 出站的就是收窄到的:页面和 `artifact/` 证据都只含收窄后的范围,被滤掉实验的 transcript、源码和 trace 不会跟着出站。不收窄就是整站导出完整结果。
353
362
 
354
363
  `--out` 只接受目录。没有单文件导出:代码视图、transcript 和 trace 依赖 artifact 文件,单个 HTML 装不下完整证据。要把结果发给别人,托管整站发链接即可。
355
364
 
@@ -357,7 +366,7 @@ NiceEval 写入 `<dir>/index.html`,并把查看器要读取的 artifact(`sou
357
366
 
358
367
  两点限制:
359
368
 
360
- - `diff.json` 和 `o11y.json` 不会被复制。查看器不读取它们,且 diff 可能达到上百 MB。
369
+ - `o11y.json` 不会被复制——报告数字已经算进页面,查看器不读取它。
361
370
  - 用 `file://` 直接打开 `index.html` 时浏览器不允许 fetch artifact,代码视图会提示源码不可用。本地预览用 http 服务打开。
362
371
 
363
372
  最简单的流程是在跑过 eval 的机器上导出,把产物目录直接部署,不需要额外步骤。要让站点随 push 自动更新,先用 `copySnapshots` 生成经过单文件预算检查的发布结果根,把它提交进仓库,再让 CI 对这个目录运行 `view --results <目录> --out`——workflow 与平台接线见[通过 CI 发布报告](/zh/how-to/publish-report)。
@@ -93,7 +93,7 @@ npx niceeval exp compare-models weather
93
93
  | `--timing` | boolean | `show` 命令专用:整个 Attempt 的统一时间树;裸 `--timing` 给有界诊断投影,`--timing=full` 逐节点展开全部 runner/已关联 OTel 节点。 |
94
94
  | `--diff` | boolean | `show` 命令专用:sandbox 里的文件改动摘要;`--diff=<文件路径>` 看单个文件的完整改动(路径必须 `=` 连写)。 |
95
95
  | `--history` | boolean | `show` 命令专用:执行时间轴——对匹配的每个 experiment × eval 分节,逐 attempt 列时间 / verdict / 摘要 / 耗时 / 成本 / locator;与 `--report` 互斥。 |
96
- | `--experiment` | string | `show` / `view` 命令专用:按路径段前缀收窄 experiment;组名会选中组内全部配置。 |
96
+ | `--exp` | string | `show` / `view` 命令专用:按路径段前缀收窄 experiment(与 `niceeval exp` 位置参数同一套匹配);组名会选中组内全部配置。`view --out` 时同一收窄决定出站内容。 |
97
97
  | `--results` | string | `show` / `view` / `sandbox enter\|list\|stop` 共用:结果根目录(`.niceeval` 之外的另一个根,如 `copySnapshots` 产出的发布根)。 |
98
98
  | `--snapshot` | string | `view` 命令专用:只打开这一份快照文件(`snapshot.json`);文件不可读时命令失败(扫描模式只跳过)。 |
99
99
  | `--report` | string | `show` / `view` 命令专用:用文件默认导出的 `defineReport(...)` 替换两者共用的默认报告。 |
@@ -151,7 +151,7 @@ npx niceeval exp compare --output ci --strict --json .niceeval/ci-summary.json -
151
151
  npx niceeval view
152
152
  ```
153
153
 
154
- 打开本地结果查看器。它和 `show` 共用同一份默认报告和同一套默认选择——对每个 experiment、每个 eval,取历次运行里最新的那份判定;只补跑部分 eval 时,其余 eval 从更早的运行补齐。默认报告按 experiment id 的父目录分组,只在同组内比较。`show` 输出终端文本,`view` 输出网页并提供可交互的证据浏览;eval ID 前缀、`--experiment`、`--results` 对两者的收窄一致。完整说明见[查看结果](/zh/how-to/viewing-results)。
154
+ 打开本地结果查看器。它和 `show` 共用同一份默认报告和同一套默认选择——对每个 experiment、每个 eval,取历次运行里最新的那份判定;只补跑部分 eval 时,其余 eval 从更早的运行补齐。默认报告按 experiment id 的父目录分组,只在同组内比较。`show` 输出终端文本,`view` 输出网页并提供可交互的证据浏览;eval ID 前缀、`--exp`、`--results` 对两者的收窄一致。完整说明见[查看结果](/zh/how-to/viewing-results)。
155
155
 
156
156
  ## `show [id-prefix...]`
157
157
 
@@ -173,8 +173,8 @@ niceeval sandbox stop --all
173
173
  **按实验回看当前水位。** 不记得定位符时,从实验入手:
174
174
 
175
175
  ```bash
176
- niceeval show --experiment compare/bub # 这个实验每道题现在的判定
177
- niceeval show memory/swelancer --experiment compare/bub # 收窄到某道题
176
+ niceeval show --exp compare/bub # 这个实验每道题现在的判定
177
+ niceeval show memory/swelancer --exp compare/bub # 收窄到某道题
178
178
  ```
179
179
 
180
180
  列表里每道题、每次 Attempt 都带定位符,接着往深处钻就回到上面的场景一 / 场景二。
@@ -208,5 +208,5 @@ niceeval view --results site-data/run
208
208
  | 装依赖失败、CLI 起不来、跑一半超时 | 重跑该 eval 加 `--keep-sandbox` → `niceeval sandbox enter <id>` |
209
209
  | 想看文件实际内容(agent 没改的、起始材料、`$HOME`) | 重跑加 `--keep-sandbox`(failed 也留)→ `sandbox enter` 进 workdir 看 |
210
210
  | 留了哪些沙箱、清理 | `sandbox list` → `sandbox stop <id>` / `--all` |
211
- | 复盘上周那次失败 | 翻出旧定位符 → `show @loc`;不记得就 `show --experiment <实验>` |
211
+ | 复盘上周那次失败 | 翻出旧定位符 → `show @loc`;不记得就 `show --exp <实验>` |
212
212
  | 一批失败一起看 | `niceeval view` → Attempt 详情 → Copy fix prompt |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "niceeval",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Agent-native eval tool — eval agents, services, functions, and coding-agent fixtures",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/cli.ts CHANGED
@@ -170,8 +170,8 @@ const FLAG_OPTIONS = {
170
170
  diff: { type: "boolean" },
171
171
  /** `show` 命令专用:执行时间轴——对匹配的每个 experiment × eval 分节,逐 attempt 列时间 / verdict / 摘要 / 耗时 / 成本 / locator;与 `--report` 互斥。 */
172
172
  history: { type: "boolean" },
173
- /** `show` / `view` 命令专用:按路径段前缀收窄 experiment;组名会选中组内全部配置。 */
174
- experiment: { type: "string" },
173
+ /** `show` / `view` 命令专用:按路径段前缀收窄 experiment(与 `niceeval exp` 位置参数同一套匹配);组名会选中组内全部配置。`view --out` 时同一收窄决定出站内容。 */
174
+ exp: { type: "string" },
175
175
  /** `show` / `view` / `sandbox enter|list|stop` 共用:结果根目录(`.niceeval` 之外的另一个根,如 `copySnapshots` 产出的发布根)。 */
176
176
  results: { type: "string" },
177
177
  /** `view` 命令专用:只打开这一份快照文件(`snapshot.json`);文件不可读时命令失败(扫描模式只跳过)。 */
@@ -308,7 +308,7 @@ function parseArgs(argv: string[]): { command: string; positionals: string[]; fl
308
308
  sandboxPath: values.path as string | undefined,
309
309
  leaveRunning: values["leave-running"] === true,
310
310
  history: values.history === true,
311
- experiment: values.experiment as string | undefined,
311
+ experiment: values.exp as string | undefined,
312
312
  results: values.results as string | undefined,
313
313
  snapshot: values.snapshot as string | undefined,
314
314
  report: values.report as string | undefined,
@@ -320,7 +320,7 @@ function parseArgs(argv: string[]): { command: string; positionals: string[]; fl
320
320
  /**
321
321
  * exp 只接受两类输入:位置参数选「跑哪些 eval」+ 调度/输出/机器出口 flag 选「对着哪个 agent、
322
322
  * 怎么跑」。show / view 专属的证据切面(`--source`/`--execution`/`--diff`)、时间轴(`--history`)、
323
- * Scope 收窄(`--experiment`/`--results`)、报告装载(`--report`/`--page`)、查看器
323
+ * Scope 收窄(`--exp`/`--results`)、报告装载(`--report`/`--page`)、查看器
324
324
  * (`--snapshot`/`--out`/`--port`/`--open`)不能被 exp 静默忽略(见 docs/feature/experiments/
325
325
  * cli.md「用法错误」)。返回第一个被误用的 flag 及其归属命令(用于报错),没有误用返回 undefined。
326
326
  */
@@ -333,7 +333,7 @@ function firstViewerOnlyFlag(flags: Flags): { flag: string; command: string } |
333
333
  if (flags.timing !== undefined) return { flag: "--timing", command: SHOW };
334
334
  if (flags.diff || flags.diffPath !== undefined) return { flag: "--diff", command: SHOW };
335
335
  if (flags.history) return { flag: "--history", command: SHOW };
336
- if (flags.experiment !== undefined) return { flag: "--experiment", command: BOTH };
336
+ if (flags.experiment !== undefined) return { flag: "--exp", command: BOTH };
337
337
  if (flags.results !== undefined) return { flag: "--results", command: BOTH };
338
338
  if (flags.report !== undefined) return { flag: "--report", command: BOTH };
339
339
  if (flags.page !== undefined) return { flag: "--page", command: BOTH };
@@ -566,11 +566,10 @@ async function main(): Promise<void> {
566
566
  }
567
567
 
568
568
  if (command === "view") {
569
- // 位置参数只有一种含义:eval id 前缀(收窄报告槽 Scope)。结果根经 --results 递入,
569
+ // 位置参数只有一种含义:eval id 前缀(收窄有效根)。结果根经 --results 递入,
570
570
  // 单开一份快照经 --snapshot 递入;--report 整槽替换报告槽(与 show --report 吃同一个文件),
571
571
  // --page 定初始页。文件与目录都不进位置参数(docs/feature/reports/view.md「打开与收窄」)。
572
- // --out 与位置参数 / --experiment 的互斥在 buildView 内校验(单点,报错文案含 copySnapshots
573
- // + filter 的下一步),经 exitOnViewUserError 按用法错误退出。
572
+ // --out 接受同一收窄:出站内容即收窄后的有效根(docs/feature/reports/view.md「静态导出」)。
574
573
  let viewInput: { input?: string; patterns: string[] };
575
574
  try {
576
575
  viewInput = resolveViewInput(cwd, positionals, {
package/src/i18n/en.ts CHANGED
@@ -84,7 +84,7 @@ export const en = {
84
84
  " --timing unified timing tree for the attempt (phases + hooks/commands/turns + per-turn OTel)\n" +
85
85
  " --diff[=file] sandbox workspace file-change summary; =file expands one file\n" +
86
86
  " --history per experiment × eval execution timeline (mutually exclusive with --report)\n" +
87
- " --results <dir> pin a results root --experiment <id> one experiment\n" +
87
+ " --results <dir> pin a results root --exp <id> one experiment\n" +
88
88
  " --report <file> custom report --page <id> pick the initial page (multi-page\n" +
89
89
  " reports render it, then list the rest as a page index with copyable commands)\n" +
90
90
  " niceeval list list discovered evals\n" +
@@ -92,7 +92,7 @@ export const en = {
92
92
  " report pages + evidence rooms; --report <file> swaps in your report\n" +
93
93
  " (same file as show); --page <id> picks the initial page;\n" +
94
94
  " --results <dir> pins a results root; --snapshot <file> opens exactly\n" +
95
- " one snapshot; --experiment <id> one experiment\n" +
95
+ " one snapshot; --exp <id> one experiment\n" +
96
96
  " --out <dir> exports a static site: index.html plus the viewer\n" +
97
97
  " artifacts, ready for any static host\n" +
98
98
  " niceeval sandbox list|enter|history|diff|stop inspect & destroy sandboxes kept by --keep-sandbox\n" +
@@ -109,7 +109,7 @@ export const en = {
109
109
  "cli.show.noResults": "No results found under {{root}}. Run `niceeval exp` first, then `niceeval show`.\n",
110
110
  "cli.show.runDirMissing": "Results directory not found: {{dir}}\n",
111
111
  "cli.show.noEvalMatch": "No results matched: {{patterns}}. Evals with results: {{evals}}\n",
112
- "cli.show.noExperimentMatch": "No experiment matched --experiment {{arg}}. Experiments with results: {{experiments}}\n",
112
+ "cli.show.noExperimentMatch": "No experiment matched --exp {{arg}}. Experiments with results: {{experiments}}\n",
113
113
  "cli.show.historyReportConflict":
114
114
  "`--history` and `--report` are mutually exclusive: both take over the main output. --history is the host's per-attempt execution timeline; for snapshot-level trends, compose exp.snapshots inside your report file instead.\n",
115
115
  "cli.show.evidenceNeedsEval":
package/src/i18n/zh-CN.ts CHANGED
@@ -80,14 +80,14 @@ export const zhCN = {
80
80
  " --timing 整个 attempt 的统一时间树(阶段 + hook/命令/turn + 轮内 OTel)\n" +
81
81
  " --diff[=文件] agent 归因的文件改动摘要;=文件 按窗口展开单个文件\n" +
82
82
  " --history 逐 experiment × eval 的执行时间轴(与 --report 互斥)\n" +
83
- " --results <目录> 钉死结果根 --experiment <id> 只看该实验\n" +
83
+ " --results <目录> 钉死结果根 --exp <id> 只看该实验\n" +
84
84
  " --report <文件> 自定义报告 --page <id> 定初始页(多页报告渲染该页,\n" +
85
85
  " 尾部再附其余页索引)\n" +
86
86
  " niceeval list 列出发现到的 eval\n" +
87
87
  " niceeval view [eval-id 前缀…] [--out 目录] [--port n] [--no-open]\n" +
88
88
  " 报告页 + 证据室;--report <文件> 整槽换成自定义报告(与 show 同一文件)\n" +
89
89
  " --page <id> 定初始页 --results <目录> 钉死结果根\n" +
90
- " --snapshot <文件> 只打开这一份快照 --experiment <id> 只看该实验\n" +
90
+ " --snapshot <文件> 只打开这一份快照 --exp <id> 只看该实验\n" +
91
91
  " --out <目录> 静态导出:index.html 连同查看器 artifact,可直接静态托管\n" +
92
92
  " niceeval sandbox list|enter|history|diff|stop 查看与销毁 --keep-sandbox 留下的现场\n" +
93
93
  " niceeval clean 删除 .niceeval/ 历史 artifact\n" +
@@ -104,7 +104,7 @@ export const zhCN = {
104
104
  "cli.show.noResults": "{{root}} 下没有结果。先 `niceeval exp` 跑一轮,再 `niceeval show`。\n",
105
105
  "cli.show.runDirMissing": "Results directory not found: {{dir}}\n",
106
106
  "cli.show.noEvalMatch": "No results matched: {{patterns}}. Evals with results: {{evals}}\n",
107
- "cli.show.noExperimentMatch": "No experiment matched --experiment {{arg}}. Experiments with results: {{experiments}}\n",
107
+ "cli.show.noExperimentMatch": "No experiment matched --exp {{arg}}. Experiments with results: {{experiments}}\n",
108
108
  "cli.show.historyReportConflict":
109
109
  "`--history` and `--report` are mutually exclusive: both take over the main output. --history is the host's per-attempt execution timeline; for snapshot-level trends, compose exp.snapshots inside your report file instead.\n",
110
110
  "cli.show.evidenceNeedsEval":
@@ -899,8 +899,8 @@ describe("内建报告", () => {
899
899
  // Hero text 面:标题行(回退链终点)+ 最后运行 meta 行在页首
900
900
  expect(multi.split("\n")[0]).toBe("Eval Results");
901
901
  expect(multi).toContain("Last run");
902
- expect(multi).toContain("niceeval show --experiment compare");
903
- expect(multi).toContain("niceeval show --experiment dev");
902
+ expect(multi).toContain("niceeval show --exp compare");
903
+ expect(multi).toContain("niceeval show --exp dev");
904
904
  expect(multi).not.toContain("Eval / Attempt"); // 不倾倒组内 experiment 明细
905
905
 
906
906
  const priced = snap({
@@ -516,20 +516,26 @@ table.nre,
516
516
  }
517
517
 
518
518
  /* ---- Hero / PoweredBy(站点标题区与品牌行) ---- */
519
- .nre-hero { margin: 0 0 18px; }
519
+ /* 居中大标题:hero 是页首的门面块,整块占满内容列宽、文本水平居中,标题随视口 clamp 放大,
520
+ meta 与品牌行居中跟随。不设 max-width——窄框居中在宽内容列里会读成左偏,占满列宽才真居中。 */
521
+ .nre-hero {
522
+ text-align: center;
523
+ margin: 0 0 clamp(28px, 5vw, 44px);
524
+ }
520
525
  .nre-hero .nre-hero-title {
521
526
  margin: 0;
522
- font-size: 24px;
523
- font-weight: 700;
527
+ font-size: clamp(38px, 5vw, 60px);
528
+ line-height: 0.98;
529
+ font-weight: 760;
524
530
  letter-spacing: -0.01em;
525
531
  }
526
532
  .nre-hero .nre-hero-meta {
527
- margin: 4px 0 0;
533
+ margin: 14px 0 0;
528
534
  color: var(--muted);
529
- font-size: 13px;
535
+ font-size: 14px;
530
536
  }
531
537
  .nre-powered-by {
532
- margin: 6px 0 0;
538
+ margin: 10px 0 0;
533
539
  font-size: 12px;
534
540
  }
535
541
  .nre-powered-by a {
@@ -576,10 +576,10 @@ function quoteArg(value: string): string {
576
576
  return /^[A-Za-z0-9._/@-]+$/.test(value) ? value : `'${value.replaceAll("'", `'"'"'`)}'`;
577
577
  }
578
578
 
579
- /** 按上下文拼组索引的可复制命令:`niceeval show <patterns> --experiment <id> [--results/--report/--page]`。 */
579
+ /** 按上下文拼组索引的可复制命令:`niceeval show <patterns> --exp <id> [--results/--report/--page]`。 */
580
580
  function experimentCommandFor(ctx: HostCommandContext): (experimentIdPrefix: string) => string {
581
581
  return (prefix) => {
582
- const parts = ["niceeval show", ...ctx.patterns.map(quoteArg), `--experiment ${quoteArg(prefix)}`];
582
+ const parts = ["niceeval show", ...ctx.patterns.map(quoteArg), `--exp ${quoteArg(prefix)}`];
583
583
  if (ctx.results !== undefined) parts.push(`--results ${quoteArg(ctx.results)}`);
584
584
  if (ctx.report !== undefined) parts.push(`--report ${quoteArg(ctx.report)}`);
585
585
  if (ctx.page !== undefined) parts.push(`--page ${quoteArg(ctx.page)}`);
@@ -71,7 +71,7 @@ export interface TextContext {
71
71
  attemptCommand(locator: AttemptLocator): string;
72
72
  /**
73
73
  * 组索引一类「按实验收窄」命令的生成;宿主注入以携带完整上下文(--results / --report /
74
- * --page 与位置参数),默认 `niceeval show --experiment <id>`。非契约字段,官方组件内部用。
74
+ * --page 与位置参数),默认 `niceeval show --exp <id>`。非契约字段,官方组件内部用。
75
75
  */
76
76
  experimentCommand(experimentIdPrefix: string): string;
77
77
  }
@@ -532,7 +532,7 @@ export function createTextContext(options?: TextRenderOptions): TextContext {
532
532
  // show 已实现的真实 CLI 语法,不需要反查 eval id 再拼一条近似命令。
533
533
  const attemptCommand = options?.attemptCommand ?? ((locator: AttemptLocator) => `niceeval show ${locator}`);
534
534
  const experimentCommand =
535
- options?.experimentCommand ?? ((prefix: string) => `niceeval show --experiment ${shellQuote(prefix)}`);
535
+ options?.experimentCommand ?? ((prefix: string) => `niceeval show --exp ${shellQuote(prefix)}`);
536
536
  const make = (w: number): TextContext => ({
537
537
  width: w,
538
538
  locale,
@@ -329,7 +329,7 @@ describe("selectCurrentResults · 现刻水位结构化身份", () => {
329
329
  expect(algebra.warnings).toEqual([]);
330
330
  });
331
331
 
332
- it("场景8 --experiment 分段前缀过滤:只留匹配段,不误配同前缀实验", async () => {
332
+ it("场景8 --exp 分段前缀过滤:只留匹配段,不误配同前缀实验", async () => {
333
333
  const root = await makeRoot();
334
334
  await writeSnapshot(root, "2026-07-01T08-00-00-000Z", { experimentId: "compare/bub", startedAt: "2026-07-01T08:00:00.000Z" }, [res("q1", "passed")]);
335
335
  await writeSnapshot(root, "2026-07-01T09-00-00-000Z", { experimentId: "compare/codex", agent: "codex", startedAt: "2026-07-01T09:00:00.000Z" }, [res("q1", "passed")]);
@@ -81,7 +81,7 @@ export function selectLatest(
81
81
 
82
82
  /** selectCurrentResults 的范围输入:experiment id 前缀与 eval id 前缀,都可缺省。 */
83
83
  export interface ResultScope {
84
- /** experiment id 前缀(--experiment),分段匹配语义同 filterExperiments。 */
84
+ /** experiment id 前缀(--exp),分段匹配语义同 filterExperiments。 */
85
85
  experiment?: string | string[];
86
86
  /** eval id 前缀(位置参数),收窄 Scope 覆盖的 eval;覆盖警告分母同步收窄到范围内。 */
87
87
  patterns?: string[];
@@ -368,7 +368,7 @@ export function isNewerSnapshot(a: Snapshot, b: Snapshot): boolean {
368
368
  return a.dir.localeCompare(b.dir) > 0;
369
369
  }
370
370
 
371
- /** experiment id 分段前缀过滤(--experiment / latest({ experiments }) 同一语义);包内使用,不进公共 barrel。 */
371
+ /** experiment id 分段前缀过滤(--exp / latest({ experiments }) 同一语义);包内使用,不进公共 barrel。 */
372
372
  export function filterExperiments(experiments: Experiment[], filter?: string | string[]): Experiment[] {
373
373
  if (filter === undefined) return experiments;
374
374
  // 允许 "compare/" 这种带尾斜杠的写法,与 "compare" 等价;分段匹配不误配 "compare2"。
@@ -47,7 +47,7 @@ export function experimentGroupOf(experimentId: string): string | undefined {
47
47
  /**
48
48
  * eval id 前缀过滤,同 CLI 位置参数语义(docs/feature/reports/show.md「打开与收窄」):
49
49
  * eval 位置参数是收窄过滤,按**裸前缀宽松匹配**——"algebra" 命中 "algebra"、"algebra/..."
50
- * 也命中 "algebra2",多命中正是它的用途(与 `--experiment` 的按路径段匹配有意不同)。
50
+ * 也命中 "algebra2",多命中正是它的用途(与 `--exp` 的按路径段匹配有意不同)。
51
51
  */
52
52
  export function evalPrefixPredicate(evals?: string | string[]): (id: string) => boolean {
53
53
  if (evals === undefined) return () => true;
package/src/show/index.ts CHANGED
@@ -7,9 +7,9 @@
7
7
  // @<locator> 精确 attempt:无证据 flag → 紧凑全景;带 flag → 对应证据切面
8
8
  // --source / --execution / --diff[=路径] 证据切面(宿主本体):出现即走证据室,不渲染报告槽
9
9
  // --history 执行时间轴(逐 experimentId + evalId 分节),与 --report 互斥
10
- // --report <文件> 整槽换成用户报告;位置前缀 / --results / --experiment 先收窄 Scope 再注入
10
+ // --report <文件> 整槽换成用户报告;位置前缀 / --results / --exp 先收窄 Scope 再注入
11
11
  // --page <id> 多页报告选页;未命中列出可用页 id 按用法错误退出
12
- // --results <目录> 结果根换成该目录;--experiment 让 Scope 只留该实验
12
+ // --results <目录> 结果根换成该目录;--exp 让 Scope 只留该实验
13
13
  //
14
14
  // 数据全部走 niceeval/results 的读取面(openResults + 合成 Scope + loadAttemptEvidence),
15
15
  // 不自己爬目录;证据可用性只由 loadAttemptEvidence 在单 Attempt 页面计算。
@@ -113,7 +113,7 @@ export function attemptsOfEval(snapshots: Snapshot[], evalId: string): AttemptHa
113
113
  /**
114
114
  * 详情块 / eval-id 前缀证据切面默认挑最新一次失败的 attempt;没有失败挑最新一次。
115
115
  * 精确选某一次 attempt 走 `@<locator>`(`resolveLocator`),不再有数字 `--attempt`——
116
- * --experiment 已在 Selection 合成时收窄。
116
+ * --exp 已在 Selection 合成时收窄。
117
117
  */
118
118
  export function pickDetailAttempt(attempts: AttemptHandle[]): AttemptHandle | undefined {
119
119
  if (attempts.length === 0) return undefined;
@@ -197,9 +197,9 @@ describe("其余页索引与索引命令上下文", () => {
197
197
  expect(text).not.toContain("https://example.com");
198
198
  });
199
199
 
200
- it("showCommand 按序携带位置参数与 --experiment / --results / --report / --page", () => {
200
+ it("showCommand 按序携带位置参数与 --exp / --results / --report / --page", () => {
201
201
  expect(
202
202
  showCommand({ patterns: ["memory/swelancer"], experiment: "dev-e2b", report: "reports/site.tsx", page: "exam" }),
203
- ).toBe("niceeval show memory/swelancer --experiment dev-e2b --report reports/site.tsx --page exam");
203
+ ).toBe("niceeval show memory/swelancer --exp dev-e2b --report reports/site.tsx --page exam");
204
204
  });
205
205
  });
@@ -320,7 +320,7 @@ export interface HostCommandContext {
320
320
  /** 按上下文拼一条可复制的 show 命令(页索引 / 组索引共用的携带规则)。 */
321
321
  export function showCommand(ctx: HostCommandContext, extra: string[] = []): string {
322
322
  const parts = ["niceeval show", ...ctx.patterns];
323
- if (ctx.experiment !== undefined) parts.push(`--experiment ${ctx.experiment}`);
323
+ if (ctx.experiment !== undefined) parts.push(`--exp ${ctx.experiment}`);
324
324
  if (ctx.results !== undefined) parts.push(`--results ${ctx.results}`);
325
325
  if (ctx.report !== undefined) parts.push(`--report ${ctx.report}`);
326
326
  if (ctx.page !== undefined) parts.push(`--page ${ctx.page}`);
@@ -592,7 +592,7 @@ describe("--report 装载", () => {
592
592
  expect(out).toContain("CUSTOM weather/brooklyn ·");
593
593
  });
594
594
 
595
- it("--experiment 让 Selection 只留该实验", async () => {
595
+ it("--exp 让 Selection 只留该实验", async () => {
596
596
  const root = await seedComposedRoot();
597
597
  await writeSnapshot(
598
598
  root,
@@ -25,8 +25,8 @@ function dataWithShell(report: ViewData["report"]): ViewData {
25
25
  return { composedRuns: 1, snapshots: [], ...(report !== undefined ? { report } : {}) };
26
26
  }
27
27
 
28
- describe("外壳:宿主无品牌位与 hero,导航只有报告页", () => {
29
- it("宿主导航壳 DOM 无品牌节点、无 hero 区:品牌与 hero 是页内组件,不归宿主", () => {
28
+ describe("外壳:宿主恒有 NiceEval 品牌位、无 hero,导航只有报告页", () => {
29
+ it("宿主页头有恒定品牌字标、无 hero 区:hero 是页内组件不归宿主,品牌归宿主", () => {
30
30
  const html = renderToStaticMarkup(
31
31
  <App
32
32
  data={dataWithShell({
@@ -38,11 +38,12 @@ describe("外壳:宿主无品牌位与 hero,导航只有报告页", () => {
38
38
  reportPages={reportPages}
39
39
  />,
40
40
  );
41
- // 宿主 DOM 无品牌节点:没有字标、没有 Powered by、没有任何官网品牌链接。
42
- expect(html).not.toContain('class="brand"');
43
- expect(html).not.toContain("NiceEval");
41
+ // 宿主页头恒有 NiceEval 品牌字标,外链官网;报告定义不能覆盖或移除。
42
+ expect(html).toContain('class="brand"');
43
+ expect(html).toContain("NiceEval");
44
+ expect(html).toContain("niceeval.com");
45
+ // 品牌位是页头字标,不是 hero 的 Powered by 行——后者是页内组件,不落宿主壳。
44
46
  expect(html).not.toContain("Powered by");
45
- expect(html).not.toContain("niceeval.com");
46
47
  // 宿主无 hero 区:标题只落浏览器 <title>(useEffect,静态渲染不执行),不落任何宿主节点。
47
48
  expect(html).not.toContain('class="hero"');
48
49
  expect(html).not.toContain("<h1");