98 lines
5.9 KiB
Markdown
98 lines
5.9 KiB
Markdown
|
|
# Experimental nested ownership routing — 2026-09-26
|
||
|
|
|
||
|
|
Status: synthetic browser comparison, editorial analysis by Codex, WP-0003 T01.
|
||
|
|
Follow-up to [stale-listener isolation](NestedDismissalIsolation.md). 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](../experiments/boundary-recovery/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](../experiments/boundary-recovery/routing-design.md)
|
||
|
|
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](../experiments/boundary-recovery/routing.mjs) 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](../experiments/boundary-recovery/routing-development-results.json)
|
||
|
|
and [original runner bytes](../experiments/boundary-recovery/routing-development-runner.mjs)
|
||
|
|
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](../experiments/boundary-recovery/routing-results.json) 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](../experiments/boundary-recovery/provenance.json) and
|
||
|
|
[Firefox comparison](../experiments/boundary-recovery/isolation-provenance.json).
|
||
|
|
|
||
|
|
## 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](../candidates/FocusReturn.md) 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.
|