From 242e2c20734bdb40b22809a67c270fa84d806ffe Mon Sep 17 00:00:00 2001 From: dhruv465 Date: Sun, 31 May 2026 21:49:14 +0530 Subject: [PATCH] fix: keep auto-layout hydration clean --- .../flows/flow-editor-state.test.ts | 43 +++++++++++++++++++ src/components/flows/flow-editor-state.tsx | 34 +++++++++------ 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/src/components/flows/flow-editor-state.test.ts b/src/components/flows/flow-editor-state.test.ts index afc506a..45b204e 100644 --- a/src/components/flows/flow-editor-state.test.ts +++ b/src/components/flows/flow-editor-state.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect } from "vitest"; import { + applyNodePositions, defaultConfigFor, uniqueNodeKey, } from "./flow-editor-state"; @@ -33,6 +34,48 @@ describe("uniqueNodeKey", () => { }); }); +describe("applyNodePositions", () => { + it("rounds and applies positions without changing unrelated nodes", () => { + const nodes: BuilderNode[] = [ + { + node_key: "start", + node_type: "start", + config: {}, + position_x: 0, + position_y: 0, + }, + { + node_key: "message", + node_type: "send_message", + config: {}, + position_x: 10, + position_y: 20, + }, + ]; + + expect( + applyNodePositions(nodes, { + start: { x: 10.4, y: 20.6 }, + }), + ).toEqual([ + { + node_key: "start", + node_type: "start", + config: {}, + position_x: 10, + position_y: 21, + }, + { + node_key: "message", + node_type: "send_message", + config: {}, + position_x: 10, + position_y: 20, + }, + ]); + }); +}); + describe("defaultConfigFor", () => { // The hook's addNode and the validator both depend on these defaults // being self-consistent. A broken default would surface as a diff --git a/src/components/flows/flow-editor-state.tsx b/src/components/flows/flow-editor-state.tsx index 6a9af29..453995f 100644 --- a/src/components/flows/flow-editor-state.tsx +++ b/src/components/flows/flow-editor-state.tsx @@ -189,6 +189,22 @@ export function defaultConfigFor(type: NodeType): Record { } } +export function applyNodePositions( + nodes: BuilderNode[], + positions: Record, +): BuilderNode[] { + return nodes.map((n) => { + const next = positions[n.node_key]; + return next + ? { + ...n, + position_x: Math.round(next.x), + position_y: Math.round(next.y), + } + : n; + }); +} + // ============================================================ // Context // ============================================================ @@ -443,21 +459,15 @@ export function FlowEditorProvider({ const updateNodePositions = useCallback( (positions: Record) => { - setState((s) => ({ + // Initial Dagre layout hydration should not dirty the editor: + // opening a legacy all-zero flow must not enable Save or arm + // beforeunload before the user actually edits anything. + setStateRaw((s) => ({ ...s, - nodes: s.nodes.map((n) => { - const next = positions[n.node_key]; - return next - ? { - ...n, - position_x: Math.round(next.x), - position_y: Math.round(next.y), - } - : n; - }), + nodes: applyNodePositions(s.nodes, positions), })); }, - [setState], + [], ); const addNode = useCallback(