Measure the drawer's inert window in a real browser (task 197) #212

Merged
rob merged 1 commit from drawer-inert-after-close into main 2026-08-21 15:18:28 +00:00
Owner

This is an advance in understanding, not a fix, and task 197 stays open. No production code is touched. Task 197's own wording is that it is "worth doing properly, not worth forcing", and a seventh attempt that narrowed the window while being reported as a fix would be worse than nothing.

Not a repeat of any of the six attempts ADR-0119 records — no relocation of OverlayFocusTrap.ExitAsync, no InvokeAsync avoidance, no @key, no same-tick fix. The approach was to stop reasoning about the mechanism and measure whether the confirmed inert window actually costs a user a click, in a real browser, at the exact trigger point the existing repro uses.

What was measured, in Chromium via Playwright:

  • A real, hit-tested mouse click on the drawer, issued the instant the closing panel's markup is confirmed removed from the DOM, landed 25 times out of 25. Never lost.
  • The harness was validated rather than assumed: the same click, issued while the panel is genuinely still open, was blocked 25 out of 25. It can detect a real loss.
  • An independent native MutationObserver, with no Playwright round trip in the timing path, found inert already false at the moment the panel's removal became observable, in all 25 timed runs.

A methodological trap, now documented so nobody repeats it: a scripted element.click() does not respect inert in this Chromium build. Any measurement built on it would report a pass regardless of the defect.

A second finding that matters more than the result. HomeAndDrawerSharedGroupStateTests's repro currently passes 25/25 with both WaitForAssertion wrappers removed, on unmodified main — but this is not evidence the defect is fixed. Commit cd2eff8 (task 263, merged the day before this pass) wired a FakeClosingAnimationDelay into that test which resolves ModalOverlay's 200ms closing wait synchronously, removing the yield point the old repro depended on to expose the race. Production code is byte-for-byte what ADR-0119 examined. That test currently proves nothing about this race in either direction, and now carries a comment saying so.

What ships:

  • A permanent regression guard, DrawerClickableImmediatelyAfterModalTaskClosesJourneyTests, in the E2E suite — at the level that can actually observe inert, which bUnit cannot.
  • Dated corrections to ADR-0119 and ADR-0116 per ADR-0170's convention, recording what the browser measurement shows and leaving the original text legible.
  • The WaitForAssertion workaround stays, now with a comment explaining why its own repro cannot currently be trusted either way.

What this does not establish. Seventy-five trials on a fast, idle machine do not prove the defect is unreachable on a slower device or a loaded one. That is untested, and is stated as untested rather than glossed. Task 197 remains open with these findings recorded on it.

Verified under the pinned SDK 10.0.100: 2,222 unit and integration tests passed, 0 failed; the full E2E suite — 62 including the 2 new — passed in about 2.5 minutes with PLACEMARK_RUN_E2E_TESTS=true confirmed set and the tests confirmed executing rather than silently skipped, each real Chromium journey taking 30–45 seconds rather than sub-second.

