fix: stabilize async interaction lifecycles (#55, #56, #57) - #65
Merged
Conversation
This was referenced Aug 15, 2026
HoverCard open/close timers don't cancel each other, causing stuck-open and premature-close bugs
#55
Closed
smiggleworth
marked this pull request as ready for review
August 15, 2026 23:42
Contributor
Author
|
Post-merge release proof is complete. Publish run 31915524483 succeeded at squash-merged main |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #55.
Closes #56.
Closes #57.
Summary
toastIdso sibling churn preserves unrelated component, timer, DOM, and focus identity@askrjs/ui@0.0.31TDD evidence
cf7bdf42a7bd863c231a6d1c900f53c1432fbfae: the focused suite failed 11 cases across Chromium, Firefox, and WebKit. HoverCard opened after immediate leave and closed after re-entry; sibling Toast registration replaced the original node; native Tooltip focus exhaustedMAX_FLUSH_DEPTHwhile producing 101 alternatingonOpenChangetransitions.e3580e38d39acda56b67cc30804caad6af7c9442: 9 focused files / 90 tests passed across all three engines, then passed three more consecutive focused runs.31912365725,31912781455,31913025321,31913224799,31913596839, and31914105719first exposed stale-node, event-delivery, and scheduler-phase timing under Ubuntu load, then isolated a product defect: reconciliation can replace a hovered trigger while the pointer remains stationary, so no leave event reaches the replacement node. Redd9628e5558ebc8a611e605047ac5f2ee3a90dad1reproduces that path with realuserEvent.hover()pointer movement and a visible outside target. Greenc25fd1f18adff4f35456b3adf96eadb715067a70passed the focused suite across Chromium, Firefox, and WebKit five consecutive times before the full gate. Exact-head run31914542143then showed the five-cycle real-pointer churn guard repeatedly exceeded the global 15-second test deadline under Windows browser load before reaching its assertion. Test guardec6a266f4a877d25d4a96e89b35a757bf31b8825keeps three complete churn cycles, adds an exact single-open transition assertion, and passes the focused suite in all three engines.npm run checkpassed 309 browser files / 1,275 browser tests plus build, lint, types, unit, policy, jsdom, publint, and packed-consumer validation.npm audit --omit=devreported 0 vulnerabilities.Root cause and permanent guardrails
pointeroverguard also evaluates the live trigger/content containment after reconciliation, clears a pending open outside, schedules at most one close for an open card, cancels that close on re-entry, and is removed on teardown. Immediate-leave, re-entry, three-cycle repeated churn with exactly one final open, transfer/focus, reconciled-node pointer exit, and listener-cleanup regressions exercise real user pointer movement across all three engines; the source-layout guard requires both installation and cleanup of the structural listener..focus(), real Tab, plain-control, controlled-rejection, and teardown regressions are mandatory in source-layout CI.<For>reconciliation now preserves eachtoastId; CI asserts unrelated DOM identity, focus identity, and original expiry deadline through sibling open/close churn.Acceptance audit
v0.0.31peels to the squash-merged main SHA, npm integrity is verified, and a clean consumer imports the repaired surfaces with a zero-vulnerability production audit.