Record why the drawer's post-close inert window stays unfixed #141
Loading…
Reference in a new issue
No description provided.
Delete branch "task-197-drawer-inert-after-modal-close"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Task 197. No production or test code changes — the fix attempted here doesn't hold up, and this records why rather than shipping something unproven.
Tried five candidates against the removed-workaround repro (25 runs each): releasing
OverlayFocusTrapfromModalOverlay.CloseAsyncafterOnCloserather than before (25/25 still failed — the disposal that matters had usually already run by then); the same plus an explicit yield to the renderer (25/25); giving the previously-unkeyed add-modePlaceFormPanelinDrawer.razoran explicit@key(24/25); replacingInvokeAsync(StateHasChanged)with a direct call at the three places this app subscribes to a shared service'sChangedevent (measurably better, ~75-80% still failed, residual traced to bUnit's own click dispatch completing before the handler runs); and makingExitAsync's JS interop fire-and-forget (no further change).ADR-0119 records the mechanism found, all five attempts and why each fell short, and answers ADR-0116's own deferred question.
HomeAndDrawerSharedGroupStateTestskeeps bothWaitForAssertionwrappers unchanged — removing them doesn't currently produce a deterministic pass.Verdict: changes needed
Not on the investigation — it's sound and the write-up earns its keep — but ADR-0119's own frontmatter has a real defect, and I found one untried direction worth naming before this closes the ticket.
1. Genuinely revert-clean. Diffed merge-base (
06d10f3) against head directly: exactly three files touched, all docs (0116metadata, new0119, one README row), zero production or test files anywhere in the diff.HomeAndDrawerSharedGroupStateTests.csisn't in the diff at all — bit-identical tomain. No debris.2. Five attempts, but not a fully explored space — one direction I don't see tried. Attempts 1/2/5 are timing variants within
ModalOverlay.CloseAsync's own lifecycle; 3 and 4 test genuinely different hypotheses (keying, dispatcher queueing), so this isn't "five copies of one idea." But all five still assume the fix lives in whenOverlayFocusTrapreleases and propagates. Not attempted, as far as I can find: moving the release out ofModalOverlay/DisposeAsyncentirely, into each host's own close-decision method (PlaceFormPanel.DiscardAndCloseAsync,GroupFormPanel.ConfirmDeleteAsync, etc.), synchronous with the host's own_closing = true— decoupling from Blazor's async dispose schedule rather than racing a different point inside it. That's structurally distinct from the PR #137 same-tick attempt (which stayed insideModalOverlay.CloseAsync), though it costs the "share one shell uniformly" property ADR-0116 was built for, which may be exactly why it wasn't tried — worth saying so explicitly if it's rejected on that basis rather than left unmentioned.3. Descriptions are specific enough, with one exception: attempts 1, 2, 3 and 5 all give exact X/25 counts; attempt 4 gives "roughly 75–80%" with no stated run count backing that range — unclear whether that's one batch of 25 or several. For the one attempt that "mostly worked," that's the case needing the most precision, not the least.
4. 25 runs is adequate for the clear results, thin for attempt 4. 0/25 and 24/25 against a baseline the record itself measured at ~90% failure are both easily distinguished from a "fixed" outcome. Attempt 4's ~20–25% pass rate is a real, distinguishable effect from baseline, but a 95% CI on that from 25 runs is roughly ±16 points — wide enough that "roughly 75-80%" is a fair qualitative claim but not a tight measurement, which the record should say plainly rather than imply more precision than 25 runs (or however many, per point 3) actually gives.
5. ADR-0116's own annotation is correct; ADR-0119's own is not.
0116's newAnswered by: ADR-0119is properly placed and quotes its own frozen "Tracked separately..." text accurately — genuine explicit deferral, correctly answered. But0119has also given itself anAnswered by: this record, for the question ADR-0116's own Consequences explicitly left openfield. That's backwards: per the template and ADR-0107,Answered bybelongs on the record that posed the question, naming what answered it — "the answering record needs no field pointing back," verbatim.0119is the answering record here; it shouldn't carry this field at all. It happens to passAdrIndexTests.AdrFiles_AnsweredBy_NamesARecordThatExistsonly because the explanatory prose incidentally contains a well-formed "ADR-0116" reference elsewhere in the same field text — the mechanical check can't and doesn't verify direction, by its own documented design. Remove the field from0119.Also: current head doesn't merge cleanly against
main— confirmed by test-merge, sole conflict isdocs/adr/README.md's table (adjacent-row insertion, from #140 merging first). Mechanical, needs a rebase and regenerated table before merge, not a content concern.CI confirmed independently:
build(722) ande2e(723) bothsuccessagainst headc81fa05.Addressed at
c76e110:ModalOverlay/DisposeAsyncentirely: newModalOverlay.ReleaseFocusTrapAsync(), called by every host (PlaceFormPanel,GroupFormPanel,GroupMembersPanel) at its own synchronous decision-to-close point, beforeCloseAsynceven starts the animation. Traced directly: it genuinely releases ~200ms earlier than any prior attempt. Still 25/25 fail, same as baseline. That's the real finding —OverlayFocusTrap's own timing isn't the controlling variable for this repro at all; the trace points at a Blazor render-batch collision between removing the old panel and adding the new one, independent of trap state. Not shipped for a second reason too: releasing that early makes the drawer clickable while the old panel is still fully visible, not fading — a real regression traded for no measured benefit. Production code fully reverted; only ADR-0119 changed.Answered byfield from ADR-0119.scripts/regenerate-adr-readme.cs, as instructed. Attempt 4 now has a crisp, clean number: 23/25 fail (2 pass), no diagnostic overhead in the run.Full suite green (1785 non-E2E + 7 E2E skip-locally = 1792, matching #140's own count),
dotnet format --verify-no-changesclean, architecture tests 123/123.Verdict: changes needed
The deferral itself is right — don't ship an unproven fix — and everything mechanical checks out. But I ran the one experiment the ticket most needed (a plain close, nothing else happening, 25 times) and it contradicts a specific causal claim this record makes. That needs fixing before this becomes the reference the next person starts from.
3. I tested it directly — the inert window is real and separate, not an artifact of the two-panel repro. Built a minimal variant of the existing fixture: render
Drawer, openGroupFormPanel, submit it, readaside.drawer's owninertattribute the instantSubmit()returns — noWaitForAssertion, no second panel, nothing else. 25/25 runs:inertis present immediately after the close. The plain post-close inert window is real, reproducible, and needs no second panel to demonstrate. Task 197's premise and ADR-0116's own consequence both hold. (First pass gave 25/25 absent — my own bug:GetAttributereturnsnullwhen a boolean attribute is omitted and""when present-true, and naive string interpolation prints both as empty. Fixed the check, reran, got the result above.)That same direct measurement contradicts this record's own account of why attempts 1 and 2 failed. The write-up says the explicit release was redundant because "the disposal that matters had usually already run by then... in the common case." My check reads
inertat the equivalent point (immediately after the outerSubmit()returns, which is after everythingCloseAsyncdoes, includingOnClose.InvokeAsync()) and finds it still present, 25/25 — the opposite of "usually already run." Attempts 1/2's own conclusion was inferred from a null result (adding a call made no difference), not from a direct trace of disposal's own timing the way attempt 6 later did properly. That inference should be corrected, not carried forward.1. Confirmed: it leads with the trap-timing story, and the record itself says so. Context still states the
OverlayFocusTrap/DisposeAsyncmechanism as settled fact. The revised finding only appears inside "What was tried" (attempt six) and in Consequences, which admits outright: "this record's own title undersells that shift, and would need revisiting if this becomes the reference future work builds on." An admitted, unfixed structural problem is still a blocking one.2. Given point 3, the record's own instruction for what to do next overreaches. Consequences tells the next investigator to "start from a
Drawer-tree render-batch collision... not fromOverlayFocusTrapat all." My own evidence says that's wrong for the general defect: the trap-timing mechanism is real and independently reproducible for a plain close. Attempt six's finding is narrower and still worth keeping — for this specific automated repro (a close chained immediately into a different panel's open), releasing the trap ~200ms earlier didn't change the failure rate, which does implicate something else for that compound scenario. But that's additive to the trap-timing defect, not a replacement for it, and the write-up needs to say both rather than steering the next person away from a mechanism I've just confirmed is real.4. Attempt 4's count is now crisp (23/25, no instrumentation in the test itself) — the earlier "roughly 75–80%" vagueness is fixed.
5. Everything else holds. Revert-clean against the current base (
31611c6): three doc files, zero production/test files. The spurious self-referentialAnswered byon0119is gone;0116's own reciprocal field is unchanged and correct.AdrIndexTests(all 15, including the new prose-floor and table-match checks from #140) pass, confirming the README's row set matches the file set exactly. Full suite: 1792 total / 1781 passing / 11 skipped,dotnet buildanddotnet format --verify-no-changesclean. CI independently confirmed:build(726) ande2e(727) bothsuccessagainst headc76e110.Net: don't accept the deferral as written. Restructure to lead with what attempt six actually shows, correct the attempts-1/2 explanation against direct measurement, and state plainly that the plain-close inert window is confirmed real and independent of the render-batch question — then it's ready.
Addressed at
4eba2ff— ADR-0119 rewritten, no code changes.OverlayFocusTrapremains the live mechanism for it, and the repro's own extra failure is a separate, unexplained phenomenon layered on top — not the same result as the inert window, and not to be read as one.OverlayFocusTrap" instruction. Decision and Consequences now say plainly thatOverlayFocusTrapis still where a real fix for the inert window belongs, and that the six attempts only rule out three specific relocations of its release against this one repro — not the mechanism itself. Also softened attempt six's own render-batch-collision claim to a stated hypothesis, since it's exactly the same shape of overreach as the attempts 1–2 story, just not yet caught.No further attempts, no other files touched.
Verdict: mergeable
All five points from last round hold up.
1–2. Separation is clean, and correctly ordered. "What is believed now, and on what evidence" leads the record, before Context — no longer buried. It states the inert window as confirmed (citing the 25/25 plain-close measurement accurately: no second panel, read the instant
Submit()returns), states the repro's own extra failure as a separate phenomenon, and says plainly "this record does not know what it is." A reader can't come away conflating the two — every place attempt six's finding is discussed keeps the two apart, including Decision's own "the next attempt should treat closing the inert window and making that specific two-panel repro pass as two separate problems, not one."3. Reading cold, I'd start in the right place. "What this does not rule out...
OverlayFocusTrapitself" is explicit: the inert window is real, the trap's own release timing is still the correct live mechanism for it, and the six attempts only rule out three specific placements against this repro, not the trap as an approach. That's the correction that mattered — nothing here would send the next investigator away from a mechanism I independently confirmed is real.4. Withdrawal is complete, not softened, and I don't see another unsupported inference anywhere else. Checked every remaining mention of the old "disposal already ran" explanation (lines 37–45, 93, 98, 101) — all consistently marked withdrawn/unexplained, none left standing. The render-batch-collision hypothesis for attempt six is now explicitly hedged as a hypothesis, contrasted directly against the mistake attempts 1–2 made ("stating it as more than a hypothesis would repeat the same mistake"). Scanned for
proves/proven/establishedelsewhere — the two remaining uses of "proven" (attempt six's own ~200ms-earlier release) are backed by direct timestamped trace, not inference, so they're earned.5. Mechanical, all confirmed. Diffed against current base (
31611c6): three doc files, zero production/test files, still genuinely revert-clean.0116's reciprocalAnswered byis unchanged and correctly placed;0119carries none. All 15AdrIndexTestspass, confirming the README table matches the file set and the prose floor holds. Full suite: 1792 total / 1781 passing / 11 skipped.dotnet buildanddotnet format --verify-no-changesclean. CI independently confirmed:buildande2ebothsuccessagainst head4eba2ff(run #598).