fix: what the batch C review found

- A redirect followed for a custom base URL (quota on) no longer carries
  the user's key or cookies to another origin, as fetch itself does, and
  a private address may redirect to another private one (already counted).
- The admin Test of an Ollama or Vertex AI entry without a URL goes where
  chat sends that entry's key: the environment's own URL variable, for
  Ollama else the local default. The Test of an Ollama Cloud key without
  a URL went to the cloud while chat went to local Ollama.
- Chat saves: each save notes the chat on screen and the order of the
  reads before reading its data. A save read before switching chats no
  longer writes into the chat switched to, and a copy that waited for its
  thumbnail no longer replaces a newer one.
- "Continue without saving" keeps its button when a later auto-save fails,
  and goes away when a new message is sent.
- A screenshot check that was waiting for its image when the user pressed
  Stop stays skipped after the next message.
- MCP History: draw.io's own copy of a diagram (after get_diagram) no
  longer adds an entry without a picture; a change of background is still
  its own version. The tab ignores an edit's answer that arrives after a
  newer AI write loaded.
- Desktop: a deleted preset is not brought back by a failed switch, and a
  request naming no preset does not stop a rollback. An origin keeping
  an access code counts as having settings.
- .env: a quoted value ending in a backslash ("C:\dir\") is read as dotenv
  reads it.
- The tool card no longer crashes on an id that does not turn into text;
  an older Test's success timer no longer ends a newer Test's spinner.
- Tests that passed without their fix now check it.
This commit is contained in:
dayuan.jiang
2026-10-05 20:09:42 +09:00
parent c0fa997186
commit 69c7896002
23 changed files with 461 additions and 63 deletions
+29 -3
View File
@@ -3,7 +3,9 @@
* Stores {xml, svg} entries in a circular buffer
*/
import { contentFingerprint } from "./edit-gate.ts"
import { log } from "./logger.ts"
import { normalizeToMxfile, parseMxfile } from "./pages.ts"
const MAX_HISTORY = 20
@@ -16,6 +18,30 @@ interface HistoryEntry {
let nextEntryId = 0
const historyStore = new Map<string, HistoryEntry[]>()
/** Each page's background colour */
function backgrounds(xml: string): string {
const doc = parseMxfile(normalizeToMxfile(xml) ?? xml)
if (!doc) return ""
return Array.from(doc.querySelectorAll("mxGraphModel"))
.map((m) => m.getAttribute("background") || "none")
.join(",")
}
// The same pages, cells and backgrounds. draw.io's own copy of a diagram
// (a sync reply) adds view and page attributes such as dx, grid and the
// page size, which the model's XML leaves out, so those are not compared.
// A document without pages has an empty fingerprint and is compared as
// text only.
function sameDiagram(a: string, b: string): boolean {
if (a === b) return true
const fingerprint = contentFingerprint(a)
return (
fingerprint !== "" &&
fingerprint === contentFingerprint(b) &&
backgrounds(a) === backgrounds(b)
)
}
export function addHistory(sessionId: string, xml: string, svg = ""): number {
let history = historyStore.get(sessionId)
if (!history) {
@@ -23,10 +49,10 @@ export function addHistory(sessionId: string, xml: string, svg = ""): number {
historyStore.set(sessionId, history)
}
// Dedupe: skip if same as last entry (a change of page settings or
// background only is a new version)
// Dedupe: skip if same as last entry, also when only re-serialized
// (a change of background only is a new version)
const last = history[history.length - 1]
if (last && last.xml === xml) {
if (last && sameDiagram(last.xml, xml)) {
if (svg && !last.svg) last.svg = svg
return history.length - 1
}
+3 -2
View File
@@ -190,8 +190,9 @@ async function pushState(xml, svg = '', baseVersion = currentVersion, source = '
pushFailing = false;
if (r.ok) {
const d = await r.json();
// An answer about a state this tab has left since
if (sid !== stateId) return;
// An answer about a state this tab has left since, or one that
// comes after a newer version was loaded or saved
if (sid !== stateId || d.version < currentVersion) return;
currentVersion = d.version;
lastXml = xml;
// The canvas changed while this edit was on its way, to
@@ -582,6 +582,15 @@ describe("history restore", () => {
expect(getHistory(id)).toHaveLength(1)
// The missing image is filled in
expect(getHistory(id)[0].svg).toBe("SVG")
// draw.io's copy of it, as a sync reply brings it back
addHistory(
id,
page("same").replace(
"<mxGraphModel>",
'<mxGraphModel dx="1244" dy="534" grid="1" pageWidth="850">',
),
)
expect(getHistory(id)).toHaveLength(1)
})
it("keeps a version that changed only the background", () => {