web-framework-svelte · diff
git:20260320.766fb9e to git:20260906.ae0cc61
92 added, 461 removed. Audit B to B.
---
name: web-framework-svelte
- description: Svelte 5 Runes reactivity - $state, $derived, $effect, $props, $bindable, components, snippets, event handling, context API
+ description: Svelte 5 runes — $state, $derived, $effect, $props, $bindable, snippets, callback props, context. Load when writing Svelte 5 components or reactive modules.
---
# Svelte 5 Patterns
- > **Quick Guide:** Svelte 5 uses Runes for explicit reactivity. Use `$state` for reactive variables, `$derived` for computed values, `$effect` only as an escape hatch. Use snippets instead of slots. Use callback props instead of event dispatchers. Keep components small and composable.
-
- ---
-
- <critical_requirements>
-
- ## CRITICAL: Before Using This Skill
-
- > **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)
-
- **(You MUST use Svelte 5 Runes syntax — NOT Svelte 4 patterns like `export let`, `$:`, or stores for component state)**
-
- **(You MUST use `$derived` for computed values — NEVER use `$effect` to synchronize state)**
+ > **Quick Guide:** Runes make reactivity explicit and portable out of `.svelte` files. `$state` for values that change, `$derived` for everything computed from them, `$effect` only for reaching outside the component. Snippets replace slots, callback props replace `createEventDispatcher`, and `onclick` replaces `on:click`. The Svelte 4 forms still compile, so nothing flags them.
- **(You MUST use snippets (`{#snippet}` / `{@render}`) instead of slots (`<slot>`))**
+ **Detailed Resources:**
- **(You MUST use callback props (`onclick`, `onsomething`) instead of `createEventDispatcher`)**
+ - [examples/core.md](examples/core.md) — `$state`, `$state.raw`, `$derived`, `$props`, `$bindable`, `$effect`, `$effect.pre`
+ - [examples/snippets.md](examples/snippets.md) — children, named snippets as props, parameters, optional and recursive snippets
+ - [examples/events.md](examples/events.md) — element events, callback props, forwarding, window events, composing handlers
+ - [examples/advanced.md](examples/advanced.md) — `$inspect`, context, shared state modules, class-based state, `$state.snapshot`, `$state.eager`
+ - [reference.md](reference.md) — rune cheat sheet, Svelte 4 → 5 migration table, decision trees, component template
- **(You MUST use `$state.raw()` for large objects/arrays that are replaced, not mutated)**
+ ---
- **(You MUST use `createContext` for type-safe context instead of raw `setContext`/`getContext` with string keys)**
+ ## Which path applies
- </critical_requirements>
+ - **Inside a `.svelte` component** — every rune is available, props arrive through `$props()`, and teardown belongs in the function an `$effect` returns.
+ - **Inside a `.svelte.ts` or `.svelte.js` module** — `$state` and `$derived` work, but a reassigned export does not propagate to importers, because the binding is copied at import. Export an object or a class holding `$state` fields instead: [examples/advanced.md](examples/advanced.md).
---
- **Auto-detection:** Svelte 5, Runes, $state, $derived, $effect, $props, $bindable, $inspect, .svelte, snippet, @render, createContext, getContext, setContext, $state.raw, $state.eager, $derived.by, $effect.pre, ClassValue
-
- **When to use:**
+ <critical_requirements>
- - Building Svelte 5 components with Runes reactivity
- - Managing component state with `$state` and computed values with `$derived`
- - Creating reusable markup with snippets (replacing slots)
- - Handling events with native event attributes and callback props
- - Sharing state across components with context API
- - Two-way binding with `$bindable` props
+ ## Before writing Svelte code
- **Key patterns covered:**
+ **Declare changing values with `$state` and compute from them with `$derived`.** A `$derived` recomputes lazily and cannot fall out of step; the same value maintained by an `$effect` updates after the DOM has already painted the old one.
- - Runes: `$state`, `$derived`, `$effect`, `$props`, `$bindable`, `$inspect`
- - Component composition with snippets and `{@render}`
- - Event handling with native attributes and callback props
- - Context API with `createContext` for type-safe cross-component state
- - Class-based reactive state with `$state` fields
- - Deep vs shallow reactivity (`$state` vs `$state.raw`)
+ **Pass composable markup as snippets — `{#snippet}` declares it, `{@render}` renders it.** Snippets are typed, take parameters, and can be passed as props; `<slot>` did none of that.
- **When NOT to use:**
+ **Let a child notify its parent through a callback prop — `onsave`, `onselect`.** The signature is checked at the call site, where a dispatched event's payload was not.
- - Meta-framework-specific patterns (routing, load functions, form actions) — use the corresponding meta-framework skill
- - Svelte 4 patterns (`export let`, `$:` reactive statements, `<slot>`, `createEventDispatcher`)
- - Server-side logic (use your meta-framework's server hooks and routes)
+ **Reach for `$state.raw()` when a value is replaced wholesale rather than mutated.** It skips the deep proxy, which is the whole cost on a large array that only ever gets reassigned.
- **Detailed Resources:**
+ **Reach for `createContext<T>()` over `setContext`/`getContext`.** It hands back a typed `[get, set]` pair with the key minted for you, so no consumer casts and no two libraries collide on a string key — [examples/advanced.md](examples/advanced.md) has it.
- - For decision frameworks and anti-patterns, see [reference.md](reference.md)
+ </critical_requirements>
- **Runes & Reactivity:**
+ ---
- - [examples/core.md](examples/core.md) - `$state`, `$derived`, `$effect`, `$props`, `$bindable`, component patterns
+ **Auto-detection:** Svelte 5, runes, $state, $derived, $effect, $props, $bindable, $inspect, .svelte, .svelte.ts, {#snippet}, {@render}, Snippet, createContext, setContext, getContext, $state.raw, $state.snapshot, $state.eager, $derived.by, $effect.pre, ClassValue
- **Component Patterns:**
+ **Applies to:**
- - [examples/snippets.md](examples/snippets.md) - Snippet blocks, `{@render}`, passing snippets as props, replacing slots
- - [examples/events.md](examples/events.md) - Event handling, component events via callback props, event modifiers
+ - Component state, derived values and side effects with runes
+ - Props, defaults, rest props and two-way binding with `$bindable`
+ - Composition with snippets, including snippets passed as props
+ - Event handling and parent-child communication
+ - Context, shared state modules and class-based reactive state
- **Advanced:**
+ **Handled elsewhere:**
- - [examples/advanced.md](examples/advanced.md) - `$inspect`, context API, `$state.raw`, `$state.eager`, class-based state, shared state modules
+ - Styling — a `<style>` block is scoped by the compiler, and which CSS approach fills it is not settled here
+ - Routing, server-side loading and form submission — a meta-framework's concern, whichever one is in use
+ - Server-state caching and invalidation
+ - Test doubles for the network
---
<philosophy>
## Philosophy
- Svelte 5 introduces **Runes** — a set of primitives that bring explicit, fine-grained reactivity to Svelte. Unlike Svelte 4's compiler magic (`$:`, `export let`), Runes make reactivity visible and portable across `.svelte` files, `.ts` files, and class definitions.
-
- **Core principles:**
-
- 1. **Explicit reactivity** — Runes (`$state`, `$derived`, `$effect`) make reactive declarations visible. No hidden compiler transformations.
- 2. **Derived over effects** — Compute values with `$derived`, not `$effect`. Effects are escape hatches, not primary tools.
- 3. **Deep reactivity by default** — `$state` creates deeply reactive proxies for objects/arrays. Mutations are tracked automatically.
- 4. **Snippets replace slots** — `{#snippet}` blocks are more powerful, typed, and composable than `<slot>` elements.
- 5. **Callback props replace event dispatchers** — Pass `onsomething` callback props instead of using `createEventDispatcher`.
- 6. **Compile-time optimization** — Svelte compiles components to efficient imperative code. No virtual DOM diffing at runtime.
-
- **When to use Svelte 5 Runes:**
-
- - All new Svelte components (Runes are the default in Svelte 5)
- - Reactive state in `.svelte.ts` or `.svelte.js` files
- - Class-based state with reactive fields
- - Any computed value that depends on reactive state
-
- **When NOT to use:**
+ Svelte 4 inferred reactivity from position: a `let` at the top level of a component was reactive, `$:` re-ran on assignment, and neither meant anything in a `.ts` file. Runes replace that with a marker on the value itself, so the same declaration behaves identically in a component, a module and a class field.
- - Non-reactive constants (use plain `const` or `let`)
- - Server-side code that doesn't need reactivity
- - Meta-framework concerns (routing, load functions, server hooks) — use the corresponding meta-framework skill
- - Svelte 4 patterns — `export let`, `$:`, stores for component state, `<slot>`, `createEventDispatcher`
+ The ordering that follows is: `$derived` for anything computable, an event handler for anything a user triggers, and `$effect` only for what is genuinely outside the component — a canvas, a third-party widget, a subscription. An `$effect` that assigns to `$state` is a `$derived` written the long way round, and it runs after the DOM update rather than before it.
</philosophy>
---
<patterns>
- ## Core Patterns
+ ## Core patterns
- ### Pattern 1: Reactive State with $state
+ ### Pattern 1: $state
- Use `$state` to declare reactive variables. Updates to `$state` variables automatically trigger UI re-renders.
+ `$state` makes a value reactive and, for objects and arrays, deeply so — `push` and property assignment are both tracked, with no immutable update dance.
```svelte
- <!-- counter.svelte -->
<script lang="ts">
let count = $state(0);
- const STEP = 5;
-
- function increment() {
- count += 1;
- }
-
- function incrementByStep() {
- count += STEP;
- }
- </script>
-
- <button onclick={increment}>
- Count: {count}
- </button>
- <button onclick={incrementByStep}>
- +{STEP}
- </button>
- ```
-
- **Why good:** Explicit reactive declaration, named constants for magic numbers, plain function event handlers
-
- ```svelte
- <!-- BAD: Svelte 4 style -->
- <script>
- let count = 0; // Not explicitly reactive in Svelte 5 mode
- $: doubled = count * 2; // Svelte 4 reactive statement
- </script>
- ```
-
- **Why bad:** `$:` is Svelte 4 syntax deprecated in Svelte 5, implicit reactivity is confusing and non-portable
-
- #### Deep Reactivity
-
- `$state` creates deep proxies for objects and arrays — mutations are tracked automatically:
-
- ```svelte
- <script lang="ts">
- interface Todo {
- done: boolean;
- text: string;
- }
-
- let todos = $state<Todo[]>([
- { done: false, text: 'Learn Svelte 5' }
- ]);
+ let todos = $state<Todo[]>([]);
function addTodo(text: string) {
- todos.push({ done: false, text }); // Mutation tracked!
- }
-
- function toggleTodo(index: number) {
- todos[index].done = !todos[index].done; // Deep mutation tracked!
+ todos.push({ done: false, text });
}
</script>
```
- **Why good:** No need for immutable update patterns, array methods like `.push()` trigger reactivity, property mutations tracked deeply
+ Full code: [examples/core.md](examples/core.md)
---
- ### Pattern 2: Computed Values with $derived
+ ### Pattern 2: $derived
- Use `$derived` for values that depend on other reactive state. Never use `$effect` to synchronize state.
+ `$derived` takes an expression, `$derived.by` a function for anything longer. Both recompute only when a dependency actually changed.
```svelte
<script lang="ts">
- let count = $state(0);
-
- // Simple expression
let doubled = $derived(count * 2);
- // Complex computation with $derived.by
- let stats = $derived.by(() => {
- const isEven = count % 2 === 0;
- const isPositive = count > 0;
- return { isEven, isPositive };
- });
- </script>
-
- <p>{count} doubled is {doubled}</p>
- <p>Even: {stats.isEven}, Positive: {stats.isPositive}</p>
- ```
-
- **Why good:** Automatically recalculates when dependencies change, no side effects, push-pull reactivity avoids unnecessary recalculations
-
- ```svelte
- <!-- BAD: Using $effect to synchronize state -->
- <script lang="ts">
- let count = $state(0);
- let doubled = $state(0);
-
- $effect(() => {
- doubled = count * 2; // WRONG: Use $derived instead
- });
+ let stats = $derived.by(() => ({
+ isEven: count % 2 === 0,
+ isPositive: count > 0,
+ }));
</script>
```
- **Why bad:** `$effect` for derived state creates unnecessary reactive subscriptions, runs after DOM update (timing issues), harder to reason about data flow
+ Full code: [examples/core.md](examples/core.md)
---
- ### Pattern 3: Component Props with $props
+ ### Pattern 3: $props
- Use `$props` to declare component inputs. Supports destructuring, defaults, rest props, and TypeScript.
+ Props are destructured out of `$props()` with defaults and rest, and typed by an interface.
```svelte
- <!-- user-card.svelte -->
<script lang="ts">
interface Props {
name: string;
- email: string;
role?: string;
class?: string;
}
- let { name, email, role = 'member', ...rest }: Props = $props();
-
- // Derived from props — updates when props change
- let initials = $derived(
- name.split(' ').map(n => n[0]).join('').toUpperCase()
- );
+ let { name, role = 'member', ...rest }: Props = $props();
+ let initials = $derived(name.split(' ').map((n) => n[0]).join(''));
</script>
-
- <div class="user-card" {...rest}>
- <span class="avatar">{initials}</span>
- <h3>{name}</h3>
- <p>{email}</p>
- <span class="badge">{role}</span>
- </div>
```
- **Why good:** Type-safe props with interface, destructuring with defaults, rest props for pass-through, derived values update with prop changes
-
- ```svelte
- <!-- BAD: Svelte 4 style -->
- <script>
- export let name; // Svelte 4 prop declaration
- export let email;
- export let role = 'member';
- </script>
- ```
+ Destructuring `$props()` is the one place it is safe — the compiler keeps the bindings live. Destructuring a `$state` object does not.
- **Why bad:** `export let` is Svelte 4 syntax deprecated in Svelte 5, no type safety, no rest props
+ Full code: [examples/core.md](examples/core.md)
---
- ### Pattern 4: Two-Way Binding with $bindable
+ ### Pattern 4: $bindable
- Use `$bindable` to declare props that support two-way binding with `bind:`. Use sparingly — prefer one-way data flow.
+ `$bindable` marks a prop the child may write back through `bind:`. Worth it for form primitives; for everything else a callback prop keeps the data flowing one way.
```svelte
<!-- text-input.svelte -->
<script lang="ts">
- interface Props {
- value: string;
- placeholder?: string;
- }
-
let { value = $bindable(''), placeholder = '' }: Props = $props();
</script>
- <input
- bind:value={value}
- {placeholder}
- class="text-input"
- />
- ```
+ <input bind:value {placeholder} />
- ```svelte
<!-- parent.svelte -->
- <script lang="ts">
- import TextInput from './text-input.svelte';
-
- let searchQuery = $state('');
- </script>
-
<TextInput bind:value={searchQuery} placeholder="Search..." />
- <p>Searching for: {searchQuery}</p>
```
- **Why good:** Explicit two-way binding declaration, parent controls the state, child can modify via `bind:`, TypeScript-safe
-
- **When to use:** Form inputs, UI primitives (sliders, toggles) where two-way binding simplifies the API
-
- **When not to use:** Most component communication — prefer callback props for explicit data flow
+ Full code: [examples/core.md](examples/core.md)
---
- ### Pattern 5: Side Effects with $effect
+ ### Pattern 5: $effect
- Use `$effect` for side effects that need to run when reactive state changes. This is an **escape hatch** — prefer `$derived` for computed values and event handlers for user-triggered actions.
+ An effect reaches outside the component; the function it returns is the teardown, run before the next execution and on unmount.
```svelte
<script lang="ts">
- let searchQuery = $state('');
- let results = $state<string[]>([]);
- const DEBOUNCE_MS = 300;
-
- // Good: Side effect for external API calls
$effect(() => {
- const query = searchQuery;
-
- if (!query) {
- results = [];
- return;
- }
-
- const timer = setTimeout(async () => {
- const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
- results = await response.json();
- }, DEBOUNCE_MS);
-
- // Cleanup function runs before next effect and on unmount
+ const timer = setTimeout(() => search(query), DEBOUNCE_MS);
return () => clearTimeout(timer);
});
</script>
-
- <input bind:value={searchQuery} placeholder="Search..." />
-
- {#each results as result}
- <p>{result}</p>
- {/each}
```
- **Why good:** External API call is a legitimate side effect, cleanup prevents stale requests, named constant for debounce
-
- #### When NOT to Use $effect
-
- ```svelte
- <script lang="ts">
- let count = $state(0);
-
- // BAD: Synchronizing state — use $derived
- // $effect(() => { doubled = count * 2; });
-
- // BAD: Logging in effect — use $inspect for debugging
- // $effect(() => { console.log(count); });
-
- // BAD: Calling functions on change — use event handlers
- // $effect(() => { if (count > 10) showAlert(); });
+ Anything of the form `$effect(() => { x = f(y) })` is a `$derived`. Anything triggered by a click is an event handler. Anything you wanted to log is `$inspect`.
- // GOOD: Use $derived for computed values
- let doubled = $derived(count * 2);
- </script>
- ```
+ Full code: [examples/core.md](examples/core.md)
---
- ### Pattern 6: Snippets (Replacing Slots)
+ ### Pattern 6: Snippets
- Snippets are reusable markup blocks declared with `{#snippet}` and rendered with `{@render}`. They replace Svelte 4's `<slot>` elements.
+ `{#snippet}` declares a block of markup and `{@render}` renders it. Content between a component's tags becomes its `children` snippet automatically; anything else is a prop typed `Snippet`.
```svelte
- <!-- card.svelte -->
<script lang="ts">
import type { Snippet } from 'svelte';
-
- interface Props {
- title: string;
- children: Snippet;
- footer?: Snippet;
- }
-
- let { title, children, footer }: Props = $props();
- </script>
-
- <div class="card">
- <h2>{title}</h2>
- <div class="card-body">
- {@render children()}
- </div>
- {#if footer}
- <div class="card-footer">
- {@render footer()}
- </div>
- {/if}
- </div>
- ```
-
- ```svelte
- <!-- usage -->
- <script lang="ts">
- import Card from './card.svelte';
+ let { title, children, footer }: { title: string; children: Snippet; footer?: Snippet } = $props();
</script>
- <Card title="Welcome">
- <p>This becomes the children snippet automatically.</p>
-
- {#snippet footer()}
- <button>Learn More</button>
- {/snippet}
- </Card>
- ```
-
- **Why good:** Type-safe with `Snippet` type, optional snippets with conditional rendering, `children` is implicit for content between tags
-
- ```svelte
- <!-- BAD: Svelte 4 slots -->
- <div class="card">
- <slot /> <!-- Deprecated in Svelte 5 -->
- <slot name="footer" /> <!-- Use snippets instead -->
- </div>
+ <h2>{title}</h2>
+ {@render children()}
+ {#if footer}{@render footer()}{/if}
```
- **Why bad:** `<slot>` is deprecated in Svelte 5, no type safety, less composable than snippets
+ Full code: [examples/snippets.md](examples/snippets.md)
---
- ### Pattern 7: Event Handling
-
- Svelte 5 uses native event attributes (`onclick`, `onsubmit`) instead of Svelte 4's `on:click` directive. Component events use callback props.
-
- #### Element Events
-
- ```svelte
- <script lang="ts">
- let count = $state(0);
-
- function handleClick(event: MouseEvent) {
- count += 1;
- }
-
- function handleSubmit(event: SubmitEvent) {
- event.preventDefault();
- // handle form
- }
- </script>
-
- <button onclick={handleClick}>Clicked {count} times</button>
-
- <!-- Inline handlers are fine for simple logic -->
- <button onclick={() => count = 0}>Reset</button>
-
- <form onsubmit={handleSubmit}>
- <input name="query" />
- <button type="submit">Search</button>
- </form>
- ```
+ ### Pattern 7: Events
- #### Component Events via Callback Props
+ Element events are plain attributes. Component events are callback props, optional ones called with `?.()`.
```svelte
- <!-- color-picker.svelte -->
<script lang="ts">
- interface Props {
- color: string;
- onchange?: (color: string) => void;
- onreset?: () => void;
- }
-
let { color, onchange, onreset }: Props = $props();
-
- const COLORS = ['red', 'green', 'blue', 'purple'] as const;
</script>
- {#each COLORS as c}
- <button
- onclick={() => onchange?.(c)}
- class={{ selected: color === c }}
- >
- {c}
- </button>
- {/each}
-
- {#if onreset}
- <button onclick={onreset}>Reset</button>
- {/if}
- ```
-
- ```svelte
- <!-- parent.svelte -->
- <script lang="ts">
- import ColorPicker from './color-picker.svelte';
-
- let selectedColor = $state('red');
- </script>
-
- <ColorPicker
- color={selectedColor}
- onchange={(c) => selectedColor = c}
- onreset={() => selectedColor = 'red'}
- />
- ```
-
- **Why good:** Type-safe callback props, optional with `?.` call, parent controls event handling, no indirection through dispatcher
-
- ```svelte
- <!-- BAD: Svelte 4 event dispatcher -->
- <script>
- import { createEventDispatcher } from 'svelte';
- const dispatch = createEventDispatcher();
-
- function handleClick() {
- dispatch('change', { color: 'red' }); // Deprecated pattern
- }
- </script>
+ <button onclick={() => onchange?.('red')}>Red</button>
+ {#if onreset}<button onclick={onreset}>Reset</button>{/if}
```
- **Why bad:** `createEventDispatcher` is deprecated in Svelte 5, no type safety, requires manual event typing
+ Full code: [examples/events.md](examples/events.md)
</patterns>
---
- <integration>
-
- ## Integration Guide
-
- **Styling integration:**
-
- - Scoped `<style>` blocks are the default — styles don't leak to other components
- - Use `:global()` for global styles or CSS custom properties for parent-to-child styling
- - Any CSS approach (CSS Modules, utility-first, preprocessors) works with Svelte
-
- **State management:**
-
- - `$state` for component-local state
- - Context API (`createContext`) for subtree-scoped state
- - Reactive classes with `$state` fields for shared state modules (`.svelte.ts`)
- - Meta-framework load functions for server state
-
- **TypeScript integration:**
-
- - Full TypeScript support in `<script lang="ts">` blocks
- - `Snippet<[ParamType]>` for typed snippet props
- - Interface-based prop typing with `$props()`
- - `ClassValue` type from `svelte/elements` for type-safe class props (Svelte 5.19+)
-
- </integration>
-
- ---
-
<red_flags>
- ## RED FLAGS
-
- **High Priority:**
-
- - Using `export let` for props — use `$props()` instead
- - Using `$:` reactive statements — use `$derived` or `$effect`
- - Using `<slot>` or `<slot name="x">` — use `{#snippet}` and `{@render}`
- - Using `createEventDispatcher` — use callback props
- - Using `$effect` to sync state — use `$derived` for computed values
- - Destructuring `$state` objects — breaks reactivity (values captured at destructure time)
-
- **Medium Priority:**
+ ## Red flags
- - Using `on:click` directive — use `onclick` attribute
- - Not using `$state.raw()` for large API responses — unnecessary proxy overhead
- - Using `setContext`/`getContext` with string keys — use `createContext` for type safety
- - Using `class:name={condition}` — use built-in `class` attribute object/array syntax (since 5.16)
+ **Breaks at runtime:**
- **Gotchas:**
+ - Destructuring a `$state` object — the values are read once at destructure time and never again
+ - Mutating a `$state.raw` value — only reassignment is tracked, which is the trade it exists to make
+ - `setContext` called from an event handler or an `$effect` — context is only settable during component initialisation
+ - `$effect` created outside component or module initialisation — calling it from an event handler is a runtime error rather than a silent no-op; `$effect.root()` is how an effect scope gets opened by hand, and it hands back its own cleanup
+ - State read after an `await` inside an `$effect` — those reads are not tracked, so the effect never re-runs for them
+ - A cleanup function returned from `$derived` — only `$effect` runs one
+ - `$effect` relied on during server rendering — it runs in the browser only
- - `$state` proxies are not the original object — use `$state.snapshot()` to get a plain copy
- - Destructuring `$state` captures values, not references — access properties directly instead
- - `$derived` return values are NOT deeply reactive — only `$state` creates deep proxies
- - `$effect` runs after DOM update — use `$effect.pre()` for pre-update timing
- - Dependencies after `await` in `$effect` are not tracked
- - Context must be set during component init — cannot call `setContext` in event handlers or `$effect`
+ **Surprising behaviour:**
- > For complete decision frameworks and the full anti-patterns list, see [reference.md](reference.md).
+ - Every Svelte 4 form still compiles: `export let`, `$:`, `<slot>`, `createEventDispatcher`, `on:click`, `<svelte:component this={X}>`. Nothing warns, so a file can be half-migrated and look fine
+ - A `$state` value is a proxy, not the object you passed — `$state.snapshot()` before serialising or handing it to a library that compares identity
+ - A `$derived` result is not deeply reactive; only `$state` creates the proxy
+ - Fallback values in `$props()` are not proxied either
+ - `$effect` runs after the DOM update — `$effect.pre` is the hook for measuring before it
+ - `$inspect` compiles to nothing in production, so it is a debugging tool rather than logging
+ - Svelte 5 delegates some events at the root, which changes what `stopPropagation()` reaches
</red_flags>
-
- ---
-
- <critical_reminders>
-
- ## CRITICAL REMINDERS
-
- > **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)
-
- **(You MUST use Svelte 5 Runes syntax — NOT Svelte 4 patterns like `export let`, `$:`, or stores for component state)**
-
- **(You MUST use `$derived` for computed values — NEVER use `$effect` to synchronize state)**
-
- **(You MUST use snippets (`{#snippet}` / `{@render}`) instead of slots (`<slot>`))**
-
- **(You MUST use callback props (`onclick`, `onsomething`) instead of `createEventDispatcher`)**
-
- **(You MUST use `$state.raw()` for large objects/arrays that are replaced, not mutated)**
-
- **(You MUST use `createContext` for type-safe context instead of raw `setContext`/`getContext` with string keys)**
-
- **Failure to follow these rules will produce outdated Svelte 4 code that is deprecated and will break in future versions.**
-
- </critical_reminders>