From eb10a70cf1f003c3e93f3c4ecc9749870e7581c6 Mon Sep 17 00:00:00 2001
From: Simon H <5968653+dummdidumm@users.noreply.github.com>
Date: Mon, 18 May 2026 17:36:17 +0200
Subject: [PATCH 1/8] fix: unset context synchronously in `run` (#18236)
Our `run` function which executes top level awaits (and synchronous
statements in-between/after) did not unset the context in time in case
the function returns an async value. In that case the context was still
around until the that promise resolves, which can be too late because
unrelated things can be intertwined with the batch.
The test shows this: Without the fix, the unrelated count incrementation
would not update the view until the top level awaits in the child are
done. In the test this just shows as a delayed visual update, but it
also can result in stale roots as shown in
https://github.com/sveltejs/svelte/issues/18221#issuecomment-4470921077
---
.changeset/tangy-icons-dig.md | 5 ++++
.../src/internal/client/reactivity/async.js | 23 ++++++++++------
.../async-run-isolated-batch/Child.svelte | 7 +++++
.../async-run-isolated-batch/_config.js | 26 +++++++++++++++++++
.../async-run-isolated-batch/main.svelte | 23 ++++++++++++++++
5 files changed, 76 insertions(+), 8 deletions(-)
create mode 100644 .changeset/tangy-icons-dig.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/async-run-isolated-batch/Child.svelte
create mode 100644 packages/svelte/tests/runtime-runes/samples/async-run-isolated-batch/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/async-run-isolated-batch/main.svelte
diff --git a/.changeset/tangy-icons-dig.md b/.changeset/tangy-icons-dig.md
new file mode 100644
index 0000000000..6d87912bbb
--- /dev/null
+++ b/.changeset/tangy-icons-dig.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: unset context synchronously in `run`
diff --git a/packages/svelte/src/internal/client/reactivity/async.js b/packages/svelte/src/internal/client/reactivity/async.js
index c1d4cbcd67..12c5e9baa5 100644
--- a/packages/svelte/src/internal/client/reactivity/async.js
+++ b/packages/svelte/src/internal/client/reactivity/async.js
@@ -303,15 +303,21 @@ export function run(thunks) {
.then(() => {
restore();
- if (errored) {
- throw errored.error;
+ try {
+ if (errored) {
+ throw errored.error;
+ }
+
+ if (aborted(active)) {
+ throw STALE_REACTION;
+ }
+
+ return fn();
+ } finally {
+ // We gotta unset context directly in case the function returns a promise, in which case
+ // unset_context in .finally() would be too late ...
+ unset_context();
}
-
- if (aborted(active)) {
- throw STALE_REACTION;
- }
-
- return fn();
})
.catch(handle_error);
@@ -320,6 +326,7 @@ export function run(thunks) {
promise.finally(() => {
blocker.settled = true;
+ // ... but we also need it after such a promise has resolved in case it restores our context
unset_context();
});
}
diff --git a/packages/svelte/tests/runtime-runes/samples/async-run-isolated-batch/Child.svelte b/packages/svelte/tests/runtime-runes/samples/async-run-isolated-batch/Child.svelte
new file mode 100644
index 0000000000..6f84dc319e
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-run-isolated-batch/Child.svelte
@@ -0,0 +1,7 @@
+
+
+{x} {y}
diff --git a/packages/svelte/tests/runtime-runes/samples/async-run-isolated-batch/_config.js b/packages/svelte/tests/runtime-runes/samples/async-run-isolated-batch/_config.js
new file mode 100644
index 0000000000..a0dc38c660
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-run-isolated-batch/_config.js
@@ -0,0 +1,26 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, target }) {
+ const [show, resolve, count] = target.querySelectorAll('button');
+
+ show.click();
+ await tick();
+ resolve.click();
+ await tick();
+ count.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ ' '
+ );
+
+ resolve.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ '1 2 '
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-run-isolated-batch/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-run-isolated-batch/main.svelte
new file mode 100644
index 0000000000..0a5346cc47
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-run-isolated-batch/main.svelte
@@ -0,0 +1,23 @@
+
+
+
+
+{#if show}
+
+{/if}
+
+
+
+
From 1e899cba354d2b25593afc1bbf04bf3555b5d26a Mon Sep 17 00:00:00 2001
From: Simon H <5968653+dummdidumm@users.noreply.github.com>
Date: Mon, 18 May 2026 17:36:41 +0200
Subject: [PATCH 2/8] fix: execute uninitialized derived even if it's destroyed
(#18228)
Turns out there are a few unavoidable cases where we have to execute the
derived even if we otherwise wouldn't, because of its lazy nature.
Fixes #18139
---
.changeset/hungry-ads-beam.md | 5 +++
.../internal/client/reactivity/deriveds.js | 7 +++-
.../Child.svelte | 10 ++++++
.../_config.js | 32 +++++++++++++++++++
.../main.svelte | 11 +++++++
5 files changed, 64 insertions(+), 1 deletion(-)
create mode 100644 .changeset/hungry-ads-beam.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/transition-derived-uninitialized/Child.svelte
create mode 100644 packages/svelte/tests/runtime-runes/samples/transition-derived-uninitialized/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/transition-derived-uninitialized/main.svelte
diff --git a/.changeset/hungry-ads-beam.md b/.changeset/hungry-ads-beam.md
new file mode 100644
index 0000000000..260d177119
--- /dev/null
+++ b/.changeset/hungry-ads-beam.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: execute uninitialized derived even if it's destroyed
diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js
index 83e23dfab6..75be018552 100644
--- a/packages/svelte/src/internal/client/reactivity/deriveds.js
+++ b/packages/svelte/src/internal/client/reactivity/deriveds.js
@@ -338,7 +338,12 @@ export function execute_derived(derived) {
var prev_active_effect = active_effect;
var parent = derived.parent;
- if (!is_destroying_effect && parent !== null && (parent.f & (DESTROYED | INERT)) !== 0) {
+ if (
+ !is_destroying_effect &&
+ parent !== null &&
+ derived.v !== UNINITIALIZED && // if it was never evaluated before, it's guaranteed to fail downstream, so we try to execute instead
+ (parent.f & (DESTROYED | INERT)) !== 0
+ ) {
w.derived_inert();
return derived.v;
diff --git a/packages/svelte/tests/runtime-runes/samples/transition-derived-uninitialized/Child.svelte b/packages/svelte/tests/runtime-runes/samples/transition-derived-uninitialized/Child.svelte
new file mode 100644
index 0000000000..20bd606d9b
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/transition-derived-uninitialized/Child.svelte
@@ -0,0 +1,10 @@
+
+
+
+ `
+ );
+
+ fly_out.click();
+ flushSync();
+ raf.tick(50);
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+
+
+ `
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/transition-derived-uninitialized/main.svelte b/packages/svelte/tests/runtime-runes/samples/transition-derived-uninitialized/main.svelte
new file mode 100644
index 0000000000..4fc3395af8
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/transition-derived-uninitialized/main.svelte
@@ -0,0 +1,11 @@
+
+
+
+
+
+{#if show}
+
+{/if}
From dc1f037fa67bf2fbdeb1c5f18b2b1f7d023077e3 Mon Sep 17 00:00:00 2001
From: Simon H <5968653+dummdidumm@users.noreply.github.com>
Date: Mon, 18 May 2026 17:37:01 +0200
Subject: [PATCH 3/8] fix: don't run teardown effects when deriveds are
unfreezed (#18227)
The logic was flawed - a teardown effect only has a teardown function
but not `fn` property, but unfreeze thought that everything with a
`teardown` needs to be unfreezed
Helps with #18221 (though likely doesn't fix it completely, at least not
the more general `batch.#roots`problems)
---
.changeset/slimy-olives-cross.md | 5 +++++
.../internal/client/reactivity/deriveds.js | 6 +++---
.../async-state-eager-const/_config.js | 21 +++++++++++++++++++
.../async-state-eager-const/main.svelte | 21 +++++++++++++++++++
4 files changed, 50 insertions(+), 3 deletions(-)
create mode 100644 .changeset/slimy-olives-cross.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/async-state-eager-const/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/async-state-eager-const/main.svelte
diff --git a/.changeset/slimy-olives-cross.md b/.changeset/slimy-olives-cross.md
new file mode 100644
index 0000000000..3309e2deab
--- /dev/null
+++ b/.changeset/slimy-olives-cross.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: don't run teardown effects when deriveds are unfreezed
diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js
index 75be018552..8d240bca31 100644
--- a/packages/svelte/src/internal/client/reactivity/deriveds.js
+++ b/packages/svelte/src/internal/client/reactivity/deriveds.js
@@ -452,8 +452,8 @@ export function freeze_derived_effects(derived) {
// make it a noop so it doesn't get called again if the derived
// is unfrozen. we don't set it to `null`, because the existence
// of a teardown function is what determines whether the
- // effect runs again during unfreezing
- e.teardown = noop;
+ // effect runs again during unfreezing (but not for teardown-only effects)
+ if (e.fn !== null) e.teardown = noop;
e.ac = null;
remove_reactions(e, 0);
@@ -471,7 +471,7 @@ export function unfreeze_derived_effects(derived) {
for (const e of derived.effects) {
// if the effect was previously frozen — indicated by the presence
// of a teardown function — unfreeze it
- if (e.teardown) {
+ if (e.teardown && e.fn !== null) {
update_effect(e);
}
}
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-eager-const/_config.js b/packages/svelte/tests/runtime-runes/samples/async-state-eager-const/_config.js
new file mode 100644
index 0000000000..1511d443e5
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-eager-const/_config.js
@@ -0,0 +1,21 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ compileOptions: { dev: true }, // for testing that teardown effect in eager $.get(loaded) doesn't lead to a crash (because it means REACTION_RAN is set, which means unfreeze_derived runs)
+ async test({ assert, target }) {
+ const [count, shift] = target.querySelectorAll('button');
+
+ shift.click();
+ await tick();
+ assert.htmlEqual(target.innerHTML, `
+
+ {#snippet pending()}{/snippet}
+
From 2bc3592eb1ba1b37bde6c893e84b28e2f630db59 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 18 May 2026 11:53:31 -0400
Subject: [PATCH 4/8] fix: use named symbols everywhere (#18238)
This isn't _really_ a fix since these should never surface to the user,
but it's useful for debugging when they do, as in
https://github.com/sveltejs/kit/pull/15779. Instead of seeing `Symbol()`
we see e.g. `Symbol(uninitialized)` which makes it easier to understand
where a bug is coming from.
---
.changeset/red-moles-talk.md | 5 +++++
packages/svelte/src/constants.js | 2 +-
packages/svelte/src/internal/client/reactivity/store.js | 2 +-
packages/svelte/src/reactivity/url-search-params.js | 2 +-
4 files changed, 8 insertions(+), 3 deletions(-)
create mode 100644 .changeset/red-moles-talk.md
diff --git a/.changeset/red-moles-talk.md b/.changeset/red-moles-talk.md
new file mode 100644
index 0000000000..7e09dc9b9a
--- /dev/null
+++ b/.changeset/red-moles-talk.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: use named symbols everywhere
diff --git a/packages/svelte/src/constants.js b/packages/svelte/src/constants.js
index a3a109b943..1e721b7d30 100644
--- a/packages/svelte/src/constants.js
+++ b/packages/svelte/src/constants.js
@@ -32,7 +32,7 @@ export const ELEMENT_IS_NAMESPACED = 1;
export const ELEMENT_PRESERVE_ATTRIBUTE_CASE = 1 << 1;
export const ELEMENT_IS_INPUT = 1 << 2;
-export const UNINITIALIZED = Symbol();
+export const UNINITIALIZED = Symbol('uninitialized');
// Dev-time component properties
export const FILENAME = Symbol('filename');
diff --git a/packages/svelte/src/internal/client/reactivity/store.js b/packages/svelte/src/internal/client/reactivity/store.js
index 7124e23db8..726765b619 100644
--- a/packages/svelte/src/internal/client/reactivity/store.js
+++ b/packages/svelte/src/internal/client/reactivity/store.js
@@ -21,7 +21,7 @@ export let legacy_is_updating_store = false;
*/
let is_store_binding = false;
-let IS_UNMOUNTED = Symbol();
+let IS_UNMOUNTED = Symbol('unmounted');
/**
* Gets the current value of a store. If the store isn't subscribed to yet, it will create a proxy
diff --git a/packages/svelte/src/reactivity/url-search-params.js b/packages/svelte/src/reactivity/url-search-params.js
index 2381e11875..2e70bf518d 100644
--- a/packages/svelte/src/reactivity/url-search-params.js
+++ b/packages/svelte/src/reactivity/url-search-params.js
@@ -4,7 +4,7 @@ import { tag } from '../internal/client/dev/tracing.js';
import { get } from '../internal/client/runtime.js';
import { get_current_url } from './url.js';
-export const REPLACE = Symbol();
+export const REPLACE = Symbol('replace');
/**
* A reactive version of the built-in [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) object.
From ca3f35bf7d3eeea6d0b5f477d208c3fd9c19988c Mon Sep 17 00:00:00 2001
From: OfirHaf
Date: Mon, 18 May 2026 18:54:31 +0300
Subject: [PATCH 5/8] fix(print): handle svelte:body and fix keyframe
percentage double-printing (#18234)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Fixes #18206 and fixes #18207 — both are printer bugs in
`packages/svelte/src/compiler/print/index.js`.
## Changes
### Fix 1: `svelte:body` crashes the printer (#18206)
`SvelteBody` was missing from the visitor map, causing a crash with
`Error: Not implemented: SvelteBody`. Added the handler (same one-liner
pattern as `SvelteDocument`, `SvelteHead`, etc.) and added `SvelteBody`
to the `is_block_element` check so whitespace is handled consistently.
### Fix 2: Keyframe percent stops print as `0%%` (#18207)
`Percentage.value` already includes the `%` sign (captured by
`/\d+(\.\d+)?%/y`), but the printer was writing `` `${node.value}%` `` —
appending a second `%`. Changed to `context.write(node.value)` to match
the `Nth` printer pattern directly above it.
Also updated the existing `style` snapshot which had `50%%` (the bug was
silently baked in), and added dedicated test samples for both fixes.
---
.../fix-printer-svelte-body-keyframe-percent.md | 5 +++++
packages/svelte/src/compiler/print/index.js | 7 ++++++-
.../samples/css-keyframes-percent/input.svelte | 7 +++++++
.../samples/css-keyframes-percent/output.svelte | 13 +++++++++++++
.../svelte/tests/print/samples/style/output.svelte | 2 +-
.../tests/print/samples/svelte-body/input.svelte | 1 +
.../tests/print/samples/svelte-body/output.svelte | 1 +
7 files changed, 34 insertions(+), 2 deletions(-)
create mode 100644 .changeset/fix-printer-svelte-body-keyframe-percent.md
create mode 100644 packages/svelte/tests/print/samples/css-keyframes-percent/input.svelte
create mode 100644 packages/svelte/tests/print/samples/css-keyframes-percent/output.svelte
create mode 100644 packages/svelte/tests/print/samples/svelte-body/input.svelte
create mode 100644 packages/svelte/tests/print/samples/svelte-body/output.svelte
diff --git a/.changeset/fix-printer-svelte-body-keyframe-percent.md b/.changeset/fix-printer-svelte-body-keyframe-percent.md
new file mode 100644
index 0000000000..6854468835
--- /dev/null
+++ b/.changeset/fix-printer-svelte-body-keyframe-percent.md
@@ -0,0 +1,5 @@
+---
+"svelte": patch
+---
+
+fix(print): handle `svelte:body` and fix keyframe percentage double-printing
diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js
index 26dc1b88e8..c511ee6da0 100644
--- a/packages/svelte/src/compiler/print/index.js
+++ b/packages/svelte/src/compiler/print/index.js
@@ -247,7 +247,7 @@ const css_visitors = {
},
Percentage(node, context) {
- context.write(`${node.value}%`);
+ context.write(node.value);
},
PseudoClassSelector(node, context) {
@@ -417,6 +417,7 @@ const svelte_visitors = (comments) => ({
const is_block_element =
child_node.type === 'RegularElement' ||
child_node.type === 'Component' ||
+ child_node.type === 'SvelteBody' ||
child_node.type === 'SvelteHead' ||
child_node.type === 'SvelteFragment' ||
child_node.type === 'SvelteBoundary' ||
@@ -821,6 +822,10 @@ const svelte_visitors = (comments) => ({
context.write('');
},
+ SvelteBody(node, context) {
+ base_element(node, context, comments);
+ },
+
SvelteBoundary(node, context) {
base_element(node, context, comments);
},
diff --git a/packages/svelte/tests/print/samples/css-keyframes-percent/input.svelte b/packages/svelte/tests/print/samples/css-keyframes-percent/input.svelte
new file mode 100644
index 0000000000..b2a0e3aa7a
--- /dev/null
+++ b/packages/svelte/tests/print/samples/css-keyframes-percent/input.svelte
@@ -0,0 +1,7 @@
+
diff --git a/packages/svelte/tests/print/samples/css-keyframes-percent/output.svelte b/packages/svelte/tests/print/samples/css-keyframes-percent/output.svelte
new file mode 100644
index 0000000000..e35a5780fa
--- /dev/null
+++ b/packages/svelte/tests/print/samples/css-keyframes-percent/output.svelte
@@ -0,0 +1,13 @@
+
diff --git a/packages/svelte/tests/print/samples/style/output.svelte b/packages/svelte/tests/print/samples/style/output.svelte
index 03168cccae..9fc5e3fbba 100644
--- a/packages/svelte/tests/print/samples/style/output.svelte
+++ b/packages/svelte/tests/print/samples/style/output.svelte
@@ -19,7 +19,7 @@
from {
opacity: 0;
}
- 50%% {
+ 50% {
opacity: 0.5;
}
to {
diff --git a/packages/svelte/tests/print/samples/svelte-body/input.svelte b/packages/svelte/tests/print/samples/svelte-body/input.svelte
new file mode 100644
index 0000000000..a4c26db5ce
--- /dev/null
+++ b/packages/svelte/tests/print/samples/svelte-body/input.svelte
@@ -0,0 +1 @@
+
diff --git a/packages/svelte/tests/print/samples/svelte-body/output.svelte b/packages/svelte/tests/print/samples/svelte-body/output.svelte
new file mode 100644
index 0000000000..e13eaae979
--- /dev/null
+++ b/packages/svelte/tests/print/samples/svelte-body/output.svelte
@@ -0,0 +1 @@
+
From f9440dc3ed28a9f1f518b0216d0e10fce00f1618 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 18 May 2026 18:44:05 +0200
Subject: [PATCH 6/8] Version Packages (#18239)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.
# Releases
## svelte@5.55.8
### Patch Changes
- fix(print): handle `svelte:body` and fix keyframe percentage
double-printing
([#18234](https://github.com/sveltejs/svelte/pull/18234))
- fix: execute uninitialized derived even if it's destroyed
([#18228](https://github.com/sveltejs/svelte/pull/18228))
- fix: use named symbols everywhere
([#18238](https://github.com/sveltejs/svelte/pull/18238))
- fix: don't run teardown effects when deriveds are unfreezed
([#18227](https://github.com/sveltejs/svelte/pull/18227))
- fix: unset context synchronously in `run`
([#18236](https://github.com/sveltejs/svelte/pull/18236))
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.../fix-printer-svelte-body-keyframe-percent.md | 5 -----
.changeset/hungry-ads-beam.md | 5 -----
.changeset/red-moles-talk.md | 5 -----
.changeset/slimy-olives-cross.md | 5 -----
.changeset/tangy-icons-dig.md | 5 -----
packages/svelte/CHANGELOG.md | 14 ++++++++++++++
packages/svelte/package.json | 2 +-
packages/svelte/src/version.js | 2 +-
8 files changed, 16 insertions(+), 27 deletions(-)
delete mode 100644 .changeset/fix-printer-svelte-body-keyframe-percent.md
delete mode 100644 .changeset/hungry-ads-beam.md
delete mode 100644 .changeset/red-moles-talk.md
delete mode 100644 .changeset/slimy-olives-cross.md
delete mode 100644 .changeset/tangy-icons-dig.md
diff --git a/.changeset/fix-printer-svelte-body-keyframe-percent.md b/.changeset/fix-printer-svelte-body-keyframe-percent.md
deleted file mode 100644
index 6854468835..0000000000
--- a/.changeset/fix-printer-svelte-body-keyframe-percent.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"svelte": patch
----
-
-fix(print): handle `svelte:body` and fix keyframe percentage double-printing
diff --git a/.changeset/hungry-ads-beam.md b/.changeset/hungry-ads-beam.md
deleted file mode 100644
index 260d177119..0000000000
--- a/.changeset/hungry-ads-beam.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: execute uninitialized derived even if it's destroyed
diff --git a/.changeset/red-moles-talk.md b/.changeset/red-moles-talk.md
deleted file mode 100644
index 7e09dc9b9a..0000000000
--- a/.changeset/red-moles-talk.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: use named symbols everywhere
diff --git a/.changeset/slimy-olives-cross.md b/.changeset/slimy-olives-cross.md
deleted file mode 100644
index 3309e2deab..0000000000
--- a/.changeset/slimy-olives-cross.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: don't run teardown effects when deriveds are unfreezed
diff --git a/.changeset/tangy-icons-dig.md b/.changeset/tangy-icons-dig.md
deleted file mode 100644
index 6d87912bbb..0000000000
--- a/.changeset/tangy-icons-dig.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: unset context synchronously in `run`
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 1255d8c6a0..36528c741b 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,19 @@
# svelte
+## 5.55.8
+
+### Patch Changes
+
+- fix(print): handle `svelte:body` and fix keyframe percentage double-printing ([#18234](https://github.com/sveltejs/svelte/pull/18234))
+
+- fix: execute uninitialized derived even if it's destroyed ([#18228](https://github.com/sveltejs/svelte/pull/18228))
+
+- fix: use named symbols everywhere ([#18238](https://github.com/sveltejs/svelte/pull/18238))
+
+- fix: don't run teardown effects when deriveds are unfreezed ([#18227](https://github.com/sveltejs/svelte/pull/18227))
+
+- fix: unset context synchronously in `run` ([#18236](https://github.com/sveltejs/svelte/pull/18236))
+
## 5.55.7
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 2f0743641f..66a555880b 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
- "version": "5.55.7",
+ "version": "5.55.8",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 6b0ed0aea8..446904cd14 100644
--- a/packages/svelte/src/version.js
+++ b/packages/svelte/src/version.js
@@ -4,5 +4,5 @@
* The current version, as set in package.json.
* @type {string}
*/
-export const VERSION = '5.55.7';
+export const VERSION = '5.55.8';
export const PUBLIC_VERSION = '5';
From a5df6616ea6c8ee0984ff0fc7b04e51472f1d01d Mon Sep 17 00:00:00 2001
From: Sean Kenneth Doherty
Date: Mon, 18 May 2026 13:53:10 -0500
Subject: [PATCH 7/8] fix: avoid unnecessary stringify in server attributes
(#18232)
### Before submitting the PR, please make sure you do the following
- [x] It's really useful if your PR references an issue where it is
discussed ahead of time. Fixes #10031.
- [x] Prefix your PR title with `feat:`, `fix:`, `chore:`, or `docs:`.
- [x] This message body should clearly illustrate what problems it
solves.
- [x] Ideally, include a test that fails without this PR but passes with
it.
- [x] If this PR changes code within `packages/svelte/src`, add a
changeset (`npx changeset`).
### What this changes
Server attribute template generation currently wraps each dynamic
expression in `$.stringify`, even when the compiler can prove the
expression is a string or a known constant. This reuses the existing
scope evaluation metadata so server output can avoid `$.stringify` for
proven string/constant chunks while keeping it for possibly nullish
unknown values.
The updated snapshot covers a mixed attribute with a known string,
mutable state, `null`, numeric/undefined constants, a known
string-producing `typeof`, and an unknown prop value.
### Tests and linting
- [x] `pnpm test snapshot -t nullish-coallescence-omittance`
- [x] `pnpm test snapshot`
- [x] `pnpm --filter svelte check`
- [x] `pnpm lint`
- [x] `pnpm prettier --check .changeset/slow-bikes-serve.md`
---------
Co-authored-by: Rich Harris
---
.changeset/slow-bikes-serve.md | 5 +++
.../server/visitors/shared/utils.js | 33 ++++++++++++-------
.../_expected/client/index.svelte.js | 13 ++++++--
.../_expected/server/index.svelte.js | 5 +--
.../index.svelte | 4 ++-
5 files changed, 42 insertions(+), 18 deletions(-)
create mode 100644 .changeset/slow-bikes-serve.md
diff --git a/.changeset/slow-bikes-serve.md b/.changeset/slow-bikes-serve.md
new file mode 100644
index 0000000000..5e1d654353
--- /dev/null
+++ b/.changeset/slow-bikes-serve.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: inline primitive constants in attribute values during SSR
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/utils.js
index 9b3ac3ad78..62e1c44094 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/utils.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/utils.js
@@ -229,18 +229,25 @@ export function build_attribute_value(
? node.data.replace(regex_whitespaces_strict, ' ')
: node.data;
} else {
- expressions.push(
- b.call(
- '$.stringify',
- transform(
- /** @type {Expression} */ (context.visit(node.expression)),
- node.metadata.expression
- )
- )
- );
+ const evaluated = context.state.scope.evaluate(node.expression);
- quasi = b.quasi('', i + 1 === value.length);
- quasis.push(quasi);
+ if (evaluated.is_known) {
+ quasi.value.cooked += (evaluated.value ?? '') + '';
+ } else {
+ const expression = transform(
+ /** @type {Expression} */ (context.visit(node.expression)),
+ node.metadata.expression
+ );
+
+ expressions.push(
+ evaluated.is_string && evaluated.is_defined
+ ? expression
+ : b.call('$.stringify', expression)
+ );
+
+ quasi = b.quasi('', i + 1 === value.length);
+ quasis.push(quasi);
+ }
}
}
@@ -248,7 +255,9 @@ export function build_attribute_value(
quasi.value.raw = sanitize_template_string(/** @type {string} */ (quasi.value.cooked));
}
- return b.template(quasis, expressions);
+ return expressions.length > 0
+ ? b.template(quasis, expressions)
+ : b.literal(/** @type {string} */ (quasi.value.cooked));
}
/**
diff --git a/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/client/index.svelte.js
index 5281a86582..837a7cd65a 100644
--- a/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/client/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/client/index.svelte.js
@@ -1,9 +1,9 @@
import 'svelte/internal/disclose-version';
import * as $ from 'svelte/internal/client';
-var root = $.from_html(` `, 1);
+var root = $.from_html(` `, 1);
-export default function Nullish_coallescence_omittance($$anchor) {
+export default function Nullish_coallescence_omittance($$anchor, $$props) {
let name = 'world';
let count = $.state(0);
var fragment = root();
@@ -23,7 +23,14 @@ export default function Nullish_coallescence_omittance($$anchor) {
var h1_1 = $.sibling(button, 2);
h1_1.textContent = 'Hello, world';
- $.template_effect(() => $.set_text(text, `Count is ${$.get(count) ?? ''}`));
+
+ var div = $.sibling(h1_1, 2);
+
+ $.template_effect(() => {
+ $.set_text(text, `Count is ${$.get(count) ?? ''}`);
+ $.set_attribute(div, 'title', `Hello, world ${$.get(count) ?? ''} 1 ${typeof $$props.value} ${$$props.value ?? ''}`);
+ });
+
$.delegated('click', button, () => $.update(count));
$.append($$anchor, fragment);
}
diff --git a/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/server/index.svelte.js
index a7e580acb8..357a887c9b 100644
--- a/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/server/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/server/index.svelte.js
@@ -1,8 +1,9 @@
import * as $ from 'svelte/internal/server';
-export default function Nullish_coallescence_omittance($$renderer) {
+export default function Nullish_coallescence_omittance($$renderer, $$props) {
let name = 'world';
let count = 0;
+ let { value } = $$props;
- $$renderer.push(`
Hello, world!
123
Hello, world
`);
+ $$renderer.push(`
Hello, world!
123
Hello, world
`);
}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/index.svelte b/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/index.svelte
index a67c574fee..ed88f5a269 100644
--- a/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/index.svelte
+++ b/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/index.svelte
@@ -1,8 +1,10 @@
+
From a63bf862c03d5c63b73e412f2ca952cb83d444d8 Mon Sep 17 00:00:00 2001
From: Mukunda Rao Katta
Date: Mon, 18 May 2026 12:42:20 -0700
Subject: [PATCH 8/8] docs: clarify context state getter pattern (#18226)
Closes #18022
This updates the context docs to show the getter pattern for state that
may be reassigned, such as primitive state. The new example passes a
function through context so children read the current value instead of
capturing the initial value.
This is a docs-only clarification and does not change runtime behavior.
Validation:
- `git diff --check`
- `pnpm exec prettier --check
documentation/docs/06-runtime/02-context.md`
Full `pnpm test`/`pnpm lint` were not run because this only changes
documentation.
---------
Co-authored-by: Rich Harris
---
documentation/docs/06-runtime/02-context.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/documentation/docs/06-runtime/02-context.md b/documentation/docs/06-runtime/02-context.md
index aee51c9a78..439272df8f 100644
--- a/documentation/docs/06-runtime/02-context.md
+++ b/documentation/docs/06-runtime/02-context.md
@@ -163,6 +163,8 @@ export const [getCounter, setCounter] = createContext();
Svelte will warn you if you get it wrong.
+Similarly, to pass primitive values through context, use functions as described in [Passing state into functions]($state#Passing-state-into-functions).
+
## Component testing
When writing [component tests](testing#Unit-and-component-tests-with-Vitest-Component-testing), it can be useful to create a wrapper component that sets the context in order to check the behaviour of a component that uses it. As of version 5.49, you can do this sort of thing: