fix: keep auto-layout hydration clean

This commit is contained in:
dhruv465 2026-05-31 21:49:14 +05:30
parent 3854502652
commit 242e2c2073
2 changed files with 65 additions and 12 deletions

View File

@ -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

View File

@ -189,6 +189,22 @@ export function defaultConfigFor(type: NodeType): Record<string, unknown> {
}
}
export function applyNodePositions(
nodes: BuilderNode[],
positions: Record<string, { x: number; y: number }>,
): 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<string, { x: number; y: number }>) => {
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(