Summary. On an instance of a variant‑switching component set, setReactionsAsync stores a single flat reactions override that is applied across all variant states. This shadows each variant's own inherited reactions and breaks the component's prototype state machine — even when the written array is byte‑identical to what was read. The editor, by contrast, can add an instance‑level reaction that coexists with the inherited per‑variant reactions and runs correctly in the prototype player. The plugin API has no way to produce that representation.
Repro. Component set Toggle, variant property State ∈ {A, B}, where State=A has ON_CLICK → CHANGE_TO B and State=B has ON_CLICK → CHANGE_TO A. Place one instance (it shows A), select it:
const i = figma.currentPage.selection[0] as InstanceNode;
await i.setReactionsAsync(JSON.parse(JSON.stringify(i.reactions))); // identity write-back
i.setProperties({ State: 'B' });
console.log(i.reactions); // still A's reaction (CHANGE_TO B) — frozen; should be B's (CHANGE_TO A)After this, the prototype can no longer toggle B → A. No data changed; only the storage mode flipped from inherited (per‑variant) to flat override (uniform across variants).
Why the API can't work around it. instance.reactions only ever returns the currently displayed variant's effective reactions — a one‑variant keyhole. Reading it and writing it back therefore captures a single variant and then freezes it across all of them. And instance.overrides reports ["reactions"] for both the broken flat override and the editor's working additive one, so the two are indistinguishable on read and the additive one is unreproducible on write.
Proposed fix. An optional, backwards‑compatible mode:
setReactionsAsync(reactions: Array<Reaction>, options?: { mode?: 'replace' | 'add' }): Promise<void>'replace' = today's behavior. 'add' stores reactions as an additive instance delta, merged per‑variant with the inherited reactions — exactly what the editor produces. (A dedicated addReactionsAsync would be equivalent.)
Secondary — make the additive layer observable so it can be read and round‑tripped safely:
Expose the instance delta separately from the effective set, e.g. InstanceNode.instanceReactions (the delta only) alongside InstanceNode.reactions (the merged, effective view). Today, reading reactions and writing it back via setReactionsAsync is unsafe precisely because the read returns the merged view while the write replaces flatly.
Have InstanceNode.overrides distinguish an additive reactions override from a full replacement (e.g. a distinct field or flag).