{"record":{"id":"04f598167442100d","repo":"DayuanJiang/next-ai-draw-io","slug":"failed-to-construct-diagram-element-for-new-page","errorCode":null,"errorMessage":"Failed to construct <diagram> element for new page","messagePattern":"Failed to construct <diagram> element for new page","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/mcp-server/src/pages.ts","lineNumber":269,"sourceCode":"            throw new Error(\n                \"addPageToDoc: opts.xml must be a bare <mxGraphModel>.\",\n            )\n        }\n        inner = trimmed\n    } else {\n        inner = `<mxGraphModel><root><mxCell id=\"0\"/><mxCell id=\"1\" parent=\"0\"/></root></mxGraphModel>`\n    }\n\n    const snippet = `<wrapper><diagram id=\"${escapeAttr(id)}\" name=\"${escapeAttr(name)}\">${inner}</diagram></wrapper>`\n    const tempDoc = new DOMParser().parseFromString(snippet, \"text/xml\")\n    if (tempDoc.querySelector(\"parsererror\")) {\n        throw new Error(\n            \"Failed to parse new page xml — make sure it is a valid <mxGraphModel>\",\n        )\n    }\n    const newDiagram = tempDoc.querySelector(\"diagram\")\n    if (!newDiagram) {\n        throw new Error(\"Failed to construct <diagram> element for new page\")\n    }\n\n    const imported = doc.importNode(newDiagram, true) as Element\n    doc.documentElement.appendChild(imported)\n\n    return {\n        id,\n        name,\n        index: existing.length,\n        cellCount: imported.querySelectorAll(\"mxCell\").length,\n    }\n}\n\n/** Rename the page matched by selector. Returns true on success. */\nexport function renamePageInDoc(\n    doc: Document,\n    selector: PageSelector,\n    newName: string,","sourceCodeStart":251,"sourceCodeEnd":287,"githubUrl":"https://github.com/DayuanJiang/next-ai-draw-io/blob/155ef4f7acd29c9d46fb6fc35c92e6e6955a6ce1/packages/mcp-server/src/pages.ts#L251-L287","documentation":"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.","triggerScenarios":"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.","commonSituations":"Page names copied from rich text containing quotes; ids generated from unsanitized user input; edge cases in the escaping helper.","solutions":["Sanitize opts.id/opts.name (strip or escape quotes, '<', '&' ) before calling","Use a generated safe id and put the fancy name in opts.name with escaping verified","Report as a library bug if id/name are plain alphanumerics — the guard should be unreachable","Check listPagesFromDoc afterward instead of relying on this error for flow control"],"exampleFix":"// before\naddPageToDoc(doc, { id: 'my\"page', name: 'My \"Quoted\" Page' })\n\n// after\nconst safe = (s: string) => s.replace(/[&<>\"]/g, (c) => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]!))\naddPageToDoc(doc, { id: 'mypage', name: safe('My \"Quoted\" Page') })","handlingStrategy":"try-catch","validationCode":"const safeId = opts.id?.replace(/[^\\w.-]/g, '_')\nconst safeName = opts.name?.replace(/[&<>\"]/g, '')\n// pass safeId/safeName to addPageToDoc","typeGuard":"function isSafePageId(id: string): boolean {\n  return /^[A-Za-z0-9._-]+$/.test(id)\n}","tryCatchPattern":"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 }","preventionTips":["Sanitize page ids and names before passing them in","Use generated ids for programmatic pages","Treat this error as a signal your escaping helper has a gap — file it upstream"],"tags":["drawio","defensive-guard","escaping","pages"],"backgroundTag":"unreachable-defensive-check-triggered","analyzedSha":"155ef4f7acd29c9d46fb6fc35c92e6e6955a6ce1","analyzedAt":"2026-08-27T11:40:38.297Z","schemaVersion":2},"datasetVersion":"2026-08-27T13:17:12.746Z"}