-
Notifications
You must be signed in to change notification settings - Fork 22
(MOT-4299) Add native local Harness E2E dashboard #749
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="utf-8"> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| <meta name="description" content="Compare two Harness E2E executions."> | ||
| <meta name="color-scheme" content="dark light"> | ||
| <title>Compare Harness E2E executions</title> | ||
| <link rel="stylesheet" href="./styles.css"> | ||
| </head> | ||
| <body> | ||
| <a class="skip-link" href="#main">Skip to comparison</a> | ||
| <div class="ambient ambient-one" aria-hidden="true"></div> | ||
| <div class="ambient ambient-two" aria-hidden="true"></div> | ||
|
|
||
| <header class="topbar"> | ||
| <a class="brand" href="./index.html" aria-label="Harness E2E dashboard"> | ||
| <span class="brand-copy"><strong>iii</strong><span>Harness benchmarks</span></span> | ||
| </a> | ||
| <nav class="topbar-actions" aria-label="Comparison actions"> | ||
| <a class="button" href="./index.html">← All executions</a> | ||
| </nav> | ||
| </header> | ||
|
|
||
| <main id="main" class="page-shell compare-shell"> | ||
| <section class="page-heading" aria-labelledby="page-title"> | ||
| <div> | ||
| <div class="eyebrow"><span class="live-dot" aria-hidden="true"></span>Local E2E</div> | ||
| <h1 id="page-title">Execution comparison</h1> | ||
| <p>Execution B minus execution A. Different scenarios and contracts remain visible.</p> | ||
| </div> | ||
| </section> | ||
|
|
||
| <section id="compare-empty" class="empty-state" hidden> | ||
| <div class="empty-icon" aria-hidden="true">⌁</div> | ||
| <h2>Select two existing executions</h2> | ||
| <p>Return to the execution dashboard and mark any two rows for comparison.</p> | ||
| </section> | ||
|
|
||
| <div id="compare-content" class="compare-content" hidden> | ||
| <section class="panel" aria-labelledby="selected-heading"> | ||
| <div class="panel-heading"> | ||
| <div> | ||
| <div class="section-kicker">Selection</div> | ||
| <h2 id="selected-heading">A versus B</h2> | ||
| </div> | ||
| </div> | ||
| <div id="compare-selection" class="compare-selection-grid"></div> | ||
| <ul id="compare-warnings" class="compare-warning-list"></ul> | ||
| </section> | ||
|
|
||
| <section class="panel executions-panel" aria-labelledby="overall-heading"> | ||
| <div class="panel-heading"> | ||
| <div> | ||
| <div class="section-kicker">Whole execution</div> | ||
| <h2 id="overall-heading">Overall delta</h2> | ||
| <p class="trend-description">Positive and negative deltas always mean B minus A.</p> | ||
| </div> | ||
| </div> | ||
| <div id="compare-metrics" class="compare-metric-grid"></div> | ||
| </section> | ||
|
|
||
| <section class="panel executions-panel" aria-labelledby="scenario-heading"> | ||
| <div class="panel-heading"> | ||
| <div> | ||
| <div class="section-kicker">Per scenario</div> | ||
| <h2 id="scenario-heading">Scenario deltas</h2> | ||
| <p class="trend-description">Changed contracts are compared but explicitly marked.</p> | ||
| </div> | ||
| </div> | ||
| <div class="table-wrap"> | ||
| <table class="compare-table"> | ||
| <thead> | ||
| <tr> | ||
| <th scope="col">Scenario</th> | ||
| <th scope="col">Execution A</th> | ||
| <th scope="col">Execution B</th> | ||
| <th scope="col">Δ score</th> | ||
| <th scope="col">Δ tokens</th> | ||
| <th scope="col">Δ cost</th> | ||
| <th scope="col">Δ time</th> | ||
| <th scope="col">Contract</th> | ||
| </tr> | ||
| </thead> | ||
| <tbody id="compare-scenarios"></tbody> | ||
| </table> | ||
| </div> | ||
| </section> | ||
| </div> | ||
| </main> | ||
|
|
||
| <script>window.HARNESS_EXECUTIONS = window.HARNESS_EXECUTIONS || null;</script> | ||
| <script src="./execution-data.js"></script> | ||
| <script src="./executions.js"></script> | ||
| <script src="./compare.js"></script> | ||
| </body> | ||
| </html> |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,199 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||
| (function renderHarnessExecutionComparison() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| "use strict"; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const api = window.HarnessExecutionData; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const manifest = window.HARNESS_EXECUTIONS || { executions: [] }; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const history = { | ||||||||||||||||||||||||||||||||||||||||||||||||
| executions: (manifest.executions || []).map(api.normalizeExecution), | ||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const parameters = new URLSearchParams(window.location.search); | ||||||||||||||||||||||||||||||||||||||||||||||||
| const left = api.findExecution(history, parameters.get("left") || ""); | ||||||||||||||||||||||||||||||||||||||||||||||||
| const right = api.findExecution(history, parameters.get("right") || ""); | ||||||||||||||||||||||||||||||||||||||||||||||||
| const elements = { | ||||||||||||||||||||||||||||||||||||||||||||||||
| content: document.querySelector("#compare-content"), | ||||||||||||||||||||||||||||||||||||||||||||||||
| empty: document.querySelector("#compare-empty"), | ||||||||||||||||||||||||||||||||||||||||||||||||
| metrics: document.querySelector("#compare-metrics"), | ||||||||||||||||||||||||||||||||||||||||||||||||
| scenarios: document.querySelector("#compare-scenarios"), | ||||||||||||||||||||||||||||||||||||||||||||||||
| selection: document.querySelector("#compare-selection"), | ||||||||||||||||||||||||||||||||||||||||||||||||
| warnings: document.querySelector("#compare-warnings"), | ||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| function escapeHtml(value) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return String(value ?? "") | ||||||||||||||||||||||||||||||||||||||||||||||||
| .replaceAll("&", "&") | ||||||||||||||||||||||||||||||||||||||||||||||||
| .replaceAll("<", "<") | ||||||||||||||||||||||||||||||||||||||||||||||||
| .replaceAll(">", ">") | ||||||||||||||||||||||||||||||||||||||||||||||||
| .replaceAll('"', """) | ||||||||||||||||||||||||||||||||||||||||||||||||
| .replaceAll("'", "'"); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| function number(value, digits = 1) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return typeof value === "number" && Number.isFinite(value) | ||||||||||||||||||||||||||||||||||||||||||||||||
| ? new Intl.NumberFormat("en-US", { maximumFractionDigits: digits }).format(value) | ||||||||||||||||||||||||||||||||||||||||||||||||
| : "—"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| function percent(value) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return typeof value === "number" ? `${number(value, 1)}%` : "—"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| function currency(value) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return typeof value === "number" | ||||||||||||||||||||||||||||||||||||||||||||||||
| ? new Intl.NumberFormat("en-US", { | ||||||||||||||||||||||||||||||||||||||||||||||||
| style: "currency", | ||||||||||||||||||||||||||||||||||||||||||||||||
| currency: "USD", | ||||||||||||||||||||||||||||||||||||||||||||||||
| minimumFractionDigits: value < 1 ? 3 : 2, | ||||||||||||||||||||||||||||||||||||||||||||||||
| maximumFractionDigits: value < 1 ? 3 : 2, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }).format(value) | ||||||||||||||||||||||||||||||||||||||||||||||||
| : "—"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| function duration(value) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeof value !== "number") return "—"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (Math.abs(value) < 60) return `${number(value, 1)}s`; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const sign = value < 0 ? "−" : ""; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const absolute = Math.abs(value); | ||||||||||||||||||||||||||||||||||||||||||||||||
| return `${sign}${Math.floor(absolute / 60)}m ${String(Math.round(absolute % 60)).padStart(2, "0")}s`; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| function date(value) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return value && !Number.isNaN(Date.parse(value)) | ||||||||||||||||||||||||||||||||||||||||||||||||
| ? new Intl.DateTimeFormat("en-US", { | ||||||||||||||||||||||||||||||||||||||||||||||||
| month: "short", | ||||||||||||||||||||||||||||||||||||||||||||||||
| day: "numeric", | ||||||||||||||||||||||||||||||||||||||||||||||||
| year: "numeric", | ||||||||||||||||||||||||||||||||||||||||||||||||
| hour: "2-digit", | ||||||||||||||||||||||||||||||||||||||||||||||||
| minute: "2-digit", | ||||||||||||||||||||||||||||||||||||||||||||||||
| }).format(new Date(value)) | ||||||||||||||||||||||||||||||||||||||||||||||||
| : "Unknown date"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| function status(value) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||
| passed: "Passed", | ||||||||||||||||||||||||||||||||||||||||||||||||
| quality_advisory: "Quality advisory", | ||||||||||||||||||||||||||||||||||||||||||||||||
| hard_gate_failed: "Hard gate failed", | ||||||||||||||||||||||||||||||||||||||||||||||||
| technical_failed: "Technical failure", | ||||||||||||||||||||||||||||||||||||||||||||||||
| infra_failed: "Infrastructure failure", | ||||||||||||||||||||||||||||||||||||||||||||||||
| incomplete: "Incomplete", | ||||||||||||||||||||||||||||||||||||||||||||||||
| cancelled: "Cancelled", | ||||||||||||||||||||||||||||||||||||||||||||||||
| }[value] || "Unknown"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+71
to
+81
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Render the active local execution status. The local presenter emits Based on supplied upstream contract, the local presenter emits Proposed fix technical_failed: "Technical failure",
infra_failed: "Infrastructure failure",
incomplete: "Incomplete",
+ running: "Running",
cancelled: "Cancelled",📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| function subjectSummary(execution) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const subjects = (execution.subjects || []).map( | ||||||||||||||||||||||||||||||||||||||||||||||||
| (subject) => `${subject.provider || "unknown"}/${subject.model || "unknown"}`, | ||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||
| return subjects.length === 1 ? subjects[0] : `${subjects.length} subjects`; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| function selectionCard(execution, side) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const label = execution.label || date(execution.completed_at); | ||||||||||||||||||||||||||||||||||||||||||||||||
| return ` | ||||||||||||||||||||||||||||||||||||||||||||||||
| <article class="compare-selection-card"> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <span>Execution ${side}</span> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <h2>${escapeHtml(label)}</h2> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <div class="compare-selection-meta"> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <small>${escapeHtml(date(execution.completed_at))}</small> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <small>${escapeHtml(subjectSummary(execution))}</small> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <small>${escapeHtml(status(execution.status))}</small> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <small>${number(execution.requested_runs, 0)} run${execution.requested_runs === 1 ? "" : "s"}</small> | ||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <a class="text-link" href="./execution.html?id=${encodeURIComponent(execution.id)}">Open diagnostic detail →</a> | ||||||||||||||||||||||||||||||||||||||||||||||||
| </article>`; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| function signed(value, formatter) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeof value !== "number") return "—"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (value === 0) return formatter(0); | ||||||||||||||||||||||||||||||||||||||||||||||||
| return `${value > 0 ? "+" : ""}${formatter(value)}`; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| function renderMetric(definition, comparison) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const values = definition.values(comparison); | ||||||||||||||||||||||||||||||||||||||||||||||||
| let deltaClass = ""; | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeof values.delta === "number" && values.delta !== 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const improved = definition.lowerIsBetter ? values.delta < 0 : values.delta > 0; | ||||||||||||||||||||||||||||||||||||||||||||||||
| deltaClass = improved ? "compare-delta-improved" : "compare-delta-regressed"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| return ` | ||||||||||||||||||||||||||||||||||||||||||||||||
| <article class="compare-metric-card"> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <span>${escapeHtml(definition.label)}</span> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <div class="compare-metric-values"> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <strong>${definition.format(values.left)}</strong> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <small>→ ${definition.format(values.right)}</small> | ||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <div class="compare-delta ${deltaClass}">${signed(values.delta, definition.format)} B−A</div> | ||||||||||||||||||||||||||||||||||||||||||||||||
| </article>`; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| function blockingFailures(execution) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return ["hard_gate_failures", "technical_failures", "missing_reports"].reduce( | ||||||||||||||||||||||||||||||||||||||||||||||||
| (total, field) => total + Number(execution.totals?.[field] || 0), | ||||||||||||||||||||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| function sideScenario(value) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (!value) return '<span class="text-incomplete">Not run</span>'; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const score = typeof value.score === "number" ? ` · score ${number(value.score, 1)}` : ""; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const passRate = | ||||||||||||||||||||||||||||||||||||||||||||||||
| typeof value.passRate === "number" ? ` · ${number(value.passRate * 100, 1)}% pass` : ""; | ||||||||||||||||||||||||||||||||||||||||||||||||
| return `<span class="table-status status-${value.status === "passed" ? "pass" : value.status === "quality_advisory" ? "advisory" : "fail"}">${escapeHtml(status(value.status))}</span>${score}${passRate}`; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if (!left || !right || left.id === right.id) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| elements.empty.hidden = false; | ||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const comparison = api.compareExecutions(left, right); | ||||||||||||||||||||||||||||||||||||||||||||||||
| elements.content.hidden = false; | ||||||||||||||||||||||||||||||||||||||||||||||||
| elements.selection.innerHTML = | ||||||||||||||||||||||||||||||||||||||||||||||||
| selectionCard(comparison.left, "A") + selectionCard(comparison.right, "B"); | ||||||||||||||||||||||||||||||||||||||||||||||||
| elements.warnings.innerHTML = comparison.warnings | ||||||||||||||||||||||||||||||||||||||||||||||||
| .map((warning) => `<li>${escapeHtml(warning)}</li>`) | ||||||||||||||||||||||||||||||||||||||||||||||||
| .join(""); | ||||||||||||||||||||||||||||||||||||||||||||||||
| elements.warnings.hidden = comparison.warnings.length === 0; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const metricDefinitions = [ | ||||||||||||||||||||||||||||||||||||||||||||||||
| { label: "Pass rate", format: percent, lowerIsBetter: false, values: (item) => item.totals.scenario_pass_rate }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| { label: "Quality score", format: number, lowerIsBetter: false, values: (item) => item.totals.average_score }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| label: "Blocking failures", | ||||||||||||||||||||||||||||||||||||||||||||||||
| format: (value) => number(value, 0), | ||||||||||||||||||||||||||||||||||||||||||||||||
| lowerIsBetter: true, | ||||||||||||||||||||||||||||||||||||||||||||||||
| values: (item) => ({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| left: blockingFailures(item.left), | ||||||||||||||||||||||||||||||||||||||||||||||||
| right: blockingFailures(item.right), | ||||||||||||||||||||||||||||||||||||||||||||||||
| delta: blockingFailures(item.right) - blockingFailures(item.left), | ||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| { label: "Tokens", format: (value) => number(value, 0), lowerIsBetter: true, values: (item) => item.totals.total_tokens }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| { label: "Function calls", format: (value) => number(value, 0), lowerIsBetter: true, values: (item) => item.totals.function_calls }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| { label: "Cost", format: currency, lowerIsBetter: true, values: (item) => item.totals.total_cost_usd }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| { label: "Runtime", format: duration, lowerIsBetter: true, values: (item) => item.totals.wall_time_seconds }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||||||||||||
| elements.metrics.innerHTML = metricDefinitions | ||||||||||||||||||||||||||||||||||||||||||||||||
| .map((definition) => renderMetric(definition, comparison)) | ||||||||||||||||||||||||||||||||||||||||||||||||
| .join(""); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| elements.scenarios.innerHTML = comparison.scenarios | ||||||||||||||||||||||||||||||||||||||||||||||||
| .map((row) => ` | ||||||||||||||||||||||||||||||||||||||||||||||||
| <tr> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <th scope="row"> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <div class="compare-scenario-name"> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <span>${escapeHtml(row.subjectLabel)}</span> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <strong>${escapeHtml(row.scenarioId.replaceAll("_", " "))}</strong> | ||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||
| </th> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <td>${sideScenario(row.left)}</td> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <td>${sideScenario(row.right)}</td> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <td>${signed(row.deltas.score, (value) => number(value, 1))}</td> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <td>${signed(row.deltas.tokens, (value) => number(value, 0))}</td> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <td>${signed(row.deltas.cost_usd, currency)}</td> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <td>${signed(row.deltas.duration_seconds, duration)}</td> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <td><span class="comparison-contract comparison-contract-${escapeHtml(row.contract)}">${escapeHtml(row.contract)}</span></td> | ||||||||||||||||||||||||||||||||||||||||||||||||
| </tr>`) | ||||||||||||||||||||||||||||||||||||||||||||||||
| .join(""); | ||||||||||||||||||||||||||||||||||||||||||||||||
| })(); | ||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Normalize the rounded duration before splitting minutes and seconds.
For example,
119.9renders as1m 60s. Round the absolute duration first. Then derive both minute and second fields from that rounded value.Proposed fix
function duration(value) { if (typeof value !== "number") return "—"; if (Math.abs(value) < 60) return `${number(value, 1)}s`; const sign = value < 0 ? "−" : ""; - const absolute = Math.abs(value); - return `${sign}${Math.floor(absolute / 60)}m ${String(Math.round(absolute % 60)).padStart(2, "0")}s`; + const seconds = Math.round(Math.abs(value)); + return `${sign}${Math.floor(seconds / 60)}m ${String(seconds % 60).padStart(2, "0")}s`; }📝 Committable suggestion
🤖 Prompt for AI Agents