Skip to content

The Svelte controls diagnostic fires on stories whose controls work, when initState is on the Story and controls are on the Variant #462

Description

@50rayn

RenderVariant.svelte logs a console.error telling the author their controls are broken, on stories whose controls work perfectly.

Reproduce

Open StateOption in the svelte5 or sveltekit book. The console carries:

[poveste] Variant "untitled" has a controls slot but no initState. Controls will render and appear to work, but their edits cannot reach the story: the story is mounted separately for each slot, so a component-local variable is not shared.

Then type into the optionApiData control. The story body updates immediately — "optionApiData": "EDITED-VIA-CONTROL". The edit reaches the story. Every claim in the message is false for this story.

Why

RenderVariant.svelte:32 tests only the variant's own initState prop:

if (!initState && shouldRender && slotName === 'controls' && $$slots.controls && !currentVariant.__pvtStateWarned) {

StateOption declares initState on the Story and the controls snippet on the Variant:

<Hst.Story title="StateOption" initState={() => ({ optionApiData: OPTION_DATA })}>
  <Hst.Variant>
    {#snippet controls({ state })}

So by the time RenderVariant runs, RenderStory.svelte:27 has already seeded the state and set the flag:

if (initState && currentVariant && !currentVariant.__pvtStateSeeded) {
  currentVariant.__pvtStateSeeded = true
  currentVariant.state = { ...currentVariant.state, ...initState() }
}

The variant-level check never consults __pvtStateSeeded — the signal it needs, already on the object it already holds. The story-level check in RenderStory is correct; only the variant one is wrong.

Fix

Add the seeded flag to the condition:

if (!initState && !currentVariant.__pvtStateSeeded && shouldRender && slotName === 'controls' && ...)

Ordering matters: RenderStory seeds during its own script, and both mounts run before the controls slot renders, so the flag is set by then. Worth confirming that holds for the iframe layout too, where the two mounts are in different realms — __pvtStateSeeded lives on the variant object, which is per-realm, so the seeding and the check need to be in the same realm to pair up. That is the one thing to verify rather than assume.

Which stories

Six, across both books — a variant-level controls slot while initState sits on the Story:

svelte5/src/stories/StateOption.story.svelte
svelte5/src/stories/StateSetup.story.svelte
svelte5/src/stories/StateSetup2.story.svelte
sveltekit/src/lib/stories/StateOption.story.svelte
sveltekit/src/lib/stories/StateSetup.story.svelte
sveltekit/src/lib/stories/StateSetup2.story.svelte

Stories that declare controls at Story level route through RenderStory and are correctly quiet.

Why this is worse than ordinary noise

The diagnostic is good and the failure it describes is real — UnmigratedState.story.svelte exists as a deliberate fixture so it has something to fire on, and its comment says as much. A false positive on correct code is what teaches people to filter the message out, and the next person to hit the genuine UnmigratedState shape will have already learned to ignore it.

It also fires on three of the repo's own example stories, so the book ships red consoles.

Coverage gap

Nothing catches this. examples/svelte5/playwright/state-sync.spec.ts asserts on page.on('console', …) for the presence of this diagnostic in the unmigrated case, but nothing asserts its absence for a correctly-written story — which is the assertion that would have failed here. A spec that opens StateOption, edits the control, asserts the story body changed and asserts no [poveste] console error, closes both halves.

Found by clicking through the dev server, not by any suite.

Metadata

Metadata

Assignees

No one assigned

    Labels

    a:pluginsPlugin API and official pluginsa:svelteSvelte plugin, SvelteKit, Hst surfacesprint:4-bugsIndependent correctness bugs; runs in parallel with any sprint

    Type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions