diff --git a/frontend/src/components/ui/treatment/VoiceReviewSheet.tsx b/frontend/src/components/ui/treatment/VoiceReviewSheet.tsx index e55e041..25f22ac 100644 --- a/frontend/src/components/ui/treatment/VoiceReviewSheet.tsx +++ b/frontend/src/components/ui/treatment/VoiceReviewSheet.tsx @@ -69,12 +69,23 @@ export function VoiceReviewSheet({ const selectedCount = countSelected(selection, available); const pickCandidate = (tooth: FdiToothId) => { - setChosen((prev) => - prev.includes(tooth) ? prev.filter((t) => t !== tooth) : [...prev, tooth], - ); - // The teeth row starts unticked whenever the recording produced no teeth of its own, - // and a picked tooth that is not ticked applies nothing. - setSelection((prev) => (prev.teeth ? prev : { ...prev, teeth: true })); + const nextChosen = chosen.includes(tooth) + ? chosen.filter((t) => t !== tooth) + : [...chosen, tooth]; + setChosen(nextChosen); + setSelection((prev) => ({ + ...prev, + // The teeth row starts unticked whenever the recording produced no teeth of its own, + // and a picked tooth that is not ticked applies nothing. + teeth: true, + // The picked tooth has no prosthesis type, which makes the map unshippable. Leaving + // the row ticked would apply a map that `assertCompleteToothProsthesisMap` rejects + // at dispatch — the exact failure the never-auto-tick-incomplete rule exists to + // prevent. Only ever unticks: re-ticking is the clinician's call. + prosthesis: + prev.prosthesis && + withChosenTeeth(result, nextChosen).prosthesis?.complete !== false, + })); }; const labelFor = (code: string | null, catalog: { code: string; label: string }[]) =>