![[Self Promo] Svelte 5 removed $capture_state — two gotchas that silently broke my component playground](https://external-preview.redd.it/KVnFn3ecYIyZzlaJYDHIz71MifJRf3mDRsFFemrPjfc.png?width=1080&crop=smart&auto=webp&s=e22e4d66c4304dae0bb51accaa2d9657773bd0bc)
[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.