can1357/oh-my-pi · error · Error

The new child is an ancestor of this node

Error message

The new child is an ancestor of this node

What it means

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.

Source

Thrown at packages/utils/src/dom/core.ts:197

	/** Text contained by this node. */
	get textContent(): string | null {
		return this.childNodes.map(child => child.textContent ?? "").join("");
	}

	set textContent(value: string | null) {
		this.replaceChildren();
		if (value) this.appendChild(this.documentForCreation().createTextNode(value));
	}

	/** Parent element, excluding document and fragments. */
	get parentElement(): Element | null {
		return this.parentNode instanceof Element ? this.parentNode : null;
	}

	/** Append a node, moving it from its old parent. */
	appendChild<T extends Node>(child: T): T {
		const node: Node = child;
		if (node === this || node.contains(this)) throw new Error("The new child is an ancestor of this node");
		if (child instanceof DocumentFragment) {
			for (const nested of [...child.childNodes]) this.appendChild(nested);
			return child;
		}
		child.parentNode?.removeChild(child);
		child.parentNode = this;
		child.setOwnerDocument(this.documentForCreation());
		this.childNodes.push(child);
		return child;
	}

	/** Insert a node before a current child, or append for null. */
	insertBefore<T extends Node>(child: T, reference: Node | null): T {
		if (reference === null) return this.appendChild(child);
		const index = this.childNodes.indexOf(reference);
		if (index < 0) throw new Error("The reference node is not a child of this node");
		if (child instanceof DocumentFragment) {
			for (const nested of [...child.childNodes]) this.insertBefore(nested, reference);

View on GitHub (pinned to 9690622007)

Solutions

  1. Check the parent reference: it must be a node outside the child's subtree — log both nodes (tagName, parent chain) before appending.
  2. If you intended to re-parent (move) a node, first remove it, then append it to the genuinely new parent.
  3. To wrap `node` in `wrapper`, insert the wrapper at node's position first, then `wrapper.appendChild(node)` — never `node.appendChild(wrapper)`.
  4. Add a guard/assert in helper code that skips the append when `newParent.contains(child)` to fail loudly at your own callsite.

Example fix

// before: append to own descendant creates a cycle
node.parentNode.appendChild(wrapper); // wrapper === node's descendant? no — but if node is ancestor of target:
element.appendChild(element.parentNode); // throws

// after: verify the target is not inside the subtree
if (!element.contains(newParent)) {
  newParent.appendChild(element);
}
Defensive patterns

Strategy: validation

Validate before calling

function canAppend(parent: Node, child: Node): boolean {
  return parent !== child && !parent.contains(child);
}
// call before: if (canAppend(newParent, node)) newParent.appendChild(node);

Try / catch

try {
  newParent.appendChild(node);
} catch (err) {
  if (err instanceof Error && err.message === "The new child is an ancestor of this node") {
    // fall back: insert at node's current position instead
    node.parentNode?.insertBefore(newParent, node);
    newParent.appendChild(node);
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/c39f550d117c43e5. Report an issue: GitHub.