refactor(web): validate and repair diagram XML with the MCP server's engine

- Delete the web app's own copy of the XML checks and repairs from
  lib/utils.ts (1,074 lines). loadDiagram now uses the MCP server's
  validateAndFixXml without the strict checks, because the XML may hold
  the user's own diagram
- display_diagram and append_diagram prepare the model's XML with the new
  shared prepareNewDiagram, also used by the MCP create_new_diagram: wrap,
  validate strictly and auto-fix while it is still a bare model (where
  duplicate ids are renamed), then turn it into an mxfile
- The streaming preview of display_diagram no longer redraws the model's
  raw cells after the tool handler loaded the checked diagram, and drops
  a queued preview once the input is complete. That redraw lost
  auto-fixes and UserObject/object wrappers, so a linked cell lost its
  label; it also showed a second error toast
- The web repair regression tests now run against the MCP functions
- New e2e test checks the canvas content after display_diagram
- Fix the e2e upload tests, whose file input locator also matched the
  template import input
This commit is contained in:
dayuan.jiang
2026-10-04 12:44:01 +09:00
parent b7c543ca70
commit e49e9f0c47
14 changed files with 276 additions and 1303 deletions
+9 -31
View File
@@ -45,6 +45,7 @@ import {
} from "./http-server.ts"
import { parseDrawioFileContent } from "./load-diagram.ts"
import { log } from "./logger.ts"
import { prepareNewDiagram } from "./new-diagram.ts"
import {
addPageToDoc,
deletePageFromDoc,
@@ -341,44 +342,21 @@ Rules: cells are siblings (never nested), ids are unique per page and start from
}
}
// Bare cells get the wrapper and root cells first: the strict
// parser rejects several top-level elements. Then validate and
// auto-fix (works for both mxfile and mxGraphModel inputs).
let xml = wrapCellsInModel(inputXml)
const { valid, error, fixed, fixes } = validateAndFixXml(xml)
if (fixed) {
xml = fixed
log.info(`XML auto-fixed: ${fixes.join(", ")}`)
}
if (!valid && error) {
log.error(`XML validation failed: ${error}`)
const prepared = prepareNewDiagram(inputXml)
if (!prepared.ok) {
log.error(prepared.error)
return {
content: [
{
type: "text",
text: `Error: XML validation failed - ${error}`,
},
{ type: "text", text: `Error: ${prepared.error}` },
],
isError: true,
}
}
// Normalise to the canonical mxfile shape so every later tool can
// assume "session.xml is always an mxfile". Bare <mxGraphModel>
// inputs are wrapped into a single-page mxfile here.
const normalized = normalizeToMxfile(xml)
if (!normalized) {
return {
content: [
{
type: "text",
text: "Error: XML must be the mxCell elements of one page, a <mxGraphModel>, or an <mxfile> with one or more <diagram> children.",
},
],
isError: true,
}
if (prepared.fixes.length > 0) {
log.info(`XML auto-fixed: ${prepared.fixes.join(", ")}`)
}
xml = normalized
// Every later tool can assume session.xml is an mxfile
const xml = prepared.xml
log.info(`Setting diagram content, ${xml.length} chars`)
+36
View File
@@ -0,0 +1,36 @@
/**
* A whole new diagram written by the model, for the create_new_diagram tool
* and the web app's display_diagram tool.
*/
import { normalizeToMxfile, wrapCellsInModel } from "./pages.ts"
import { validateAndFixXml } from "./xml-validation.ts"
export type NewDiagram =
| { ok: true; xml: string; fixes: string[] }
| { ok: false; error: string }
/**
* Bare cells get the wrapper and root cells first, since the strict parser
* rejects several top-level elements. Then the XML is validated and
* auto-fixed while it is still a bare model, where duplicate ids are
* renamed, and finally turned into an <mxfile>.
*/
export function prepareNewDiagram(
input: string,
page: { pageId?: string; pageName?: string } = {},
): NewDiagram {
let xml = wrapCellsInModel(input)
const { valid, error, fixed, fixes } = validateAndFixXml(xml)
if (fixed) xml = fixed
if (!valid) {
return { ok: false, error: `XML validation failed - ${error}` }
}
const normalized = normalizeToMxfile(xml, page)
if (!normalized) {
return {
ok: false,
error: "XML must be the mxCell elements of one page, a <mxGraphModel>, or an <mxfile> with one or more <diagram> children.",
}
}
return { ok: true, xml: normalized, fixes }
}