fix: saved pipelines not marked whole also get the media slots their preset gained

Reproduced with the real H3 preset slots: video reference was counted as not fitting.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CHWxqHtxwWrq4kEyFTaHds
This commit is contained in:
Claude
2026-10-21 02:08:45 +00:00
parent 4e770e2046
commit 770962e209
2 changed files with 26 additions and 9 deletions
+7 -9
View File
@@ -647,16 +647,14 @@ export function createPipelineState(rawApi, { answerMs = ANSWER_MS } = {}) {
// too, and simply goes without that slot.
const isWhole = typeof wholeSaved === "boolean" ? wholeSaved
: [...byId.keys()].some((id) => !have.has(id)) && offered.some((d) => !gone.has(d.id) && !byId.has(d.id));
// A whole pipeline started from a preset lacks the media inputs an update gave that preset since (a video or audio
// A pipeline started from a preset lacks the media inputs an update gave that preset since (a video or audio
// reference slot): those take no setting and nothing else depends on them, so they are laid in beside the saved ones.
let grown = [];
if (isWhole) {
try {
const isAsset = (s) => (s.roles || []).length && s.roles.every((r) => String(r.at || "").startsWith("assets."));
const from = ((await API.pipelinePresets()).presets || []).find((pr) => (pr.slots || []).some((s) => isAsset(s) && byId.has(s.id)));
if (from) grown = from.slots.filter((s) => isAsset(s) && sound(s) && !byId.has(s.id) && !gone.has(s.id));
} catch (_) { /* the saved pipeline goes in as it is */ }
}
try {
const isAsset = (s) => (s.roles || []).length && s.roles.every((r) => String(r.at || "").startsWith("assets."));
const from = ((await API.pipelinePresets()).presets || []).find((pr) => (pr.slots || []).some((s) => isAsset(s) && byId.has(s.id)));
if (from) grown = from.slots.filter((s) => isAsset(s) && sound(s) && !byId.has(s.id) && !gone.has(s.id));
} catch (_) { /* the saved pipeline goes in as it is */ }
const lay = (extras) => isWhole ? JSON.parse(JSON.stringify([...saved.filter(sound), ...(extras ? grown : [])])) : offered.filter((def) => !gone.has(def.id)).map((def) => {
const mine = byId.get(def.id);
if (!mine) return JSON.parse(JSON.stringify(def));
@@ -670,7 +668,7 @@ export function createPipelineState(rawApi, { answerMs = ANSWER_MS } = {}) {
if (typeof mine.group === "string" && mine.group) out.group = mine.group;
if (typeof mine.bypassed === "boolean") out.bypassed = mine.bypassed;
return out;
}).concat(extras ? saved.filter((s) => sound(s) && !have.has(s.id)) : []);
}).concat(extras ? [...saved.filter((s) => sound(s) && !have.has(s.id)), ...grown.filter((s) => !have.has(s.id))] : []);
// Replaces whatever pipeline was live: nothing queued for the old one may land on the new.
const mine = ++epoch;
pendingValues = {}; // the new project's values win, not an edit made to the old one
+19
View File
@@ -751,3 +751,22 @@ test("the Project tab, the frame grid and the scene tab read the pipeline throug
assert.match(read("inspector/index.js"), /app\.pipeline\.view/);
assert.match(read("inspector/scene.js"), /app\.pipeline\.view/);
});
test("a preset-started project gains the media slots its preset got since, and nothing else", async () => {
const media = (id, at) => ({ id, node: "LoadIt", roles: [{ at, input: "media_id" }], inputs: { media_id: "" } });
const oldPreset = [{ id: "ckpt", node: "Checkpoint", inputs: { file: "h3.safetensors" } }, media("ref_source_1", "assets.reference_1")];
const newPreset = [...oldPreset, { id: "ckpt2", node: "Checkpoint", inputs: {} }, media("ref_video_source_1", "assets.reference_video_1")];
const PS = load([]);
lastApi.pipelinePresets = async () => ({ presets: [{ id: "p", slots: newPreset }] });
await PS.adopt(oldPreset, ["model", "gen"], {}, true);
assert.deepStrictEqual(PS.slots().map((s) => s.id), ["ckpt", "ref_source_1", "ref_video_source_1"]);
});
test("the same holds when the saved copy is not marked whole", async () => {
const media = (id, at) => ({ id, node: "LoadIt", roles: [{ at, input: "media_id" }], inputs: { media_id: "" } });
const old = [{ id: "ckpt", node: "Checkpoint", inputs: {} }, media("ref_source_1", "assets.reference_1")];
const PS = load([]);
lastApi.pipelinePresets = async () => ({ presets: [{ id: "p", slots: [...old, media("ref_video_source_1", "assets.reference_video_1")] }] });
await PS.adopt(old, ["model"], {}, false);
assert.ok(PS.slots().some((s) => s.id === "ref_video_source_1"));
});