Skip to content

fix(report): redesign evaluation card, add run duration, fix suite labeling - #227

Merged
jithin23-kv merged 3 commits into
KeyValueSoftwareSystems:masterfrom
jithin23-kv:fix/report-evaluation-card-and-suite-labeling
Jul 30, 2026
Merged

fix(report): redesign evaluation card, add run duration, fix suite labeling#227
jithin23-kv merged 3 commits into
KeyValueSoftwareSystems:masterfrom
jithin23-kv:fix/report-evaluation-card-and-suite-labeling

Conversation

@jithin23-kv

@jithin23-kv jithin23-kv commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Problem

The Evaluation Results card in the shared HTML report (used by CLI, MCP server, SDK, and the browser extension) didn't match the Figma design: a redundant two-level accordion (click the card to open, then click again to expand the transcript), boxed grey "meta grid" for Verdict/Score/Confidence/Pattern, a Standards chip that didn't make sense once shown, wrong turn styling (attacker/agent not visually distinguished, no highlight for judge-flagged turns), and mismatched header typography (pill badges instead of plain colored text).

Separately, two functional bugs surfaced during review:

  • The report always showed a run's full evaluation suite name (e.g. "owasp-llm-top10") even when only a handful of that suite's evaluators were actually selected/run — overstating what was actually tested. Confirmed against real browser-extension reports where 1 evaluator ran out of a 27-evaluator suite, yet the suite name was shown unqualified. The CLI had the mirror-image bug: a custom (non-suite) evaluator selection left the "Evaluation Suite" field blank.
  • No run duration was ever captured or shown anywhere in the report.

