Editing an auto-props control on one variant changes the others. The control belongs to one component in one variant, and _hPropState is keyed that way, but the value reaches every variant in the story.
Found while bringing auto-props to Svelte (#233) — the Svelte implementation does not do this, which is how it surfaced.
Reproduce
conformance-auto-props in examples/vue3 (or nuxt4), grid layout, two variants:
naked — <AutoStateProps />, no bindings, no initState
state — initState returning { name: 'Fry' }, bound with :name="state.name"
Open ?variantId=naked and set the name control under <AutoStateProps> to Bender:
before ["Hello world!", "Hello Fry!"]
after ["Hello Bender!", "Hello Bender!"]
The state variant has its own initState and its own binding, and neither survives an edit made on a different variant.
Cause
implicitState is created once per story — Story.ts:39 — and every variant's state is two-way synced with it:
mountStateSync = syncStateBundledAndExternal(mountVariant.value.state, implicitState())
Variant.ts:86 runs that for every variant in mount mode, not only the current one. The render-mode sibling at :164 is guarded by renderContext.currentVariant?.id === variant.id; the mount one has no such guard, because sharing story state across variants is the intended behaviour there.
_hPropState then rides the same channel. It is not story state — it is per-variant, per-component-index bookkeeping that only means anything next to the _hPropDefs it was recorded against — so two variants that both render a component at index 0 overwrite each other.
Worth deciding
Excluding the auto-props bookkeeping from that sync looks right: _hPropState and _hPropDefs are addressed by a component index that is only meaningful within one variant, so sharing them across variants cannot be correct even when it looks harmless.
There is precedent for treating them as not-story-state: toPresetState already omits _hPropDefs when storing a preset (packages/poveste-app/src/app/util/state.ts:62), and StatePresets.vue carries the same omit list. This would be a third place naming those keys, which suggests the list belongs somewhere shared rather than being written out again.
What it must not break is the behaviour the mount-mode sync exists for: a variant with no initState picking up the story's own state.
Coverage
Nothing catches it. e2e/auto-props.spec.ts runs on all four books and deliberately asserts no isolation, with a comment saying why — the two plugins disagree, and the spec could not claim a shared contract that does not exist. Once this is fixed, that exclusion is what should be deleted, and the assertion belongs there: edit one variant's control, the sibling is untouched.
Related: #233, #431.
Editing an auto-props control on one variant changes the others. The control belongs to one component in one variant, and
_hPropStateis keyed that way, but the value reaches every variant in the story.Found while bringing auto-props to Svelte (#233) — the Svelte implementation does not do this, which is how it surfaced.
Reproduce
conformance-auto-propsinexamples/vue3(ornuxt4), grid layout, two variants:naked—<AutoStateProps />, no bindings, noinitStatestate—initStatereturning{ name: 'Fry' }, bound with:name="state.name"Open
?variantId=nakedand set thenamecontrol under<AutoStateProps>toBender:The
statevariant has its owninitStateand its own binding, and neither survives an edit made on a different variant.Cause
implicitStateis created once per story —Story.ts:39— and every variant's state is two-way synced with it:Variant.ts:86runs that for every variant in mount mode, not only the current one. The render-mode sibling at :164 is guarded byrenderContext.currentVariant?.id === variant.id; the mount one has no such guard, because sharing story state across variants is the intended behaviour there._hPropStatethen rides the same channel. It is not story state — it is per-variant, per-component-index bookkeeping that only means anything next to the_hPropDefsit was recorded against — so two variants that both render a component at index 0 overwrite each other.Worth deciding
Excluding the auto-props bookkeeping from that sync looks right:
_hPropStateand_hPropDefsare addressed by a component index that is only meaningful within one variant, so sharing them across variants cannot be correct even when it looks harmless.There is precedent for treating them as not-story-state:
toPresetStatealready omits_hPropDefswhen storing a preset (packages/poveste-app/src/app/util/state.ts:62), andStatePresets.vuecarries the same omit list. This would be a third place naming those keys, which suggests the list belongs somewhere shared rather than being written out again.What it must not break is the behaviour the mount-mode sync exists for: a variant with no
initStatepicking up the story's own state.Coverage
Nothing catches it.
e2e/auto-props.spec.tsruns on all four books and deliberately asserts no isolation, with a comment saying why — the two plugins disagree, and the spec could not claim a shared contract that does not exist. Once this is fixed, that exclusion is what should be deleted, and the assertion belongs there: edit one variant's control, the sibling is untouched.Related: #233, #431.