DayuanJiang/next-ai-draw-io · error · Error

Failed to construct <diagram> element for new page

Error message

Failed to construct <diagram> element for new page

What it means

A defensive unreachable-in-practice guard in addPageToDoc: the temp document parsed without a parsererror, but querySelector('diagram') returned null. Because the wrapper snippet is constructed with an explicit <diagram> element, this can only fire if the wrapper itself was corrupted — e.g. id or name attributes containing characters (unescaped quotes) that broke the snippet so the diagram tag wasn't recognized.

Source

Thrown at packages/mcp-server/src/pages.ts:269

            throw new Error(
                "addPageToDoc: opts.xml must be a bare <mxGraphModel>.",
            )
        }
        inner = trimmed
    } else {
        inner = `<mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/></root></mxGraphModel>`
    }

    const snippet = `<wrapper><diagram id="${escapeAttr(id)}" name="${escapeAttr(name)}">${inner}</diagram></wrapper>`
    const tempDoc = new DOMParser().parseFromString(snippet, "text/xml")
    if (tempDoc.querySelector("parsererror")) {
        throw new Error(
            "Failed to parse new page xml — make sure it is a valid <mxGraphModel>",
        )
    }
    const newDiagram = tempDoc.querySelector("diagram")
    if (!newDiagram) {
        throw new Error("Failed to construct <diagram> element for new page")
    }

    const imported = doc.importNode(newDiagram, true) as Element
    doc.documentElement.appendChild(imported)

    return {
        id,
        name,
        index: existing.length,
        cellCount: imported.querySelectorAll("mxCell").length,
    }
}

/** Rename the page matched by selector. Returns true on success. */
export function renamePageInDoc(
    doc: Document,
    selector: PageSelector,
    newName: string,

View on GitHub (pinned to 155ef4f7ac)

Solutions

  1. Sanitize opts.id/opts.name (strip or escape quotes, '<', '&' ) before calling
  2. Use a generated safe id and put the fancy name in opts.name with escaping verified
  3. Report as a library bug if id/name are plain alphanumerics — the guard should be unreachable
  4. Check listPagesFromDoc afterward instead of relying on this error for flow control

Example fix

// before
addPageToDoc(doc, { id: 'my"page', name: 'My "Quoted" Page' })

// after
const safe = (s: string) => s.replace(/[&<>"]/g, (c) => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]!))
addPageToDoc(doc, { id: 'mypage', name: safe('My "Quoted" Page') })
Defensive patterns

Strategy: try-catch

Validate before calling

const safeId = opts.id?.replace(/[^\w.-]/g, '_')
const safeName = opts.name?.replace(/[&<>"]/g, '')
// pass safeId/safeName to addPageToDoc

Type guard

function isSafePageId(id: string): boolean {
  return /^[A-Za-z0-9._-]+$/.test(id)
}

Try / catch

try { addPageToDoc(doc, { id, name }) } catch (e) { if ((e as Error).message.includes('Failed to construct')) throw new Error(`id/name broke xml construction: ${JSON.stringify({ id, name })}`) else throw e }

Prevention

When it happens

Trigger: Passing an opts.id or opts.name containing double quotes or '<' that escapeAttr failed to neutralize, producing '<diagram id="a"b" ...>' which parses into unexpected structure rather than a clean parsererror.

Common situations: Page names copied from rich text containing quotes; ids generated from unsanitized user input; edge cases in the escaping helper.

Related errors


AI-assisted analysis of DayuanJiang/next-ai-draw-io@155ef4f7ac (2026-08-27). Data as JSON: /api/errors/04f598167442100d. Report an issue: GitHub.