feat(frontend): Add minimum movement threshold for node position history tracking (#11481)

This PR implements a minimum movement threshold of 50 pixels for node
position changes before they are logged to the history system. This
prevents the undo/redo history from being cluttered with minor,
unintentional movements that occur when users interact with block inputs
or accidentally nudge nodes.

### Changes 🏗️

- **Added movement threshold for history tracking**: Implemented a 50px
minimum movement requirement before logging node position changes to
history
- **Prevents history spam**: Stops small, unintentional movements (like
clicking on inputs inside blocks) from cluttering the undo/redo history
- **Tracks drag start positions**: Maintains initial positions when
dragging begins to accurately calculate total movement distance
- **Improved history management**: Only significant node movements are
now recorded, matching the behavior of the old builder
- **Memory efficient**: Cleans up tracked positions after drag
operations complete

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
- [x] Drag a node less than 50px and verify no history entry is created
  - [x] Drag a node more than 50px and verify history entry is created
- [x] Click on inputs inside blocks and verify no history entries are
created
  - [x] Test undo/redo functionality works correctly with the threshold
  - [x] Verify adding/removing nodes still creates history entries
  - [x] Test multiple nodes being dragged simultaneously
This commit is contained in:
Abhimanyu Yadav
2025-12-01 11:13:50 +05:30
committed by GitHub
parent 321ab8a48a
commit ea4b55f967

View File

@@ -13,6 +13,13 @@ import { useHistoryStore } from "./historyStore";
import { useEdgeStore } from "./edgeStore";
import { BlockUIType } from "../components/types";
// Minimum movement (in pixels) required before logging position change to history
// Prevents spamming history with small movements when clicking on inputs inside blocks
const MINIMUM_MOVE_BEFORE_LOG = 50;
// Track initial positions when drag starts (outside store to avoid re-renders)
const dragStartPositions: Record<string, XYPosition> = {};
type NodeStore = {
nodes: CustomNode[];
nodeCounter: number;
@@ -61,12 +68,44 @@ export const useNodeStore = create<NodeStore>((set, get) => ({
nodes: get().nodes,
edges: useEdgeStore.getState().edges,
};
const shouldTrack = changes.some(
(change) =>
change.type === "remove" ||
change.type === "add" ||
(change.type === "position" && change.dragging === false),
// Track initial positions when drag starts
changes.forEach((change) => {
if (change.type === "position" && change.dragging === true) {
if (!dragStartPositions[change.id]) {
const node = get().nodes.find((n) => n.id === change.id);
if (node) {
dragStartPositions[change.id] = { ...node.position };
}
}
}
});
// Check if we should track this change in history
let shouldTrack = changes.some(
(change) => change.type === "remove" || change.type === "add",
);
// For position changes, only track if movement exceeds threshold
if (!shouldTrack) {
changes.forEach((change) => {
if (change.type === "position" && change.dragging === false) {
const startPos = dragStartPositions[change.id];
if (startPos && change.position) {
const distanceMoved = Math.sqrt(
Math.pow(change.position.x - startPos.x, 2) +
Math.pow(change.position.y - startPos.y, 2),
);
if (distanceMoved > MINIMUM_MOVE_BEFORE_LOG) {
shouldTrack = true;
}
}
// Clean up tracked position after drag ends
delete dragStartPositions[change.id];
}
});
}
set((state) => ({
nodes: applyNodeChanges(changes, state.nodes),
}));