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 @@
+
+
+