diff --git a/frontend/src/components/test-detail/TeamTabs.tsx b/frontend/src/components/test-detail/TeamTabs.tsx
index c2baed2..513e015 100644
--- a/frontend/src/components/test-detail/TeamTabs.tsx
+++ b/frontend/src/components/test-detail/TeamTabs.tsx
@@ -189,6 +189,12 @@ export default function TeamTabs({
? blueDraft.detection_result === "detected" || blueDraft.detection_result === "partially_detected"
: test.detection_result === "detected" || test.detection_result === "partially_detected";
+ // Containment Time only makes sense when something was actually contained —
+ // "Not Contained" has no containment moment to record.
+ const isContained = canEditBlue
+ ? blueDraft.containment_result === "contained" || blueDraft.containment_result === "partially_contained"
+ : test.containment_result === "contained" || test.containment_result === "partially_contained";
+
// Hint messages shown to operators when editing is locked
const redLockedHint =
test.state === "draft" && role === "red_tech"
@@ -520,7 +526,9 @@ export default function TeamTabs({
)}
- {/* Detection & Containment times — only when detected/partially_detected */}
+ {/* Detection & Containment times — Detection Time whenever detected;
+ Containment Time only when something was actually contained —
+ "Not Contained" has no containment moment to record. */}
{isDetected && (
@@ -541,24 +549,26 @@ export default function TeamTabs({
)}
-
-
- {canEditBlue ? (
-
onBlueFieldChange("containment_time", e.target.value)}
- className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-200 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
- />
- ) : (
-
- {test.containment_time ? new Date(test.containment_time + "Z").toUTCString().replace(" GMT", " UTC") : "—"}
-
- )}
-
+ {isContained && (
+
+
+ {canEditBlue ? (
+
onBlueFieldChange("containment_time", e.target.value)}
+ className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-200 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
+ />
+ ) : (
+
+ {test.containment_time ? new Date(test.containment_time + "Z").toUTCString().replace(" GMT", " UTC") : "—"}
+
+ )}
+
+ )}
)}
diff --git a/frontend/src/pages/TestDetailPage.tsx b/frontend/src/pages/TestDetailPage.tsx
index f493a89..852fdd1 100644
--- a/frontend/src/pages/TestDetailPage.tsx
+++ b/frontend/src/pages/TestDetailPage.tsx
@@ -446,7 +446,17 @@ export default function TestDetailPage() {
};
const handleBlueFieldChange = (field: string, value: string) => {
- setBlueDraft((prev) => ({ ...prev, [field]: value }));
+ setBlueDraft((prev) => {
+ const next = { ...prev, [field]: value };
+ // Containment Time only applies when something was actually
+ // contained — clear a stale value left over from a previous
+ // "Contained"/"Partially Contained" selection so it doesn't get
+ // silently resubmitted once the field is hidden again.
+ if (field === "containment_result" && value !== "contained" && value !== "partially_contained") {
+ next.containment_time = "";
+ }
+ return next;
+ });
};
const handleUploadEvidence = async (file: File, team: TeamSide) => {
@@ -624,8 +634,9 @@ export default function TestDetailPage() {