diff --git a/.changeset/four-mice-hammer.md b/.changeset/four-mice-hammer.md
new file mode 100644
index 000000000000..22d1af592544
--- /dev/null
+++ b/.changeset/four-mice-hammer.md
@@ -0,0 +1,5 @@
+---
+"svelte": patch
+---
+
+fix: improve internal proxied state signal heuristic
diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js
index bf4e15c5ffd5..6c6926241852 100644
--- a/packages/svelte/src/internal/client/proxy.js
+++ b/packages/svelte/src/internal/client/proxy.js
@@ -1,6 +1,11 @@
import { DEV } from 'esm-env';
-import { get, batch_inspect, current_component_context, untrack } from './runtime.js';
-import { effect_active } from './reactivity/effects.js';
+import {
+ get,
+ batch_inspect,
+ current_component_context,
+ untrack,
+ current_effect
+} from './runtime.js';
import {
array_prototype,
define_property,
@@ -206,7 +211,7 @@ const state_proxy_handler = {
// but only if it's an own property and not a prototype property
if (
s === undefined &&
- (effect_active() || updating_derived) &&
+ (current_effect !== null || updating_derived) &&
(!(prop in target) || get_descriptor(target, prop)?.writable)
) {
s = (metadata.i ? source : mutable_source)(proxy(target[prop], metadata.i, metadata.o));
@@ -250,7 +255,10 @@ const state_proxy_handler = {
const has = Reflect.has(target, prop);
let s = metadata.s.get(prop);
- if (s !== undefined || (effect_active() && (!has || get_descriptor(target, prop)?.writable))) {
+ if (
+ s !== undefined ||
+ (current_effect !== null && (!has || get_descriptor(target, prop)?.writable))
+ ) {
if (s === undefined) {
s = (metadata.i ? source : mutable_source)(
has ? proxy(target[prop], metadata.i, metadata.o) : UNINITIALIZED
@@ -273,7 +281,7 @@ const state_proxy_handler = {
// we do so otherwise if we read it later, then the write won't be tracked and
// the heuristics of effects will be different vs if we had read the proxied
// object property before writing to that property.
- if (s === undefined && effect_active()) {
+ if (s === undefined && current_effect !== null) {
// the read creates a signal
untrack(() => receiver[prop]);
s = metadata.s.get(prop);
diff --git a/packages/svelte/tests/runtime-runes/samples/proxied-state-property-access/Child.svelte b/packages/svelte/tests/runtime-runes/samples/proxied-state-property-access/Child.svelte
new file mode 100644
index 000000000000..900577e32f4a
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/proxied-state-property-access/Child.svelte
@@ -0,0 +1,5 @@
+
+
+Child: {settings.showInRgb}
diff --git a/packages/svelte/tests/runtime-runes/samples/proxied-state-property-access/_config.js b/packages/svelte/tests/runtime-runes/samples/proxied-state-property-access/_config.js
new file mode 100644
index 000000000000..ffccfd8e2009
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/proxied-state-property-access/_config.js
@@ -0,0 +1,22 @@
+import { flushSync } from '../../../../src/index-client';
+import { test } from '../../test';
+
+export default test({
+ html: ` Child: true`,
+
+ async test({ assert, target }) {
+ const btn = target.querySelector('button');
+
+ flushSync(() => {
+ btn?.click();
+ });
+
+ assert.htmlEqual(target.innerHTML, ` Child: false`);
+
+ flushSync(() => {
+ btn?.click();
+ });
+
+ assert.htmlEqual(target.innerHTML, ` Child: true`);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/proxied-state-property-access/main.svelte b/packages/svelte/tests/runtime-runes/samples/proxied-state-property-access/main.svelte
new file mode 100644
index 000000000000..f195c0c877ab
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/proxied-state-property-access/main.svelte
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+