Developer Docs

๐Ÿ› Svelte 5 State Shadowing

When migrating to or developing with Svelte 5 Runes, assigning class-based or external store singletons to local variables can inadvertently trigger silent rendering crashes due to Svelte 4 legacy compatibility rules.

This document outlines the State Shadowing Bug, its symptoms, the underlying AST interpretation collision, and the canonical fix.


๐Ÿ›‘ The Bug

In Svelte 5, state is managed via the $state rune. However, Svelte 5 also maintains backwards compatibility with Svelte 4 cross-file reactivity patterns. In Svelte 4, prefixing any variable with $ (e.g., $authStore) automatically compiles down to a .subscribe() listener for a Svelte Store.

If you declare a local variable named state, and subsequently use the Svelte 5 $state rune, Svelte prioritizes the Svelte 4 auto-subscription pattern over the rune macro.

โŒ Problematic Code

<script lang="ts">
	// 1. You import an external class or state object
	import { createMaletState } from '../../../stores/createMaletStore.svelte';

	// 2. You alias it locally using the exact name "state"
	const state = createMaletState;

	// 3. Somewhere else in the script, you define local reactivity using the Svelte 5 Rune
	let needsUpgrade = $state(false);
</script>

Compilation & Runtime Failure

When the Svelte compiler analyzes the AST (Abstract Syntax Tree), it sees $state(false). Due to the existence of the const state variable, Svelte mistakenly compiles $state into a store subscription against your local variable, expecting it to implement Svelte's readable store contract.

When the component attempts to mount, it throws a fatal, silent runtime exception:

Uncaught Svelte error: store_invalid_shape
`state` is not a store with a `subscribe` method
https://svelte.dev/e/store_invalid_shape

๐Ÿ“‰ Symptoms in Production

Because the Svelte 5 rendering loop executes synchronously during the component layout phase (especially inside {#if} or {#key} blocks), this exception halts DOM updates exactly at the point the component attempts to mount.

Symptoms include:

  • Visual UI freezes where buttons or events silently "do nothing".
  • Partial view updates: Components higher in the DOM tree (e.g. progress bars) successfully update, while the problematic component and its siblings downstream halt or fail to swap.
  • No explicit build errors generated by svelte-check or Vite during compilation.

๐Ÿ”ฌ How We Found It

This bug was discovered using the Frontend Debugging Playbook. The short version:

  1. Stripped reactive wrappers โ€” removed $derived and {#key} to minimize abstraction layers.
  2. Injected a console.log directly into the onclick handler of the failing "Review" button.
  3. Opened the browser DevTools console and clicked through the wizard.
  4. Read the output โ€” the logs confirmed state was advancing (step: 5), but Svelte threw a fatal store_invalid_shape error immediately after:
[DEBUG] NEXT clicked: {step: 4, canProceed: true}
Uncaught Svelte error: store_invalid_shape
`state` is not a store with a `subscribe` method
    at $state (ReviewLaunch.svelte:18:2)
  1. Traced the stack โ€” the at $state (ReviewLaunch.svelte:18:2) line pointed directly to let needsUpgrade = $state(false), which Svelte misinterpreted as a store subscription against the local const state variable.

For the full step-by-step process (including agent-specific guidance), see the Frontend Debugging Playbook.


โœ… The Fix

The fix is entirely localized to lexical scoping. You must rename any local proxy or variable alias to prevent shadowing the state identifier.

<script lang="ts">
	import { createMaletState } from '../../../stores/createMaletStore.svelte';

	// Assign the alias to something other than 'state'
	const wizardState = createMaletState;
	
	// The $state rune now evaluates correctly as a macro
	let needsUpgrade = $state(false);
</script>

When implementing the CreateMaletState wizard or interacting with core frontend forms, explicitly favor domain-specific variable names such as wizardState, maletConfig, or formContext over generic tokens like state.