From 9412c5861c365610d7c6c0e4ecd3572ac3fe5860 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 21 Jul 2025 22:17:25 -0400 Subject: [PATCH 1/6] chore: log effect functions in log_effect_tree (#16468) --- packages/svelte/src/internal/client/dev/debug.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/svelte/src/internal/client/dev/debug.js b/packages/svelte/src/internal/client/dev/debug.js index c47080ed2f1e..2714a3af1f69 100644 --- a/packages/svelte/src/internal/client/dev/debug.js +++ b/packages/svelte/src/internal/client/dev/debug.js @@ -63,6 +63,13 @@ export function log_effect_tree(effect, depth = 0) { // eslint-disable-next-line no-console console.log(callsite); + } else { + // eslint-disable-next-line no-console + console.groupCollapsed(`%cfn`, `font-weight: normal`); + // eslint-disable-next-line no-console + console.log(effect.fn); + // eslint-disable-next-line no-console + console.groupEnd(); } if (effect.deps !== null) { From 1deb31082a383fb5f4f9ae86f1ff12657823bcd7 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 22 Jul 2025 12:56:32 -0400 Subject: [PATCH 2/6] fix: abort and reschedule `$effect.pre` when necessary (#16335) * unskip failing test * fix * tidy up * skip_no_async * add comment --- .../runtime-runes/samples/effect-order-7/_config.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/svelte/tests/runtime-runes/samples/effect-order-7/_config.js b/packages/svelte/tests/runtime-runes/samples/effect-order-7/_config.js index 29c33c7b1886..f0a9c2e867bd 100644 --- a/packages/svelte/tests/runtime-runes/samples/effect-order-7/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/effect-order-7/_config.js @@ -2,14 +2,18 @@ import { flushSync } from 'svelte'; import { test } from '../../test'; export default test({ - skip: true, + // For this to work in non-async mode, we would need to abort + // inside `#traverse_effect_tree`, which would be very + // complicated and annoying. Since this hasn't been + // a real issue (AFAICT), we ignore it + skip_no_async: true, - async test({ assert, target, logs }) { + async test({ target }) { const [open, close] = target.querySelectorAll('button'); flushSync(() => open.click()); - flushSync(() => close.click()); - assert.deepEqual(logs, [true]); + // if the effect queue isn't aborted after the state change, this will throw + flushSync(() => close.click()); } }); From 68372460e999e48ac09b1c2bd4935ceaea8c33e9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 22 Jul 2025 13:29:15 -0400 Subject: [PATCH 3/6] chore: small tidy up (#16476) --- .../src/internal/client/reactivity/batch.js | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index ec082bb595ff..c45221189404 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -193,6 +193,8 @@ export class Batch { // if we didn't start any new async work, and no async work // is outstanding from a previous flush, commit if (this.#async_effects.length === 0 && this.#pending === 0) { + this.#commit(); + var render_effects = this.#render_effects; var effects = this.#effects; @@ -200,8 +202,6 @@ export class Batch { this.#effects = []; this.#block_effects = []; - this.#commit(); - flush_queued_effects(render_effects); flush_queued_effects(effects); @@ -539,43 +539,43 @@ function flush_queued_effects(effects) { var length = effects.length; if (length === 0) return; - for (var i = 0; i < length; i++) { - var effect = effects[i]; - - if ((effect.f & (DESTROYED | INERT)) === 0) { - if (is_dirty(effect)) { - var wv = write_version; - - update_effect(effect); - - // Effects with no dependencies or teardown do not get added to the effect tree. - // Deferred effects (e.g. `$effect(...)`) _are_ added to the tree because we - // don't know if we need to keep them until they are executed. Doing the check - // here (rather than in `update_effect`) allows us to skip the work for - // immediate effects. - if (effect.deps === null && effect.first === null && effect.nodes_start === null) { - // if there's no teardown or abort controller we completely unlink - // the effect from the graph - if (effect.teardown === null && effect.ac === null) { - // remove this effect from the graph - unlink_effect(effect); - } else { - // keep the effect in the graph, but free up some memory - effect.fn = null; - } - } + var i = 0; - // if state is written in a user effect, abort and re-schedule, lest we run - // effects that should be removed as a result of the state change - if (write_version > wv && (effect.f & USER_EFFECT) !== 0) { - break; + while (i < length) { + var effect = effects[i++]; + + if ((effect.f & (DESTROYED | INERT)) === 0 && is_dirty(effect)) { + var wv = write_version; + + update_effect(effect); + + // Effects with no dependencies or teardown do not get added to the effect tree. + // Deferred effects (e.g. `$effect(...)`) _are_ added to the tree because we + // don't know if we need to keep them until they are executed. Doing the check + // here (rather than in `update_effect`) allows us to skip the work for + // immediate effects. + if (effect.deps === null && effect.first === null && effect.nodes_start === null) { + // if there's no teardown or abort controller we completely unlink + // the effect from the graph + if (effect.teardown === null && effect.ac === null) { + // remove this effect from the graph + unlink_effect(effect); + } else { + // keep the effect in the graph, but free up some memory + effect.fn = null; } } + + // if state is written in a user effect, abort and re-schedule, lest we run + // effects that should be removed as a result of the state change + if (write_version > wv && (effect.f & USER_EFFECT) !== 0) { + break; + } } } - for (; i < length; i += 1) { - schedule_effect(effects[i]); + while (i < length) { + schedule_effect(effects[i++]); } } From 8e2f4b51c50a2e10bc482fac6d900be921dd8b74 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 23 Jul 2025 07:55:51 -0400 Subject: [PATCH 4/6] fix: unset batch before flushing queued effects (#16482) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * - add state changes resulting from an $effect to a separate new batch - schedule rerunning effects based on the sources that are dirty, not just rerunning them all blindly (excempting async effects which will have run by that time already) * test * better fix * tests * this fixes the last test somehow * fix #16477 * typo * copy over changeset from #16477 * copy over changeset from #16464 * changeset * dedupe * move flushing_sync check inside Batch.ensure * unused * flushing_sync -> is_flushing_sync * remove flush_effects method * dedupe declaration * tweak * tweak * update comment — it _does_ feel slightly wrong, but no wronger than the rest of this cursed function --------- Co-authored-by: Simon Holthausen --- .changeset/grumpy-boats-beg.md | 5 + .changeset/shiny-walls-fix.md | 5 + .changeset/thick-mice-kick.md | 5 + .../src/internal/client/reactivity/batch.js | 190 ++++++++++-------- .../src/internal/client/reactivity/sources.js | 11 +- .../Component.svelte | 7 + .../1000-reading-derived-effects/_config.js | 5 + .../1000-reading-derived-effects/main.svelte | 8 + .../_config.js | 26 +++ .../main.svelte | 27 +++ .../async-effect-triggers-await/_config.js | 32 +++ .../async-effect-triggers-await/main.svelte | 24 +++ .../binding-update-while-focused-2/_config.js | 24 +++ .../main.svelte | 21 ++ 14 files changed, 298 insertions(+), 92 deletions(-) create mode 100644 .changeset/grumpy-boats-beg.md create mode 100644 .changeset/shiny-walls-fix.md create mode 100644 .changeset/thick-mice-kick.md create mode 100644 packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/Component.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/main.svelte diff --git a/.changeset/grumpy-boats-beg.md b/.changeset/grumpy-boats-beg.md new file mode 100644 index 000000000000..f677743defa5 --- /dev/null +++ b/.changeset/grumpy-boats-beg.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: keep input in sync when binding updated via effect diff --git a/.changeset/shiny-walls-fix.md b/.changeset/shiny-walls-fix.md new file mode 100644 index 000000000000..91ed548728a3 --- /dev/null +++ b/.changeset/shiny-walls-fix.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: prevent infinite async loop diff --git a/.changeset/thick-mice-kick.md b/.changeset/thick-mice-kick.md new file mode 100644 index 000000000000..eec55b77eee4 --- /dev/null +++ b/.changeset/thick-mice-kick.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: exclude derived writes from effect abort and rescheduling diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index c45221189404..ce413fa1e186 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -21,14 +21,13 @@ import { is_updating_effect, set_is_updating_effect, set_signal_status, - update_effect, - write_version + update_effect } from '../runtime.js'; import * as e from '../errors.js'; import { flush_tasks } from '../dom/task.js'; import { DEV } from 'esm-env'; import { invoke_error_boundary } from '../error-handling.js'; -import { old_values } from './sources.js'; +import { mark_reactions, old_values } from './sources.js'; import { unlink_effect } from './effects.js'; import { unset_context } from './async.js'; @@ -70,13 +69,15 @@ let last_scheduled_effect = null; let is_flushing = false; +let is_flushing_sync = false; + export class Batch { /** * The current values of any sources that are updated in this batch * They keys of this map are identical to `this.#previous` * @type {Map} */ - #current = new Map(); + current = new Map(); /** * The values of any sources that are updated in this batch _before_ those updates took place. @@ -156,7 +157,7 @@ export class Batch { * * @param {Effect[]} root_effects */ - #process(root_effects) { + process(root_effects) { queued_root_effects = []; /** @type {Map | null} */ @@ -169,7 +170,7 @@ export class Batch { current_values = new Map(); batch_deriveds = new Map(); - for (const [source, current] of this.#current) { + for (const [source, current] of this.current) { current_values.set(source, { v: source.v, wv: source.wv }); source.v = current; } @@ -202,9 +203,22 @@ export class Batch { this.#effects = []; this.#block_effects = []; + // If sources are written to, then work needs to happen in a separate batch, else prior sources would be mixed with + // newly updated sources, which could lead to infinite loops when effects run over and over again. + current_batch = null; + flush_queued_effects(render_effects); flush_queued_effects(effects); + // Reinstate the current batch if there was no new one created, as `process()` runs in a loop in `flush_effects()`. + // That method expects `current_batch` to be set, and could run the loop again if effects result in new effects + // being scheduled but without writes happening in which case no new batch is created. + if (current_batch === null) { + current_batch = this; + } else { + batches.delete(this); + } + this.#deferred?.resolve(); } else { // otherwise mark effects clean so they get scheduled on the next run @@ -300,7 +314,7 @@ export class Batch { this.#previous.set(source, value); } - this.#current.set(source, source.v); + this.current.set(source, source.v); } activate() { @@ -327,13 +341,13 @@ export class Batch { flush() { if (queued_root_effects.length > 0) { - this.flush_effects(); + flush_effects(); } else { this.#commit(); } if (current_batch !== this) { - // this can happen if a `flushSync` occurred during `this.flush_effects()`, + // this can happen if a `flushSync` occurred during `flush_effects()`, // which is permitted in legacy mode despite being a terrible idea return; } @@ -345,52 +359,6 @@ export class Batch { this.deactivate(); } - flush_effects() { - var was_updating_effect = is_updating_effect; - is_flushing = true; - - try { - var flush_count = 0; - set_is_updating_effect(true); - - while (queued_root_effects.length > 0) { - if (flush_count++ > 1000) { - if (DEV) { - var updates = new Map(); - - for (const source of this.#current.keys()) { - for (const [stack, update] of source.updated ?? []) { - var entry = updates.get(stack); - - if (!entry) { - entry = { error: update.error, count: 0 }; - updates.set(stack, entry); - } - - entry.count += update.count; - } - } - - for (const update of updates.values()) { - // eslint-disable-next-line no-console - console.error(update.error); - } - } - - infinite_loop_guard(); - } - - this.#process(queued_root_effects); - old_values.clear(); - } - } finally { - is_flushing = false; - set_is_updating_effect(was_updating_effect); - - last_scheduled_effect = null; - } - } - /** * Append and remove branches to/from the DOM */ @@ -412,19 +380,8 @@ export class Batch { this.#pending -= 1; if (this.#pending === 0) { - for (const e of this.#render_effects) { - set_signal_status(e, DIRTY); - schedule_effect(e); - } - - for (const e of this.#effects) { - set_signal_status(e, DIRTY); - schedule_effect(e); - } - - for (const e of this.#block_effects) { - set_signal_status(e, DIRTY); - schedule_effect(e); + for (const source of this.current.keys()) { + mark_reactions(source, DIRTY, false); } this.#render_effects = []; @@ -445,12 +402,12 @@ export class Batch { return (this.#deferred ??= deferred()).promise; } - static ensure(autoflush = true) { + static ensure() { if (current_batch === null) { const batch = (current_batch = new Batch()); batches.add(current_batch); - if (autoflush) { + if (!is_flushing_sync) { Batch.enqueue(() => { if (current_batch !== batch) { // a flushSync happened in the meantime @@ -487,32 +444,85 @@ export function flushSync(fn) { e.flush_sync_in_effect(); } - var result; + var was_flushing_sync = is_flushing_sync; + is_flushing_sync = true; - const batch = Batch.ensure(false); + try { + var result; - if (fn) { - batch.flush_effects(); + if (fn) { + flush_effects(); + result = fn(); + } - result = fn(); - } + while (true) { + flush_tasks(); - while (true) { - flush_tasks(); + if (queued_root_effects.length === 0) { + current_batch?.flush(); - if (queued_root_effects.length === 0) { - if (batch === current_batch) { - batch.flush(); + // we need to check again, in case we just updated an `$effect.pending()` + if (queued_root_effects.length === 0) { + // this would be reset in `flush_effects()` but since we are early returning here, + // we need to reset it here as well in case the first time there's 0 queued root effects + last_scheduled_effect = null; + + return /** @type {T} */ (result); + } } - // this would be reset in `batch.flush_effects()` but since we are early returning here, - // we need to reset it here as well in case the first time there's 0 queued root effects - last_scheduled_effect = null; + flush_effects(); + } + } finally { + is_flushing_sync = was_flushing_sync; + } +} + +function flush_effects() { + var was_updating_effect = is_updating_effect; + is_flushing = true; + + try { + var flush_count = 0; + set_is_updating_effect(true); + + while (queued_root_effects.length > 0) { + var batch = Batch.ensure(); + + if (flush_count++ > 1000) { + if (DEV) { + var updates = new Map(); + + for (const source of batch.current.keys()) { + for (const [stack, update] of source.updated ?? []) { + var entry = updates.get(stack); + + if (!entry) { + entry = { error: update.error, count: 0 }; + updates.set(stack, entry); + } + + entry.count += update.count; + } + } + + for (const update of updates.values()) { + // eslint-disable-next-line no-console + console.error(update.error); + } + } + + infinite_loop_guard(); + } - return /** @type {T} */ (result); + batch.process(queued_root_effects); + old_values.clear(); } + } finally { + is_flushing = false; + set_is_updating_effect(was_updating_effect); - batch.flush_effects(); + last_scheduled_effect = null; } } @@ -545,7 +555,7 @@ function flush_queued_effects(effects) { var effect = effects[i++]; if ((effect.f & (DESTROYED | INERT)) === 0 && is_dirty(effect)) { - var wv = write_version; + var n = current_batch ? current_batch.current.size : 0; update_effect(effect); @@ -568,7 +578,11 @@ function flush_queued_effects(effects) { // if state is written in a user effect, abort and re-schedule, lest we run // effects that should be removed as a result of the state change - if (write_version > wv && (effect.f & USER_EFFECT) !== 0) { + if ( + current_batch !== null && + current_batch.current.size > n && + (effect.f & USER_EFFECT) !== 0 + ) { break; } } diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index f6b14f3360de..3b28c8fdceeb 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -179,7 +179,7 @@ export function internal_set(source, value) { source.v = value; - const batch = Batch.ensure(); + var batch = Batch.ensure(); batch.capture(source, old_value); if (DEV) { @@ -301,9 +301,10 @@ export function increment(source) { /** * @param {Value} signal * @param {number} status should be DIRTY or MAYBE_DIRTY + * @param {boolean} schedule_async * @returns {void} */ -function mark_reactions(signal, status) { +export function mark_reactions(signal, status, schedule_async = true) { var reactions = signal.reactions; if (reactions === null) return; @@ -323,14 +324,16 @@ function mark_reactions(signal, status) { continue; } + var should_schedule = (flags & DIRTY) === 0 && (schedule_async || (flags & ASYNC) === 0); + // don't set a DIRTY reaction to MAYBE_DIRTY - if ((flags & DIRTY) === 0) { + if (should_schedule) { set_signal_status(reaction, status); } if ((flags & DERIVED) !== 0) { mark_reactions(/** @type {Derived} */ (reaction), MAYBE_DIRTY); - } else if ((flags & DIRTY) === 0) { + } else if (should_schedule) { schedule_effect(/** @type {Effect} */ (reaction)); } } diff --git a/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/Component.svelte b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/Component.svelte new file mode 100644 index 000000000000..7a54323cb97f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/Component.svelte @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/_config.js b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/_config.js new file mode 100644 index 000000000000..2e4a27cf0912 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + async test() {} +}); diff --git a/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/main.svelte b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/main.svelte new file mode 100644 index 000000000000..bd326edfb92a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/main.svelte @@ -0,0 +1,8 @@ + + +{#each arr} + +{/each} diff --git a/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/_config.js b/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/_config.js new file mode 100644 index 000000000000..782ae945f9c3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/_config.js @@ -0,0 +1,26 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + await tick(); + + const [input] = target.querySelectorAll('input'); + + input.focus(); + input.value = '3'; + input.dispatchEvent(new InputEvent('input', { bubbles: true })); + await tick(); + + assert.equal(input.value, '3'); + assert.htmlEqual(target.innerHTML, `

3

`); + + input.focus(); + input.value = '1'; + input.dispatchEvent(new InputEvent('input', { bubbles: true })); + await tick(); + + assert.equal(input.value, '2'); + assert.htmlEqual(target.innerHTML, `

2

`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/main.svelte new file mode 100644 index 000000000000..763ce6ebf073 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/main.svelte @@ -0,0 +1,27 @@ + + + +

{await value}

+ + + {#snippet pending()} +

loading...

+ {/snippet} +
diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/_config.js b/packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/_config.js new file mode 100644 index 000000000000..c551cc6b8c39 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/_config.js @@ -0,0 +1,32 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + await tick(); + + const [increment] = target.querySelectorAll('button'); + + increment.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + +

1

+

1

+ ` + ); + + increment.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + +

