Let React Flow own node state so drag frames render inside the pointer event - #327
Merged
Merged
Conversation
…r event Working node editors update a dragged node's position and the connection paths attached to it synchronously, in the same mousemove handler. Drawflow is a clear reference: its drag handler writes the node's position and then immediately recomputes and writes every affected connection's path, with no framework state and no deferred work in between. This canvas did the opposite. Node state was controlled, so every drag frame travelled through this component's React state, a re-render, and React Flow's own prop-sync effect - a passive effect, which React runs after the browser has already had the opportunity to paint - before the renderer learned the new position. Node cards and connection geometry are both produced by React Flow from that state, so the whole canvas was being updated a hop later than the gesture that drove it. React Flow now owns the node collection (defaultNodes). A drag frame is applied to its store synchronously inside the pointer event, and the node card and its connections re-render together from that single write. The component keeps a mirror of the collection for its own logic - drag corrections, delete routing, scene merging - and pushes backend scene snapshots into the store explicitly, which is not per-frame work. Combined with the drag corrections already running inside React Flow's change pipeline, the position a gesture produces is now computed once and rendered once, with nothing between the pointer event and the frame. Also adds web_ui/src/app/canvas/drag/dragCorrections.ts: the drag correction maths (speed factor, smart-guide snap, group cascade) as a pure, framework-free module with an explicit contract, so the rule "one position, computed once" is enforceable in one reviewable place. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Problem
Working node editors update a dragged node's position and its connection paths synchronously, in the same mousemove handler. Drawflow is a clear reference: its drag handler writes the node's position, then immediately recomputes and writes the
dattribute of every affected connection path. No framework state, no deferred work, nothing between the input event and the updated geometry.This canvas did the opposite. Node state was controlled by the component, so every drag frame travelled through React state, a re-render, and React Flow's own prop-sync effect before the renderer learned the new position. That effect is a passive one, which React runs after the browser has already had the opportunity to paint. Node cards and connection geometry are both produced by React Flow from that state, so the entire canvas was being updated a hop later than the gesture driving it.
Change
React Flow now owns the node collection (
defaultNodes). A drag frame is applied to its store synchronously inside the pointer event, and the node card and its connections re-render together from that single store write. The component keeps a mirror of the collection for its own logic - drag corrections, delete routing, scene merging - and pushes backend scene snapshots into the store explicitly, which is not per-frame work.Combined with the drag corrections already running inside React Flow's change pipeline (#326), the position a gesture produces is now computed once and rendered once.
Also adds
web_ui/src/app/canvas/drag/dragCorrections.ts: the drag correction maths - speed factor, smart-guide snap, group cascade - extracted as a pure, framework-free module with an explicit contract, so the rule "one position, computed once" lives in one reviewable place rather than inline in a component.Test plan