From 7ad5772309dc27313ff044b183e44b67e8e14d2b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 8 Jan 2026 17:28:30 -0500 Subject: [PATCH] fix: notify deriveds of changes to sources inside forks (#17437) * failing tests * update tests * update write versions when committing forks * remove second test, for now * changeset * fix: correctly update writable deriveds inside forks (#17438) * fix: correctly update writable deriveds inside forks * tweak * changeset * on second thoughts, minimise the diff, and revisit later * missed a spot --- .changeset/empty-paths-smile.md | 5 +++++ .changeset/seven-llamas-care.md | 5 +++++ .../src/internal/client/reactivity/batch.js | 13 ++++++++--- .../async-fork-derived-writable/_config.js | 14 ++++++++++++ .../async-fork-derived-writable/main.svelte | 22 +++++++++++++++++++ .../samples/async-fork-derived/_config.js | 16 ++++++++++++++ .../samples/async-fork-derived/main.svelte | 16 ++++++++++++++ 7 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 .changeset/empty-paths-smile.md create mode 100644 .changeset/seven-llamas-care.md create mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-derived-writable/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-derived-writable/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-derived/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-fork-derived/main.svelte diff --git a/.changeset/empty-paths-smile.md b/.changeset/empty-paths-smile.md new file mode 100644 index 0000000000..687ad19b24 --- /dev/null +++ b/.changeset/empty-paths-smile.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: notify deriveds of changes to sources inside forks diff --git a/.changeset/seven-llamas-care.md b/.changeset/seven-llamas-care.md new file mode 100644 index 0000000000..1b41b4848a --- /dev/null +++ b/.changeset/seven-llamas-care.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: correctly update writable deriveds inside forks diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index d6d23dc9c5..9b80df34e4 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -25,6 +25,7 @@ import { deferred, define_property } from '../../shared/utils.js'; import { active_effect, get, + increment_write_version, is_dirty, is_updating_effect, set_is_updating_effect, @@ -920,13 +921,18 @@ export function fork(fn) { flushSync(fn); - batch_values = null; - // revert state changes for (var [source, value] of batch.previous) { source.v = value; } + // make writable deriveds dirty, so they recalculate correctly + for (source of batch.current.keys()) { + if ((source.f & DERIVED) !== 0) { + set_signal_status(source, DIRTY); + } + } + return { commit: async () => { if (committed) { @@ -942,9 +948,10 @@ export function fork(fn) { batch.is_fork = false; - // apply changes + // apply changes and update write versions so deriveds see the change for (var [source, value] of batch.current) { source.v = value; + source.wv = increment_write_version(); } // trigger any `$state.eager(...)` expressions with the new state. diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-derived-writable/_config.js b/packages/svelte/tests/runtime-runes/samples/async-fork-derived-writable/_config.js new file mode 100644 index 0000000000..b089b714ec --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-fork-derived-writable/_config.js @@ -0,0 +1,14 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + const [btn] = target.querySelectorAll('button'); + + btn.click(); + await tick(); + // d should be 10 (real-world: s=1, d=1*10) before commit, not 20 (fork: s=2, d=2*10) + // After commit, d should be 99 (the written value) + assert.deepEqual(logs, [10, 99]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-derived-writable/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-derived-writable/main.svelte new file mode 100644 index 0000000000..bc118a558a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-fork-derived-writable/main.svelte @@ -0,0 +1,22 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-derived/_config.js b/packages/svelte/tests/runtime-runes/samples/async-fork-derived/_config.js new file mode 100644 index 0000000000..59ae376167 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-fork-derived/_config.js @@ -0,0 +1,16 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + const [increment] = target.querySelectorAll('button'); + + increment.click(); + await tick(); + assert.deepEqual(logs, [1, 2]); + + increment.click(); + await tick(); + assert.deepEqual(logs, [1, 2, 2, 3]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-derived/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-derived/main.svelte new file mode 100644 index 0000000000..93761869d6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-fork-derived/main.svelte @@ -0,0 +1,16 @@ + + +