2

+

2

+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/main.svelte new file mode 100644 index 000000000000..153fe03f0d89 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/main.svelte @@ -0,0 +1,24 @@ + + + + +

{JSON.stringify((await data), null, 2)}

+ {#if true} + +

{unrelated}

+ {/if} + + {#snippet pending()} +

loading...

+ {/snippet} +
diff --git a/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/_config.js b/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/_config.js new file mode 100644 index 000000000000..7a56c79d7176 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/_config.js @@ -0,0 +1,24 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + test({ assert, target }) { + const [input] = target.querySelectorAll('input'); + + input.focus(); + input.value = '3'; + input.dispatchEvent(new InputEvent('input', { bubbles: true })); + flushSync(); + + assert.equal(input.value, '3'); + assert.htmlEqual(target.innerHTML, `

3

`); + + input.focus(); + input.value = '1'; + input.dispatchEvent(new InputEvent('input', { bubbles: true })); + flushSync(); + + assert.equal(input.value, '2'); + assert.htmlEqual(target.innerHTML, `

2

`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/main.svelte new file mode 100644 index 000000000000..b0597c223b99 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/main.svelte @@ -0,0 +1,21 @@ + + +

{value}

+ From c26365bdda1a13e98a2e75f17da655e08ff4f8bc Mon Sep 17 00:00:00 2001 From: Hyunbin Seo <47051820+hyunbinseo@users.noreply.github.com> Date: Wed, 23 Jul 2025 21:02:28 +0900 Subject: [PATCH 5/6] chore: rename form `accept-charset` attribute (#16478) * chore: rename form accept-charset attribute * chore: utf-8 to lowercase Co-authored-by: Rich Harris * Update .changeset/healthy-carpets-deny.md --------- Co-authored-by: Rich Harris --- .changeset/healthy-carpets-deny.md | 5 +++++ packages/svelte/elements.d.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/healthy-carpets-deny.md diff --git a/.changeset/healthy-carpets-deny.md b/.changeset/healthy-carpets-deny.md new file mode 100644 index 000000000000..8f8db7fa9cd3 --- /dev/null +++ b/.changeset/healthy-carpets-deny.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: rename form accept-charset attribute diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts index 1492f777925d..604241592aff 100644 --- a/packages/svelte/elements.d.ts +++ b/packages/svelte/elements.d.ts @@ -996,7 +996,7 @@ export interface HTMLFieldsetAttributes extends HTMLAttributes { - acceptcharset?: string | undefined | null; + 'accept-charset'?: 'utf-8' | (string & {}) | undefined | null; action?: string | undefined | null; autocomplete?: AutoFillBase | undefined | null; enctype?: From f8820956d2591288de53927c5e173242a13f125a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:23:52 -0400 Subject: [PATCH 6/6] Version Packages (#16484) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/grumpy-boats-beg.md | 5 ----- .changeset/healthy-carpets-deny.md | 5 ----- .changeset/shiny-walls-fix.md | 5 ----- .changeset/thick-mice-kick.md | 5 ----- packages/svelte/CHANGELOG.md | 12 ++++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 7 files changed, 14 insertions(+), 22 deletions(-) delete mode 100644 .changeset/grumpy-boats-beg.md delete mode 100644 .changeset/healthy-carpets-deny.md delete mode 100644 .changeset/shiny-walls-fix.md delete mode 100644 .changeset/thick-mice-kick.md diff --git a/.changeset/grumpy-boats-beg.md b/.changeset/grumpy-boats-beg.md deleted file mode 100644 index f677743defa5..000000000000 --- a/.changeset/grumpy-boats-beg.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: keep input in sync when binding updated via effect diff --git a/.changeset/healthy-carpets-deny.md b/.changeset/healthy-carpets-deny.md deleted file mode 100644 index 8f8db7fa9cd3..000000000000 --- a/.changeset/healthy-carpets-deny.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: rename form accept-charset attribute diff --git a/.changeset/shiny-walls-fix.md b/.changeset/shiny-walls-fix.md deleted file mode 100644 index 91ed548728a3..000000000000 --- a/.changeset/shiny-walls-fix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: prevent infinite async loop diff --git a/.changeset/thick-mice-kick.md b/.changeset/thick-mice-kick.md deleted file mode 100644 index eec55b77eee4..000000000000 --- a/.changeset/thick-mice-kick.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: exclude derived writes from effect abort and rescheduling diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 5a5e532a081e..8cd385046041 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,17 @@ # svelte +## 5.36.14 + +### Patch Changes + +- fix: keep input in sync when binding updated via effect ([#16482](https://github.com/sveltejs/svelte/pull/16482)) + +- fix: rename form accept-charset attribute ([#16478](https://github.com/sveltejs/svelte/pull/16478)) + +- fix: prevent infinite async loop ([#16482](https://github.com/sveltejs/svelte/pull/16482)) + +- fix: exclude derived writes from effect abort and rescheduling ([#16482](https://github.com/sveltejs/svelte/pull/16482)) + ## 5.36.13 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 4bf9a5df22e4..7fe1d161f288 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.36.13", + "version": "5.36.14", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 7d47fbc5f14c..cd9d8b459c32 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.36.13'; +export const VERSION = '5.36.14'; export const PUBLIC_VERSION = '5';