frontend-patterns/research/reviews/NestedOwnershipRouting.md
tegwick 29ccad7002 Test lifecycle-aware Escape ownership routing without settling delays
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0cffe-206f-7323-89d3-b20ca4cf4bf9
2026-09-26 22:58:07 +02:00

5.9 KiB

Experimental nested ownership routing — 2026-09-26

Status: synthetic browser comparison, editorial analysis by Codex, WP-0003 T01. Follow-up to stale-listener isolation. The aim is to preserve the intended Escape action during ownership changes, not merely prevent a wrong close or wait an arbitrary duration. This does not deploy or recommend a vendor patch. Installed dependencies and earlier experiments remain unchanged.

Proposed mechanism

routing-transform.mjs replaces only the pinned dismissable-layer module's Escape handling during bundling. A document registry holds connected owners and a single capture listener. Layout registration/cleanup changes ownership; each Escape snapshots the current owner, invokes its callback once, honors cancellation and does not fall through to a parent. Removing the last owner removes the listener. The design contract states what this simple mount-order registry does and does not model.

This addresses both observed problems: the old effect-based listener handoff could route to the parent, while the prior guard could reject the stale handler and lose the action. The new approach changes how the handler is selected, not just whether a selected handler is allowed to close. Focus restoration remains the responsibility of the dialog/application; it is not implied by routing success.

Comparison and validity

Runner compares raw and routed builds in Chromium 151.0.7922.34 and Firefox 155.0, development and production. Six scenarios run once per condition: ordinary close, immediate nested close, cancelled inner close, removed invoker with explicit application return target, programmatic inner removal and reopening, and ancestor teardown followed by Escape. The routed teardown check also verifies balanced registrations and removal of the document listener.

No sleep, readiness wait or retry is used. Browser focus polling synchronizes on observable focus, not on the router's private registration state. The raw control uses the same new fixture as the routed condition. Its controlled inner state differs from the original isolation fixture, so counts across those experiments are not a paired comparison. One run per scenario/condition is bounded mechanism evidence, not a failure rate or complete regression suite.

The initial development matrix contained a harness mistake: its cancellation check counted only accessibility-exposed dialogs, excluding the parent hidden by the modal. Raw observations showed two DOM dialogs and retained focus even when that assertion failed. The corrected check includes hidden dialogs; listener cleanup is also asserted. Development receipt and original runner bytes are preserved. Its files.routing.mjs hash resolves to the archived runner name. Do not pool those erroneous cancellation scores with corrected results.

Results

Browser Build Unmodified Experimental router
Chromium development 4/6 6/6
Chromium production 4/6 6/6
Firefox development 6/6 6/6
Firefox production 6/6 6/6

The router meets 24/24 assertions; the control meets 20/24. The four control failures are immediate nesting and removal/reopening in both Chromium builds: the outer Escape callback fires and focus returns to the outer trigger. The routed cases deliver the inner callback and preserve the parent. Unlike the previous isolation matrix, this new raw production fixture does reproduce wrong-owner dismissal. This changes the observed scope, not the historical results.

Cancelled inner Escape remains inside the inner task. Removed-trigger return still uses explicit application policy. Ancestor teardown leaves no router listener, and Escape then causes no stale dismissal callback. No keypress loss was observed in these six scenarios; other input timing patterns remain untested.

Corrected raw results retain all outcomes, browser errors, focus, dialog transitions and router dispatch/cleanup traces. Source, transformed module, bundle and input hashes identify the exact experimental implementation. Browser/package provenance is inherited from the unchanged pinned experiment and Firefox comparison.

Research disposition

This supplies a concrete candidate mechanism for the tested lifecycle. Keep its claims narrower than “nested dialogs fixed”: insertion order is not a complete z-order model, and multiple roots/library copies, exiting animations, shadow DOM, iframes, repeated held keys, same-turn input bursts and competing Escape handlers are not covered. React StrictMode, SSR and assistive-technology behavior are also untested. No upstream message or deployment was performed.

Refine Focus Return with an explicit input-owner lifecycle and separate return-target policy. A dismissal callback is a request to close; a controlled application may refuse it. Temporal ownership remains a proposed interaction contract, not a newly admitted pattern or general library abstraction.

The immediate research question now has an executable routing candidate and bounded comparison. WP-0003 T01 retains any recommendation gate: test animation-preserved exits and competing owners before considering broader reuse. Resume the other breadth work—feature/import versus release boundaries and navigation/history/cache evidence— without requiring this repository to maintain a production dialog fork. Sixteen hypotheses, M1 human validation and M2 completion status remain unchanged.