wavetermdev/waveterm · error

Invalid node

Error message

Invalid node

What it means

balanceNode walks the layout tree and validates each node via validateNode before rebalancing flexDirection and flattening single-child wrappers. A node failing structural validation (e.g. missing required fields or inconsistent parent/child links) aborts the operation with "Invalid node".

Source

Thrown at frontend/layout/lib/layoutNode.ts:190

    afterWalkCallback?.(node);
}

/**
 * Recursively corrects the tree to minimize nested single-child nodes, remove invalid nodes, and correct invalid flex direction order.
 * @param node The node to start the balancing from.
 * @param beforeWalkCallback Any optional callback to run before walking a node's children.
 * @param afterWalkCallback An optional callback to run after walking a node's children.
 * @returns The corrected node.
 */
export function balanceNode(
    node: LayoutNode,
    beforeWalkCallback?: (node: LayoutNode) => void,
    afterWalkCallback?: (node: LayoutNode) => void
): LayoutNode {
    walkNodes(
        node,
        (node) => {
            if (!validateNode(node)) throw new Error("Invalid node");
            node.children = node.children?.flatMap((child) => {
                if (child.flexDirection === node.flexDirection) {
                    child.flexDirection = reverseFlexDirection(node.flexDirection);
                }
                if (child.children?.length == 1 && child.children[0].children) {
                    return child.children[0].children;
                }
                if (child.children?.length === 0) return;
                return child;
            });
            beforeWalkCallback?.(node);
        },
        (node) => {
            node.children = node.children?.filter((v) => v);
            if (node.children?.length === 1 && !node.children[0].children) {
                node.data = node.children[0].data;
                node.id = node.children[0].id;
                node.children = undefined;

View on GitHub (pinned to a4447c1563)

Solutions

  1. Log/inspect the node passed to validateNode to identify which invariant fails
  2. Reset the persisted layout state so a fresh default tree is built
  3. Fix the code path producing the malformed node (check all addChildAt/removeChild call sites)
  4. Add validation at deserialization time to sanitize old persisted layouts

Example fix

// before
const newTree = balanceNode(parsedTree);

// after
const safeTree = validateAndSanitizeTree(parsedTree) ?? createDefaultTree();
const newTree = balanceNode(safeTree);
Defensive patterns

Strategy: validation

Validate before calling

function isValidTree(n: any): boolean {
    return n != null && typeof n === "object" &&
        (n.children == null || (Array.isArray(n.children) && n.children.every(isValidTree)));
}
const tree = isValidTree(parsed) ? parsed : createDefaultTree();

Type guard

function isLayoutNode(n: unknown): n is LayoutNode {
    return n != null && typeof n === "object" && "id" in n && "flexDirection" in n;
}

Try / catch

let tree;
try {
    tree = balanceNode(loadedTree);
} catch (e) {
    if (e.message === "Invalid node") {
        tree = createDefaultTree(); // discard corrupt layout
    } else { throw e; }
}

Prevention

When it happens

Trigger: updateTree or newNode1-4 invoke balanceNode on a tree containing a node that fails validateNode — corrupt or malformed node shape in the layout tree.

Common situations: Deserializing a layout from persisted state (waveai/localstorage) that predates a schema change; a reducer mutation leaving a node with children/props inconsistent; race during concurrent node insertion/removal.

Related errors


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