Full-screen perio-chart overlay (periodontal-arc sub-project P2). Task 1
shipped the shell (dialog contract, open/close API) with an empty
#perioOverlayGrid placeholder; THIS task (Task 2) fills it with the full-mouth
grid + a summary bar, bound to the P1 data core
(setPerioSite/getToothPerio/getToothCal/getPerioSummary/
getPerioChart). Keyboard auto-advance between cells is Task 3 — plain
change listeners are enough here.
The 32-tooth x 6-site grid (~450+ interactive cells) is built with plain
DOM (buildArch), NOT JSX/React state, and updated via targeted
syncToothCells calls rather than a full React re-render (R3 perf) — see
syncOneTooth/fullResync below. Only the compact summary bar is
React-controlled (useState), since re-rendering ~4 numbers on every edit
is cheap. suppressResyncRef prevents the grid's own edits from ALSO
triggering a redundant full resync via the onStateChange subscription
(setPerioSite/setToothMobility both fire it synchronously) — external
edits (dual-state chart-mode switch, or another consumer editing perio
data while the overlay is open) still trigger the full resync normally.
Layers OVER the odontogram, which it never unmounts: position: fixed,
full-screen, high z-index (.perio-overlay in index.css). Mirrors
SettingsModal's dialog contract — role="dialog" + aria-modal, Esc
closes, backdrop click closes, focus trap + focus-restore on close — on a
single element (#perioOverlay itself is the dialog; there is no separate
backdrop element, unlike SettingsModal).
"Dental Chart" graphical redesign, Task 1 (presentation only): the
optional inline prop selects a second chrome for the SAME body (grid +
summary bar) — a plain panel (#perioInlinePanel) meant to fill the chart
area in place of the hidden-but-mounted odontogram, instead of the
fixed-position modal dialog. open/onClose are the MODAL chrome's
contract and are ignored when inline is true (the caller controls
mount/unmount of an inline instance directly via conditional rendering,
the same way any other React content area would be swapped) — there is
nothing to "close" in an embedded panel. Dialog-only concerns (focus
trap/restore, Esc-to-close, backdrop click, role="dialog") do not apply
to the inline chrome at all.
Full-screen perio-chart overlay (periodontal-arc sub-project P2). Task 1 shipped the shell (dialog contract, open/close API) with an empty
#perioOverlayGridplaceholder; THIS task (Task 2) fills it with the full-mouth grid + a summary bar, bound to the P1 data core (setPerioSite/getToothPerio/getToothCal/getPerioSummary/getPerioChart). Keyboard auto-advance between cells is Task 3 — plainchangelisteners are enough here.The 32-tooth x 6-site grid (~450+ interactive cells) is built with plain DOM (
buildArch), NOT JSX/React state, and updated via targetedsyncToothCellscalls rather than a full React re-render (R3 perf) — seesyncOneTooth/fullResyncbelow. Only the compact summary bar is React-controlled (useState), since re-rendering ~4 numbers on every edit is cheap.suppressResyncRefprevents the grid's own edits from ALSO triggering a redundant full resync via theonStateChangesubscription (setPerioSite/setToothMobility both fire it synchronously) — external edits (dual-state chart-mode switch, or another consumer editing perio data while the overlay is open) still trigger the full resync normally.Layers OVER the odontogram, which it never unmounts:
position: fixed, full-screen, high z-index (.perio-overlayinindex.css). MirrorsSettingsModal's dialog contract —role="dialog"+aria-modal, Esc closes, backdrop click closes, focus trap + focus-restore on close — on a single element (#perioOverlayitself is the dialog; there is no separate backdrop element, unlikeSettingsModal)."Dental Chart" graphical redesign, Task 1 (presentation only): the optional
inlineprop selects a second chrome for the SAME body (grid + summary bar) — a plain panel (#perioInlinePanel) meant to fill the chart area in place of the hidden-but-mounted odontogram, instead of the fixed-position modal dialog.open/onCloseare the MODAL chrome's contract and are ignored wheninlineis true (the caller controls mount/unmount of an inline instance directly via conditional rendering, the same way any other React content area would be swapped) — there is nothing to "close" in an embedded panel. Dialog-only concerns (focus trap/restore, Esc-to-close, backdrop click,role="dialog") do not apply to the inline chrome at all.