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
- Log/inspect the node passed to validateNode to identify which invariant fails
- Reset the persisted layout state so a fresh default tree is built
- Fix the code path producing the malformed node (check all addChildAt/removeChild call sites)
- 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
- Validate/sanitize persisted layout JSON on load against the current schema
- Never mutate nodes directly; use the reducer helpers so parent/child links stay consistent
- Reset layout state when upgrading between schema versions
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
- Layout model not found
- Block not found: ${blockId}
- Block not found in tab: ${blockId}
- rpc command "${msg.command}" not supported by [${this.routeI
- Invalid direction: ${direction}
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/40f39311f2d0f004.
Report an issue: GitHub.