diff --git a/.changeset/rich-turtles-learn.md b/.changeset/rich-turtles-learn.md
new file mode 100644
index 000000000000..99f87ecb1712
--- /dev/null
+++ b/.changeset/rich-turtles-learn.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: ensure derived reactions have their version kept in sync with the reactive graph
diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js
index 795417cc0fdb..4cda2564ebc3 100644
--- a/packages/svelte/src/internal/client/reactivity/deriveds.js
+++ b/packages/svelte/src/internal/client/reactivity/deriveds.js
@@ -1,4 +1,4 @@
-/** @import { Derived, Effect } from '#client' */
+/** @import { Derived, Effect, Value } from '#client' */
import { DEV } from 'esm-env';
import { CLEAN, DERIVED, DIRTY, EFFECT_HAS_DERIVED, MAYBE_DIRTY, UNOWNED } from '../constants.js';
import {
@@ -151,6 +151,25 @@ function execute_derived(derived) {
return value;
}
+/**
+ * @param {Value} value
+ */
+function sync_reaction_versions(value) {
+ var reactions = value.reactions;
+
+ if (reactions !== null) {
+ for (var i = 0; i < reactions.length; i++) {
+ var reaction = reactions[i];
+ if ((value.f & UNOWNED) === 0 && value.wv > reaction.wv) {
+ reaction.wv = value.wv;
+ }
+ if ((reaction.f & DERIVED) !== 0) {
+ sync_reaction_versions(/** @type {Derived} */ (reaction));
+ }
+ }
+ }
+}
+
/**
* @param {Derived} derived
* @returns {void}
@@ -165,5 +184,7 @@ export function update_derived(derived) {
if (!derived.equals(value)) {
derived.v = value;
derived.wv = increment_write_version();
+ } else {
+ sync_reaction_versions(derived);
}
}
diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js
index 486c819f3671..d8f6291aedfa 100644
--- a/packages/svelte/src/internal/client/runtime.js
+++ b/packages/svelte/src/internal/client/runtime.js
@@ -206,7 +206,7 @@ export function check_dirtiness(reaction) {
update_derived(/** @type {Derived} */ (dependency));
}
- if (dependency.wv > reaction.wv) {
+ if (dependency.wv > reaction.wv || (reaction.f & DIRTY) !== 0) {
return true;
}
}
diff --git a/packages/svelte/tests/runtime-runes/samples/derived-version-sync/_config.js b/packages/svelte/tests/runtime-runes/samples/derived-version-sync/_config.js
new file mode 100644
index 000000000000..30012f4d0303
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/derived-version-sync/_config.js
@@ -0,0 +1,28 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, target, logs }) {
+ let [btn1] = target.querySelectorAll('button');
+
+ btn1?.click();
+ flushSync();
+
+ btn1?.click();
+ flushSync();
+
+ logs.length = 0;
+
+ btn1?.click();
+ flushSync();
+
+ assert.deepEqual(logs, ['a', { value: 1 }]);
+
+ logs.length = 0;
+
+ btn1?.click();
+ flushSync();
+
+ assert.deepEqual(logs, ['a', { value: 2 }, 'b', { a: 2 }, 'c', { b: 2 }]);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/derived-version-sync/main.svelte b/packages/svelte/tests/runtime-runes/samples/derived-version-sync/main.svelte
new file mode 100644
index 000000000000..8450d2f16ec3
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/derived-version-sync/main.svelte
@@ -0,0 +1,28 @@
+
+
+
diff --git a/packages/svelte/tests/runtime-runes/samples/derived-write-read-write-read/main.svelte b/packages/svelte/tests/runtime-runes/samples/derived-write-read-write-read/main.svelte
index 1e449198beb3..b4db23942717 100644
--- a/packages/svelte/tests/runtime-runes/samples/derived-write-read-write-read/main.svelte
+++ b/packages/svelte/tests/runtime-runes/samples/derived-write-read-write-read/main.svelte
@@ -10,4 +10,4 @@
// resulting in the same value should not prevent pending render effects from updating
z;
y = 0;
-}}>{z}
\ No newline at end of file
+}}>{z}