frontend-patterns/research/reviews/BoundaryRecoveryExperiment.md
tegwick 85ae4f62af Isolate nested dismissal ownership across browsers and builds
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0cffe-206f-7323-89d3-b20ca4cf4bf9
2026-09-26 22:35:12 +02:00

4.6 KiB
Raw Permalink Blame History

Boundary recovery observations — 2026-09-26

Status: executed synthetic browser fixture, editorial interpretation by Codex. WP-0003 T01; SRC-0029 pinned packages and SRC-0030 Radix documentation. This advances implementation evidence for Failure Boundary and Focus Return, not full-pattern recurrence, application validation or the M1/M2 gates. Sixteen hypotheses remain.

Runner, fixture and raw results use React 19.3.0, Radix Dialog 1.1.23 and Chromium 151.0.7922.34. Package archives, inspected implementation hashes and browser provenance are recorded. Sources of injected faults and application focus policy are our own fixture, not vendor examples. No live model calls; future inference remains through llm-connect.

Observations and implications

Evidence Executed observation Bounded interpretation
X01 Inner boundary catches render fault; sibling button remains usable Local rendering containment works for this subtree and fault
X02 Event-handler exception is globally reported; no boundary catch Do not promise catch-all UI recovery from a rendering boundary
X03 GET /failure returns 503; explicit application catch displays failure state Request state is separate; this does not establish behavior of unhandled async errors
X04 Broken inner fallback reaches outer fallback Recovery UI can fail and needs an enclosing policy
X05 Escape returns focus to surviving trigger A partial implementation of ordinary modal return
X06 Removing the trigger and closing leaves BODY focused A generic trigger reference cannot choose the next workflow target
X07 Application onCloseAutoFocus override selects next record A narrow application policy repairs this removed-trigger case
X08 Immediate Escape after nested activation closed outer dialog in the recorded final immediate run; 100 ms pause preserved inner-then-outer return Activation/focus arrival alone is insufficient evidence of stable dismissal ownership; timing cause remains unproven

Source inspection and uncertainty

Radix Dialog docs expose close-autofocus customization and describe Escape dismissal. Inspected package code @radix-ui/react-dialog/dist/index.mjs lines 154–156 cancels the default unmount focus action and attempts focus on the trigger reference. Our explicit target in X07 is an application addition, not a built-in workflow-selection guarantee.

In the pinned @radix-ui/react-focus-scope/dist/index.mjs, lines 94–103 defer unmount focus handling. The pinned dismissable-layer module, lines 90–120, selects the highest layer and registers Escape listeners/layers through effects. These are plausible places to investigate the timing discrepancy, not a demonstrated causal diagnosis. See file hashes and transitive versions.

The failing X08 transition log is outer-open, inner-open, outer-close; it does not show inner-close before outer-close. The final settled run shows the intended order. Three earlier development runs also alternated fail/pass/fail. Do not describe this as an unconditional nested-focus guarantee, estimate a rate from five exploratory runs, or prescribe a 100 ms sleep as a fix. Current result files record identical fixture/runner/lock hashes, with the timing parameter explicitly different.

Candidate revisions and remaining work

Failure Boundary gains executed partial support and concrete exclusions. Focus Return gains a negative control, a conditional recovery policy and an unresolved nested-lifecycle case. Both stay hypotheses. One synthetic fixture is not an independent application use.

Next under WP-0003 T01: isolate nested dismissal with event/effect timing instrumentation and compare production build plus another browser before assigning cause. Preserve the immediate path as a regression probe; test explicit readiness/ownership rather than adding a delay. Screen-reader behavior remains untested. Feature/release coupling inspection and navigation/history/cache checks remain separate pending work.

Follow-up disposition

Nested isolation now captures the stale-listener interval and compares builds/browsers. The mechanism is located in an instrumented fixture; a complete product remedy remains open. This supersedes the earlier lack of timing diagnosis, while preserving the original receipts and their limitations.