Story 27: Coverage tracking (full-stack)

Add end-to-end coverage tracking: backend collects vitest coverage,
records metrics with threshold/baseline tracking, and blocks acceptance
on regression. Frontend displays coverage in gate/review panels with
a "Collect Coverage" button. Includes 20 Rust tests, 17 Vitest tests,
and 14 Playwright E2E tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dave
2026-02-19 14:45:57 +00:00
parent 8f0bc971bf
commit 8f684a6ca4
20 changed files with 1216 additions and 34 deletions

View File

@@ -1,3 +1,9 @@
interface CoverageReport {
currentPercent: number;
thresholdPercent: number;
baselinePercent: number | null;
}
interface GateState {
canAccept: boolean;
reasons: string[];
@@ -8,6 +14,7 @@ interface GateState {
failed: number;
};
missingCategories: string[];
coverageReport: CoverageReport | null;
}
interface GatePanelProps {
@@ -16,8 +23,11 @@ interface GatePanelProps {
gateStatusColor: string;
isGateLoading: boolean;
gateError: string | null;
coverageError: string | null;
lastGateRefresh: Date | null;
onRefresh: () => void;
onCollectCoverage: () => void;
isCollectingCoverage: boolean;
}
const formatTimestamp = (value: Date | null): string => {
@@ -35,8 +45,11 @@ export function GatePanel({
gateStatusColor,
isGateLoading,
gateError,
coverageError,
lastGateRefresh,
onRefresh,
onCollectCoverage,
isCollectingCoverage,
}: GatePanelProps) {
return (
<div
@@ -83,6 +96,27 @@ export function GatePanel({
>
Refresh
</button>
<button
type="button"
onClick={onCollectCoverage}
disabled={isCollectingCoverage || isGateLoading}
style={{
padding: "4px 10px",
borderRadius: "999px",
border: "1px solid #333",
background:
isCollectingCoverage || isGateLoading ? "#2a2a2a" : "#2f2f2f",
color: isCollectingCoverage || isGateLoading ? "#777" : "#aaa",
cursor:
isCollectingCoverage || isGateLoading
? "not-allowed"
: "pointer",
fontSize: "0.75em",
fontWeight: 600,
}}
>
{isCollectingCoverage ? "Collecting..." : "Collect Coverage"}
</button>
</div>
<div
style={{
@@ -147,6 +181,27 @@ export function GatePanel({
Summary: {gateState.summary.passed}/{gateState.summary.total}{" "}
passing, {gateState.summary.failed} failing
</div>
{gateState.coverageReport && (
<div
style={{
fontSize: "0.85em",
color:
gateState.coverageReport.currentPercent <
gateState.coverageReport.thresholdPercent
? "#ff7b72"
: "#7ee787",
}}
>
Coverage: {gateState.coverageReport.currentPercent.toFixed(1)}%
(threshold: {gateState.coverageReport.thresholdPercent.toFixed(1)}
%)
</div>
)}
{coverageError && (
<div style={{ fontSize: "0.85em", color: "#ff7b72" }}>
Coverage error: {coverageError}
</div>
)}
{gateState.missingCategories.length > 0 && (
<div style={{ fontSize: "0.85em", color: "#ffb86c" }}>
Missing: {gateState.missingCategories.join(", ")}