{"record":{"id":"c39f550d117c43e5","repo":"can1357/oh-my-pi","slug":"the-new-child-is-an-ancestor-of-this-node","errorCode":null,"errorMessage":"The new child is an ancestor of this node","messagePattern":"The new child is an ancestor of this node","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/utils/src/dom/core.ts","lineNumber":197,"sourceCode":"\t/** Text contained by this node. */\n\tget textContent(): string | null {\n\t\treturn this.childNodes.map(child => child.textContent ?? \"\").join(\"\");\n\t}\n\n\tset textContent(value: string | null) {\n\t\tthis.replaceChildren();\n\t\tif (value) this.appendChild(this.documentForCreation().createTextNode(value));\n\t}\n\n\t/** Parent element, excluding document and fragments. */\n\tget parentElement(): Element | null {\n\t\treturn this.parentNode instanceof Element ? this.parentNode : null;\n\t}\n\n\t/** Append a node, moving it from its old parent. */\n\tappendChild<T extends Node>(child: T): T {\n\t\tconst node: Node = child;\n\t\tif (node === this || node.contains(this)) throw new Error(\"The new child is an ancestor of this node\");\n\t\tif (child instanceof DocumentFragment) {\n\t\t\tfor (const nested of [...child.childNodes]) this.appendChild(nested);\n\t\t\treturn child;\n\t\t}\n\t\tchild.parentNode?.removeChild(child);\n\t\tchild.parentNode = this;\n\t\tchild.setOwnerDocument(this.documentForCreation());\n\t\tthis.childNodes.push(child);\n\t\treturn child;\n\t}\n\n\t/** Insert a node before a current child, or append for null. */\n\tinsertBefore<T extends Node>(child: T, reference: Node | null): T {\n\t\tif (reference === null) return this.appendChild(child);\n\t\tconst index = this.childNodes.indexOf(reference);\n\t\tif (index < 0) throw new Error(\"The reference node is not a child of this node\");\n\t\tif (child instanceof DocumentFragment) {\n\t\t\tfor (const nested of [...child.childNodes]) this.insertBefore(nested, reference);","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/dom/core.ts#L179-L215","documentation":"This mini-DOM's Node.appendChild enforces the same hierarchy invariant as the standard DOM: a node cannot be appended to itself or to one of its descendants (append would create a cycle, making parent chains infinite). The guard `node === this || node.contains(this)` detects that case and throws before any mutation, so the tree is left unchanged.","triggerScenarios":"Calling `parent.appendChild(child)` where `child === parent`, or where `child` is any ancestor of `parent` (e.g. `element.parentNode.appendChild(element)` — a bug that moves an element under its own child). Also triggered indirectly through appendChild-based helpers: insertBefore(null path), append(), textContent setters, innerHTML assignments, and cloneNode re-parenting that walk through this code.","commonSituations":"Building trees bottom-up with a mistaken parent reference; wrapping an element with a new parent but accidentally re-appending the element to its own subtree; cloning logic that inserts the original instead of the clone; document restructuring loops where the 'new parent' variable still points at a node inside the moved subtree.","solutions":["Check the parent reference: it must be a node outside the child's subtree — log both nodes (tagName, parent chain) before appending.","If you intended to re-parent (move) a node, first remove it, then append it to the genuinely new parent.","To wrap `node` in `wrapper`, insert the wrapper at node's position first, then `wrapper.appendChild(node)` — never `node.appendChild(wrapper)`.","Add a guard/assert in helper code that skips the append when `newParent.contains(child)` to fail loudly at your own callsite."],"exampleFix":"// before: append to own descendant creates a cycle\nnode.parentNode.appendChild(wrapper); // wrapper === node's descendant? no — but if node is ancestor of target:\nelement.appendChild(element.parentNode); // throws\n\n// after: verify the target is not inside the subtree\nif (!element.contains(newParent)) {\n  newParent.appendChild(element);\n}","handlingStrategy":"validation","validationCode":"function canAppend(parent: Node, child: Node): boolean {\n  return parent !== child && !parent.contains(child);\n}\n// call before: if (canAppend(newParent, node)) newParent.appendChild(node);","typeGuard":null,"tryCatchPattern":"try {\n  newParent.appendChild(node);\n} catch (err) {\n  if (err instanceof Error && err.message === \"The new child is an ancestor of this node\") {\n    // fall back: insert at node's current position instead\n    node.parentNode?.insertBefore(newParent, node);\n    newParent.appendChild(node);\n  } else {\n    throw err;\n  }\n}","preventionTips":["Before moving nodes, assert the destination is not inside the moved subtree (`!parent.contains(child)`).","For wrapping operations, insert the wrapper at the node's position first, then move the node into it.","Keep one direction of re-parenting logic; avoid generic 'attach node A to B' helpers without the cycle guard."],"tags":["dom","tree-structure","hierarchy-cycle"],"backgroundTag":"dom-hierarchy-cycle","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}