Review found the save() queue's pendingBody merge was shallow: two queued
edits that both touch the top-level "inputs" key (two different slots, or
even two fields on the same slot from models.js's per-call-fresh objects)
had the second's inputs object silently REPLACE the first's rather than
combine -- the earlier edit vanished with no error, the field just
reverted on the next render. engine_settings.js's own calls happened to be
immune by accident (one long-lived mutated `values` object, so the last
queued snapshot already contained every earlier edit) -- that immunity
was never a property of save() itself, and models.js's fresh-object-per-
call shape hit it immediately. Fixed with a one-level-deeper merge for
"inputs"/"values" specifically. Verified live via two real race
reproductions run through the browser console: two edits on different
slots queued with no await between them, and two edits on the SAME slot --
both survive now, confirmed against the actual saved graph state.
Also closed a load race: ensureLoaded()'s only guard was `slots !== null`,
which doesn't cover the window while a first load is still in flight -- two
panels mounting close together could both start a fetch. Now every caller
awaits the same in-flight promise, so exactly one fetch ever goes out per
session.
Documented (not fixed, deliberately) that Models & Pipeline's node-level
pin target is currently unreachable: pinTarget() only ever returns a
group-level pin today, since there's no "currently open node" concept in
this flat slot list yet -- the openNode/ctx.sub("node:...") plumbing stays
for whatever old pin data might still reference it, but nothing produces
a fresh one. Real gap against v4's per-node pinning, not a regression.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>