frontend-patterns/research/experiments/boundary-recovery/README.md
tegwick 3eecd74b0d Probe rendering and modal focus boundaries in pinned browser fixture
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0cffe-206f-7323-89d3-b20ca4cf4bf9
2026-09-26 22:01:35 +02:00

63 lines
3.5 KiB
Markdown

# Rendering and focus boundary experiment
Executed 2026-09-26. Real React 19.3.0 / Radix Dialog 1.1.23 in a synthetic browser
fixture; no deployed application, provider inference or production requests.
[Review and interpretation](../../reviews/BoundaryRecoveryExperiment.md).
## Reproduce
From this directory:
```bash
npm ci --ignore-scripts --cache /tmp/fep-npm-cache
FEP_CHROMIUM_PATH=/path/to/chrome FEP_RESULT_PATH=/tmp/immediate.json npm test
FEP_CHROMIUM_PATH=/path/to/chrome FEP_NESTED_SETTLE_MS=100 FEP_RESULT_PATH=/tmp/settled.json npm test
```
Use a locally available Chromium executable, or omit `FEP_CHROMIUM_PATH` to use
Playwright's installed browser. The recorded runs used preinstalled Chromium
151.0.7922.34, not an assertion of compatibility with every browser. Version and
executable hash are in [provenance](provenance.json); the lockfile pins all packages
with archive integrity. Installation does not download a browser automatically here.
The runner binds an ephemeral loopback port, bundles the fixture in memory, and
closes browser/server in `finally`. No external network is used by the cases.
Output defaults to `/tmp/fep-boundary-result.json`; reviewed receipts are not
overwritten automatically. A failed obligation makes the runner exit nonzero.
The immediate nested-dialog case is known to be timing-sensitive; do not hide it
with retries or interpret a later pass as its resolution. `FEP_NESTED_SETTLE_MS`
changes only the pause after nested activation before the first Escape. It is an
experimental variable, not a proposed product fix. The browser uses React's development
build, without StrictMode. Production builds and other browsers remain untested.
## Recorded cases
| Case | Observation | Immediate / 100 ms settled |
| --- | --- | --- |
| X01 | Render fault uses inner fallback; sibling stays interactive | pass / pass |
| X02 | Event-handler exception reaches browser error reporting, not boundary | pass / pass |
| X03 | Synthetic GET returns 503; explicit catch sets request-failure state | pass / pass |
| X04 | Fault in inner fallback reaches outer boundary | pass / pass |
| X05 | Closing modal returns focus to surviving invoker | pass / pass |
| X06 | Removing invoker leaves BODY focused with default behavior | pass / pass (negative control) |
| X07 | Application close policy selects surviving next-record button | pass / pass |
| X08 | Nested close should return to parent trigger, then outer trigger | fail / pass |
[result-immediate.json](result-immediate.json) records 7/8; [result-settled.json](result-settled.json)
records 8/8 with identical code/input hashes, except the declared timing parameter.
“Pass” means an observation matched its assertion, not that the UX is acceptable:
X06 deliberately expects the missing workflow target.
[development-results.json](development-results.json) preserves three earlier runs
in order: 7/8, 8/8, 7/8. They preceded final diagnostic instrumentation; their old
runner bytes are not archived, so they are development history, not exact reproducible
snapshots. Current-code receipts above retain their matching SHA-256 input hashes.
All five runs are reported; no estimate of failure frequency is justified.
## Scope
Focus checks inspect activeElement and keyboard behavior, not screen-reader output
or full accessibility conformance. X03 handles a response error explicitly; it does
not test unhandled promises, timeouts, mutation idempotency or remote rollback.
Nested dismissal remains unresolved. Dependency/version compatibility and fixture
lifecycle effects must be separated before attributing a library defect.