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
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtMar 7, 2026, 5:57 PM

Improve ScrollSync Lock Release Mechanism (rAF vs setTimeout)

Closed v12.1.0/archive-v12-1-0-chunk-1 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
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.