niceeval 0.9.1 → 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.
- package/INDEX.md +31 -33
- package/dist/agents/types.d.ts +20 -17
- package/dist/context/types.d.ts +21 -21
- package/dist/i18n/en.d.ts +10 -2
- package/dist/i18n/zh-CN.d.ts +14 -6
- package/dist/o11y/execution-tree.d.ts +103 -0
- package/dist/o11y/otlp/select.d.ts +22 -0
- package/dist/report/{react → assets}/colors.d.ts +8 -0
- package/dist/report/{react → assets}/colors.js +23 -0
- package/dist/report/built-in/index.d.ts +2 -2
- package/dist/report/built-in/index.js +2 -2
- package/dist/report/built-in/standard.d.ts +8 -1
- package/dist/report/built-in/standard.js +17 -7
- package/dist/report/components/attempt-detail/AttemptAssertions.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptAssertions.js +17 -0
- package/dist/report/components/attempt-detail/AttemptConversation.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptConversation.js +34 -0
- package/dist/report/components/attempt-detail/AttemptDiagnostics.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptDiagnostics.js +7 -0
- package/dist/report/components/attempt-detail/AttemptDiff.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptDiff.js +11 -0
- package/dist/report/components/attempt-detail/AttemptError.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptError.js +16 -0
- package/dist/report/components/attempt-detail/AttemptFixPrompt.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptFixPrompt.js +7 -0
- package/dist/report/components/attempt-detail/AttemptSource.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptSource.js +27 -0
- package/dist/report/components/attempt-detail/AttemptSummary.d.ts +8 -0
- package/dist/report/components/attempt-detail/AttemptSummary.js +20 -0
- package/dist/report/components/attempt-detail/AttemptTimeline.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptTimeline.js +28 -0
- package/dist/report/components/attempt-detail/AttemptTrace.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptTrace.js +29 -0
- package/dist/report/components/attempt-detail/AttemptUsage.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptUsage.js +20 -0
- package/dist/report/components/attempt-detail/compute.d.ts +24 -0
- package/dist/report/components/attempt-detail/compute.js +254 -0
- package/dist/report/components/attempt-detail/faces.d.ts +14 -0
- package/dist/report/components/attempt-detail/faces.js +235 -0
- package/dist/report/components/attempt-detail/index.d.ts +38 -0
- package/dist/report/components/attempt-detail/index.js +527 -0
- package/dist/report/{react → components}/cell.d.ts +5 -3
- package/dist/report/{react → components}/cell.js +3 -3
- package/dist/report/{react → components/entity-lists}/AttemptList.d.ts +8 -4
- package/dist/report/{react → components/entity-lists}/AttemptList.js +16 -10
- package/dist/report/{react → components/entity-lists}/EvalList.d.ts +3 -3
- package/dist/report/{react → components/entity-lists}/EvalList.js +0 -0
- package/dist/report/{react → components/entity-lists}/ExperimentList.d.ts +3 -3
- package/dist/report/{react → components/entity-lists}/ExperimentList.js +10 -10
- package/dist/report/components/entity-lists/compute.d.ts +23 -0
- package/dist/report/components/entity-lists/compute.js +171 -0
- package/dist/report/components/entity-lists/faces.d.ts +5 -0
- package/dist/report/components/entity-lists/faces.js +174 -0
- package/dist/report/components/entity-lists/index.d.ts +51 -0
- package/dist/report/components/entity-lists/index.js +165 -0
- package/dist/report/{react → components}/fixtures.d.ts +2 -2
- package/dist/report/{react → components}/fixtures.js +1 -1
- package/dist/report/{react → components/metric-views}/DeltaTable.d.ts +2 -2
- package/dist/report/{react → components/metric-views}/DeltaTable.js +4 -4
- package/dist/report/{react → components/metric-views}/MetricBars.d.ts +3 -3
- package/dist/report/{react → components/metric-views}/MetricBars.js +3 -3
- package/dist/report/{react → components/metric-views}/MetricLine.d.ts +2 -2
- package/dist/report/{react → components/metric-views}/MetricLine.js +8 -5
- package/dist/report/{react → components/metric-views}/MetricMatrix.d.ts +3 -3
- package/dist/report/{react → components/metric-views}/MetricMatrix.js +4 -4
- package/dist/report/components/metric-views/MetricScatter.d.ts +11 -0
- package/dist/report/{react → components/metric-views}/MetricScatter.js +26 -23
- package/dist/report/{react → components/metric-views}/MetricTable.d.ts +3 -3
- package/dist/report/{react → components/metric-views}/MetricTable.js +4 -4
- package/dist/report/{react → components/metric-views}/Scoreboard.d.ts +2 -2
- package/dist/report/{react → components/metric-views}/Scoreboard.js +3 -3
- package/dist/report/components/metric-views/compute.d.ts +89 -0
- package/dist/report/{compute.js → components/metric-views/compute.js} +32 -415
- package/dist/report/components/metric-views/faces.d.ts +13 -0
- package/dist/report/components/metric-views/faces.js +381 -0
- package/dist/report/components/metric-views/index.d.ts +50 -0
- package/dist/report/components/metric-views/index.js +273 -0
- package/dist/report/{text → components/metric-views}/plot.js +1 -1
- package/dist/report/components/shared-compute.d.ts +22 -0
- package/dist/report/components/shared-compute.js +47 -0
- package/dist/report/components/shared-faces.d.ts +9 -0
- package/dist/report/components/shared-faces.js +26 -0
- package/dist/report/components/shared.d.ts +68 -0
- package/dist/report/components/shared.js +125 -0
- package/dist/report/{react → components/site-components}/CopyFixPrompt.d.ts +2 -2
- package/dist/report/{react → components/site-components}/CopyFixPrompt.js +2 -2
- package/dist/report/{react → components/site-components}/HeroCard.d.ts +2 -2
- package/dist/report/{react → components/site-components}/HeroCard.js +2 -2
- package/dist/report/{react → components/site-components}/ScopeWarnings.d.ts +2 -2
- package/dist/report/{react → components/site-components}/ScopeWarnings.js +3 -3
- package/dist/report/{react → components/site-components}/TraceWaterfall.d.ts +3 -3
- package/dist/report/{react → components/site-components}/TraceWaterfall.js +5 -5
- package/dist/report/components/site-components/compute.d.ts +28 -0
- package/dist/report/components/site-components/compute.js +132 -0
- package/dist/report/components/site-components/faces.d.ts +21 -0
- package/dist/report/components/site-components/faces.js +75 -0
- package/dist/report/components/site-components/index.d.ts +74 -0
- package/dist/report/components/site-components/index.js +220 -0
- package/dist/report/{scope-warnings.d.ts → components/site-components/scope-warnings.d.ts} +2 -2
- package/dist/report/{scope-warnings.js → components/site-components/scope-warnings.js} +2 -2
- package/dist/report/{react → components/summaries}/ScopeSummary.d.ts +2 -2
- package/dist/report/{react → components/summaries}/ScopeSummary.js +17 -8
- package/dist/report/components/summaries/compute.d.ts +7 -0
- package/dist/report/components/summaries/compute.js +51 -0
- package/dist/report/components/summaries/faces.d.ts +7 -0
- package/dist/report/components/summaries/faces.js +38 -0
- package/dist/report/components/summaries/index.d.ts +27 -0
- package/dist/report/components/summaries/index.js +78 -0
- package/dist/report/definition/grid-layout.d.ts +50 -0
- package/dist/report/definition/grid-layout.js +89 -0
- package/dist/report/{primitives.d.ts → definition/primitives.d.ts} +34 -4
- package/dist/report/{primitives.js → definition/primitives.js} +101 -10
- package/dist/report/{report.d.ts → definition/report.d.ts} +36 -65
- package/dist/report/{report.js → definition/report.js} +58 -90
- package/dist/report/{text/table.d.ts → definition/table-text.d.ts} +2 -2
- package/dist/report/{text/table.js → definition/table-text.js} +2 -2
- package/dist/report/{tree.d.ts → definition/tree.d.ts} +43 -11
- package/dist/report/{tree.js → definition/tree.js} +9 -11
- package/dist/report/index.d.ts +33 -18
- package/dist/report/index.js +23 -14
- package/dist/report/{aggregate.d.ts → model/aggregate.d.ts} +12 -4
- package/dist/report/{aggregate.js → model/aggregate.js} +28 -8
- package/dist/report/{flag.d.ts → model/flag.d.ts} +14 -1
- package/dist/report/{flag.js → model/flag.js} +35 -0
- package/dist/report/{format.d.ts → model/format.d.ts} +11 -5
- package/dist/report/{format.js → model/format.js} +48 -4
- package/dist/report/{locale.d.ts → model/locale.d.ts} +17 -17
- package/dist/report/{locale.js → model/locale.js} +31 -33
- package/dist/report/{metrics.d.ts → model/metrics.d.ts} +2 -2
- package/dist/report/{metrics.js → model/metrics.js} +2 -2
- package/dist/report/{types.d.ts → model/types.d.ts} +140 -20
- package/dist/report/react/index.d.ts +32 -21
- package/dist/report/react/index.js +33 -21
- package/dist/report/{load.d.ts → runtime/load.d.ts} +1 -1
- package/dist/report/{load.js → runtime/load.js} +1 -1
- package/dist/report/runtime/text.d.ts +67 -0
- package/dist/report/runtime/text.js +114 -0
- package/dist/report/runtime/web.d.ts +35 -0
- package/dist/report/runtime/web.js +70 -0
- package/dist/results/annotated-source.d.ts +87 -0
- package/dist/results/attempt-evidence.d.ts +74 -0
- package/dist/results/attempt-source.d.ts +15 -0
- package/dist/results/select.d.ts +11 -1
- package/dist/results/select.js +27 -5
- package/dist/runner/eval-selection.d.ts +26 -0
- package/dist/runner/feedback/sink.d.ts +26 -1
- package/dist/runner/types.d.ts +149 -23
- package/dist/sandbox/docker.d.ts +6 -0
- package/dist/sandbox/types.d.ts +23 -21
- package/dist/scoring/display.d.ts +7 -2
- package/dist/scoring/display.js +42 -4
- package/dist/scoring/types.d.ts +3 -3
- package/dist/shared/aggregate.d.ts +14 -0
- package/dist/shared/aggregate.js +31 -0
- package/dist/shared/types.d.ts +6 -1
- package/docs-site/images/logo-dark.svg +7 -0
- package/docs-site/images/logo.svg +6 -5
- package/docs-site/zh/README.md +8 -7
- package/docs-site/zh/examples/ai-agent-application.mdx +5 -5
- package/docs-site/zh/examples/coding-agent-extensions.mdx +4 -4
- package/docs-site/zh/examples/index.mdx +3 -3
- package/docs-site/zh/examples/integrations/ai-sdk-v7.mdx +1 -1
- package/docs-site/zh/examples/integrations/claude-sdk.mdx +3 -3
- package/docs-site/zh/examples/integrations/codex-sdk.mdx +3 -3
- package/docs-site/zh/examples/integrations/langgraph.mdx +3 -3
- package/docs-site/zh/examples/integrations/pi-sdk.mdx +2 -2
- package/docs-site/zh/explanation/adapter.mdx +19 -19
- package/docs-site/zh/explanation/assert.mdx +9 -9
- package/docs-site/zh/explanation/drive.mdx +4 -4
- package/docs-site/zh/explanation/evals.mdx +8 -8
- package/docs-site/zh/explanation/experiment.mdx +8 -8
- package/docs-site/zh/explanation/hitl.mdx +13 -13
- package/docs-site/zh/explanation/judge.mdx +3 -3
- package/docs-site/zh/explanation/overview.mdx +7 -7
- package/docs-site/zh/explanation/runner.mdx +11 -11
- package/docs-site/zh/explanation/tier.mdx +6 -6
- package/docs-site/zh/index.mdx +14 -14
- package/docs-site/zh/introduction.mdx +13 -16
- package/docs-site/zh/reference/builtin-agents.mdx +61 -28
- package/docs-site/zh/reference/capabilities.mdx +7 -27
- package/docs-site/zh/reference/cli.mdx +35 -32
- package/docs-site/zh/reference/define-agent.mdx +32 -31
- package/docs-site/zh/reference/define-config.mdx +6 -6
- package/docs-site/zh/reference/define-eval.mdx +33 -20
- package/docs-site/zh/reference/events.mdx +4 -4
- package/docs-site/zh/reference/expect.mdx +3 -3
- package/docs-site/zh/reference/official-adapters.mdx +17 -17
- package/docs-site/zh/reference/report-components.mdx +193 -121
- package/docs-site/zh/reference/results-data.mdx +13 -13
- package/docs-site/zh/troubleshooting/debug-sandbox.mdx +11 -11
- package/docs-site/zh/troubleshooting/debugging.mdx +40 -16
- package/docs-site/zh/{how-to → tutorials}/agent-feedback-loop.mdx +38 -38
- package/docs-site/zh/tutorials/agent-onboarding.mdx +100 -0
- package/docs-site/zh/tutorials/authoring.mdx +211 -0
- package/docs-site/zh/{how-to → tutorials}/ci-integration.mdx +8 -8
- package/docs-site/zh/{how-to → tutorials}/connect-otel.mdx +22 -22
- package/docs-site/zh/{how-to → tutorials}/connect-your-agent.mdx +58 -63
- package/docs-site/zh/tutorials/custom-reports.mdx +453 -0
- package/docs-site/zh/{how-to → tutorials}/dataset-fanout.mdx +4 -4
- package/docs-site/zh/tutorials/experiments.mdx +103 -0
- package/docs-site/zh/{how-to → tutorials}/fixtures.mdx +9 -9
- package/docs-site/zh/{how-to → tutorials}/publish-report.mdx +6 -6
- package/docs-site/zh/tutorials/quickstart.mdx +18 -18
- package/docs-site/zh/{how-to → tutorials}/reporters.mdx +6 -6
- package/docs-site/zh/{how-to → tutorials}/sandbox-agent.mdx +6 -6
- package/docs-site/zh/{how-to → tutorials}/sandbox-providers.mdx +41 -19
- package/docs-site/zh/{how-to → tutorials}/scoring-guide.mdx +4 -4
- package/docs-site/zh/{how-to → tutorials}/viewing-results.mdx +49 -49
- package/docs-site/zh/tutorials/write-experiment.mdx +355 -0
- package/docs-site/zh/{how-to → tutorials}/write-send.mdx +40 -40
- package/package.json +8 -4
- package/src/agents/bub.ts +21 -8
- package/src/agents/claude-code.ts +23 -10
- package/src/agents/codex.test.ts +22 -7
- package/src/agents/codex.ts +22 -9
- package/src/agents/index.ts +2 -2
- package/src/agents/openai-compat.ts +1 -1
- package/src/agents/post-setup.ts +45 -22
- package/src/agents/streaming.ts +1 -1
- package/src/agents/types.ts +20 -17
- package/src/agents/ui-message-stream.test.ts +10 -0
- package/src/agents/ui-message-stream.ts +12 -1
- package/src/cli.ts +71 -57
- package/src/context/types.ts +21 -21
- package/src/define.ts +13 -0
- package/src/i18n/en.ts +27 -12
- package/src/i18n/zh-CN.ts +27 -12
- package/src/index.ts +2 -0
- package/src/report/{react → assets}/colors.ts +22 -0
- package/src/report/{react → assets}/enhance.js +1 -33
- package/src/report/{react → assets}/styles.css +124 -106
- package/src/report/built-in/index.tsx +2 -2
- package/src/report/built-in/standard.tsx +18 -6
- package/src/report/components/attempt-detail/AttemptAssertions.tsx +67 -0
- package/src/report/components/attempt-detail/AttemptConversation.tsx +110 -0
- package/src/report/components/attempt-detail/AttemptDiagnostics.tsx +38 -0
- package/src/report/components/attempt-detail/AttemptDiff.tsx +35 -0
- package/src/report/components/attempt-detail/AttemptError.tsx +31 -0
- package/src/report/components/attempt-detail/AttemptFixPrompt.tsx +26 -0
- package/src/report/components/attempt-detail/AttemptSource.tsx +80 -0
- package/src/report/components/attempt-detail/AttemptSummary.tsx +74 -0
- package/src/report/components/attempt-detail/AttemptTimeline.tsx +108 -0
- package/src/report/components/attempt-detail/AttemptTrace.tsx +63 -0
- package/src/report/components/attempt-detail/AttemptUsage.tsx +29 -0
- package/src/report/components/attempt-detail/attempt-components.test.tsx +680 -0
- package/src/report/components/attempt-detail/compute.ts +287 -0
- package/src/report/components/attempt-detail/faces.ts +257 -0
- package/src/report/components/attempt-detail/index.tsx +583 -0
- package/src/report/components/attempt-detail/validate.test.ts +235 -0
- package/src/report/{react → components}/cell.tsx +6 -3
- package/src/report/{report.test.ts → components/compute.test.ts} +316 -46
- package/src/report/{react → components/entity-lists}/AttemptList.tsx +24 -16
- package/src/report/{react → components/entity-lists}/EvalList.tsx +0 -0
- package/src/report/{react → components/entity-lists}/ExperimentList.tsx +23 -20
- package/src/report/components/entity-lists/compute.ts +196 -0
- package/src/report/components/entity-lists/faces.ts +207 -0
- package/src/report/components/entity-lists/index.tsx +241 -0
- package/src/report/components/entity-lists/validate.test.ts +114 -0
- package/src/report/{react → components}/fixtures.ts +2 -2
- package/src/report/{react → components/metric-views}/DeltaTable.tsx +5 -5
- package/src/report/{react → components/metric-views}/MetricBars.tsx +5 -5
- package/src/report/{react → components/metric-views}/MetricLine.tsx +9 -6
- package/src/report/{react → components/metric-views}/MetricMatrix.tsx +6 -6
- package/src/report/{react → components/metric-views}/MetricScatter.tsx +41 -33
- package/src/report/{react → components/metric-views}/MetricTable.tsx +6 -6
- package/src/report/{react → components/metric-views}/Scoreboard.tsx +4 -4
- package/src/report/{compute.ts → components/metric-views/compute.ts} +51 -477
- package/src/report/components/metric-views/faces.ts +423 -0
- package/src/report/components/metric-views/index.tsx +352 -0
- package/src/report/{text → components/metric-views}/plot.ts +1 -1
- package/src/report/components/metric-views/validate.test.ts +211 -0
- package/src/report/{react → components}/render.test.tsx +47 -17
- package/src/report/components/shared-compute.ts +59 -0
- package/src/report/components/shared-faces.ts +30 -0
- package/src/report/components/shared.ts +192 -0
- package/src/report/{react → components/site-components}/CopyFixPrompt.tsx +3 -3
- package/src/report/{react → components/site-components}/HeroCard.tsx +3 -3
- package/src/report/{react → components/site-components}/ScopeWarnings.tsx +4 -4
- package/src/report/{react → components/site-components}/TraceWaterfall.tsx +13 -10
- package/src/report/components/site-components/compute.ts +144 -0
- package/src/report/components/site-components/faces.ts +80 -0
- package/src/report/components/site-components/index.tsx +265 -0
- package/src/report/{scope-warnings.ts → components/site-components/scope-warnings.ts} +3 -3
- package/src/report/{site-components.test.tsx → components/site-components/site-components.test.tsx} +23 -21
- package/src/report/components/site-components/validate.test.ts +89 -0
- package/src/report/{react → components/summaries}/ScopeSummary.tsx +26 -11
- package/src/report/components/summaries/compute.ts +56 -0
- package/src/report/components/summaries/faces.ts +48 -0
- package/src/report/components/summaries/index.tsx +127 -0
- package/src/report/components/summaries/validate.test.ts +49 -0
- package/src/report/definition/grid-layout.test.ts +124 -0
- package/src/report/definition/grid-layout.ts +146 -0
- package/src/report/{primitives.tsx → definition/primitives.tsx} +168 -18
- package/src/report/{report.ts → definition/report.ts} +98 -165
- package/src/report/{shell-head.test.ts → definition/shell-head.test.ts} +2 -2
- package/src/report/{text/table.ts → definition/table-text.ts} +4 -4
- package/src/report/{tree.ts → definition/tree.ts} +56 -20
- package/src/report/index.ts +126 -66
- package/src/report/{aggregate.ts → model/aggregate.ts} +31 -9
- package/src/report/{flag.ts → model/flag.ts} +39 -1
- package/src/report/{format.ts → model/format.ts} +59 -5
- package/src/report/{locale.ts → model/locale.ts} +34 -36
- package/src/report/{metrics.ts → model/metrics.ts} +3 -3
- package/src/report/{types.ts → model/types.ts} +143 -22
- package/src/report/react/index.tsx +52 -26
- package/src/report/{dual-render.test.tsx → runtime/dual-render.test.tsx} +564 -49
- package/src/report/runtime/host.test.ts +44 -0
- package/src/report/runtime/host.ts +138 -0
- package/src/report/{load.ts → runtime/load.ts} +1 -1
- package/src/report/runtime/text.ts +192 -0
- package/src/report/runtime/web.ts +106 -0
- package/src/results/attempt-evidence.ts +5 -1
- package/src/results/host-equivalence.test.ts +53 -2
- package/src/results/select.ts +29 -5
- package/src/runner/attempt.test.ts +48 -1
- package/src/runner/attempt.ts +62 -42
- package/src/runner/cleanup-timeout.test.ts +16 -0
- package/src/runner/cleanup-timeout.ts +23 -0
- package/src/runner/discover.ts +1 -2
- package/src/runner/eval-selection.test.ts +187 -0
- package/src/runner/eval-selection.ts +109 -0
- package/src/runner/experiment-cleanup-registry.test.ts +89 -0
- package/src/runner/experiment-cleanup-registry.ts +39 -0
- package/src/runner/experiment-labels.test.ts +71 -0
- package/src/runner/feedback/agent.test.ts +33 -1
- package/src/runner/feedback/agent.ts +12 -16
- package/src/runner/feedback/ci.test.ts +35 -1
- package/src/runner/feedback/ci.ts +16 -18
- package/src/runner/feedback/coordinator.ts +28 -0
- package/src/runner/feedback/human.test.ts +108 -1
- package/src/runner/feedback/human.ts +71 -21
- package/src/runner/feedback/reducer.test.ts +64 -0
- package/src/runner/feedback/reducer.ts +26 -0
- package/src/runner/feedback/sink.ts +40 -1
- package/src/runner/fingerprint.ts +2 -1
- package/src/runner/report.test.ts +14 -0
- package/src/runner/report.ts +3 -1
- package/src/runner/run.test.ts +390 -12
- package/src/runner/run.ts +259 -26
- package/src/runner/sandbox-selection.test.ts +13 -3
- package/src/runner/sandbox-selection.ts +4 -2
- package/src/runner/types.ts +158 -23
- package/src/sandbox/docker.ts +7 -0
- package/src/sandbox/e2b.ts +12 -13
- package/src/sandbox/types.ts +23 -21
- package/src/scoring/display.test.ts +28 -2
- package/src/scoring/display.ts +38 -5
- package/src/scoring/types.ts +3 -3
- package/src/shared/aggregate.test.ts +52 -0
- package/src/shared/aggregate.ts +29 -0
- package/src/shared/types.ts +6 -1
- package/src/show/command.test.ts +34 -0
- package/src/show/command.ts +16 -0
- package/src/show/compose.ts +1 -1
- package/src/show/index.ts +56 -20
- package/src/show/render.ts +15 -239
- package/src/show/show.test.ts +127 -34
- package/src/view/app/App.test.tsx +1 -1
- package/src/view/app/App.tsx +119 -47
- package/src/view/app/i18n.ts +7 -147
- package/src/view/app/lib/attempt-dialog.test.ts +65 -0
- package/src/view/app/lib/attempt-dialog.ts +69 -0
- package/src/view/app/main.tsx +0 -1
- package/src/view/app/types.ts +3 -86
- package/src/view/artifact-serving.test.ts +22 -38
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +15 -22
- package/src/view/data.test.ts +48 -36
- package/src/view/data.ts +105 -83
- package/src/view/index.ts +1 -1
- package/src/view/server.ts +32 -2
- package/src/view/shared/types.ts +6 -31
- package/src/view/site-parity.test.ts +24 -1
- package/src/view/site.ts +144 -17
- package/src/view/styles.css +0 -805
- package/src/view/view-report.test.ts +141 -22
- package/dist/report/components.d.ts +0 -179
- package/dist/report/components.js +0 -544
- package/dist/report/compute.d.ts +0 -139
- package/dist/report/react/ExperimentComparison.d.ts +0 -10
- package/dist/report/react/ExperimentComparison.js +0 -12
- package/dist/report/react/MetricScatter.d.ts +0 -9
- package/dist/report/react/format.d.ts +0 -3
- package/dist/report/react/format.js +0 -7
- package/dist/report/text/faces.d.ts +0 -45
- package/dist/report/text/faces.js +0 -671
- package/dist/report/web.d.ts +0 -32
- package/dist/report/web.js +0 -48
- package/docs-site/zh/how-to/authoring.mdx +0 -162
- package/docs-site/zh/how-to/custom-reports.mdx +0 -414
- package/docs-site/zh/how-to/experiments.mdx +0 -86
- package/docs-site/zh/how-to/write-experiment.mdx +0 -164
- package/src/report/components.tsx +0 -925
- package/src/report/react/ExperimentComparison.tsx +0 -73
- package/src/report/react/format.ts +0 -9
- package/src/report/text/faces.ts +0 -767
- package/src/report/web.ts +0 -77
- package/src/show/report-host.test.ts +0 -205
- package/src/show/report-host.ts +0 -389
- package/src/view/app/components/AttemptModal.tsx +0 -496
- package/src/view/app/components/CodeView.test.tsx +0 -142
- package/src/view/app/components/CodeView.tsx +0 -310
- package/src/view/app/components/CopyControls.tsx +0 -106
- package/src/view/app/components/Trace.tsx +0 -100
- package/src/view/app/components/Transcript.tsx +0 -157
- package/src/view/app/components/ui/badge.tsx +0 -21
- package/src/view/app/lib/artifact-url.ts +0 -17
- package/src/view/app/lib/attempt-route.test.ts +0 -80
- package/src/view/app/lib/attempt-route.ts +0 -52
- package/src/view/app/lib/format.ts +0 -70
- package/src/view/app/lib/guards.test.ts +0 -108
- package/src/view/app/lib/guards.ts +0 -71
- package/src/view/app/lib/rows.ts +0 -22
- package/src/view/app/lib/transcript-data.tsx +0 -151
- package/src/view/app/lib/verdict.ts +0 -23
- package/src/view/app/shared.ts +0 -8
- /package/dist/report/{react → components/metric-views}/chart-math.d.ts +0 -0
- /package/dist/report/{react → components/metric-views}/chart-math.js +0 -0
- /package/dist/report/{text → components/metric-views}/plot.d.ts +0 -0
- /package/dist/report/{react → components/site-components}/PoweredBy.d.ts +0 -0
- /package/dist/report/{react → components/site-components}/PoweredBy.js +0 -0
- /package/dist/report/{text/layout.d.ts → model/text-layout.d.ts} +0 -0
- /package/dist/report/{text/layout.js → model/text-layout.js} +0 -0
- /package/dist/report/{types.js → model/types.js} +0 -0
- /package/src/report/{react → components/metric-views}/chart-math.test.ts +0 -0
- /package/src/report/{react → components/metric-views}/chart-math.ts +0 -0
- /package/src/report/{react → components/site-components}/PoweredBy.tsx +0 -0
- /package/src/report/{text/layout.ts → model/text-layout.ts} +0 -0
package/src/show/show.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// cases: docs/engineering/unit-tests/reports/cases.md
|
|
2
|
-
// niceeval show 终端宿主的测试(行为规范:docs-site/zh/
|
|
2
|
+
// niceeval show 终端宿主的测试(行为规范:docs-site/zh/tutorials/viewing-results.mdx;
|
|
3
3
|
// 组合语义:docs/feature/reports/architecture.md「Selection 是计算入口」)。覆盖:
|
|
4
4
|
// - 榜单合成口径:每 experiment × eval 取最新判定,局部重跑从更早快照补齐,头部标注合成自几个快照;
|
|
5
5
|
// - 前缀过滤收窄 Selection,覆盖警告分母 = 已知并集 ∩ 范围;
|
|
@@ -22,7 +22,7 @@ import { RESULTS_FORMAT, RESULTS_SCHEMA_VERSION, type EvalResult, type TimingNod
|
|
|
22
22
|
import { selectCurrentResults } from "../results/select.ts";
|
|
23
23
|
import { attemptHistory } from "./compose.ts";
|
|
24
24
|
import { runShow, type ShowFlags } from "./index.ts";
|
|
25
|
-
import { stringWidth } from "../report/text
|
|
25
|
+
import { stringWidth } from "../report/model/text-layout.ts";
|
|
26
26
|
|
|
27
27
|
// ───────────────────────── fixture 工具 ─────────────────────────
|
|
28
28
|
|
|
@@ -152,12 +152,12 @@ describe("默认报告:跨快照合成的现刻水位(ExperimentComparison text
|
|
|
152
152
|
const root = await seedComposedRoot();
|
|
153
153
|
const { out, code } = await show(root, []);
|
|
154
154
|
expect(code).toBe(0);
|
|
155
|
-
expect(out).toContain("No data to plot Cost ×
|
|
155
|
+
expect(out).toContain("No data to plot Cost × Pass rate");
|
|
156
156
|
expect(out).toContain("1 point missing data");
|
|
157
157
|
expect(out).not.toContain("needed to compare");
|
|
158
158
|
expect(out).not.toContain("COMPARISON");
|
|
159
159
|
expect(out).toMatch(/\bbub\s+default\s+bub\s+1s\s+50%/);
|
|
160
|
-
expect(out).toMatch(/1 passed[\s\S]
|
|
160
|
+
expect(out).toMatch(/1 passed[\s\S]*?· 1\s+failed/);
|
|
161
161
|
expect(out).toMatch(/✗ failed\s+fixtures\/button[\s\S]*└─ @1[0-9a-z]{7}[\s\S]*fileChanged/);
|
|
162
162
|
expect(out).toMatch(/✓ passed\s+weather\/brooklyn[\s\S]*└─ @1[0-9a-z]{7}/);
|
|
163
163
|
expect(out).not.toMatch(/\[[EXD⏱,]+\]/);
|
|
@@ -220,8 +220,8 @@ describe("默认报告:跨快照合成的现刻水位(ExperimentComparison text
|
|
|
220
220
|
try {
|
|
221
221
|
const { out, code } = await show(root, []);
|
|
222
222
|
expect(code).toBe(0);
|
|
223
|
-
expect(out).toContain("成本 ×
|
|
224
|
-
expect(out).toContain("1 通过
|
|
223
|
+
expect(out).toContain("成本 × 通过率 没有可绘制的数据");
|
|
224
|
+
expect(out).toContain("1 通过 · 1 失败");
|
|
225
225
|
expect(out).toMatch(/\bbub\s+默认\s+bub\s+1s\s+50%/);
|
|
226
226
|
// 页首 Hero 与尾部页索引同样跟随 locale(内置文案与页名)。
|
|
227
227
|
expect(out.split("\n")[0]).toBe("Eval 运行结果");
|
|
@@ -259,7 +259,10 @@ describe("默认报告:跨快照合成的现刻水位(ExperimentComparison text
|
|
|
259
259
|
expect(code).toBe(0);
|
|
260
260
|
// 成本轴(better: lower)反向,「更好」恒指向右上;两轴都声明 better → 提示在场
|
|
261
261
|
expect(out).toContain("better → upper right");
|
|
262
|
-
|
|
262
|
+
// 无 line 声明:按 agent 归类、不连线;图例一行一个 series(显示键字典序),标记按图例顺序分配
|
|
263
|
+
expect(out).toContain("grouped by agent");
|
|
264
|
+
expect(out).toContain("claude A compare/b");
|
|
265
|
+
expect(out).toContain("codex B compare/a");
|
|
263
266
|
expect(out).toMatch(/\bb\s+large\s+claude\s+1s\s+100%/);
|
|
264
267
|
expect(out).toMatch(/\ba\s+mini\s+codex\s+1s\s+50%/);
|
|
265
268
|
expect(out).toMatch(/✓ passed\s+q1[\s\S]*└─ @1[0-9a-z]{7}/);
|
|
@@ -480,12 +483,23 @@ describe("--report 装载", () => {
|
|
|
480
483
|
" locator: ctx.scope.snapshots[0].evals[0].attempts[0].locator,",
|
|
481
484
|
" },",
|
|
482
485
|
"});",
|
|
486
|
+
"const AttemptPage = () => null;",
|
|
487
|
+
"AttemptPage[FACES] = {",
|
|
488
|
+
" resolve: (props, ctx) => ({ locator: ctx.page.locator, verdict: ctx.page.evidence.result.verdict }),",
|
|
489
|
+
" web: () => null,",
|
|
490
|
+
" text: (props) => `CUSTOM ATTEMPT PAGE · ${props.locator} · ${props.verdict}`,",
|
|
491
|
+
"};",
|
|
483
492
|
"const definition = {",
|
|
484
493
|
' kind: "report",',
|
|
485
494
|
" links: [],",
|
|
486
495
|
" scripts: [],",
|
|
487
496
|
" styles: [],",
|
|
488
|
-
|
|
497
|
+
" pages: [",
|
|
498
|
+
' { id: "report", title: "Report", input: "scope", navigation: true, content: { type: Wrapper, props: {} } },',
|
|
499
|
+
// 报告需要声明一张 attempt-input page,attemptCommand 生成器才存在(architecture.md
|
|
500
|
+
// 「Attempt 详情是一张参数化 page」);navigation:false 不进「其余页」索引,单页断言不受影响。
|
|
501
|
+
' { id: "attempt", title: "Attempt", input: "attempt", navigation: false, content: { type: AttemptPage, props: {} } },',
|
|
502
|
+
" ],",
|
|
489
503
|
"};",
|
|
490
504
|
"Object.defineProperty(definition, DEFINITION, { value: true });",
|
|
491
505
|
"export default definition;",
|
|
@@ -660,6 +674,75 @@ describe("--report 装载", () => {
|
|
|
660
674
|
expect(builtin.err).toContain('page "typo" not found in the built-in report');
|
|
661
675
|
expect(builtin.err).toContain("Available pages: report, attempts, traces");
|
|
662
676
|
});
|
|
677
|
+
|
|
678
|
+
/** 只有一张 scope-input page,没有声明 attempt-input page。 */
|
|
679
|
+
async function writeReportFileNoAttemptPage(dir: string): Promise<string> {
|
|
680
|
+
const path = join(dir, "no-attempt-page.mjs");
|
|
681
|
+
await writeFile(
|
|
682
|
+
path,
|
|
683
|
+
[
|
|
684
|
+
'const FACES = Symbol.for("niceeval.report.faces");',
|
|
685
|
+
'const DEFINITION = Symbol.for("niceeval.report.definition");',
|
|
686
|
+
"const Overview = () => null;",
|
|
687
|
+
"Overview[FACES] = { web: () => null, text: () => \"OVERVIEW\" };",
|
|
688
|
+
"const definition = {",
|
|
689
|
+
' kind: "report",',
|
|
690
|
+
" links: [],",
|
|
691
|
+
" scripts: [],",
|
|
692
|
+
" styles: [],",
|
|
693
|
+
' pages: [{ id: "report", title: "Report", input: "scope", navigation: true, content: { type: Overview, props: {} } }],',
|
|
694
|
+
"};",
|
|
695
|
+
"Object.defineProperty(definition, DEFINITION, { value: true });",
|
|
696
|
+
"export default definition;",
|
|
697
|
+
"",
|
|
698
|
+
].join("\n"),
|
|
699
|
+
"utf-8",
|
|
700
|
+
);
|
|
701
|
+
return path;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
it("show @<locator> --report <file>:选中该定义自己声明的 attempt-input page,不是内建固定首页(docs/engineering/unit-tests/reports/cases.md 第 202 行)", async () => {
|
|
705
|
+
const root = await seedComposedRoot();
|
|
706
|
+
const report = await writeReportFile(root);
|
|
707
|
+
const results = await openResults(root);
|
|
708
|
+
const evals = results.experiments[0]!.snapshots.flatMap((s) => s.evals);
|
|
709
|
+
const locator = evals.find((e) => e.id === "weather/brooklyn")!.attempts[0]!.locator!;
|
|
710
|
+
|
|
711
|
+
const { out, code } = await show(root, [locator], { report });
|
|
712
|
+
expect(code).toBe(0);
|
|
713
|
+
expect(out).toBe(`CUSTOM ATTEMPT PAGE · ${locator} · passed\n`);
|
|
714
|
+
// 不是内建 standard 的固定首页文案(AttemptSummary/AttemptAssessment 等)
|
|
715
|
+
expect(out).not.toContain("niceeval show");
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
it("自定义报告没有 attempt-input page 时,裸 show @<locator> --report <file> 报完整用户反馈,指引三种解决路径,不回退到内建详情(第 204 行)", async () => {
|
|
719
|
+
const root = await seedComposedRoot();
|
|
720
|
+
const report = await writeReportFileNoAttemptPage(root);
|
|
721
|
+
const results = await openResults(root);
|
|
722
|
+
const locator = results.experiments[0]!.latest.evals[0]!.attempts[0]!.locator!;
|
|
723
|
+
|
|
724
|
+
const { err, code } = await show(root, [locator], { report });
|
|
725
|
+
expect(code).toBe(1);
|
|
726
|
+
expect(err).toContain(report);
|
|
727
|
+
expect(err).toContain("has no attempt-input page");
|
|
728
|
+
expect(err).toContain("extends: standard");
|
|
729
|
+
expect(err).toContain("standardAttemptPage");
|
|
730
|
+
expect(err).toContain('input: "attempt"');
|
|
731
|
+
// 不静默回退渲染内建 standard 的详情页(那会让用户以为自定义报告本来就有这页)
|
|
732
|
+
expect(err).not.toContain("Eval Results");
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
it("--source/--execution/--diff 直接读 AttemptEvidence,不经 page content:没有 attempt page 的自定义报告下仍正常工作(第 203 行)", async () => {
|
|
736
|
+
const root = await seedComposedRoot();
|
|
737
|
+
const report = await writeReportFileNoAttemptPage(root);
|
|
738
|
+
const results = await openResults(root);
|
|
739
|
+
const locator = results.experiments[0]!.latest.evals[0]!.attempts[0]!.locator!;
|
|
740
|
+
|
|
741
|
+
const { out, code } = await show(root, [locator], { report, diff: true });
|
|
742
|
+
expect(code).toBe(0);
|
|
743
|
+
expect(out).toContain(locator);
|
|
744
|
+
expect(out).not.toContain("has no attempt-input page");
|
|
745
|
+
});
|
|
663
746
|
});
|
|
664
747
|
|
|
665
748
|
// ───────────────────────── 证据切面:--execution ─────────────────────────
|
|
@@ -812,7 +895,7 @@ describe("show @<locator>", () => {
|
|
|
812
895
|
const locator = results.experiments[0]!.latest.evals[0]!.attempts[0]!.locator!;
|
|
813
896
|
const { out } = await show(root, [locator]);
|
|
814
897
|
expect(out).toContain("gate · Issue 15193");
|
|
815
|
-
expect(out).toContain("
|
|
898
|
+
expect(out).toContain("equals(4)");
|
|
816
899
|
expect(out).toContain("expected: 4");
|
|
817
900
|
expect(out).toContain("received: 1");
|
|
818
901
|
expect(out).toContain("source: evals/manager.eval.ts:40:11");
|
|
@@ -829,10 +912,13 @@ describe("show @<locator>", () => {
|
|
|
829
912
|
|
|
830
913
|
const { out, code } = await show(root, [locator]);
|
|
831
914
|
expect(code).toBe(0);
|
|
832
|
-
|
|
833
|
-
expect(out).toContain(
|
|
834
|
-
expect(out).toContain("
|
|
835
|
-
|
|
915
|
+
// 首行不做整段精确匹配:startedAt 按本机时区格式化,时间部分跨时区不稳定,只断言与时区无关的片段。
|
|
916
|
+
expect(out).toContain(`${locator} · weather/brooklyn · compare/bub · attempt 1 · ✓ passed`);
|
|
917
|
+
expect(out).toContain("· 1.0s");
|
|
918
|
+
// 没有 events/diff 的组件零输出(不是"execution: unavailable"这类占位文案)——
|
|
919
|
+
// 与「Attempt 详情组件族:非空/空证据矩阵」的单元测试同一条契约,这里是集成层确认。
|
|
920
|
+
expect(out).not.toContain("execution:");
|
|
921
|
+
expect(out).not.toContain("changes:");
|
|
836
922
|
expect(out).not.toContain("evidence:");
|
|
837
923
|
expect(out).not.toContain("available:");
|
|
838
924
|
});
|
|
@@ -857,22 +943,22 @@ describe("show @<locator>", () => {
|
|
|
857
943
|
|
|
858
944
|
const { out, code } = await show(root, [locator]);
|
|
859
945
|
expect(code).toBe(0);
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
expect(out).toContain("
|
|
946
|
+
// 同上:跳过 startedAt 的时区敏感部分,只断言与时区无关的片段。
|
|
947
|
+
expect(out).toContain(`${locator} · agent-029 · compare/claude-e2b · attempt 1 · ! errored`);
|
|
948
|
+
expect(out).toContain("· 1.0s");
|
|
949
|
+
// 结构化 error 块:code 折进 error: 首行,phase/message/cause 各一行(AttemptError)
|
|
950
|
+
expect(out).toContain("error: sandbox-rate-limit");
|
|
863
951
|
expect(out).toContain("phase: sandbox.create");
|
|
864
|
-
expect(out).toContain("code: sandbox-rate-limit");
|
|
865
952
|
expect(out).toContain("message: E2B sandbox allocation failed after 5 attempts");
|
|
866
953
|
expect(out).toContain("cause: RateLimitError · too many concurrent sandboxes");
|
|
867
|
-
// attempt 级 diagnostics
|
|
868
|
-
expect(out).toContain("
|
|
869
|
-
expect(out).toContain("warning ·
|
|
870
|
-
|
|
871
|
-
// errored 且无断言:不打印空的 assertions 汇总行
|
|
954
|
+
// attempt 级 diagnostics 按 lifecycle phase 分组(AttemptDiagnostics),标题即 phase 名
|
|
955
|
+
expect(out).toContain("sandbox.teardown:");
|
|
956
|
+
expect(out).toContain("warning · teardown-failed — container stop timed out");
|
|
957
|
+
// errored 且无断言:AttemptAssertions 零输出,不打印空的 assertions 汇总行
|
|
872
958
|
expect(out).not.toContain("assertions:");
|
|
873
959
|
});
|
|
874
960
|
|
|
875
|
-
it("
|
|
961
|
+
it("证据下钻命令散落在各自区块行尾(带 locator),不存在的能力不生成假命令,也没有合并式 available/next 尾块", async () => {
|
|
876
962
|
const root = await makeRoot();
|
|
877
963
|
const dir = await writeSnapshot(root, "2026-07-08T10-00-00-000Z", { experimentId: "compare/bub", startedAt: "2026-07-08T10:00:00.000Z" }, [
|
|
878
964
|
res("weather/brooklyn", "failed", { hasEvents: true }),
|
|
@@ -889,9 +975,14 @@ describe("show @<locator>", () => {
|
|
|
889
975
|
|
|
890
976
|
const { out, code } = await show(root, [locator]);
|
|
891
977
|
expect(code).toBe(0);
|
|
892
|
-
|
|
978
|
+
// AttemptSource / AttemptConversation 各自行尾带命令(不是集中列在一个 available: 块里)
|
|
979
|
+
expect(out).toContain(`niceeval show ${locator} --source`);
|
|
980
|
+
expect(out).toContain(`niceeval show ${locator} --execution`);
|
|
981
|
+
// 没有 diff/timing 能力(fixture 没写 diff.json,也没有 phases)时不生成假命令
|
|
893
982
|
expect(out).not.toContain(`niceeval show ${locator} --diff`);
|
|
983
|
+
expect(out).not.toContain(`niceeval show ${locator} --timing`);
|
|
894
984
|
expect(out).not.toContain("evidence:");
|
|
985
|
+
expect(out).not.toContain("available:");
|
|
895
986
|
expect(out).not.toContain("next:");
|
|
896
987
|
});
|
|
897
988
|
|
|
@@ -1054,7 +1145,7 @@ describe("show @<locator>", () => {
|
|
|
1054
1145
|
expect(out).not.toContain("git show ×");
|
|
1055
1146
|
});
|
|
1056
1147
|
|
|
1057
|
-
it("attempt 首页 timing
|
|
1148
|
+
it("attempt 首页 timing 逐阶段一行,收尾段单独归拢(AttemptTimeline 不做单行宽度收窄,--timing 才是完整分解)", async () => {
|
|
1058
1149
|
const root = await makeRoot();
|
|
1059
1150
|
const dir = await writeSnapshot(root, "2026-07-08T10-00-00-000Z", { experimentId: "compare/bub", startedAt: "2026-07-08T10:00:00.000Z" }, [
|
|
1060
1151
|
res("weather/brooklyn", "passed", {
|
|
@@ -1080,15 +1171,17 @@ describe("show @<locator>", () => {
|
|
|
1080
1171
|
const locator = results.experiments[0]!.latest.evals[0]!.attempts[0]!.locator!;
|
|
1081
1172
|
|
|
1082
1173
|
const { out } = await show(root, [locator]);
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
expect(
|
|
1086
|
-
expect(
|
|
1087
|
-
expect(
|
|
1088
|
-
expect(
|
|
1089
|
-
expect(
|
|
1090
|
-
expect(
|
|
1091
|
-
expect(
|
|
1174
|
+
// 每个主链阶段各占一行(不再是旧格式的单行拼接,那条规则本身是为单行可读服务的,
|
|
1175
|
+
// 多行块没有这个约束了),收尾段(sandbox.stop)单独归拢在 teardown: 之下。
|
|
1176
|
+
expect(out).toContain("timing: 1m 24s");
|
|
1177
|
+
expect(out).toContain("sandbox.queue 0ms");
|
|
1178
|
+
expect(out).toContain("sandbox.create 3.0s");
|
|
1179
|
+
expect(out).toContain("workspace.baseline 310ms");
|
|
1180
|
+
expect(out).toContain("telemetry.configure 358ms");
|
|
1181
|
+
expect(out).toContain("eval.run 45.0s");
|
|
1182
|
+
expect(out).toContain("telemetry.collect 35.3s");
|
|
1183
|
+
expect(out).toContain("teardown:");
|
|
1184
|
+
expect(out).toContain("sandbox.stop 8.5s");
|
|
1092
1185
|
});
|
|
1093
1186
|
|
|
1094
1187
|
it("语法不对的 locator 报「not a valid attempt locator」,退出码 1,不崩", async () => {
|
|
@@ -22,7 +22,7 @@ const reportPages = {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
function dataWithShell(report: ViewData["report"]): ViewData {
|
|
25
|
-
return { composedRuns: 1,
|
|
25
|
+
return { composedRuns: 1, ...(report !== undefined ? { report } : {}) };
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
describe("外壳:宿主恒有 NiceEval 品牌位、无 hero,导航只有报告页", () => {
|
package/src/view/app/App.tsx
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties } from "react";
|
|
2
2
|
import { detectLocale, makeTranslator, persistLocale, setDocumentLocale } from "./i18n.ts";
|
|
3
|
-
import type { Locale, LocalizedText, ReportSlotHtml, Tab, ViewData, ViewReportPageMeta
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import type { Locale, LocalizedText, ReportSlotHtml, Tab, ViewData, ViewReportPageMeta } from "./types.ts";
|
|
4
|
+
import {
|
|
5
|
+
attemptHrefFor,
|
|
6
|
+
attemptLocatorFromHref,
|
|
7
|
+
hashForAttempt,
|
|
8
|
+
locatorFromHash,
|
|
9
|
+
parseAttemptDocument,
|
|
10
|
+
type AttemptDocumentContent,
|
|
11
|
+
} from "./lib/attempt-dialog.ts";
|
|
12
|
+
import { Dialog, DialogClose, DialogContent, DialogTitle } from "./components/ui/dialog.tsx";
|
|
7
13
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./components/ui/tabs.tsx";
|
|
8
14
|
|
|
9
15
|
// 导航组成只有一条规则(docs/feature/reports/view.md「页面构成」):导航项 = 报告定义声明的页,
|
|
@@ -17,6 +23,20 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "./components/ui/tabs.t
|
|
|
17
23
|
// utm_medium 区分点击来自哪个品牌位(shell.md「行为约束」)。
|
|
18
24
|
const BRAND_HREF = "https://niceeval.com/?utm_source=report&utm_medium=brand";
|
|
19
25
|
|
|
26
|
+
/** 屏幕阅读器可用、视觉上不占位:Radix Dialog 需要一个可访问标题,内容本身(身份 / verdict)
|
|
27
|
+
* 已经在 dialog 里可见,不需要再视觉重复一遍。 */
|
|
28
|
+
const VISUALLY_HIDDEN: CSSProperties = {
|
|
29
|
+
position: "absolute",
|
|
30
|
+
width: 1,
|
|
31
|
+
height: 1,
|
|
32
|
+
padding: 0,
|
|
33
|
+
margin: -1,
|
|
34
|
+
overflow: "hidden",
|
|
35
|
+
clip: "rect(0, 0, 0, 0)",
|
|
36
|
+
whiteSpace: "nowrap",
|
|
37
|
+
border: 0,
|
|
38
|
+
};
|
|
39
|
+
|
|
20
40
|
/**
|
|
21
41
|
* LocalizedText 的确定回退(docs/feature/reports/library/shell.md):当前 locale → en →
|
|
22
42
|
* 按 locale 键字典序的第一个非空值。字符串原样返回。
|
|
@@ -32,19 +52,6 @@ export function localizedText(text: LocalizedText | undefined, locale: Locale):
|
|
|
32
52
|
return undefined;
|
|
33
53
|
}
|
|
34
54
|
|
|
35
|
-
/** 初始 URL → 直接打开的 attempt:先认 #/attempt/@<locator> 深链,回退旧版 ?modal= 参数。 */
|
|
36
|
-
function modalResultFromLocation(snapshots: ViewData["snapshots"]): ViewResult | null {
|
|
37
|
-
const locator = parseAttemptHash(location.hash);
|
|
38
|
-
if (locator) {
|
|
39
|
-
const found = resolveAttemptLocator(snapshots, locator);
|
|
40
|
-
if (found) return found;
|
|
41
|
-
// 定位不到(locator 不在、快照未加载、旧格式数据):不开空 modal,页面照常渲染。
|
|
42
|
-
console.warn(unresolvedAttemptWarning(location.hash));
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
return resultFromUrl(snapshots);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
55
|
/**
|
|
49
56
|
* 报告槽:React 19 对 dangerouslySetInnerHTML 只比较对象身份,身份一变就无条件重设
|
|
50
57
|
* innerHTML(不再比对 __html 字符串值)。{__html} 必须 memo 住,否则 App 任意一次重渲染
|
|
@@ -68,7 +75,6 @@ function hashForTab(tab: Tab): string {
|
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
export function App({ data, reportPages }: { data: ViewData; reportPages: Record<string, ReportSlotHtml> }) {
|
|
71
|
-
const snapshots = data.snapshots ?? [];
|
|
72
78
|
const [locale, setLocale] = useState<Locale>(() => detectLocale());
|
|
73
79
|
const t = useMemo(() => makeTranslator(locale), [locale]);
|
|
74
80
|
|
|
@@ -79,11 +85,16 @@ export function App({ data, reportPages }: { data: ViewData; reportPages: Record
|
|
|
79
85
|
const initialPageId = data.report?.initialPageId ?? pages[0]!.id;
|
|
80
86
|
|
|
81
87
|
const [tab, setTab] = useState<Tab>(() => tabFromHash(location.hash, pages) ?? `page:${initialPageId}`);
|
|
82
|
-
|
|
83
|
-
//
|
|
88
|
+
|
|
89
|
+
// attempt 详情弹窗:内容是独立文档(attempt/<locator>.html)fetch 回来的同一份 server-rendered
|
|
90
|
+
// 片段,不维护第二份客户端渲染(docs/feature/reports/view.md「静态导出」)。dialogLocator 为
|
|
91
|
+
// null 即关闭。
|
|
92
|
+
const [dialogLocator, setDialogLocator] = useState<string | null>(null);
|
|
93
|
+
const [dialogContent, setDialogContent] = useState<AttemptDocumentContent | null>(null);
|
|
94
|
+
// 当前 dialog 的 hash 历史条目前面是否还有本页条目(点击链接 push 的 / 前进键回到的):
|
|
84
95
|
// true → UI 关闭走 history.back(),前进键还能重新打开;false(深链直接落地)→ 原地抹 hash,
|
|
85
96
|
// 免得 back 把用户弹出站外。
|
|
86
|
-
const
|
|
97
|
+
const dialogOwnsHistory = useRef(false);
|
|
87
98
|
|
|
88
99
|
useEffect(() => {
|
|
89
100
|
setDocumentLocale(locale);
|
|
@@ -98,48 +109,88 @@ export function App({ data, reportPages }: { data: ViewData; reportPages: Record
|
|
|
98
109
|
document.title = shellTitle;
|
|
99
110
|
}, [shellTitle]);
|
|
100
111
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
112
|
+
/** fetch 一个 locator 的独立文档、抠出两种语言内容并打开 dialog;定位不到就直说,不开空 dialog。 */
|
|
113
|
+
const openAttempt = useCallback(async (locator: string, ownsHistory: boolean) => {
|
|
114
|
+
try {
|
|
115
|
+
const res = await fetch(attemptHrefFor(locator));
|
|
116
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
117
|
+
const content = parseAttemptDocument(await res.text());
|
|
118
|
+
if (!content) throw new Error("response is not a recognized attempt document");
|
|
119
|
+
dialogOwnsHistory.current = ownsHistory;
|
|
120
|
+
setDialogLocator(locator);
|
|
121
|
+
setDialogContent(content);
|
|
122
|
+
} catch (e) {
|
|
123
|
+
console.warn(
|
|
124
|
+
`[niceeval view] failed to open attempt "${locator}": ${e instanceof Error ? e.message : String(e)}`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}, []);
|
|
128
|
+
|
|
129
|
+
const closeDialog = useCallback(() => {
|
|
130
|
+
setDialogLocator(null);
|
|
131
|
+
setDialogContent(null);
|
|
132
|
+
if (dialogOwnsHistory.current) {
|
|
133
|
+
dialogOwnsHistory.current = false;
|
|
105
134
|
history.back();
|
|
106
135
|
return;
|
|
107
136
|
}
|
|
108
137
|
try {
|
|
109
|
-
//
|
|
110
|
-
history.replaceState(null, "", location.pathname);
|
|
138
|
+
// 深链直接落地:没有可回退的本页条目,原地还原成无 hash 的 URL。
|
|
139
|
+
history.replaceState(null, "", location.pathname + location.search);
|
|
111
140
|
} catch {
|
|
112
141
|
// 还原 URL 失败不影响关闭。
|
|
113
142
|
}
|
|
114
143
|
}, []);
|
|
115
144
|
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
// attempt
|
|
119
|
-
//
|
|
145
|
+
// 初始 URL 已经是 #/attempt/@<locator> 深链:直接打开(不经 hashchange——那只在后续变化时触发)。
|
|
146
|
+
// 有效根即收窄后的结果(view.md「打开与收窄」):这份路由对完整结果根解析,不受当前统计口径
|
|
147
|
+
// (现刻水位)限制,深链因此对历史 attempt 同样可达;收窄之外的 locator 由这份文档自身
|
|
148
|
+
// 的宿主寻址语义处理(本地越过收窄解析,导出站按证据缺失呈现,不是这里的关注点)。
|
|
149
|
+
// 空依赖数组是有意的:只在挂载时检查一次初始 hash,openAttempt 本身是 useCallback(deps: [])
|
|
150
|
+
// 的稳定引用,不会随后续渲染变化。
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
const locator = locatorFromHash(location.hash);
|
|
153
|
+
if (locator) void openAttempt(locator, false);
|
|
154
|
+
}, [openAttempt]);
|
|
155
|
+
|
|
156
|
+
// 浏览器前进/后退、手改 hash、页内链接点击(经下面的点击拦截转成 hash 变化)统一从
|
|
157
|
+
// hashchange 分发:attempt hash 开证据室 dialog,页 hash 切当前 tab。
|
|
120
158
|
useEffect(() => {
|
|
121
159
|
const onHashChange = () => {
|
|
122
|
-
const locator =
|
|
160
|
+
const locator = locatorFromHash(location.hash);
|
|
123
161
|
if (locator) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
console.warn(unresolvedAttemptWarning(location.hash));
|
|
127
|
-
setModalResult(null);
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
// 经浏览器导航打开:前一条历史仍是本页,UI 关闭可以安全 back()。
|
|
131
|
-
modalOwnsHistory.current = true;
|
|
132
|
-
setModalResult(found);
|
|
162
|
+
// 经浏览器导航打开:前一条历史仍是本页,dialog 关闭可以安全 back()。
|
|
163
|
+
void openAttempt(locator, true);
|
|
133
164
|
return;
|
|
134
165
|
}
|
|
135
|
-
|
|
136
|
-
|
|
166
|
+
setDialogLocator(null);
|
|
167
|
+
setDialogContent(null);
|
|
137
168
|
const routed = tabFromHash(location.hash, pages);
|
|
138
169
|
if (routed) setTab(routed);
|
|
139
170
|
};
|
|
140
171
|
window.addEventListener("hashchange", onHashChange);
|
|
141
172
|
return () => window.removeEventListener("hashchange", onHashChange);
|
|
142
|
-
}, [
|
|
173
|
+
}, [pages, openAttempt]);
|
|
174
|
+
|
|
175
|
+
// 拦截 attempt 文档链接(报告页里的 locator 引用,attemptHref 缺省产出的
|
|
176
|
+
// `attempt/<encodeURIComponent(locator)>.html`):点击时改写成 hash 路由,交给上面的
|
|
177
|
+
// hashchange 统一打开——无 JavaScript 时这些链接原样导航到独立文档,同样完整可读。
|
|
178
|
+
// 修饰键 / 非左键点击放行,让「新标签页打开」这类浏览器原生行为不受影响。
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
const onClick = (e: MouseEvent) => {
|
|
181
|
+
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
|
|
182
|
+
const target = e.target;
|
|
183
|
+
if (!(target instanceof Element)) return;
|
|
184
|
+
const anchor = target.closest("a[href]");
|
|
185
|
+
if (!anchor) return;
|
|
186
|
+
const locator = attemptLocatorFromHref(anchor.getAttribute("href") ?? "");
|
|
187
|
+
if (!locator) return;
|
|
188
|
+
e.preventDefault();
|
|
189
|
+
location.hash = hashForAttempt(locator);
|
|
190
|
+
};
|
|
191
|
+
document.addEventListener("click", onClick);
|
|
192
|
+
return () => document.removeEventListener("click", onClick);
|
|
193
|
+
}, []);
|
|
143
194
|
|
|
144
195
|
const selectTab = useCallback((value: Tab) => {
|
|
145
196
|
setTab(value);
|
|
@@ -202,7 +253,7 @@ export function App({ data, reportPages }: { data: ViewData; reportPages: Record
|
|
|
202
253
|
<TabsContent key={`page:${page.id}`} value={`page:${page.id}`} id={`tab-page-${page.id}`}>
|
|
203
254
|
{/* 报告槽:server 侧逐页渲染好的静态 HTML(含 <Style> 产物),按当前页与界面语言
|
|
204
255
|
摆放对应块;hero、品牌行、Scope 警告、批量修复 prompt 都是页内组件,壳不再渲染。
|
|
205
|
-
attempt 深链是普通 <a href="
|
|
256
|
+
attempt 深链是普通 <a href="attempt/…html">,经上面的点击拦截打开 dialog。 */}
|
|
206
257
|
<ReportSlot html={reportPages[page.id]?.[locale] || reportPages[page.id]?.en || ""} />
|
|
207
258
|
</TabsContent>
|
|
208
259
|
))}
|
|
@@ -212,7 +263,28 @@ export function App({ data, reportPages }: { data: ViewData; reportPages: Record
|
|
|
212
263
|
<span className="site-footer-text">{footerText}</span>
|
|
213
264
|
</footer>
|
|
214
265
|
) : null}
|
|
215
|
-
{
|
|
266
|
+
{dialogLocator && dialogContent ? (
|
|
267
|
+
<Dialog open onOpenChange={(o) => { if (!o) closeDialog(); }}>
|
|
268
|
+
<DialogContent aria-describedby={undefined}>
|
|
269
|
+
{/* 屏幕阅读器用的可访问标题:视觉上隐藏,身份 / verdict 等实际内容已经在下面
|
|
270
|
+
fetch 回来的片段里可见,这里不重复渲染。 */}
|
|
271
|
+
<DialogTitle style={VISUALLY_HIDDEN}>{t("dialog.attemptTitle")}</DialogTitle>
|
|
272
|
+
<div className="flex min-w-0 shrink-0 items-center justify-end border-b border-line px-7 pb-3 pt-4">
|
|
273
|
+
<DialogClose
|
|
274
|
+
aria-label={t("action.close")}
|
|
275
|
+
className="grid h-7 w-7 shrink-0 place-items-center rounded-md border border-transparent text-sm text-muted transition-colors hover:border-line hover:bg-panel-2 hover:text-text"
|
|
276
|
+
>
|
|
277
|
+
x
|
|
278
|
+
</DialogClose>
|
|
279
|
+
</div>
|
|
280
|
+
{/* 与直接打开 attempt/<locator>.html 看到的是同一份 server-rendered 片段
|
|
281
|
+
(docs/engineering/unit-tests/reports/cases.md 第 207 行),不是客户端重新渲染。 */}
|
|
282
|
+
<div className="flex-1 overflow-y-auto px-7 pb-7 pt-2">
|
|
283
|
+
<ReportSlot html={dialogContent[locale] || dialogContent.en} />
|
|
284
|
+
</div>
|
|
285
|
+
</DialogContent>
|
|
286
|
+
</Dialog>
|
|
287
|
+
) : null}
|
|
216
288
|
</Tabs>
|
|
217
289
|
);
|
|
218
290
|
}
|