{"record":{"id":"40f39311f2d0f004","repo":"wavetermdev/waveterm","slug":"invalid-node","errorCode":null,"errorMessage":"Invalid node","messagePattern":"Invalid node","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/layout/lib/layoutNode.ts","lineNumber":190,"sourceCode":"    afterWalkCallback?.(node);\n}\n\n/**\n * Recursively corrects the tree to minimize nested single-child nodes, remove invalid nodes, and correct invalid flex direction order.\n * @param node The node to start the balancing from.\n * @param beforeWalkCallback Any optional callback to run before walking a node's children.\n * @param afterWalkCallback An optional callback to run after walking a node's children.\n * @returns The corrected node.\n */\nexport function balanceNode(\n    node: LayoutNode,\n    beforeWalkCallback?: (node: LayoutNode) => void,\n    afterWalkCallback?: (node: LayoutNode) => void\n): LayoutNode {\n    walkNodes(\n        node,\n        (node) => {\n            if (!validateNode(node)) throw new Error(\"Invalid node\");\n            node.children = node.children?.flatMap((child) => {\n                if (child.flexDirection === node.flexDirection) {\n                    child.flexDirection = reverseFlexDirection(node.flexDirection);\n                }\n                if (child.children?.length == 1 && child.children[0].children) {\n                    return child.children[0].children;\n                }\n                if (child.children?.length === 0) return;\n                return child;\n            });\n            beforeWalkCallback?.(node);\n        },\n        (node) => {\n            node.children = node.children?.filter((v) => v);\n            if (node.children?.length === 1 && !node.children[0].children) {\n                node.data = node.children[0].data;\n                node.id = node.children[0].id;\n                node.children = undefined;","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/frontend/layout/lib/layoutNode.ts#L172-L208","documentation":"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\".","triggerScenarios":"updateTree or newNode1-4 invoke balanceNode on a tree containing a node that fails validateNode — corrupt or malformed node shape in the layout tree.","commonSituations":"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.","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"],"exampleFix":"// before\nconst newTree = balanceNode(parsedTree);\n\n// after\nconst safeTree = validateAndSanitizeTree(parsedTree) ?? createDefaultTree();\nconst newTree = balanceNode(safeTree);","handlingStrategy":"validation","validationCode":"function isValidTree(n: any): boolean {\n    return n != null && typeof n === \"object\" &&\n        (n.children == null || (Array.isArray(n.children) && n.children.every(isValidTree)));\n}\nconst tree = isValidTree(parsed) ? parsed : createDefaultTree();","typeGuard":"function isLayoutNode(n: unknown): n is LayoutNode {\n    return n != null && typeof n === \"object\" && \"id\" in n && \"flexDirection\" in n;\n}","tryCatchPattern":"let tree;\ntry {\n    tree = balanceNode(loadedTree);\n} catch (e) {\n    if (e.message === \"Invalid node\") {\n        tree = createDefaultTree(); // discard corrupt layout\n    } else { throw e; }\n}","preventionTips":["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"],"tags":["layout","tree-validation","frontend"],"backgroundTag":"invalid-tree-node","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}