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

67 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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](../experiments/boundary-recovery/README.md) 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](https://www.radix-ui.com/primitives/docs/components/dialog)
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](../experiments/boundary-recovery/provenance.json).
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](../candidates/FailureBoundary.md) gains executed partial support
and concrete exclusions. [Focus Return](../candidates/FocusReturn.md) 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](../../workplans/FEP-WP-0003-contrasting-schools.md): 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](NestedDismissalIsolation.md) 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.