Dictating "12 روکش PFM, 13 روکش PFZ" previewed correctly and then landed in
the form as PFM on both teeth.
The stored data was never wrong — the dev database holds 12 → pfm_crown and
13 → pfz_crown with selectionGroupIds matching the detail's groups exactly,
and a page reload renders it correctly. The damage was live client state:
the dispatch panel's "last type used for this lab" default rebuilt the
*entire* map from one code, so a single row reading as unfilled destroyed
every type already set.
Two changes:
- It fills blanks now, and leaves every entry that already carries a type
alone. The bulk "apply to all" select only pre-sets itself when the fill
really did cover every tooth, instead of claiming one type while the rows
below disagree.
- A lab case created by confirming a voice result is exempt from the
default entirely. The review sheet is a contract: topping the case up
with a type for a tooth the preview never showed makes the confirmation
step a lie about what it was going to fill.
The exemption is tracked in workspace state rather than on LabCaseDraft
because a draft field is dropped by mapLabCaseDraftFromApi on the first
server round-trip — exactly the window this failure lives in.
isProsthesisMapComplete is deliberately untouched: its strict
selectionGroupId match succeeds on the real data, so loosening it would
have been a blind change to a working path.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>