From 7ccb2b7941d0d56fd07272a96a698d9712aae7d2 Mon Sep 17 00:00:00 2001 From: Diego Imbert Date: Fri, 15 May 2026 15:17:05 +0200 Subject: [PATCH] fix(frontend): break useMany reconcile feedback loop The reconcile effect read `handles.length` / `handles[i]` for the "unchanged?" early-exit optimisation and then `handles.splice(...)` to publish the new array. Reading `handles` inside the effect registered it as a dependency; the subsequent splice re-fired the effect; ad infinitum (Svelte threw `effect_update_depth_exceeded`). Wrap the comparison reads in `untrack` so the effect's only tracked dependency stays `getSpecs()`. The splice still fires the downstream readers of `handles` (the whole point of `useMany`'s reactivity); it just doesn't re-enter its own producer. --- frontend/src/lib/userDraft.svelte.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/userDraft.svelte.ts b/frontend/src/lib/userDraft.svelte.ts index 8ccf6e63e2..11c74f36f9 100644 --- a/frontend/src/lib/userDraft.svelte.ts +++ b/frontend/src/lib/userDraft.svelte.ts @@ -415,7 +415,14 @@ export const UserDraft = { // are cached by mapKey, so two reconciles with the same spec set // produce reference-equal arrays. Avoids dirtying downstream // reactive readers on no-op `$effect` re-runs. - const unchanged = handles.length === next.length && handles.every((h, i) => h === next[i]) + // + // The read side is wrapped in `untrack` so the effect doesn't + // register `handles` as one of its own dependencies; otherwise + // the splice below would re-fire the effect, splice again, … + // (Svelte raises `effect_update_depth_exceeded`). + const unchanged = untrack( + () => handles.length === next.length && handles.every((h, i) => h === next[i]) + ) if (!unchanged) handles.splice(0, handles.length, ...next) }