refactor(canvas): extract flow reconciliation hook

Move Convex-to-local node and edge reconciliation into a dedicated hook so canvas.tsx has a cleaner sync boundary during modularization. Add hook-level tests for optimistic edge carry and drag-lock behavior to preserve the existing UX.
This commit is contained in:
2026-04-03 21:36:02 +02:00
parent 5223d3d8d7
commit d1c14c93e5
5 changed files with 500 additions and 102 deletions

View File

@@ -1,7 +1,12 @@
import type { Edge as RFEdge, Node as RFNode } from "@xyflow/react";
import type { Doc, Id } from "@/convex/_generated/dataModel";
import { convexEdgeToRF, convexEdgeToRFWithSourceGlow } from "@/lib/canvas-utils";
import {
convexEdgeToRF,
convexEdgeToRFWithSourceGlow,
convexNodeDocWithMergedStorageUrl,
convexNodeToRF,
} from "@/lib/canvas-utils";
import {
applyPinnedNodePositionsReadOnly,
@@ -13,6 +18,7 @@ import {
OPTIMISTIC_NODE_PREFIX,
positionsMatchPin,
rfEdgeConnectionSignature,
withResolvedCompareData,
} from "./canvas-helpers";
type FlowConvexNodeRecord = Pick<Doc<"nodes">, "_id" | "type">;
@@ -21,6 +27,22 @@ type FlowConvexEdgeRecord = Pick<
"_id" | "sourceNodeId" | "targetNodeId" | "sourceHandle" | "targetHandle"
>;
export function buildIncomingCanvasFlowNodes(args: {
convexNodes: Doc<"nodes">[];
storageUrlsById: Record<string, string | undefined> | undefined;
previousNodes: RFNode[];
edges: RFEdge[];
}): RFNode[] {
const previousDataById = new Map(
args.previousNodes.map((node) => [node.id, node.data as Record<string, unknown>]),
);
const enrichedNodes = args.convexNodes.map((node) =>
convexNodeDocWithMergedStorageUrl(node, args.storageUrlsById, previousDataById),
);
return withResolvedCompareData(enrichedNodes.map(convexNodeToRF), args.edges);
}
export function inferPendingConnectionNodeHandoff(args: {
previousNodes: RFNode[];
incomingConvexNodes: FlowConvexNodeRecord[];