// (finding B57) - the overlay container is GET-OR-CREATE, never a bare lookup. // // What a bare lookup cost // The first-sign-in agreement gate - the dialog every new user must accept before using the product - // rendered its Decline and Accept buttons as **bare browser defaults**: no fill, no padding, no radius, // inheriting the host WordPress theme's typography, under the practice's own domain. // // The mechanism, measured rather than inferred: // 1. All 68 `.btn*` rules are scoped to `#akashic-health-records-patient-portal-root` (68 scoped, // 0 unscoped, enumerated in the deployed stylesheet). // 2. The buttons carried the RIGHT CLASSES and computed to `background: rgba(0,0,0,0)`, `padding: 0`, // `border-radius: 0` - right classes, zero applied rules. // 3. `#akashic-overlay-root` is rendered by React at `App.tsx:1765`, at the END of the tree inside // `OrganizationCheck`. This dialog opens on FIRST PAINT, before that node exists. // 4. So the lookup returned `null`, `container ?? getAkashicPortalContainer() ?? undefined` // (`ui/dialog.tsx:31`) collapsed to `undefined`, and Radix defaulted to `document.body` - outside // the scoped root, for the dialog's whole lifetime. // // THE `?? undefined` WAS THE DEFECT: it turned *"the container is not ready yet"* into *"portal to // body"*, silently. Returning a real element instead removes the null that made that collapse possible. // // WHY IT READ AS "A BIT OFF" RATHER THAN BROKEN, which is why two textual passes of the card-235 walk // went straight past it: the damage is SELECTIVE. Global Tailwind utilities (`bg-gray-50`, `text-gray-700`) // are unscoped and still applied, so the panel looked fine - only the scoped component classes died. // Any dialog using `.btn` that can open during first render had this, and none of them would look // obviously broken. // // THE FIX IS THE CONTAINER, NEVER THE SELECTOR. Do not unscope `.btn`: the scoping is what keeps the // host WordPress theme out of the app, and P8's touch-target compound selectors // (`#id[data-akashic-mount="frontend"]`) depend on it. // - this is a recorded rule the code drifted from, not a new // discovery. The memory exists *because* portalling outside the app root causes bleed; a bare // `getElementById` with no fallback is how it came back. const OVERLAY_ROOT_ID = "akashic-overlay-root" /** * Every mount of this plugin renders this same element id - the patient shortcode, the staff shortcode, * both public-form paths and the wp-admin page (see `main.tsx`). Anchoring to it means "inside whichever * of them is on screen", which is exactly the scope the stylesheet is written against. */ const APP_ROOT_ID = "akashic-health-records-patient-portal-root" /** * The container every portal renders into. **Never returns null**, so a caller cannot accidentally hand * `undefined` to Radix and land in ``. * * The created node is deliberately BARE - no inline styles. `#akashic-overlay-root`'s appearance and * stacking live in `index.css:1771`, and that file's header (lines 18-22) explains why it must NOT get * `isolation: isolate`: the overlay's z-index has to compete at document level to cover host-theme chrome. * Styling it here would fork that decision into two places, and the inline copy would win. * * If React later mounts its own `#akashic-overlay-root` (App.tsx still does), `getElementById` finds * that one first and this function never creates anything - the two cannot both exist, because the * lookup precedes the create. */ export function getAkashicPortalContainer(): HTMLElement { const existing = document.getElementById(OVERLAY_ROOT_ID) if (existing) return existing const created = document.createElement("div") created.id = OVERLAY_ROOT_ID // Inside the app root when there is one - that is the whole point, since the scoped rules key on it. // Falling back to only when the app root itself is absent is not a regression: in that state // there is no scoped stylesheet to be inside, and returning null instead would put us back in the // exact `?? undefined` collapse this function exists to remove. const appRoot = document.getElementById(APP_ROOT_ID) ;(appRoot ?? document.body).appendChild(created) return created } /** * Whether a scoped app root exists at all - i.e. whether a portal will land somewhere the plugin's * stylesheet actually reaches. * * THIS EXISTS BECAUSE THE GET-OR-CREATE ABOVE QUIETLY BROKE A DELIBERATE FAIL-CLOSED GUARD, and the * regression was real rather than a stale test. `FloatingWindow` used to read *"if the container is null, * render nothing"* - its comment: **"fail loudly rather than silently falling back to body"**. Once this * module stopped returning null that guard became dead code, and a floating window with no app root * present would render straight into `` - precisely the CSS bleed it was written to refuse. * * The two cases are genuinely different and must not be collapsed: * - **app root present, overlay root absent** the container is created inside the app root, the * scoped rules apply, and rendering is CORRECT. This is the first-paint case fixed. * - **no app root at all** nothing on the page is scoped; a portal lands in `` and drags the * host WordPress theme in. A caller that cares should refuse to render. * * Most callers should NOT use this. A dialog with no app root has nothing to be inside, so `` is * the honest destination. It is for surfaces that would rather show nothing than leak. * * AN EXISTING OVERLAY ROOT COUNTS, and getting that wrong is worth recording: a first version of this * checked the app root ALONE and turned 30 `FloatingWindow` tests red at once. Those mount the overlay * root directly - as does any host that provides its own - and refusing to render into a container the * app explicitly created is a different bug, not extra safety. The question is *"is there anywhere scoped * to render?"*, and either node answers yes. */ export function hasAkashicPortalHost(): boolean { return document.getElementById(OVERLAY_ROOT_ID) !== null || document.getElementById(APP_ROOT_ID) !== null }