The Save bar fired a request per edited input, all at once, and they all mutated
the same execution. That is how two typed values went missing: the bulk global
endpoint replaced the whole set of globals, so the list input's save landed last
and took its neighbours down with it. The endpoint now merges (service-side fix),
but a save should not depend on request ordering to be correct.
Every edited global now goes in a single PUT /executions/{id}/globals, and the
node inputs follow one at a time - there is no bulk endpoint per step, so the
best available is not to have them in flight together. The single-input save
uses the same two requests, a global batch of exactly one, so there is one code
path and one value normalisation instead of a second copy that could drift.
planInputSaves and preparedInputValue are pure and live with the other viewer
utils, which is what made them testable: the component has no spec harness (14
injected services), and the parts worth pinning are which endpoint gets called
and what shape the value takes.
The fake now writes globals to context.globalInputs and the descriptors, where
the viewer actually reads them. The older single-key fakes only ever touched
context.inputs, so a saved global never showed up in development at all.
A failed batch reports the same error on every input in it: it failed as a
batch, and guessing a culprit would be worse than saying so.
487 frontend tests green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>