- Replace all occurrences of 'ss-tools' with 'superset-tools' in 104 files - Rename git bundle file ss-tools.bundle → superset-tools.bundle - Update .gitignore pattern accordingly - Preserve variable names (hasSsTools etc.) and code identifiers
70 lines
2.9 KiB
Markdown
70 lines
2.9 KiB
Markdown
# [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 superset-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 (superset-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]
|