mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-12 12:39:52 +08:00
fix(mcp-server): review fixes for the shell's commits and export timeout
A write taken in place that leaves the canvas as it is gets no autosave from draw.io, so the marker for its copy stayed armed and ate the user's next paper-size edit: arm it only when the write changed the document. The 15 s export timeout now retires the export's sequence number, so a result POST that answers late no longer restores the document a second time.
This commit is contained in:
@@ -348,12 +348,17 @@ export function createMcpSync(options: SyncOptions): McpSync {
|
||||
const loadFromServer = (xml: string, how: "decide" | "full") => {
|
||||
lastXml = xml
|
||||
latestXml = xml
|
||||
const before = canvas.currentXml()
|
||||
const decision: LoadDecision =
|
||||
how === "decide"
|
||||
? decideLoad(canvas.currentXml(), xml, canvas.currentPageId())
|
||||
? decideLoad(before, xml, canvas.currentPageId())
|
||||
: { mode: "load" }
|
||||
canvas.load(xml, decision)
|
||||
awaitingCommitCopy = decision.mode === "commit"
|
||||
// draw.io autosaves its copy only when the write changed its
|
||||
// document: after one that left the canvas as it was, the next
|
||||
// autosave is the user's
|
||||
awaitingCommitCopy =
|
||||
decision.mode === "commit" && !sameDiagram(before, xml)
|
||||
// currentVersion is the write's version
|
||||
thumbExport = { stateId, version: currentVersion, xml }
|
||||
setTimeout(captureThumbnail, 500)
|
||||
@@ -595,9 +600,11 @@ export function createMcpSync(options: SyncOptions): McpSync {
|
||||
// The whole export, result delivery included, ends in time: a result
|
||||
// POST that never answers must not keep the projection on screen
|
||||
// past the server's deadline (15 s). Only for this export: a later
|
||||
// one may be running by then.
|
||||
// one may be running by then. The export is over: its result POST
|
||||
// answering after this must not restore the document a second time.
|
||||
setTimeout(() => {
|
||||
if (pendingMcpExport && seq === mcpExportSeq) {
|
||||
mcpExportSeq++
|
||||
pendingMcpExport = null
|
||||
restoreFromProjection()
|
||||
}
|
||||
|
||||
@@ -753,6 +753,24 @@ describe("MCP sync export requests", () => {
|
||||
expect(t.sync.read().projectionActive).toBe(false)
|
||||
})
|
||||
|
||||
it("does not restore the document twice when the result POST answers after the 15 s", async () => {
|
||||
const { t, result } = await projectionExported()
|
||||
await vi.advanceTimersByTimeAsync(15000)
|
||||
t.next("GET").answer(state("S1", 2, "<mxfile>A</mxfile>"))
|
||||
await t.settle()
|
||||
await t.settle()
|
||||
expect(t.loads).toHaveLength(2)
|
||||
// The POST answers before draw.io reports the restore load: no
|
||||
// second restore poll, no second load
|
||||
result.answer({ status: 200, body: { success: true } })
|
||||
await t.settle()
|
||||
await t.settle()
|
||||
expect(t.calls.filter((c) => c.method === "GET")).toHaveLength(0)
|
||||
expect(t.loads).toHaveLength(2)
|
||||
t.sync.onDrawioLoad()
|
||||
expect(t.sync.read().projectionActive).toBe(false)
|
||||
})
|
||||
|
||||
it("drops an export and reloads the document when draw.io starts over", async () => {
|
||||
const t = await inStep()
|
||||
const poll = t.sync.poll()
|
||||
@@ -1241,4 +1259,44 @@ describe("MCP sync with server writes taken in place", () => {
|
||||
baseVersion: 3,
|
||||
})
|
||||
})
|
||||
|
||||
it("a change of the paper size after a write that changed nothing is pushed", async () => {
|
||||
const t = await inStep({ currentPageId: "p1" })
|
||||
t.canvas.xml = canvasXml
|
||||
let poll = t.sync.poll()
|
||||
t.next("GET").answer(state("S1", 3, written))
|
||||
await poll
|
||||
const drawioCopy = file(
|
||||
drawioPage("p1", "One", cell("a", "A") + cell("c", "C", 300)) +
|
||||
drawioPage("p2", "Two", cell("b", "B")),
|
||||
)
|
||||
t.sync.onAutoSave(drawioCopy)
|
||||
t.canvas.xml = drawioCopy
|
||||
await t.settle()
|
||||
expect(t.posts()).toHaveLength(0)
|
||||
// The same document written again (the file loaded once more): the
|
||||
// server has a new version, draw.io sees no change and sends no copy
|
||||
poll = t.sync.poll()
|
||||
t.next("GET").answer(state("S1", 4, written))
|
||||
await poll
|
||||
expect(t.loads.at(-1)?.decision.mode).toBe("commit")
|
||||
// The user picks A4: an edit, not the copy of the write
|
||||
const a4 = file(
|
||||
page(
|
||||
"p1",
|
||||
"One",
|
||||
cell("a", "A") + cell("c", "C", 300),
|
||||
' dx="1000" dy="600" grid="1" pageWidth="827" pageHeight="1169"',
|
||||
) + drawioPage("p2", "Two", cell("b", "B")),
|
||||
)
|
||||
t.sync.onAutoSave(a4)
|
||||
expect(t.exports.map((e) => e.request.format)).toEqual(["svg"])
|
||||
t.nextExport().answer({ data: "<svg/>" })
|
||||
await t.settle()
|
||||
expect(t.next("POST").body).toMatchObject({
|
||||
xml: a4,
|
||||
source: "edit",
|
||||
baseVersion: 4,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user