diff --git a/.changeset/clean-badgers-battle.md b/.changeset/clean-badgers-battle.md
new file mode 100644
index 000000000000..be9b7de2cbed
--- /dev/null
+++ b/.changeset/clean-badgers-battle.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: ensure effect_tracking correctly handles tracking reactions
diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js
index 2050c142c586..82baeae28805 100644
--- a/packages/svelte/src/internal/client/reactivity/effects.js
+++ b/packages/svelte/src/internal/client/reactivity/effects.js
@@ -15,7 +15,8 @@ import {
set_is_destroying_effect,
set_is_flushing_effect,
set_signal_status,
- untrack
+ untrack,
+ skip_reaction
} from '../runtime.js';
import {
DIRTY,
@@ -167,7 +168,9 @@ export function effect_tracking() {
return false;
}
- return (active_reaction.f & UNOWNED) === 0;
+ // If it's skipped, that's because we're inside an unowned
+ // that is not being tracked by another reaction
+ return !skip_reaction;
}
/**
diff --git a/packages/svelte/tests/runtime-runes/samples/derived-unowned-10/_config.js b/packages/svelte/tests/runtime-runes/samples/derived-unowned-10/_config.js
new file mode 100644
index 000000000000..7e057e94b2c6
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/derived-unowned-10/_config.js
@@ -0,0 +1,19 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, target, logs }) {
+ let btn1 = target.querySelector('button');
+
+ btn1?.click();
+ flushSync();
+
+ btn1?.click();
+ flushSync();
+
+ btn1?.click();
+ flushSync();
+
+ assert.deepEqual(logs, ['light', 'dark', 'light']);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/derived-unowned-10/main.svelte b/packages/svelte/tests/runtime-runes/samples/derived-unowned-10/main.svelte
new file mode 100644
index 000000000000..d35c706aecfb
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/derived-unowned-10/main.svelte
@@ -0,0 +1,13 @@
+
+
+
+
+{themeState.value.theme}
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-runes/samples/derived-unowned-10/theme.svelte.js b/packages/svelte/tests/runtime-runes/samples/derived-unowned-10/theme.svelte.js
new file mode 100644
index 000000000000..30c187cbe913
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/derived-unowned-10/theme.svelte.js
@@ -0,0 +1,18 @@
+import { fromStore, writable } from 'svelte/store';
+
+export const store = writable({ theme: 'dark' });
+
+class ThemeState {
+ #storeState = fromStore(store);
+ value = $derived(this.#storeState.current);
+
+ constructor() {
+ $effect.root(() => {
+ $effect(() => {
+ console.log(this.value.theme);
+ });
+ });
+ }
+}
+
+export const themeState = new ThemeState();