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:
@@ -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(", ")}
|
||||
|
||||
Reference in New Issue
Block a user