Skip to content

perf(popup-menu): track the pointer trail only while a submenu trigger is hovered - #586

Draft
kianbazza wants to merge 1 commit into
ui-643-batch-sticky-row-width-reads-and-writesfrom
ui-644-track-the-pointer-trail-only-while-a-submenu-trigger-is
Draft

kianbazza wants to merge 1 commit into
ui-643-batch-sticky-row-width-reads-and-writesfrom
ui-644-track-the-pointer-trail-only-while-a-submenu-trigger-is

Conversation

@kianbazza

@kianbazza kianbazza commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Every mounted SubmenuTrigger kept a shared window pointermove listener alive and recorded a point on every move anywhere on the page, even with nothing near a trigger. The trail now records only while something holds it: the trigger while it's hovered, and its leave monitor while it runs.

 useMouseTrail(4)
-  on mount: subscribe to window pointermove
-  on unmount: unsubscribe
+  returns { trailRef, hold(reason), release(reason) }   // reason: 'hover' | 'monitor'
+  records while any hold remains

 <SubmenuTrigger>
+  onPointerEnter / onPointerMove:  hold('hover')
   onPointerLeave:                  compute heading from the trail
+                                     (adds the leave point if only one sample)
+                                   then release('hover')
+  leave monitor start / cleanup:   hold('monitor') / release('monitor')
+  trigger hidden by filtering:     end the monitor, release('hover')

The trail starts empty when the pointer enters the trigger. A fast pass that lands only one sample inside the trigger gets the leave point as its second point, so its heading follows the real path through the trigger instead of the fallback that aims at the submenu.

The per-move aim guard traces (leave-monitor-pointermove, leave-monitor-hit-eval, pointermove-blocked-by-guard, pointermove-open-suppressed-after-explicit-close) now pass their details as a function, so nothing is built unless logAimGuardEvents is on.

Evidence

pointer-trail.test.tsx counts live window pointermove listeners.

                                       before   after
menu open, nothing hovered               1        0
hovering a plain item                    1        0
hovering the submenu trigger             1        1
leaving toward the submenu (monitor)     2        2
pointer inside the submenu               1        0
leaving with a miss (no monitor)         1        0
hovered trigger filtered out             1        0
  • Before: AssertionError: expected 1 to be +0
    After: passes

Fast passes, one pointer sample inside the trigger:

flick straight down onto the row below    guard stays off, row below highlights
pass heading toward the submenu           guard activates

Both pass. Without the leave point, "flick straight down" fails: a single sample falls back to aiming at the submenu and the guard activates.

The aim guard tests in popup-menu.test.tsx (lifecycle, continuous aim monitoring, closeDelay, safe triangle) pass unchanged. use-mouse-trail.test.tsx now takes a hold before checking that subscribers share one listener, since mounting no longer subscribes.

Merge Danger

Door: two-way

Blast Radius: small

Submenu aim only. The heading at leave now comes from the moves over the trigger (plus the leave point when there's only one), not from moves before the pointer reached it. When the pointer crosses the trigger in a single sample, that is a shorter path than before. A trigger hidden by filtering while a leave monitor runs now ends that monitor right away instead of at its timeout.

Closes UI-644

@kianbazza
kianbazza added this pull request to stack #587 October 10, 2026 04:48
@vercel

vercel Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
ui-canary Ready Ready Preview Oct 10, 2026 11:33pm UTC

Request Review

@linear-code

linear-code Bot commented Oct 10, 2026

Copy link
Copy Markdown

UI-644

@kianbazza
kianbazza force-pushed the ui-644-track-the-pointer-trail-only-while-a-submenu-trigger-is branch from dfbd7a0 to e665f20 Compare October 10, 2026 23:33

This branch was successfully deployed

1 active deployment
Preview – ui-canary — e665f203 Deployed Oct 10, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant