feat(phase-30): add coverage snapshots, temporal comparison and auto re-testing (T-230 to T-232)

This commit is contained in:
2026-02-10 08:34:29 +01:00
parent 2ac8e7f4a5
commit 4d124b42dd
20 changed files with 1517 additions and 4 deletions

View File

@@ -14,6 +14,7 @@ import {
validateAsBlueLead,
reopenTest,
getTestTimeline,
getRetestChain,
} from "../api/tests";
import { uploadEvidence, getEvidence } from "../api/evidence";
import { useAuth } from "../context/AuthContext";
@@ -79,6 +80,12 @@ export default function TestDetailPage() {
enabled: !!testId,
});
const { data: retestChain = [] } = useQuery({
queryKey: ["retest-chain", testId],
queryFn: () => getRetestChain(testId!),
enabled: !!testId && !!test && (test.retest_of !== null || test.retest_count > 0),
});
// Hydrate drafts from test data
useEffect(() => {
if (test) {
@@ -442,6 +449,55 @@ export default function TestDetailPage() {
)}
</dl>
</div>
{/* Retest Chain */}
{(test.retest_of || test.retest_count > 0 || retestChain.length > 1) && (
<div className="rounded-xl border border-gray-800 bg-gray-900 p-6">
<h2 className="mb-4 text-lg font-semibold text-white">Retest Chain</h2>
{test.retest_of && (
<div className="mb-3">
<span className="text-xs font-medium uppercase text-gray-500">
Retest {test.retest_count} / 3
</span>
<div className="mt-1 h-2 w-full rounded-full bg-gray-800 overflow-hidden">
<div
className="h-full rounded-full bg-cyan-500 transition-all"
style={{ width: `${(test.retest_count / 3) * 100}%` }}
/>
</div>
</div>
)}
<div className="space-y-2">
{retestChain.map((entry) => (
<button
key={entry.id}
onClick={() => entry.id !== testId && navigate(`/tests/${entry.id}`)}
className={`flex w-full items-center justify-between rounded-lg border px-3 py-2 text-left text-sm transition-colors ${
entry.id === testId
? "border-cyan-500/30 bg-cyan-900/30 text-cyan-400"
: "border-gray-700 bg-gray-800/50 text-gray-300 hover:border-cyan-500/30 hover:text-cyan-400"
}`}
>
<div className="flex items-center gap-2 truncate">
<span className="truncate text-xs">
{entry.retest_of ? `#${entry.retest_count}` : "Original"}
</span>
<span className="truncate">{entry.name}</span>
</div>
<span className={`shrink-0 rounded-full px-2 py-0.5 text-xs ${
entry.state === "validated"
? "bg-green-900/50 text-green-400"
: entry.state === "rejected"
? "bg-red-900/50 text-red-400"
: "bg-gray-800/50 text-gray-500"
}`}>
{entry.state || "draft"}
</span>
</button>
))}
</div>
</div>
)}
</div>
</div>