Solution

  • Rebuilt the Evaluation Results card to match the Figma spec: single-click-to-expand only for the transcript, plain Reasoning/Evidence text, large Confidence number, attacker turns as plain indented text vs. agent turns as a left-anchored bubble, and a pink dashed highlight on the turn(s) the judge's failingTurns actually flagged (previously this checked a per-turn judge field that is never populated in the real pipeline — the judgement is only ever made once, over the whole transcript).
  • Added a durationMs field, captured via wall-clock timing in runAll/runAllBrowser, surfaced as a 5th box in the Executive Summary ("12m 34s").
  • Added a suiteId field that's the real suite id only when the full suite was actually run; otherwise "Custom Suite" — computed from the evaluator selection mode in the CLI/SDK/MCP path, and from a selected-vs-suite-membership comparison in the extension.
  • Removed the Standards chip from the card body per feedback (it was confusing and redundant with the header).
  • Fixed a pre-existing, unrelated test bug along the way (knowledge.test.ts expected a vuln-class id of "prompt-injection"; the actual category id — matching both the directory name and the README's own frontmatter — is "injection").

Changes

  • core/src/report/render.ts, core/src/report/types.ts, core/src/report/buildReport.ts — report template/view-model
  • core/src/execute/runAll.ts, runAllBrowser.ts, aggregate.ts, types.ts — duration + suite-label computation
  • runners/sdk/src/run.ts, report.ts, types.ts — thread suiteId/durationMs through
  • runners/extension/popup.js — suite-label fix for the extension's own report builder
  • core/src/browser.ts — re-export renderReport/view-model types for the extension bundle
  • Test fixture/assertion updates in core/tests/, runners/cli/tests/, runners/sdk/tests/

Issue

N/A

How to test

  • npm run build from repo root, then npm test, npm test --workspace=runners/cli, npm test --workspace=runners/sdk — all green (core 184/184, CLI 31/31, SDK 49/49).
  • Generate a report via opfor run with a full suite selected → "Evaluation Suite" shows the suite id. Re-run with --evaluators (a hand-picked subset) → shows "Custom Suite" and a Duration box in the Executive Summary.
  • In the browser extension, pick a suite, then uncheck a few evaluators before running → the downloaded report should show "Custom Suite" instead of the suite's name.
  • Expand an evaluator card with a multi-turn transcript whose judge cited specific FailingTurns → only those turns render with the pink dashed highlight; others render as plain text/bubble.

Screenshots

Not attached — verified via synthetic reports rendered locally through headless Chrome during development (see conversation history for before/after captures).

Summary by CodeRabbit

  • New Features
    • Introduced a redesigned unified HTML report with an executive summary, evaluation results with verdict tags, evidence/reasoning, expandable transcript UI, and updated styling.
    • Reports now include suite labeling and an overall wall-clock run duration.
    • SDK outputs and generated reports now carry suite metadata, optional duration, and additional judge scoring/details (score/confidence, reasoning/error info, failing turns).
    • Browser and extension report generation now use the shared rendering pipeline for consistent presentation.
  • Bug Fixes
    • Transcript toggle targets are now stable across repeated renders.
    • Extension suite naming logic is more accurate when suites are not fully completed.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 31f1b2f0-8aea-4ef6-ac5b-5280b38cc798

📥 Commits

Reviewing files that changed from the base of the PR and between 392878b and 191adc3.

📒 Files selected for processing (2)
  • core/src/report/render.ts
  • runners/extension/popup.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/src/report/render.ts

Walkthrough

The change adds suite identifiers and run durations to unified and SDK reports, expands report view models, replaces the shared HTML report layout, and routes SDK and extension report generation through the shared renderer with updated judgment details and fixtures.

Changes

Report pipeline

Layer / File(s) Summary
Execution metadata contracts
core/src/execute/*, core/src/report/types.ts, core/src/report/buildReport.ts
Reports now carry suite identifiers and optional wall-clock durations across native and browser execution paths, with expanded report view-model fields.
Shared report renderer
core/src/report/render.ts, core/src/browser.ts
HTML reports now include executive summaries, evaluator detail cards, transcripts, duration displays, and revised responsive styling.
SDK and extension integrations
runners/sdk/src/*, runners/extension/popup.js
SDK and extension reports are adapted to and rendered through the shared view model; SDK results expose additional judgment metadata.
Validation updates
core/tests/*, runners/cli/tests/*, runners/sdk/tests/*
Fixtures and assertions cover suite identifiers, non-deterministic durations, and the injection evaluator category.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SDK
  participant Extension
  participant ReportViewModel
  participant renderReport
  SDK->>ReportViewModel: Adapt SDK results
  Extension->>ReportViewModel: Adapt extension report
  ReportViewModel->>renderReport: Render shared HTML report
Loading

Possibly related PRs

Suggested reviewers: achuvyas-kv, arunsunnykvs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main changes: report redesign, duration tracking, and suite-label fixes.
Description check ✅ Passed The description follows the required template and covers problem, solution, changes, issue, testing, and screenshots.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
core/src/report/render.ts (1)

560-560: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Module-level transcriptIdCounter makes renderer output non-deterministic across calls.

The counter is never reset, so rendering the same ReportViewModel twice (e.g. a long-lived MCP server, or snapshot tests) yields different data-for/data-target values, and the value grows unbounded for the process lifetime. The IDs only need to be unique within one document — derive them from the evaluator/result indices instead.

♻️ Proposed refactor to derive IDs from indices
-let transcriptIdCounter = 0;
 function resultDetailCard(
   r: ResultViewModel,
   index: number,
   _mode: "agent" | "mcp",
-  showTestHeading: boolean
+  showTestHeading: boolean,
+  evalIndex: number
 ): string {
-  const tId = `t${transcriptIdCounter++}`;
+  const tId = `t${evalIndex}-${index}`;

And at the call site (line 200):

-        .map((r, i) => resultDetailCard(r, i, model.mode, showTestHeading))
+        .map((r, i) => resultDetailCard(r, i, model.mode, showTestHeading, idx))

Also applies to: 667-667

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/src/report/render.ts` at line 560, Remove the module-level
transcriptIdCounter and make transcript identifiers deterministic per render by
deriving them from the evaluator/result indices. Update the relevant transcript
ID generation and its call site in the renderer so repeated rendering of the
same ReportViewModel produces identical data-for/data-target values while
remaining unique within a document.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@core/src/report/render.ts`:
- Around line 449-451: Remove the inline onclick handler from the copy button in
the report ID markup and store the escaped report ID in a data attribute such as
data-copy. In the existing script block, add the delegated .copy-btn click
listener to read that attribute, write it via navigator.clipboard, and update
the icon color while preserving the current copy behavior.

In `@runners/extension/popup.js`:
- Around line 1402-1434: Update toReportViewModel to populate the summary
duration from summary.durationMs, using the popup state’s run start/end delta
when that is the source of duration for extension runs. Ensure the resulting
summary includes the same duration value expected by the Executive Summary
instead of leaving it undefined.
- Around line 1157-1164: Update resolveSuiteLabel so the suite lookup safely
handles an absent state.catalog.suites collection, not just an absent catalog.
Extend the optional chaining at the suites access before calling find, while
preserving the existing Custom Suite fallback and fullSuite logic.

---

Nitpick comments:
In `@core/src/report/render.ts`:
- Line 560: Remove the module-level transcriptIdCounter and make transcript
identifiers deterministic per render by deriving them from the evaluator/result
indices. Update the relevant transcript ID generation and its call site in the
renderer so repeated rendering of the same ReportViewModel produces identical
data-for/data-target values while remaining unique within a document.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 07c19fd0-6513-4020-834d-f52ef5b373a5

📥 Commits

Reviewing files that changed from the base of the PR and between 5f4b355 and 733a9f3.

📒 Files selected for processing (18)
  • core/src/browser.ts
  • core/src/execute/aggregate.ts
  • core/src/execute/runAll.ts
  • core/src/execute/runAllBrowser.ts
  • core/src/execute/types.ts
  • core/src/report/buildReport.ts
  • core/src/report/render.ts
  • core/src/report/types.ts
  • core/tests/orchestrator.equivalence.test.ts
  • runners/cli/tests/jsonlEventListener.test.ts
  • runners/cli/tests/knowledge.test.ts
  • runners/extension/popup.js
  • runners/sdk/src/report.ts
  • runners/sdk/src/run.ts
  • runners/sdk/src/types.ts
  • runners/sdk/tests/opfor-class.test.ts
  • runners/sdk/tests/report.test.ts
  • runners/sdk/tests/types.test.ts

Comment thread core/src/report/render.ts
Comment thread runners/extension/popup.js
Comment thread runners/extension/popup.js
@jithin23-kv

Copy link
Copy Markdown
Collaborator Author

Addressed all three CodeRabbit findings in 392878b:

  1. Copy-button JS-injection vector — replaced the inline onclick with a data-copy attribute + delegated click listener in the existing script block, per the suggested fix.
  2. Unguarded .suites in resolveSuiteLabel — added the missing ?. so a partially-hydrated catalog doesn't throw.
  3. Extension duration always "—" — the popup now tracks state.runStartedAt (set on a fresh run, persisted/restored across popup close/reopen and pause/resume via the existing opforPopupRun storage bucket), computes durationMs at report-build time, and threads it through toReportViewModel the same way CLI/SDK reports already do.

Build, lint, and all three test suites (core 184/184, SDK 49/49, CLI 31/31) still green.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
runners/extension/popup.js (2)

1161-1167: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Base the suite label on completed evaluators.

Line 1164 checks the selected suite, so a user-stopped run can still display the full suite ID despite unrun evaluators. Derive fullSuite from completed, non-cancelled state.results IDs instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@runners/extension/popup.js` around lines 1161 - 1167, Update
resolveSuiteLabel so fullSuite is determined from state.results entries that
completed successfully and are not cancelled, using their evaluator IDs rather
than state.selectedEvaluators. Require those completed IDs to exactly match
suite.evaluatorIds, preserving the existing "Custom Suite" fallback for
incomplete or cancelled runs.

3106-3112: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Restore the start time for long-running recovered runs.

When the popup reopens after more than five minutes, popupQueueActive is false even if opforRunStatus.running is true. Line 3111 is then skipped, leaving runStartedAt null and omitting duration from the final report. Restore the timestamp from the matching persisted snapshot independently of the freshness check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@runners/extension/popup.js` around lines 3106 - 3112, Restore runStartedAt
from the matching persisted opforPopupRun snapshot independently of the
popupQueueActive check, so recovered runs with opforRunStatus.running retain
their original start time even after the freshness window expires. Keep the
existing fallback behavior when no persisted timestamp is available.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@runners/extension/popup.js`:
- Around line 1161-1167: Update resolveSuiteLabel so fullSuite is determined
from state.results entries that completed successfully and are not cancelled,
using their evaluator IDs rather than state.selectedEvaluators. Require those
completed IDs to exactly match suite.evaluatorIds, preserving the existing
"Custom Suite" fallback for incomplete or cancelled runs.
- Around line 3106-3112: Restore runStartedAt from the matching persisted
opforPopupRun snapshot independently of the popupQueueActive check, so recovered
runs with opforRunStatus.running retain their original start time even after the
freshness window expires. Keep the existing fallback behavior when no persisted
timestamp is available.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2ab1dfb1-b151-408b-bf5d-5dbeb9ed2aba

📥 Commits

Reviewing files that changed from the base of the PR and between 733a9f3 and 392878b.

📒 Files selected for processing (2)
  • core/src/report/render.ts
  • runners/extension/popup.js

@jithin23-kv

Copy link
Copy Markdown
Collaborator Author

Addressed the latest round in 191adc3 (these were flagged as "outside diff range" / nitpick, so no inline thread to reply to directly — replying here instead):

  1. Suite label should reflect what actually completed, not what was selected — valid catch. A run stopped/cancelled partway through a full-suite selection still had state.selectedEvaluators matching the whole suite, so it kept showing the suite name even though not everything ran. resolveSuiteLabel() now derives fullSuite from state.results entries with verdict !== "CANCELLED", matched exactly against suite.evaluatorIds.
  2. runStartedAt restore skipped for stale-but-still-active runs — valid. It was nested inside if (popupQueueActive), so a run still genuinely active per opforRunStatus.running (just past the 5-minute popup-queue freshness window) lost its original start time on popup reopen. Restoring it now happens unconditionally from opforPopupRun.runStartedAt before that check.
  3. Non-deterministic transcriptIdCounter (nitpick from the first review round, hadn't gotten to it yet) — fixed too: replaced the module-level counter with a ${evalIndex}-${resultIndex} id derived from the render loop, so re-rendering the same ReportViewModel (long-lived MCP server, snapshot tests) now produces identical data-for/data-target values.

Build, lint, and all three suites still green (core 184/184, SDK 49/49, CLI 31/31).

@jithin23-kv
jithin23-kv merged commit c252675 into KeyValueSoftwareSystems:master Jul 30, 2026
8 checks passed
@jithin23-kv
jithin23-kv deleted the fix/report-evaluation-card-and-suite-labeling branch July 30, 2026 08:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants