diff --git a/.changeset/clever-dodos-jam.md b/.changeset/clever-dodos-jam.md
new file mode 100644
index 0000000000..bdeb979184
--- /dev/null
+++ b/.changeset/clever-dodos-jam.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: untrack `$inspect.with` and add check for unsafe mutation
diff --git a/documentation/docs/98-reference/.generated/client-errors.md b/documentation/docs/98-reference/.generated/client-errors.md
index 32348bb781..111b0b8940 100644
--- a/documentation/docs/98-reference/.generated/client-errors.md
+++ b/documentation/docs/98-reference/.generated/client-errors.md
@@ -125,7 +125,7 @@ Cannot set prototype of `$state` object
### state_unsafe_mutation
```
-Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
+Updating state inside `$derived(...)`, `$inspect(...)` or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
```
This error occurs when state is updated while evaluating a `$derived`. You might encounter it while trying to 'derive' two pieces of state in one go:
diff --git a/packages/svelte/messages/client-errors/errors.md b/packages/svelte/messages/client-errors/errors.md
index c4e68f8fee..6d96770eba 100644
--- a/packages/svelte/messages/client-errors/errors.md
+++ b/packages/svelte/messages/client-errors/errors.md
@@ -82,7 +82,7 @@ See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-long
## state_unsafe_mutation
-> Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
+> Updating state inside `$derived(...)`, `$inspect(...)` or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
This error occurs when state is updated while evaluating a `$derived`. You might encounter it while trying to 'derive' two pieces of state in one go:
diff --git a/packages/svelte/src/internal/client/dev/inspect.js b/packages/svelte/src/internal/client/dev/inspect.js
index e13ef470cf..e15c66901c 100644
--- a/packages/svelte/src/internal/client/dev/inspect.js
+++ b/packages/svelte/src/internal/client/dev/inspect.js
@@ -1,6 +1,7 @@
import { UNINITIALIZED } from '../../../constants.js';
import { snapshot } from '../../shared/clone.js';
import { inspect_effect, validate_effect } from '../reactivity/effects.js';
+import { untrack } from '../runtime.js';
/**
* @param {() => any[]} get_value
@@ -28,7 +29,10 @@ export function inspect(get_value, inspector = console.log) {
}
if (value !== UNINITIALIZED) {
- inspector(initial ? 'init' : 'update', ...snapshot(value, true));
+ var snap = snapshot(value, true);
+ untrack(() => {
+ inspector(initial ? 'init' : 'update', ...snap);
+ });
}
initial = false;
diff --git a/packages/svelte/src/internal/client/errors.js b/packages/svelte/src/internal/client/errors.js
index 429dd99da9..962593b48d 100644
--- a/packages/svelte/src/internal/client/errors.js
+++ b/packages/svelte/src/internal/client/errors.js
@@ -307,12 +307,12 @@ export function state_prototype_fixed() {
}
/**
- * Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
+ * Updating state inside `$derived(...)`, `$inspect(...)` or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
* @returns {never}
*/
export function state_unsafe_mutation() {
if (DEV) {
- const error = new Error(`state_unsafe_mutation\nUpdating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without \`$state\`\nhttps://svelte.dev/e/state_unsafe_mutation`);
+ const error = new Error(`state_unsafe_mutation\nUpdating state inside \`$derived(...)\`, \`$inspect(...)\` or a template expression is forbidden. If the value should not be reactive, declare it without \`$state\`\nhttps://svelte.dev/e/state_unsafe_mutation`);
error.name = 'Svelte error';
throw error;
diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js
index 4959bc1abc..0db3530232 100644
--- a/packages/svelte/src/internal/client/reactivity/sources.js
+++ b/packages/svelte/src/internal/client/reactivity/sources.js
@@ -135,9 +135,11 @@ export function mutate(source, value) {
export function set(source, value, should_proxy = false) {
if (
active_reaction !== null &&
- !untracking &&
+ // since we are untracking the function inside `$inspect.with` we need to add this check
+ // to ensure we error if state is set inside an inspect effect
+ (!untracking || (active_reaction.f & INSPECT_EFFECT) !== 0) &&
is_runes() &&
- (active_reaction.f & (DERIVED | BLOCK_EFFECT)) !== 0 &&
+ (active_reaction.f & (DERIVED | BLOCK_EFFECT | INSPECT_EFFECT)) !== 0 &&
!(reaction_sources?.[1].includes(source) && reaction_sources[0] === active_reaction)
) {
e.state_unsafe_mutation();
diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-state-unsafe-mutation/_config.js b/packages/svelte/tests/runtime-runes/samples/inspect-state-unsafe-mutation/_config.js
new file mode 100644
index 0000000000..7e8fcd2d48
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/inspect-state-unsafe-mutation/_config.js
@@ -0,0 +1,9 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ compileOptions: {
+ dev: true
+ },
+ error: 'state_unsafe_mutation'
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-state-unsafe-mutation/main.svelte b/packages/svelte/tests/runtime-runes/samples/inspect-state-unsafe-mutation/main.svelte
new file mode 100644
index 0000000000..3361087ff7
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/inspect-state-unsafe-mutation/main.svelte
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-with-untracked/_config.js b/packages/svelte/tests/runtime-runes/samples/inspect-with-untracked/_config.js
new file mode 100644
index 0000000000..cdb242c416
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/inspect-with-untracked/_config.js
@@ -0,0 +1,20 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ compileOptions: {
+ dev: true
+ },
+ async test({ assert, target, logs }) {
+ const [a, b] = target.querySelectorAll('button');
+ assert.deepEqual(logs, ['init', 0]);
+ flushSync(() => {
+ b?.click();
+ });
+ assert.deepEqual(logs, ['init', 0]);
+ flushSync(() => {
+ a?.click();
+ });
+ assert.deepEqual(logs, ['init', 0, 'update', 1]);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-with-untracked/main.svelte b/packages/svelte/tests/runtime-runes/samples/inspect-with-untracked/main.svelte
new file mode 100644
index 0000000000..5bcf2bd348
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/inspect-with-untracked/main.svelte
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file