From 62deff0523d68935697dd813dc3690909b6de6ee Mon Sep 17 00:00:00 2001 From: Amin Mousavi Date: Fri, 21 Aug 2026 05:04:15 +0800 Subject: [PATCH] fix(frontend): untick prosthesis when a picked tooth breaks its map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit initialVoiceSelection deliberately never auto-ticks an incomplete prosthesis map, because a detail with an untyped tooth cannot ship — it fails at dispatch instead. Picking a candidate tooth walked straight through that rule: the tick was seeded once, so a map that was complete at extraction stayed ticked after a tooth with no prosthesis type joined it, and Apply attached a map assertCompleteToothProsthesisMap rejects. Recomputed on each pick, and only ever downwards — re-ticking is the clinician's call, not a side effect of un-picking. Co-Authored-By: Claude Opus 5 (1M context) --- .../ui/treatment/VoiceReviewSheet.tsx | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) 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 }[]) =>