wavetermdev/waveterm · error

Invalid InsertOperation

Error message

Invalid InsertOperation

What it means

moveNode inserts a node either under a computed parent (possibly adding an intermediate node) or under the action's parent; if neither a parent nor a root insertion path is available, the InsertOperation is structurally invalid and the function throws. This guards against inserting a node with no resolvable parent/index.

Source

Thrown at frontend/layout/lib/layoutTree.ts:270

        if (oldParent.id === parent.id) {
            const curIndexInParent = parent.children!.indexOf(node);
            if (curIndexInParent >= action.index) {
                startingIndex = action.index + 1;
            }
        } else {
            node.size = DefaultNodeSize;
        }
    }

    if (!parent && action.insertAtRoot) {
        if (!rootNode.children) {
            addIntermediateNode(rootNode);
        }
        addChildAt(rootNode, action.index, node);
    } else if (parent) {
        addChildAt(parent, action.index, node);
    } else {
        throw new Error("Invalid InsertOperation");
    }

    // Remove nodeToInsert from its old parent
    if (oldParent) {
        removeChild(oldParent, node, startingIndex);
    }
}

export function insertNode(layoutState: LayoutTreeState, action: LayoutTreeInsertNodeAction) {
    if (!action?.node) {
        console.error("insertNode cannot run, no insert node action provided");
        return;
    }
    if (!layoutState.rootNode) {
        layoutState.rootNode = action.node;
    } else {
        const insertLoc = findNextInsertLocation(layoutState.rootNode, DEFAULT_MAX_CHILDREN);
        addChildAt(insertLoc.node, insertLoc.index, action.node);

View on GitHub (pinned to a4447c1563)

Solutions

  1. Verify action.parentId targets an existing node at the time the reducer runs
  2. Recompute or drop the pending action if the referenced parent was removed
  3. Clear stale pendingActions on layout mutation (close/delete of nodes)

Example fix

// before
treeReducer({ type: "move", parentId: oldParentId, ... });

// after
const parent = findNode(tree, oldParentId);
if (!parent) return; // drop stale move action
treeReducer({ type: "move", parentId: oldParentId, ... });
Defensive patterns

Strategy: validation

Validate before calling

const parent = findNode(tree, action.parentId);
if (action.parentId != null && parent == null) {
    return; // stale operation — parent no longer exists
}

Type guard

function hasValidParent(tree: LayoutTree, action: InsertOperation): boolean {
    return action.parentId == null || findNode(tree, action.parentId) != null;
}

Try / catch

try {
    treeReducer(state, moveAction);
} catch (e) {
    if (e.message === "Invalid InsertOperation") {
        state = clearPendingAction(state); // drop stale move
    } else { throw e; }
}

Prevention

When it happens

Trigger: treeReducer dispatches an insert/move where action.parentId refers to a node no longer in the tree and the target node is not the root — parent resolves to null and no root path applies.

Common situations: Stale move operation referencing a node deleted concurrently; corrupted layout state after removing a parent before applying a queued insert; race between block close and pending move action.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/873f6efcf35a049f. Report an issue: GitHub.