fix(canvas): re-sort parents-before-children after nest mutation
React Flow requires parent nodes to appear before their children in
the nodes array. When they don't, it logs "Parent node {id} not
found. Please make sure that parent nodes are in front of their
child nodes in the nodes array" and — more importantly — renders
the child at canvas-absolute coords instead of parent-relative,
flashing it far outside the parent.
topology's buildNodesAndEdges already enforced this at hydrate, but
nestNode + batchNest weren't re-sorting after mutating parentId.
A freshly-nested child often ended up after-first-drag at the
wrong screen position because its new parent sat later in the
array than itself.
Extract sortParentsBeforeChildren() into canvas-topology as a
reusable DFS visit; call it at the tail of both nestNode's set()
and batchNest's commit set(). 923 tests still green — no behaviour
change beyond eliminating the warning and the position flash.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2a8977c946
commit
2d6ff11c4e
@ -9,6 +9,35 @@ const V_SPACING = 200;
|
||||
// (first render, before React Flow reports dimensions). These match the
|
||||
// min-width / min-height that WorkspaceNode.tsx sets, so a parent built
|
||||
// from them will never start too small for its children on first paint.
|
||||
/**
|
||||
* Re-orders a React Flow node array so parents always appear BEFORE
|
||||
* their children. React Flow requires this ordering; when it's
|
||||
* violated RF logs "Parent node ... not found" and renders the child
|
||||
* at canvas-absolute coords (losing the parent-relative transform).
|
||||
*
|
||||
* We call this every time nestNode / batchNest mutates parentId —
|
||||
* without a re-sort a freshly-nested child can appear AFTER its new
|
||||
* parent in the array, which breaks the next drag.
|
||||
*/
|
||||
export function sortParentsBeforeChildren<T extends { id: string; parentId?: string }>(
|
||||
nodes: T[],
|
||||
): T[] {
|
||||
const byId = new Map(nodes.map((n) => [n.id, n]));
|
||||
const visited = new Set<string>();
|
||||
const out: T[] = [];
|
||||
const visit = (n: T) => {
|
||||
if (visited.has(n.id)) return;
|
||||
if (n.parentId) {
|
||||
const parent = byId.get(n.parentId);
|
||||
if (parent && !visited.has(parent.id)) visit(parent);
|
||||
}
|
||||
visited.add(n.id);
|
||||
out.push(n);
|
||||
};
|
||||
for (const n of nodes) visit(n);
|
||||
return out;
|
||||
}
|
||||
|
||||
export const CHILD_DEFAULT_WIDTH = 260;
|
||||
export const CHILD_DEFAULT_HEIGHT = 140;
|
||||
export const PARENT_HEADER_PADDING = 60; // room for the parent's own header
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
buildNodesAndEdges,
|
||||
computeAutoLayout,
|
||||
defaultChildSlot,
|
||||
sortParentsBeforeChildren,
|
||||
CHILD_DEFAULT_HEIGHT,
|
||||
CHILD_DEFAULT_WIDTH,
|
||||
PARENT_BOTTOM_PADDING,
|
||||
@ -482,6 +483,10 @@ export const useCanvasStore = create<CanvasState>((set, get) => ({
|
||||
(e) => !movedIds.has(e.source) && !movedIds.has(e.target),
|
||||
),
|
||||
});
|
||||
// Keep parents before children in the array (same invariant
|
||||
// nestNode enforces). Needed after multi-select re-parent because
|
||||
// the selection order is user-driven.
|
||||
set({ nodes: sortParentsBeforeChildren(get().nodes) });
|
||||
|
||||
// Fire every PATCH in parallel. Individual failures roll back just
|
||||
// that node (others remain committed, matching the single-node
|
||||
@ -684,6 +689,12 @@ export const useCanvasStore = create<CanvasState>((set, get) => ({
|
||||
}),
|
||||
edges: newEdges,
|
||||
});
|
||||
// React Flow requires parents before children in the array. Without
|
||||
// this re-sort a newly-nested child can end up ahead of its new
|
||||
// parent, which makes RF log "Parent node not found" and render the
|
||||
// child at canvas-absolute coords (far outside the parent, which
|
||||
// is the flash-bug the user just flagged).
|
||||
set({ nodes: sortParentsBeforeChildren(get().nodes) });
|
||||
|
||||
try {
|
||||
// One round-trip per nest: the /workspaces/:id PATCH handler
|
||||
|
||||
Loading…
Reference in New Issue
Block a user