DayuanJiang/next-ai-draw-io · error · Error
Error replacing nodes: ${error}
Error message
Error replacing nodes: ${error} What it means
replaceNodes wraps any failure while cloning, splicing, or re-serializing DOM nodes during XML mutation into a single catch-all 'Error replacing nodes: ${error}'. It is a re-throw that loses the original stack but preserves the cause's message. The underlying failure is usually a malformed XPath/selector, an orphaned node, or invalid XML produced by the edit.
Source
Thrown at lib/utils.ts:471
if (!hasCell1) {
const cell1 = currentDoc.createElement("mxCell")
cell1.setAttribute("id", "1")
cell1.setAttribute("parent", "0")
// Insert after cell0 if possible
const cell0 = currentRoot.querySelector('mxCell[id="0"]')
if (cell0?.nextSibling) {
currentRoot.insertBefore(cell1, cell0.nextSibling)
} else {
currentRoot.appendChild(cell1)
}
}
// Convert the modified DOM back to a string
const serializer = new XMLSerializer()
return serializer.serializeToString(currentDoc)
} catch (error) {
throw new Error(`Error replacing nodes: ${error}`)
}
}
// ============================================================================
// ID-based Diagram Operations
// ============================================================================
export interface OperationError {
type: "update" | "add" | "delete"
cellId: string
message: string
}
export interface ApplyOperationsResult {
result: string
errors: OperationError[]
}
View on GitHub (pinned to 155ef4f7ac)
Solutions
- Log or inspect the wrapped 'error' value — the real cause (e.g. 'Failed to execute importNode' or 'The node to be replaced is not a child of this node') is in the appended message, not this wrapper
- Verify each node you pass was obtained from the same parsed Document as currentDoc (re-parse after serializing)
- Check that XPath/selector results are non-null elements before calling replaceNodes
- Re-parse the XML string and retry once if nodes came from a previous serialization round-trip
Example fix
// before
const out = replacedXML(xml, [{ oldNode, newNode }]) // oldNode from a different doc
// after
const doc = new DOMParser().parseFromString(xml, 'application/xml')
const oldNode = doc.querySelector('#target')
if (!oldNode) throw new Error('target node not found')
const out = replaceNodes(doc, [{ oldNode, newNode: doc.importNode(newNode, true) }]) Defensive patterns
Strategy: validation
Validate before calling
const doc = new DOMParser().parseFromString(xml, 'application/xml')
if (doc.querySelector('parsererror')) throw new Error('input xml is malformed')
const targets = selectors.map(s => doc.querySelector(s))
if (targets.some(t => !(t instanceof Element))) throw new Error('selector matched no element') Type guard
function nodesBelongToDoc(doc: Document, nodes: (Node | null)[]): boolean {
return nodes.every(n => n != null && n.ownerDocument === doc)
} Try / catch
try {
result = replaceNodes(doc, ops)
} catch (e) {
throw new Error(`replaceNodes failed for ops on ${docName}: ${e instanceof Error ? e.message : e}`)
} Prevention
- Re-parse XML after every serialize round-trip before reusing node handles
- Validate all selectors resolve to Elements before mutating
- Never mix nodes from two different Documents; use doc.importNode
When it happens
Trigger: Calling replaceNodes (directly or via replacedXML) with node references that do not belong to the document being edited, an XPath that resolves to null/a non-element node, or input XML that failed to parse upstream so currentDoc is undefined.
Common situations: Programmatic draw.io/diagram XML editing pipelines where callers hold stale node handles after a previous mutation round-tripped through serializeToString; passing serialized strings back in without re-parsing; namespaces getting mangled by XMLSerializer.
Related errors
AI-assisted analysis of DayuanJiang/next-ai-draw-io@155ef4f7ac (2026-08-27).
Data as JSON: /api/errors/1212738598ed2260.
Report an issue: GitHub.