**This is an advance in understanding, not a fix, and task 197 stays open.** No production code is touched. Task 197's own wording is that it is "worth doing properly, not worth forcing", and a seventh attempt that narrowed the window while being reported as a fix would be worse than nothing. **Not a repeat of any of the six attempts ADR-0119 records** — no relocation of `OverlayFocusTrap.ExitAsync`, no `InvokeAsync` avoidance, no `@key`, no same-tick fix. The approach was to stop reasoning about the mechanism and measure whether the confirmed `inert` window actually costs a user a click, in a real browser, at the exact trigger point the existing repro uses. **What was measured, in Chromium via Playwright:** - A real, hit-tested mouse click on the drawer, issued the instant the closing panel's markup is confirmed removed from the DOM, **landed 25 times out of 25**. Never lost. - **The harness was validated rather than assumed**: the same click, issued while the panel is genuinely still open, was blocked 25 out of 25. It can detect a real loss. - An independent native `MutationObserver`, with no Playwright round trip in the timing path, found `inert` **already `false`** at the moment the panel's removal became observable, in all 25 timed runs. **A methodological trap, now documented so nobody repeats it:** a scripted `element.click()` does **not** respect `inert` in this Chromium build. Any measurement built on it would report a pass regardless of the defect. **A second finding that matters more than the result.** `HomeAndDrawerSharedGroupStateTests`'s repro currently passes 25/25 with both `WaitForAssertion` wrappers removed, on unmodified `main` — but **this is not evidence the defect is fixed.** Commit `cd2eff8` (task 263, merged the day before this pass) wired a `FakeClosingAnimationDelay` into that test which resolves `ModalOverlay`'s 200ms closing wait synchronously, removing the yield point the old repro depended on to expose the race. Production code is byte-for-byte what ADR-0119 examined. **That test currently proves nothing about this race in either direction**, and now carries a comment saying so. **What ships:** - A permanent regression guard, `DrawerClickableImmediatelyAfterModalTaskClosesJourneyTests`, in the E2E suite — at the level that can actually observe `inert`, which bUnit cannot. - Dated corrections to **ADR-0119** and **ADR-0116** per ADR-0170's convention, recording what the browser measurement shows and leaving the original text legible. - The `WaitForAssertion` workaround **stays**, now with a comment explaining why its own repro cannot currently be trusted either way. **What this does not establish.** Seventy-five trials on a fast, idle machine do not prove the defect is unreachable on a slower device or a loaded one. That is untested, and is stated as untested rather than glossed. Task 197 remains open with these findings recorded on it. Verified under the pinned SDK 10.0.100: 2,222 unit and integration tests passed, 0 failed; the full E2E suite — 62 including the 2 new — passed in about 2.5 minutes with `PLACEMARK_RUN_E2E_TESTS=true` confirmed set and the tests confirmed executing rather than silently skipped, each real Chromium journey taking 30–45 seconds rather than sub-second.
Measure the drawer's inert window in a real browser (task 197)
All checks were successful
CI / build (pull_request) Successful in 5m16s
CI / container-images (pull_request) Successful in 4s
CI / e2e (pull_request) Successful in 4m53s
88836682b8
rob left a comment

Verdict: mergeable

Checked against the five things that would actually undermine this investigation, none held up:

  • Negative control is real. Close_PanelStillOpen_ClickOnTheDrawerNeverReachesIt drives the same page.Mouse.ClickAsync mechanism at the same computed button coordinates as the positive measurement, against a genuinely open panel. All selectors it and the positive test use (modal-overlay-panel, modal-overlay-close, accordion-add-place, place-form-name) exist in production markup — not a check against a selector that vacuously never matches.
  • element.click() claim is consistent with the shipped test: both tests use page.Mouse.ClickAsync throughout for the timing-sensitive assertions; Locator.ClickAsync() only appears for closing panels, which isn't timing-sensitive.
  • cd2eff8/FakeClosingAnimationDelay explanation checks out at the code level. ModalOverlay.CloseAsync does await ClosingDelay.WaitAsync(...) then await OnClose.InvokeAsync(); FakeClosingAnimationDelay.WaitAsync returns Task.CompletedTask unless Hold() is called, and await on an already-completed task doesn't yield — it removes exactly the yield point the six ADR-0119 attempts relied on to expose the race. Traced the wiring further: Drawer's <aside inert="@(!IsOpen || OverlayFocus.IsOpen)"> only clears on a second, separately-scheduled render fired by OverlayFocusTrap.Changed, itself only raised from ModalOverlay.DisposeAsync — genuinely two Blazor dispatch cycles apart from the render that detaches the panel's DOM. That's real production wiring, not stubbed, so the new E2E test's trigger point is tied to a genuine race window rather than something that can only ever pass.
  • Couldn't execute a mutation to directly watch the E2E guard redden — file edits in the read-only worktree were blocked by the sandbox. Said so rather than claiming a result I didn't observe. The static trace above is reasonable indirect evidence the guard isn't vacuous, but it's not the same as watching it fail.
  • ADR corrections are additive and honestly scoped: both are +only diffs, dated, qualify rather than overclaim ("qualified, not withdrawn"), and AdrFiles_Corrected_HasAMatchingInlineMarkerInBothDirections passes (ran it locally against this head).
  • CI genuinely executed: run 890 (SHA 8883668, matches head) — e2e job checked out the same SHA and logged Passed! - Failed: 0, Passed: 62, ... Total: 62 in 3m10s wall time; build job shows the same 62 tests individually [SKIP]ped with no PLACEMARK_RUN_E2E_TESTS set there, and the workflow only sets that env var in the e2e job. Baseline main (5cb644a) E2E total is 60 — confirms the 60→62 claim.

