# Svelte 5 Reactivity Safety (PARAMOUNT) Svelte 5's reactive system triggers re-evaluation when it detects a new reference. Misuse causes infinite loops that pin CPU at 100%+. ## Rules ### `$derived` — NEVER create new objects or arrays ```typescript // WRONG — .filter()/.map()/.reduce()/??[] create new references every evaluation let messages = $derived(session?.messages ?? []); // new [] each time let fileRefs = $derived(messages.filter(m => m.toolPath)); // new array each time let grouped = $derived(items.reduce((a, i) => ..., {})); // new object each time // RIGHT — plain getter functions called in template function getMessages() { return session?.messages ?? EMPTY; } function getFileRefs() { return getMessages().filter(m => m.toolPath); } ``` `$derived` is safe ONLY for primitives (`number`, `string`, `boolean`) or stable references (reading a field from a `$state` object). ### `$effect` — NEVER write to `$state` that the effect reads ```typescript // WRONG — reads openDirs, writes openDirs → infinite loop $effect(() => { openDirs = new Set(openDirs); // read + write same $state }); // WRONG — calls function that writes to $state $effect(() => { loadTasks(); // internally does ++pollToken which is $state }); // RIGHT — use onMount for initialization onMount(() => { loadTasks(); openDirs = new Set([cwd]); }); ``` ### `$effect` — NEVER use for async initialization ```typescript // WRONG — async writes to $state during effect evaluation $effect(() => { loadData(); // writes to channels, agents, etc. }); // RIGHT — onMount for all async init + side effects onMount(() => { loadData(); const timer = setInterval(poll, 30000); return () => clearInterval(timer); }); ``` ### Props that change frequently — NEVER pass as component props ```typescript // WRONG — blinkVisible changes every 500ms → re-renders ENTIRE ProjectCard tree // RIGHT — store-based, only the leaf component (StatusDot) reads it // blink-store.svelte.ts owns the timer // StatusDot imports getBlinkVisible() directly ``` ## Quick Reference | Pattern | Safe? | Why | |---------|-------|-----| | `$derived(count + 1)` | Yes | Primitive, stable | | `$derived(obj.field)` | Yes | Stable reference | | `$derived(arr.filter(...))` | **NO** | New array every eval | | `$derived(x ?? [])` | **NO** | New `[]` when x is undefined | | `$derived(items.map(...))` | **NO** | New array + new objects | | `$effect(() => { state = ... })` | **NO** | Write triggers re-run | | `$effect(() => { asyncFn() })` | **NO** | Async writes to state | | `onMount(() => { state = ... })` | Yes | Runs once, no re-trigger | | Plain function in template | Yes | Svelte tracks inner reads |