u/50rayn

[Self Promo] Svelte 5 removed $capture_state — two gotchas that silently broke my component playground
▲ 3 r/vuejs+1 crossposts

[Self Promo] Svelte 5 removed $capture_state — two gotchas that silently broke my component playground

Self-promo disclosure: I maintain poveste, a component playground continuing histoire. Two Svelte 5 fixes worth sharing — both failed silently, and both apply to anyone wrapping or re-mounting user components.

1. $capture_state / $inject_state are gone

A playground mounts your story twice — once for the controls panel, once for the preview. histoire bridged those two instances with $capture_state / $inject_state. They were dev-only compiler internals, and Svelte 5 removed them.

Without the bridge, a plain let exists twice. The checkbox writes to one copy; your component renders the other. The control moves, nothing happens, no warning.

Fix: stop reading state out of components and own it instead.

<script>
  const initState = () => ({ disabled: false })
</script>

<Hst.Story {initState}>
  {#snippet children({ state })}
    <MyButton disabled={state.disabled} />
  {/snippet}
  {#snippet controls({ state })}
    <Hst.Checkbox bind:value={state.disabled} title="Disabled" />
  {/snippet}
</Hst.Story>

One object, both snippets. It also survives crossing into the preview iframe, which the old approach never could.

2. Spreading a bound prop drops its setter

Svelte 5 passes bound props as getter/setter pairs. Spread them onto a wrapper:

<Inner {...props} />

...and the spread invokes the getter, handing the child a plain value. The setter is gone, so bind: silently degrades to one-way. Forward the prop explicitly instead of spreading it.


Since the first failure was invisible, a story with controls but no initState now logs an error naming itself. Happy to answer migration questions.

u/50rayn — 1 day ago