# [DEF:ADR-0007:ADR] # @STATUS REJECTED # @PURPOSE Document the decision to forbid `fromStore` + multiple `$derived` in Svelte 5 components due to infinite reactive flush loop. # @RELATION DEPENDS_ON -> [ADR-0006:ADR] # @RATIONALE fromStore(store) returns a reactive object with .current property. Using it with multiple $derived creates persistent render_effect instances on every reactive re-evaluation, leading to an infinite flush loop and 100% CPU usage after ~300 poll cycles. # @REJECTED fromStore with multiple $derived in the same component — causes infinite reactive flush loop in Svelte 5 runtime. # ADR-0007: REJECTED — `fromStore` + multiple `$derived` in Svelte 5 ## Status REJECTED — pattern forbidden in ss-tools frontend. ## Context `fromStore(store)` returns a reactive object whose `.current` property reflects the underlying Svelte writable store. Using it with multiple `$derived(runState.current?.xxx)` values creates persistent `render_effect` instances via Svelte's `createSubscriber` on every reactive re-evaluation. ### Observed failure (ss-tools, May 2026) `TranslationRunGlobalIndicator` used 6 `$derived` reading `runState.current`. Each `$derived` re-evaluation (triggered by store update) called `subscribe()` which called `render_effect()` inside `createSubscriber`. The `render_effect` teardown queues a microtask that decrements the subscriber count. When all teardowns execute, subscriber count drops to zero, which triggers `increment(version)`, which marks all `$derived` as dirty again — creating an **infinite reactive flush loop**. ### Symptoms - After ~300 store poll cycles, the browser tab reaches 100% CPU on 2 cores. - Svelte runtime (`flush` → `Ve` → `yn` → `fn`) throws `Uncaught` repeatedly. - `CTFFznDe.js` chunk enters infinite loop. - Page becomes completely unresponsive. ## Decision `fromStore` MUST NOT be used with `$derived` when the `.current` object is read by more than one `$derived` in the same component. Use `$effect(() => store.subscribe(s => state = s))` instead — this creates exactly ONE subscription per component lifecycle, with no accumulating `render_effect` instances. ## Consequences ### Positive - Store subscription is created once per component mount and cleaned up on destroy. - No accumulating `render_effect` instances → no infinite reactive loop. - Works correctly in Svelte 5.43.x. ### Negative - Requires explicit `$state` initial value and `$effect` subscription. - Slightly more verbose than `fromStore`. ## Migration Replace: ```javascript const state = fromStore(store); let foo = $derived(state.current?.foo || null); let bar = $derived(state.current?.bar || 0); ``` With: ```javascript let _state = $state(initialValue); $effect(() => { const unsub = store.subscribe(s => { _state = s; }); return () => unsub(); }); let foo = $derived(_state?.foo || null); let bar = $derived(_state?.bar || 0); ``` # [/DEF:ADR-0007:ADR]