One minor, non-blocking discrepancy: the PR body claims "2,222 unit and integration tests passed" — CI's actual sum (Domain 39 + Contracts 139 + Architecture 148 + WebUI 916 + Infrastructure 360 + Api 622) is 2,224. Off by two; doesn't change anything about the substance of the investigation.

The stated limit ("75 trials on a fast idle machine ... untested on a slower or loaded device") is honest and matches what was actually measured — nothing here claims the defect doesn't exist, only that it wasn't reproduced under the conditions tried.

Verdict: mergeable Checked against the five things that would actually undermine this investigation, none held up: - **Negative control is real.** `Close_PanelStillOpen_ClickOnTheDrawerNeverReachesIt` drives the same `page.Mouse.ClickAsync` mechanism at the same computed button coordinates as the positive measurement, against a genuinely open panel. All selectors it and the positive test use (`modal-overlay-panel`, `modal-overlay-close`, `accordion-add-place`, `place-form-name`) exist in production markup — not a check against a selector that vacuously never matches. - **`element.click()` claim is consistent with the shipped test**: both tests use `page.Mouse.ClickAsync` throughout for the timing-sensitive assertions; `Locator.ClickAsync()` only appears for closing panels, which isn't timing-sensitive. - **`cd2eff8`/`FakeClosingAnimationDelay` explanation checks out at the code level.** `ModalOverlay.CloseAsync` does `await ClosingDelay.WaitAsync(...)` then `await OnClose.InvokeAsync()`; `FakeClosingAnimationDelay.WaitAsync` returns `Task.CompletedTask` unless `Hold()` is called, and `await` on an already-completed task doesn't yield — it removes exactly the yield point the six ADR-0119 attempts relied on to expose the race. Traced the wiring further: `Drawer`'s `<aside inert="@(!IsOpen || OverlayFocus.IsOpen)">` only clears on a *second*, separately-scheduled render fired by `OverlayFocusTrap.Changed`, itself only raised from `ModalOverlay.DisposeAsync` — genuinely two Blazor dispatch cycles apart from the render that detaches the panel's DOM. That's real production wiring, not stubbed, so the new E2E test's trigger point is tied to a genuine race window rather than something that can only ever pass. - **Couldn't execute a mutation to directly watch the E2E guard redden** — file edits in the read-only worktree were blocked by the sandbox. Said so rather than claiming a result I didn't observe. The static trace above is reasonable indirect evidence the guard isn't vacuous, but it's not the same as watching it fail. - **ADR corrections are additive and honestly scoped**: both are +only diffs, dated, qualify rather than overclaim ("qualified, not withdrawn"), and `AdrFiles_Corrected_HasAMatchingInlineMarkerInBothDirections` passes (ran it locally against this head). - **CI genuinely executed**: run 890 (SHA `8883668`, matches head) — `e2e` job checked out the same SHA and logged `Passed! - Failed: 0, Passed: 62, ... Total: 62` in 3m10s wall time; `build` job shows the same 62 tests individually `[SKIP]`ped with no `PLACEMARK_RUN_E2E_TESTS` set there, and the workflow only sets that env var in the `e2e` job. Baseline `main` (5cb644a) E2E total is 60 — confirms the 60→62 claim. One minor, non-blocking discrepancy: the PR body claims "2,222 unit and integration tests passed" — CI's actual sum (Domain 39 + Contracts 139 + Architecture 148 + WebUI 916 + Infrastructure 360 + Api 622) is 2,224. Off by two; doesn't change anything about the substance of the investigation. The stated limit ("75 trials on a fast idle machine ... untested on a slower or loaded device") is honest and matches what was actually measured — nothing here claims the defect doesn't exist, only that it wasn't reproduced under the conditions tried.
rob merged commit 8c47370f97 into main 2026-08-21 15:18:28 +00:00
rob deleted branch drawer-inert-after-close 2026-08-21 15:18:29 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rob/PlaceMark!212
No description provided.