Add verification control detail page with clickable catalog rows

Adds /verifications/:slug route with detail page showing control
description, stat cards, evaluation history, recent runs, checks/examples,
and sibling navigation. Makes verification catalog rows fully clickable.
Fixes dark mode hydration mismatch with suppressHydrationWarning.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-01 22:25:00 -05:00
parent bda63295ed
commit ae72cd059d
5 changed files with 660 additions and 11 deletions

View file

@ -214,3 +214,269 @@ export function getCriteriaSummary(criteria: readonly Criterion[]) {
export function getAllCriteria(categories: readonly VerificationCategory[]) {
return categories.flatMap((c) => c.criteria);
}
export function slugify(name: string): string {
return name
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/(^-|-$)/g, "");
}
export function findCriterionBySlug(slug: string): {
criterion: Criterion;
category: VerificationCategory;
performance: CriterionPerformance;
} | null {
for (const category of verificationCategories) {
for (const criterion of category.criteria) {
if (slugify(criterion.name) === slug) {
const performance = criterionPerformance[criterion.name];
if (performance) {
return { criterion, category, performance };
}
}
}
}
return null;
}
export interface ControlDetail {
description: string;
checks: string[];
passExample: string;
failExample: string;
}
export const controlDetails: Record<string, ControlDetail> = {
"Motivation": {
description: "Verifies that every change traces back to a clear origin — whether a ticket, RFC, customer request, or incident. Without documented motivation, reviewers lack context for evaluating whether the change is appropriate.",
checks: ["PR body or linked issue explains why the change is needed", "Commit messages reference a ticket or context", "No orphaned changes without traceable origin"],
passExample: "PR links to JIRA-1234 and explains the user-facing pain point being resolved.",
failExample: "PR description is empty or says only 'fix stuff'.",
},
"Specifications": {
description: "Checks that functional and non-functional requirements are written down before implementation begins. Specifications prevent scope creep and ensure everyone agrees on what done looks like.",
checks: ["Acceptance criteria listed in the issue or PR", "Edge cases documented", "Non-functional requirements (performance, security) stated when relevant"],
passExample: "Issue includes acceptance criteria with three testable scenarios.",
failExample: "Issue body says 'implement the feature' with no acceptance criteria.",
},
"Documentation": {
description: "Ensures developer-facing and user-facing documentation is added or updated alongside code changes. Stale docs degrade team velocity and increase onboarding cost.",
checks: ["README or docs updated for new features", "API documentation reflects endpoint changes", "Inline comments for non-obvious logic"],
passExample: "New API endpoint has corresponding OpenAPI spec update and usage example in docs.",
failExample: "New CLI flag added with no mention in README or --help text.",
},
"Minimization": {
description: "Flags extraneous changes that inflate the diff — formatting-only edits, unrelated refactors, or drive-by fixes. Keeping PRs focused improves review quality and reduces revert risk.",
checks: ["No unrelated formatting or whitespace changes", "Refactors separated from feature work", "Each commit addresses a single concern"],
passExample: "PR touches only files directly related to the new caching layer.",
failExample: "PR adds a feature but also reformats 12 unrelated files.",
},
"Formatting": {
description: "Validates that code layout conforms to the project's formatting standard (e.g., Prettier, rustfmt). Automated formatting removes subjective style debates from code review.",
checks: ["All files pass the project formatter", "No manual formatting overrides without justification"],
passExample: "All changed files pass `prettier --check` and `rustfmt --check`.",
failExample: "Several files have inconsistent indentation that the formatter would fix.",
},
"Linting": {
description: "Confirms that static analysis findings are resolved. Linter warnings left unaddressed accumulate into tech debt and mask real issues.",
checks: ["No new linter warnings introduced", "Existing warnings not suppressed without explanation", "Lint config not weakened"],
passExample: "ESLint and Clippy pass with zero warnings on changed files.",
failExample: "New `// eslint-disable-next-line` added to suppress a legitimate warning.",
},
"Style": {
description: "Evaluates whether the code follows the team's house style conventions beyond what automated formatters catch — naming, file organization, import ordering, and idiomatic patterns.",
checks: ["Naming conventions followed (camelCase, snake_case as appropriate)", "Import ordering matches project convention", "Idiomatic patterns used for the language"],
passExample: "New TypeScript module uses camelCase variables, groups imports by source, and uses `Map` instead of plain objects for lookups.",
failExample: "Mix of camelCase and snake_case in the same module with random import ordering.",
},
"Completeness": {
description: "Checks that the implementation fully covers the specified requirements. Partial implementations ship broken experiences and create follow-up tickets that could have been avoided.",
checks: ["All acceptance criteria addressed", "Edge cases handled", "Error states implemented"],
passExample: "Feature handles all three specified user roles with appropriate permissions.",
failExample: "Only the happy path is implemented; error and empty states are missing.",
},
"Defects": {
description: "Identifies potential or likely bugs through static analysis and AI review. Catching defects before merge is orders of magnitude cheaper than finding them in production.",
checks: ["No off-by-one errors in loops or slices", "Null/undefined handled at boundaries", "Race conditions considered in async code"],
passExample: "API handler validates input, handles missing fields gracefully, and returns appropriate HTTP status codes.",
failExample: "Array index accessed without bounds check; crashes on empty input.",
},
"Performance": {
description: "Assesses whether the change impacts hot paths or introduces algorithmic regressions. Performance problems that ship to production are expensive to diagnose and fix.",
checks: ["No N+1 queries introduced", "Large collections not processed synchronously", "Caching considered for repeated expensive operations"],
passExample: "Database query uses a JOIN instead of N separate queries for related records.",
failExample: "Loop makes a separate HTTP call for each item in a 1000-element list.",
},
"Test Coverage": {
description: "Measures whether production code is exercised by automated tests. Coverage gaps mean regressions can ship undetected.",
checks: ["New code has corresponding unit tests", "Coverage does not decrease", "Critical paths have integration tests"],
passExample: "New service method has 6 unit tests covering happy path, error cases, and edge cases.",
failExample: "New 200-line module has zero test files.",
},
"Test Quality": {
description: "Evaluates whether tests are robust, readable, and actually verify behavior rather than implementation details. Low-quality tests give false confidence.",
checks: ["Tests verify behavior, not implementation", "Assertions are specific and meaningful", "Tests are independent and deterministic"],
passExample: "Tests assert on API response shape and status codes, not on internal method call counts.",
failExample: "Tests mock every dependency and only verify that mocks were called.",
},
"E2E Coverage": {
description: "Checks that user-facing workflows are exercised by end-to-end browser automation. E2E tests catch integration issues that unit tests miss.",
checks: ["Critical user flows have Playwright/Cypress tests", "E2E tests run in CI", "No flaky E2E tests introduced"],
passExample: "New checkout flow has a Playwright test that completes a purchase end-to-end.",
failExample: "New multi-step wizard has no browser automation tests.",
},
"Architecture": {
description: "Validates that layering and dependency directions conform to the project's architectural design. Architectural violations compound over time and make systems harder to evolve.",
checks: ["Dependencies point inward (domain doesn't depend on infra)", "No circular dependencies introduced", "Module boundaries respected"],
passExample: "New repository implementation depends on domain interfaces, not the other way around.",
failExample: "Domain model imports directly from the HTTP framework package.",
},
"Interfaces": {
description: "Reviews public API surfaces for clarity, consistency, and backward compatibility. Interfaces are contracts — once published, they're expensive to change.",
checks: ["Public API types are well-defined", "Breaking changes documented", "Consistent naming across endpoints"],
passExample: "New endpoint follows existing naming and error format conventions.",
failExample: "New endpoint uses different error format than all other endpoints.",
},
"Duplication": {
description: "Detects similar or identical code blocks that could be consolidated. Duplication increases maintenance burden and creates inconsistency risk.",
checks: ["No copy-pasted logic across files", "Shared utilities used for common patterns", "Similar test setup consolidated"],
passExample: "Date formatting logic extracted into a shared utility used by 4 components.",
failExample: "Same 15-line validation function copy-pasted into three different handlers.",
},
"Simplicity": {
description: "Flags unnecessarily complex code that could be simplified without changing behavior. Simpler code is easier to review, debug, and extend.",
checks: ["No premature abstractions", "Control flow is straightforward", "Functions are focused and short"],
passExample: "Conditional logic uses early returns instead of deeply nested if-else chains.",
failExample: "Three-level generic abstraction for a function called in one place.",
},
"Dead Code": {
description: "Identifies unexecuted code paths and unused dependencies. Dead code misleads readers and bloats bundles.",
checks: ["No unreachable code paths", "Unused imports and variables removed", "Deprecated functions removed if no longer called"],
passExample: "Old feature flag and its associated code paths removed after rollout completed.",
failExample: "Commented-out function left in file 'in case we need it later'.",
},
"Vulnerabilities": {
description: "Scans for known security vulnerabilities using both AI analysis and static scanning tools. Shipping known vulnerabilities exposes users and the organization to risk.",
checks: ["No SQL injection or XSS vectors", "User input sanitized at boundaries", "Authentication/authorization checks present"],
passExample: "User input passed through parameterized queries; HTML output escaped.",
failExample: "Raw SQL string concatenation with user-supplied values.",
},
"IaC Scanning": {
description: "Validates infrastructure-as-code definitions against security best practices. Misconfigured infrastructure is a leading cause of data breaches.",
checks: ["No publicly accessible storage buckets", "Encryption at rest enabled", "Least-privilege IAM policies"],
passExample: "Terraform module creates S3 bucket with encryption, versioning, and private ACL.",
failExample: "CloudFormation template creates an RDS instance with no encryption and public accessibility.",
},
"Dependency Alerts": {
description: "Checks that third-party dependencies are free from known CVEs. Vulnerable dependencies are an easy attack vector that automated tools can detect.",
checks: ["No dependencies with known critical CVEs", "Lock file updated to patched versions", "Unused dependencies removed"],
passExample: "Dependabot alert resolved by updating lodash from 4.17.20 to 4.17.21.",
failExample: "Package.json pins a version of axios with a known SSRF vulnerability.",
},
"Security Controls": {
description: "Verifies that organization-specific security standards are applied — rate limiting, audit logging, CORS policies, and secret management.",
checks: ["Secrets not hardcoded in source", "Rate limiting on public endpoints", "Audit logging for sensitive operations"],
passExample: "API key loaded from environment variable; rate limiter configured on login endpoint.",
failExample: "AWS credentials committed in a config file.",
},
"Compatibility": {
description: "Detects breaking changes in APIs, database schemas, or wire formats that could disrupt consumers. Breaking changes require coordination that surprises prevent.",
checks: ["No removed or renamed public API fields", "Database migrations are backward-compatible", "Wire format changes are additive"],
passExample: "New field added to API response; no existing fields removed or renamed.",
failExample: "Column renamed in migration while old code is still deployed.",
},
"Rollout / Rollback": {
description: "Confirms that the change has a clear deployment plan and can be safely rolled back if issues arise. Every production deploy should be reversible.",
checks: ["Feature flag available for gradual rollout", "Database migration is reversible", "Rollback procedure documented"],
passExample: "Feature behind a LaunchDarkly flag with 10% initial rollout and documented rollback steps.",
failExample: "Irreversible database migration with no rollback plan.",
},
"Observability": {
description: "Ensures that logging, metrics, and tracing are instrumented for new code paths. Without observability, production issues are invisible until users report them.",
checks: ["Structured logging for new operations", "Metrics emitted for key business events", "Distributed tracing propagated"],
passExample: "New payment endpoint logs transaction IDs, emits latency metrics, and propagates trace context.",
failExample: "New background job has no logging or metrics; failures are silent.",
},
"Cost": {
description: "Estimates the infrastructure and operational cost impact of the change. Unchecked cost growth erodes margins and can cause budget surprises.",
checks: ["New infrastructure resources sized appropriately", "No unbounded resource consumption", "Cost estimate provided for significant changes"],
passExample: "New Lambda function has memory limit set and estimated monthly cost noted in PR.",
failExample: "New service provisions a db.r5.4xlarge for a table with 100 rows.",
},
"Change Control": {
description: "Validates that separation-of-duties policies are met — the author is not the sole reviewer, approvals are obtained, and the change went through the proper process.",
checks: ["PR has at least one approval from non-author", "Required reviewers have signed off", "No self-merging without policy exception"],
passExample: "PR approved by two team members before merge; CI checks all green.",
failExample: "Author approved and merged their own PR with no other reviewers.",
},
"AI Governance": {
description: "Checks that AI-generated or AI-assisted code meets the organization's governance requirements — attribution, review depth, and acceptable use.",
checks: ["AI-generated code clearly attributed", "Human review of AI suggestions documented", "AI usage within acceptable-use policy"],
passExample: "PR notes that implementation was AI-assisted; human reviewer verified logic and tests.",
failExample: "Entire module generated by AI with no human review or attribution.",
},
"Privacy": {
description: "Ensures that personally identifiable information (PII) is identified, classified, and handled according to privacy standards (GDPR, CCPA).",
checks: ["PII fields identified and documented", "Data retention policies applied", "Consent mechanisms in place for data collection"],
passExample: "New user profile endpoint masks email in logs and respects data deletion requests.",
failExample: "User email addresses logged in plaintext to application logs.",
},
"Accessibility": {
description: "Verifies that UI changes meet accessibility requirements (WCAG 2.1 AA). Inaccessible software excludes users and creates legal risk.",
checks: ["Semantic HTML elements used", "ARIA labels present on interactive elements", "Color contrast meets WCAG AA standards"],
passExample: "New modal uses <dialog>, has aria-labelledby, and focus is trapped within.",
failExample: "Custom dropdown built with <div> elements, no keyboard navigation, no ARIA roles.",
},
"Licensing": {
description: "Ensures that all third-party dependencies comply with the organization's intellectual property policy. License violations can have severe legal consequences.",
checks: ["No GPL-licensed dependencies in proprietary code", "License file present for new dependencies", "Supply chain attestation where required"],
passExample: "New dependency uses MIT license; added to approved dependency list.",
failExample: "AGPL-licensed library added to a closed-source commercial product.",
},
};
export interface RecentControlResult {
runId: string;
runTitle: string;
workflow: string;
result: VerificationStatus;
timestamp: string;
}
export const recentControlResults: Record<string, RecentControlResult[]> = {
"Motivation": [
{ runId: "run-047", runTitle: "PR #312 — Add OAuth2 PKCE flow", workflow: "code_review", result: "pass", timestamp: "2h ago" },
{ runId: "run-046", runTitle: "PR #311 — Update rate limiter config", workflow: "code_review", result: "pass", timestamp: "5h ago" },
{ runId: "run-044", runTitle: "PR #309 — Migrate to pnpm", workflow: "code_review", result: "fail", timestamp: "1d ago" },
{ runId: "run-042", runTitle: "PR #307 — Fix session timeout", workflow: "fix_build", result: "pass", timestamp: "2d ago" },
{ runId: "run-040", runTitle: "PR #305 — Add webhook retries", workflow: "code_review", result: "pass", timestamp: "3d ago" },
],
"Documentation": [
{ runId: "run-047", runTitle: "PR #312 — Add OAuth2 PKCE flow", workflow: "code_review", result: "pass", timestamp: "2h ago" },
{ runId: "run-046", runTitle: "PR #311 — Update rate limiter config", workflow: "code_review", result: "fail", timestamp: "5h ago" },
{ runId: "run-044", runTitle: "PR #309 — Migrate to pnpm", workflow: "code_review", result: "pass", timestamp: "1d ago" },
{ runId: "run-042", runTitle: "PR #307 — Fix session timeout", workflow: "fix_build", result: "pass", timestamp: "2d ago" },
{ runId: "run-040", runTitle: "PR #305 — Add webhook retries", workflow: "code_review", result: "fail", timestamp: "3d ago" },
],
"Rollout / Rollback": [
{ runId: "run-047", runTitle: "PR #312 — Add OAuth2 PKCE flow", workflow: "code_review", result: "fail", timestamp: "2h ago" },
{ runId: "run-046", runTitle: "PR #311 — Update rate limiter config", workflow: "code_review", result: "pass", timestamp: "5h ago" },
{ runId: "run-044", runTitle: "PR #309 — Migrate to pnpm", workflow: "code_review", result: "fail", timestamp: "1d ago" },
{ runId: "run-042", runTitle: "PR #307 — Fix session timeout", workflow: "fix_build", result: "fail", timestamp: "2d ago" },
{ runId: "run-040", runTitle: "PR #305 — Add webhook retries", workflow: "code_review", result: "pass", timestamp: "3d ago" },
],
};
// Default recent results for controls without specific data
const defaultRecentResults: RecentControlResult[] = [
{ runId: "run-047", runTitle: "PR #312 — Add OAuth2 PKCE flow", workflow: "code_review", result: "pass", timestamp: "2h ago" },
{ runId: "run-046", runTitle: "PR #311 — Update rate limiter config", workflow: "code_review", result: "pass", timestamp: "5h ago" },
{ runId: "run-044", runTitle: "PR #309 — Migrate to pnpm", workflow: "code_review", result: "pass", timestamp: "1d ago" },
{ runId: "run-042", runTitle: "PR #307 — Fix session timeout", workflow: "fix_build", result: "pass", timestamp: "2d ago" },
{ runId: "run-040", runTitle: "PR #305 — Add webhook retries", workflow: "code_review", result: "pass", timestamp: "3d ago" },
];
export function getRecentResults(criterionName: string): RecentControlResult[] {
return recentControlResults[criterionName] ?? defaultRecentResults;
}

