Skip to content

fix: stabilize async interaction lifecycles (#55, #56, #57) - #65

Merged
smiggleworth merged 11 commits into
mainfrom
fix/55-57-async-lifecycle
Aug 15, 2026
Merged

fix: stabilize async interaction lifecycles (#55, #56, #57)#65
smiggleworth merged 11 commits into
mainfrom
fix/55-57-async-lifecycle

Conversation

@smiggleworth

@smiggleworth smiggleworth commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Closes #55.
Closes #56.
Closes #57.

Summary

  • make HoverCard open/close scheduling mutually exclusive and clear both timer directions on teardown
  • bound Tooltip native-focus adoption across reactive trigger replacement without emitting idempotent controlled updates
  • key Toast registrations by toastId so sibling churn preserves unrelated component, timer, DOM, and focus identity
  • add lifecycle-specific source guards and three-engine browser regressions
  • release @askrjs/ui@0.0.31

TDD evidence

  • Red 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 exhausted MAX_FLUSH_DEPTH while producing 101 alternating onOpenChange transitions.
  • Green e3580e38d39acda56b67cc30804caad6af7c9442: 9 focused files / 90 tests passed across all three engines, then passed three more consecutive focused runs.
  • Hosted red runs 31912365725, 31912781455, 31913025321, 31913224799, 31913596839, and 31914105719 first 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. Red d9628e5558ebc8a611e605047ac5f2ee3a90dad1 reproduces that path with real userEvent.hover() pointer movement and a visible outside target. Green c25fd1f18adff4f35456b3adf96eadb715067a70 passed the focused suite across Chromium, Firefox, and WebKit five consecutive times before the full gate. Exact-head run 31914542143 then 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 guard ec6a266f4a877d25d4a96e89b35a757bf31b8825 keeps three complete churn cycles, adds an exact single-open transition assertion, and passes the focused suite in all three engines.
  • Full local gate: npm run check passed 309 browser files / 1,275 browser tests plus build, lint, types, unit, policy, jsdom, publint, and packed-consumer validation.
  • All four benchmark tiers passed, including three-engine Tooltip and Toast lifecycle tiers; npm audit --omit=dev reported 0 vulnerabilities.

Root cause and permanent guardrails

  • HoverCard independently scheduled opposing timers, and an older callback could clear or overwrite the newer transition. Separate timer cancellation functions now enforce one pending direction. A lifecycle-owned document pointerover guard 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.
  • Tooltip native focus synchronously opened the root; reconciliation replaced the focused trigger, whose blur attempted to close the root and restarted the open/close render cycle. A bounded focus-adoption episode ignores only that replacement blur, re-focuses the live trigger, suppresses idempotent updates, and cancels pending frame work on teardown. Native .focus(), real Tab, plain-control, controlled-rejection, and teardown regressions are mandatory in source-layout CI.
  • Toast rendered registrations with a raw reactive array map, so any sibling registration change recreated unrelated views. Keyed <For> reconciliation now preserves each toastId; CI asserts unrelated DOM identity, focus identity, and original expiry deadline through sibling open/close churn.

Acceptance audit

  • Reporter ownership and issue contracts were read back from GitHub and clarified against the supported APIs.
  • Regression tests were committed red before implementation.
  • The same focused regressions pass green and repeatedly across Chromium, Firefox, and WebKit.
  • Permanent structural and behavioral guardrails cover race direction, keyed identity, real native focus, controlled rejection, and teardown.
  • Full local release, package, benchmark, and production-audit gates pass.
  • Exact-head hosted CI succeeds on macOS, Windows, and Ubuntu.
  • Every linked issue acceptance checkbox is checked with evidence before ready-for-review and squash merge.
  • v0.0.31 peels to the squash-merged main SHA, npm integrity is verified, and a clean consumer imports the repaired surfaces with a zero-vulnerability production audit.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@smiggleworth
smiggleworth merged commit ed14de4 into main Aug 15, 2026
3 checks passed
@smiggleworth
smiggleworth deleted the fix/55-57-async-lifecycle branch August 15, 2026 23:43
@smiggleworth

Copy link
Copy Markdown
Contributor Author

Post-merge release proof is complete. Publish run 31915524483 succeeded at squash-merged main ed14de4e5238929d82820986e74d59a525b05a1e; annotated v0.0.31 peels to that exact commit. npm serves @askrjs/ui@0.0.31 with integrity sha512-298Cy6QncLsypleH4lY2FSD30vE77L5qgqasBAlvSKP3K/EFuIqPVZBGxX9iv2zE/8PbwDzRYFP2GjnB+9S5KA==. A fresh registry consumer imported the HoverCard, Toast, and Tooltip root/trigger/host surfaces through their public subpaths and passed npm audit --omit=dev with 0 vulnerabilities.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants