LearnNewsExamplesServices
Frontmatter
id9375
titleImprove ScrollSync Lock Release Mechanism (rAF vs setTimeout)
stateClosed
labels
enhancementaiarchitecture
assigneestobiu
createdAtMar 7, 2026, 5:54 PM
updatedAtMar 7, 2026, 5:57 PM
githubUrlhttps://github.com/neomjs/neo/issues/9375
authortobiu
commentsCount1
parentIssue9369
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMar 7, 2026, 5:57 PM

Improve ScrollSync Lock Release Mechanism (rAF vs setTimeout)

Closed v12.1.0 enhancementaiarchitecture
tobiu
tobiu commented on Mar 7, 2026, 5:54 PM

Part of Epic #9369

Currently, the granular lock (coordinatingNodes Set) in ScrollSync.mjs is released on the next requestAnimationFrame. However, GridDragScroll utilizes a continuous requestAnimationFrame loop for momentum scrolling. Because native DOM scroll events are asynchronous to the render pipeline, they might dispatch to the event queue after the lock is released on the next frame, defeating the gatekeeper and causing a ping-pong loop.

Investigate and implement a safer lock release mechanism (e.g., using setTimeout to push the release to the end of the macrotask queue) to ensure the lock remains held across continuous 16ms programmatic mutations until all resulting native scroll events have been flushed by the browser.

tobiu added the enhancement label on Mar 7, 2026, 5:54 PM
tobiu added the ai label on Mar 7, 2026, 5:54 PM
tobiu added the architecture label on Mar 7, 2026, 5:54 PM
tobiu added parent issue #9369 on Mar 7, 2026, 5:54 PM
tobiu referenced in commit 78ba094 - "fix(Grid): change ScrollSync lock release to use debounced setTimeout (#9375)" on Mar 7, 2026, 5:56 PM
tobiu
tobiu Mar 7, 2026, 5:56 PM

Input from Gemini 3.1 Pro:

✦ I have updated the lock release mechanism in ScrollSync.mjs to use a debounced setTimeout (default 50ms) instead of requestAnimationFrame.

Why: requestAnimationFrame releases the lock on the very next render tick. However, native DOM scroll events are dispatched asynchronously. If GridDragScroll's momentum loop fires an update every 16ms, a native scroll event might arrive after the rAF tick has already released the lock, causing a false positive in the interceptor and triggering a feedback loop.

The Fix: By using a Map of node IDs to timeout IDs, the lockNode method now clears any existing timeout and sets a new 50ms timer. This guarantees that during a continuous momentum throw (which updates the DOM every 16ms), the lock remains firmly held the entire time. It only releases 50ms after the final momentum update has settled, providing plenty of time for all trailing native asynchronous scroll events to be safely ignored by the gatekeeper.

tobiu closed this issue on Mar 7, 2026, 5:57 PM
tobiu assigned to @tobiu on Mar 7, 2026, 5:57 PM