fix: ask before replacing a saved preset

Saving a character, object, or scene reused an existing name and
overwrote that preset without asking. Updating the preset already
linked to the region still saves directly.
This commit is contained in:
by-ae
2026-11-01 07:31:19 +10:00
parent 17858e1963
commit 479e0bb069
4 changed files with 61 additions and 5 deletions
+2
View File
@@ -73,6 +73,8 @@ Load shows the request error (for example `HTTP 500`) when the store cannot be r
- Ensure the ComfyUI user directory is writable
- Try a shorter name (max 64 characters)
Saving asks before it replaces a character, object, or scene that already uses that name. Saving again to the preset already linked to the region does not ask. If the preset list cannot be loaded, the save still goes ahead.
### Connected import JSON did not change the prompt
The `import_json` input overrides the editor only when it is a JSON object. A leading UTF-8 byte-order mark is ignored. If the value is empty, not valid JSON, or a JSON array, the node keeps the editor caption and prints a line starting with `[ByAE Ideogram]`.
+23 -1
View File
@@ -8,7 +8,9 @@ globalThis.window = {
},
};
const { loadStores, deleteStoreEntry, saveStoreEntry, normalizeStoreName } = await import("../web/js/ideogram4_shared.js");
const {
loadStores, deleteStoreEntry, saveStoreEntry, normalizeStoreName, savedPresetCollision,
} = await import("../web/js/ideogram4_shared.js");
describe("loadStores", () => {
const originalFetch = globalThis.fetch;
@@ -54,6 +56,26 @@ describe("loadStores", () => {
});
});
describe("savedPresetCollision", () => {
it("names a different preset that would be replaced", () => {
assert.equal(savedPresetCollision(["Ada", "Bea"], "Bea"), "Bea");
assert.equal(savedPresetCollision(["Ada"], "Cara"), null);
assert.equal(savedPresetCollision(["Ada"], " "), null);
assert.equal(savedPresetCollision([], "Ada"), null);
});
it("does not treat an update of the linked preset as a replacement", () => {
assert.equal(savedPresetCollision(["Ada"], "Ada", "Ada"), null);
assert.equal(savedPresetCollision(["Ada", "Bea"], "Bea", "Ada"), "Bea");
});
it("matches a composed name to the stored spelling", () => {
const nfd = "Émilie".normalize("NFD");
assert.equal(savedPresetCollision(["Émilie"], nfd), "Émilie");
assert.equal(savedPresetCollision(["Émilie"], ` ${nfd} `, "Émilie"), null);
});
});
describe("saveStoreEntry", () => {
const originalFetch = globalThis.fetch;
+23 -4
View File
@@ -5,7 +5,7 @@ import {
} from "./utility.js";
import {
loadStores, saveStoreEntry, deleteStoreEntry, installBuilderQueueHooks, installBgUpdateListener,
normalizeStoreName,
normalizeStoreName, savedPresetCollision,
} from "./ideogram4_shared.js";
import {
captionText, cleanPalette, dimsFromAspect, elementToRegion, ideogramResolutionWarning,
@@ -1959,9 +1959,11 @@ app.registerExtension({
sceneSaveBtn.addEventListener("mousedown", (e) => e.stopPropagation());
sceneSaveBtn.addEventListener("click", () => {
showModal("Save Scene", [{ key: "name", label: "Scene name", placeholder: "my-scene" }], async (r) => {
const name = (r.name || "").trim();
const name = normalizeStoreName(r.name || "");
if (!name) return;
try {
const collision = savedPresetCollision(await existingPresetNames("scenes"), name);
if (collision && !confirm(`Replace saved scene "${collision}"? This cannot be undone.`)) return;
await saveStoreEntry("scenes", name, captureSceneState());
console.log("ByAE Ideogram: saved scene:", name);
} catch (e) {
@@ -2056,13 +2058,30 @@ app.registerExtension({
return ta;
}
async function existingPresetNames(bucket) {
try {
const stores = await loadStores();
return Object.keys(stores[bucket] || {});
} catch (e) {
console.warn("ByAE Ideogram: could not check existing presets before save", e);
return [];
}
}
async function persistBoxToStore(b, type, name) {
const bucket = type === "object" ? "objects" : "characters";
const safeName = normalizeStoreName(name);
if (!safeName) return;
const linked = b.storeRef && b.storeRef.type === type ? b.storeRef.name : "";
try {
const savedName = await saveStoreEntry(bucket, name, boxPayload(b));
if (normalizeStoreName(linked) !== safeName) {
const collision = savedPresetCollision(await existingPresetNames(bucket), safeName, linked);
if (collision && !confirm(`Replace saved ${type} "${collision}"? This cannot be undone.`)) return;
}
const savedName = await saveStoreEntry(bucket, safeName, boxPayload(b));
b.storeRef = { type, name: savedName };
commit();
console.log("ByAE Ideogram: saved", type, name);
console.log("ByAE Ideogram: saved", type, savedName);
} catch (e) {
console.error("ByAE Ideogram: failed to save:", e);
alert("Failed to save: " + e.message);
+13
View File
@@ -13,6 +13,19 @@ export function normalizeStoreName(name) {
return String(name ?? "").trim().normalize("NFC");
}
/**
* Stored name this save would replace, or null.
* Saving the preset already linked to the region is an update, not a replacement.
*/
export function savedPresetCollision(existingNames, name, linkedName) {
const target = normalizeStoreName(name);
if (!target) return null;
const match = (existingNames || []).find((entry) => normalizeStoreName(entry) === target);
if (!match) return null;
if (linkedName && normalizeStoreName(linkedName) === target) return null;
return match;
}
export async function loadStores() {
try {
const resp = await fetch(STORE_API, {