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

  1. 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
  2. Verify each node you pass was obtained from the same parsed Document as currentDoc (re-parse after serializing)
  3. Check that XPath/selector results are non-null elements before calling replaceNodes
  4. 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

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.