View file

@ -31,7 +31,7 @@ export const links: Route.LinksFunction = () => [
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className="h-full bg-atmosphere">
<html lang="en" className="h-full bg-atmosphere" suppressHydrationWarning>
<head>
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
<meta charSet="utf-8" />

View file

@ -28,6 +28,7 @@ export default [
route("retro", "routes/run-retro.tsx"),
]),
route("verifications", "routes/verifications.tsx"),
route("verifications/:slug", "routes/verification-detail.tsx"),
route("retros", "routes/retros.tsx"),
route("insights", "routes/insights.tsx", [
index("routes/insights-editor.tsx"),

View file

@ -0,0 +1,374 @@
import { Link, useParams } from "react-router";
import {
ChevronRightIcon,
LightBulbIcon,
ClipboardDocumentListIcon,
BookOpenIcon,
FunnelIcon,
Bars3BottomLeftIcon,
WrenchIcon,
PaintBrushIcon,
CheckBadgeIcon,
BugAntIcon,
BoltIcon,
BeakerIcon,
StarIcon,
ComputerDesktopIcon,
CubeTransparentIcon,
ArrowsRightLeftIcon,
DocumentDuplicateIcon,
SparklesIcon,
ArchiveBoxXMarkIcon,
ShieldExclamationIcon,
ServerStackIcon,
ExclamationTriangleIcon,
LockClosedIcon,
PuzzlePieceIcon,
ArrowUturnLeftIcon,
EyeIcon,
CurrencyDollarIcon,
ClipboardDocumentCheckIcon,
CpuChipIcon,
FingerPrintIcon,
HandRaisedIcon,
ScaleIcon,
MapPinIcon,
DocumentTextIcon,
ShieldCheckIcon,
WrenchScrewdriverIcon,
KeyIcon,
RocketLaunchIcon,
BuildingLibraryIcon,
CheckCircleIcon,
XCircleIcon,
MinusCircleIcon,
} from "@heroicons/react/20/solid";
import {
findCriterionBySlug,
slugify,
typeConfig,
modeConfig,
statusConfig,
criterionPerformance,
controlDetails,
getRecentResults,
} from "../data/verifications";
import type {
VerificationType,
VerificationMode,
EvaluationResult,
VerificationStatus,
} from "../data/verifications";
import type { Route } from "./+types/verification-detail";
export const handle = { hideHeader: true };
export function meta({ params }: Route.MetaArgs) {
const match = findCriterionBySlug(params.slug ?? "");
const name = match?.criterion.name ?? "Verification";
return [{ title: `${name} — Verifications — Arc` }];
}
type IconComponent = React.ComponentType<{ className?: string }>;
const criterionIcons: Record<string, IconComponent> = {
"Motivation": LightBulbIcon,
"Specifications": ClipboardDocumentListIcon,
"Documentation": BookOpenIcon,
"Minimization": FunnelIcon,
"Formatting": Bars3BottomLeftIcon,
"Linting": WrenchIcon,
"Style": PaintBrushIcon,
"Completeness": CheckBadgeIcon,
"Defects": BugAntIcon,
"Performance": BoltIcon,
"Test Coverage": BeakerIcon,
"Test Quality": StarIcon,
"E2E Coverage": ComputerDesktopIcon,
"Architecture": CubeTransparentIcon,
"Interfaces": ArrowsRightLeftIcon,
"Duplication": DocumentDuplicateIcon,
"Simplicity": SparklesIcon,
"Dead Code": ArchiveBoxXMarkIcon,
"Vulnerabilities": ShieldExclamationIcon,
"IaC Scanning": ServerStackIcon,
"Dependency Alerts": ExclamationTriangleIcon,
"Security Controls": LockClosedIcon,
"Compatibility": PuzzlePieceIcon,
"Rollout / Rollback": ArrowUturnLeftIcon,
"Observability": EyeIcon,
"Cost": CurrencyDollarIcon,
"Change Control": ClipboardDocumentCheckIcon,
"AI Governance": CpuChipIcon,
"Privacy": FingerPrintIcon,
"Accessibility": HandRaisedIcon,
"Licensing": ScaleIcon,
};
const categoryIcons: Record<string, IconComponent> = {
"Traceability": MapPinIcon,
"Readability": DocumentTextIcon,
"Reliability": ShieldCheckIcon,
"Code Coverage": BeakerIcon,
"Maintainability": WrenchScrewdriverIcon,
"Security": KeyIcon,
"Deployability": RocketLaunchIcon,
"Compliance": BuildingLibraryIcon,
};
function TypeBadge({ type }: { type: VerificationType | null }) {
if (type === null) return null;
const config = typeConfig[type];
return (
<span
className={`rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider ${config.color} ${config.bg}`}
>
{config.label}
</span>
);
}
function ModeBadge({ mode }: { mode: VerificationMode }) {
const config = modeConfig[mode];
return (
<span
className={`rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider ${config.color} ${config.bg}`}
>
{config.label}
</span>
);
}
function StatCard({ label, value, warn }: { label: string; value: string; warn?: boolean }) {
return (
<div className="rounded-md border border-line bg-panel/60 px-4 py-3">
<p className="text-xs font-medium uppercase tracking-wider text-fg-muted">{label}</p>
<p className={`mt-1 font-mono text-lg font-semibold tabular-nums ${warn ? "text-amber" : "text-fg"}`}>
{value}
</p>
</div>
);
}
function EvaluationBar({ evaluations }: { evaluations: readonly EvaluationResult[] }) {
if (evaluations.length === 0) {
return <p className="text-sm italic text-fg-muted">No evaluations yet</p>;
}
const passCount = evaluations.filter((e) => e === "pass").length;
const failCount = evaluations.filter((e) => e === "fail").length;
const skipCount = evaluations.filter((e) => e === "skip").length;
return (
<div>
<div className="flex items-center gap-1">
{evaluations.map((result, i) => (
<div
key={i}
className={`h-6 flex-1 rounded-sm ${
result === "pass"
? "bg-mint/70"
: result === "fail"
? "bg-coral/70"
: "bg-navy-600/50"
}`}
/>
))}
</div>
<div className="mt-2 flex gap-4 text-xs text-fg-muted">
<span className="flex items-center gap-1">
<span className="inline-block size-2 rounded-sm bg-mint/70" />
{passCount} pass
</span>
<span className="flex items-center gap-1">
<span className="inline-block size-2 rounded-sm bg-coral/70" />
{failCount} fail
</span>
{skipCount > 0 && (
<span className="flex items-center gap-1">
<span className="inline-block size-2 rounded-sm bg-navy-600/50" />
{skipCount} skip
</span>
)}
</div>
</div>
);
}
function ResultIcon({ result }: { result: VerificationStatus }) {
const config = statusConfig[result];
if (result === "pass") return <CheckCircleIcon className={`size-4 ${config.color}`} />;
if (result === "fail") return <XCircleIcon className={`size-4 ${config.color}`} />;
return <MinusCircleIcon className={`size-4 ${config.color}`} />;
}
export default function VerificationDetail() {
const { slug } = useParams();
const match = findCriterionBySlug(slug ?? "");
if (!match) {
return <p className="py-8 text-center text-sm text-fg-muted">Verification not found.</p>;
}
const { criterion, category, performance } = match;
const Icon = criterionIcons[criterion.name];
const CatIcon = categoryIcons[category.name];
const detail = controlDetails[criterion.name];
const recentResults = getRecentResults(criterion.name);
const siblings = category.criteria.filter((c) => c.name !== criterion.name);
const passRate = performance.evaluations.length > 0
? (performance.evaluations.filter((e) => e === "pass").length / performance.evaluations.length * 100).toFixed(0)
: null;
return (
<div className="space-y-6">
{/* Breadcrumb */}
<nav className="flex items-center gap-1 text-sm text-fg-muted">
<Link to="/verifications" className="text-fg-3 hover:text-fg">Verifications</Link>
<ChevronRightIcon className="size-3" />
<span className="text-fg-3">{category.name}</span>
<ChevronRightIcon className="size-3" />
<span>{criterion.name}</span>
</nav>
{/* Header */}
<div className="flex items-start gap-3">
{Icon && <Icon className="mt-0.5 size-6 text-fg-3" />}
<div className="min-w-0 flex-1">
<h2 className="text-xl font-semibold text-fg">{criterion.name}</h2>
<p className="mt-1 text-sm text-fg-muted">{criterion.description}</p>
<div className="mt-2 flex items-center gap-2">
<TypeBadge type={criterion.type} />
<ModeBadge mode={performance.mode} />
</div>
</div>
</div>
{/* Description block */}
{detail && (
<div className="rounded-md border border-line bg-panel/60 px-5 py-4">
<p className="text-sm leading-relaxed text-fg-2">{detail.description}</p>
</div>
)}
{/* Stat cards */}
<div className="grid grid-cols-4 gap-3">
<StatCard label="Accuracy (F1)" value={performance.f1 != null ? performance.f1.toFixed(2) : "—"} />
<StatCard label="pass@1" value={performance.passAt1 != null ? performance.passAt1.toFixed(2) : "—"} />
<StatCard label="Pass Rate" value={passRate != null ? `${passRate}%` : "—"} />
<StatCard label="Total Evals" value={String(performance.evaluations.length)} />
</div>
{/* Evaluation history */}
<div>
<h3 className="mb-3 text-sm font-semibold text-fg">Evaluation History</h3>
<EvaluationBar evaluations={performance.evaluations} />
</div>
{/* Recent runs table */}
<div>
<h3 className="mb-3 text-sm font-semibold text-fg">Recent Runs</h3>
<div className="rounded-md border border-line overflow-hidden">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-line bg-panel/60 text-left text-xs text-fg-muted">
<th className="py-2.5 pl-4 pr-3 font-medium">Run</th>
<th className="py-2.5 px-3 font-medium w-8">Result</th>
<th className="py-2.5 px-3 font-medium">Workflow</th>
<th className="py-2.5 pl-3 pr-4 font-medium text-right">Time</th>
</tr>
</thead>
<tbody>
{recentResults.map((run) => (
<tr key={run.runId} className="border-b border-line last:border-b-0 transition-colors hover:bg-overlay">
<td className="py-2.5 pl-4 pr-3">
<Link to={`/runs/${run.runId}`} className="font-medium text-fg-2 hover:text-fg">
{run.runTitle}
</Link>
</td>
<td className="py-2.5 px-3">
<ResultIcon result={run.result} />
</td>
<td className="py-2.5 px-3 text-fg-muted">{run.workflow}</td>
<td className="py-2.5 pl-3 pr-4 text-right text-fg-muted">{run.timestamp}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* What this checks / Examples */}
{detail && (
<div className="grid grid-cols-2 gap-4">
<div className="rounded-md border border-line bg-panel/60 px-5 py-4">
<h3 className="mb-3 text-sm font-semibold text-fg">What This Checks</h3>
<ul className="space-y-2 text-sm text-fg-2">
{detail.checks.map((check) => (
<li key={check} className="flex items-start gap-2">
<span className="mt-1.5 size-1.5 shrink-0 rounded-full bg-fg-muted" />
{check}
</li>
))}
</ul>
</div>
<div className="space-y-4">
<div className="rounded-md border border-line bg-panel/60 px-5 py-4">
<h3 className="mb-2 text-sm font-semibold text-mint">Pass Example</h3>
<p className="text-sm text-fg-2">{detail.passExample}</p>
</div>
<div className="rounded-md border border-line bg-panel/60 px-5 py-4">
<h3 className="mb-2 text-sm font-semibold text-coral">Fail Example</h3>
<p className="text-sm text-fg-2">{detail.failExample}</p>
</div>
</div>
</div>
)}
{/* Sibling controls */}
{siblings.length > 0 && (
<div>
<h3 className="mb-3 text-sm font-semibold text-fg">
{CatIcon && <CatIcon className="mr-1.5 inline size-4 text-fg-3" />}
Other {category.name} Controls
</h3>
<div className="rounded-md border border-line overflow-hidden">
<table className="w-full text-sm">
<tbody>
{siblings.map((sibling) => {
const SibIcon = criterionIcons[sibling.name];
const sibPerf = criterionPerformance[sibling.name];
return (
<tr key={sibling.name} className="border-b border-line last:border-b-0 transition-colors hover:bg-overlay">
<td className="w-8 py-2.5 pl-4 pr-0">
{SibIcon && <SibIcon className="size-4 text-fg-3" />}
</td>
<td className="py-2.5 pl-2 pr-3">
<Link
to={`/verifications/${slugify(sibling.name)}`}
className="font-medium text-fg-2 hover:text-fg"
>
{sibling.name}
</Link>
</td>
<td className="py-2.5 px-3 text-fg-muted">
{sibling.description || <span className="italic">Not configured</span>}
</td>
<td className="whitespace-nowrap py-2.5 px-3 text-right">
<TypeBadge type={sibling.type} />
</td>
<td className="whitespace-nowrap py-2.5 pl-3 pr-4">
{sibPerf && <ModeBadge mode={sibPerf.mode} />}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
)}
</div>
);
}

View file

@ -1,4 +1,5 @@
import { useState } from "react";
import { useNavigate } from "react-router";
import {
Disclosure,
DisclosureButton,
@ -54,6 +55,7 @@ import {
typeConfig,
modeConfig,
criterionPerformance,
slugify,
} from "../data/verifications";
import type {
VerificationType,
@ -142,6 +144,18 @@ function TypeBadge({ type }: { type: VerificationType | null }) {
type ViewMode = "grouped" | "ungrouped";
function CriterionRow({ slug, children }: { slug: string; children: React.ReactNode }) {
const navigate = useNavigate();
return (
<tr
className="border-b border-line last:border-b-0 cursor-pointer transition-colors hover:bg-overlay"
onClick={() => navigate(`/verifications/${slug}`)}
>
{children}
</tr>
);
}
function CategoryCard({ category }: { category: VerificationCategory }) {
return (
<Disclosure
@ -180,10 +194,7 @@ function CategoryCard({ category }: { category: VerificationCategory }) {
const Icon = criterionIcons[criterion.name];
const perf = criterionPerformance[criterion.name];
return (
<tr
key={criterion.name}
className="border-b border-line last:border-b-0 transition-colors hover:bg-overlay"
>
<CriterionRow key={criterion.name} slug={slugify(criterion.name)}>
<td className="w-8 py-2.5 pl-5 pr-0">
{Icon && <Icon className="size-4 text-fg-3" />}
</td>
@ -204,7 +215,7 @@ function CategoryCard({ category }: { category: VerificationCategory }) {
<td className="whitespace-nowrap py-2.5 pl-1 pr-4">
{perf && <EvaluationDots evaluations={perf.evaluations} />}
</td>
</tr>
</CriterionRow>
);
})}
</tbody>
@ -281,10 +292,7 @@ function UngroupedView({ categories }: { categories: readonly VerificationCatego
const Icon = criterionIcons[criterion.name];
const perf = criterionPerformance[criterion.name];
return (
<tr
key={`${category.name}-${criterion.name}`}
className="border-b border-line last:border-b-0 transition-colors hover:bg-overlay"
>
<CriterionRow key={`${category.name}-${criterion.name}`} slug={slugify(criterion.name)}>
<td className="w-8 py-2.5 pl-4 pr-0">
{Icon && <Icon className="size-4 text-fg-3" />}
</td>
@ -314,7 +322,7 @@ function UngroupedView({ categories }: { categories: readonly VerificationCatego
<td className="whitespace-nowrap py-2.5 pl-3 pr-4">
{perf && <EvaluationDots evaluations={perf.evaluations} />}
</td>
</tr>
</CriterionRow>
);
}),
)}