Fix upload wizard tab crash: dispatch must not track the model signal
CI / backend (push) Failing after 21s
CI / frontend (push) Successful in 1m18s
CI / api-client-drift (push) Successful in 1m27s

createUploadController runs an effect() that calls dispatch. store.ts
dispatch was `model.set(update(model(), msg))` — the reactive model()
read made the effect depend on its own write and re-schedule forever,
livelocking the main thread. Angular's NG0103 guard doesn't cover effect
self-rescheduling, so no error was thrown; Firefox just killed the
unresponsive tab. Only /registreren and /herregistratie (which mount the
upload controller) were affected.

dispatch now uses model.update((m) => update(m, msg)) — the current value
is read untracked, so no effect can loop on its own dispatch. Hardens all
wizard stores. Adds a regression spec.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
eho
2026-07-01 10:27:50 +02:00
co-authored by Claude Opus 4.8
parent 57940234b2
commit 4a1fd7c581
2 changed files with 37 additions and 1 deletions
+5 -1
View File
@@ -24,6 +24,10 @@ export function createStore<Model, Msg>(
const model = signal(init);
return {
model: model.asReadonly(),
dispatch: (msg) => model.set(update(model(), msg)),
// Use `.update` (raw current value, no tracked read) not `set(update(model(), …))`:
// dispatch is a command and must never subscribe its caller to `model`. Reading
// `model()` here inside an effect that also dispatches makes the effect depend on
// its own write and livelock the main thread (crashed the upload wizards).
dispatch: (msg) => model.update((m) => update(m, msg)),
};
}