From 58297c5a73e6a06687c3892efbfc807eec3f23af Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 24 Sep 2026 04:19:37 -0400 Subject: [PATCH 01/14] chore: extract some minor changes from #18861 (#18866) doing my usual thing of extracting no-brainer changes from a giant PR to try and make it slightly less giant --- packages/svelte/src/internal/client/dev/tracing.js | 4 +++- packages/svelte/src/internal/client/dom/blocks/each.js | 4 ++-- packages/svelte/src/internal/client/reactivity/batch.js | 4 ++++ packages/svelte/src/internal/client/reactivity/sources.js | 6 +++--- packages/svelte/src/internal/client/runtime.js | 6 +++--- packages/svelte/src/reactivity/date.js | 4 ++-- 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/svelte/src/internal/client/dev/tracing.js b/packages/svelte/src/internal/client/dev/tracing.js index c6edfde933..9fea8175bd 100644 --- a/packages/svelte/src/internal/client/dev/tracing.js +++ b/packages/svelte/src/internal/client/dev/tracing.js @@ -131,8 +131,10 @@ export function trace(label, fn) { } /** - * @param {Value} source + * @template {Value} T + * @param {T} source * @param {string} label + * @returns {T} */ export function tag(source, label) { source.label = label; diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index 9a2504f887..ea828f893b 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -1,4 +1,4 @@ -/** @import { EachItem, EachOutroGroup, EachState, Effect, EffectNodes, MaybeSource, Source, TemplateNode, TransitionManager, Value } from '#client' */ +/** @import { EachItem, EachOutroGroup, EachState, Effect, EffectNodes, MaybeSource, TemplateNode, TransitionManager } from '#client' */ /** @import { Batch } from '../../reactivity/batch.js'; */ import { EACH_INDEX_REACTIVE, @@ -664,7 +664,7 @@ function reconcile(state, array, anchor, flags, get_key) { * @param {V} value * @param {unknown} key * @param {number} index - * @param {(anchor: Node, item: V | Source, index: number | Value, collection: () => V[]) => void} render_fn + * @param {(anchor: Node, item: MaybeSource, index: MaybeSource, collection: () => V[]) => void} render_fn * @param {number} flags * @param {() => V[]} get_collection * @returns {EachItem} diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 75ce33b1c5..f620c2b4c8 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -1295,6 +1295,10 @@ export function eager(fn) { let version = version_map.get(parent) ?? source(0); version_map.set(parent, version); + if (DEV) { + version.label ??= '$state.eager version'; + } + teardown(() => { if (parent.f & DESTROYING) version_map.delete(parent); }); diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 006494f0b5..4b10ddbd31 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -46,7 +46,7 @@ import { set_signal_status, update_derived_status } from './status.js'; /** @type {Set} */ export let eager_effects = new Set(); -/** @type {Map} */ +/** @type {Map} */ export const old_values = new Map(); /** @@ -141,7 +141,7 @@ export function mutate(source, value) { /** * @template V - * @param {Source} source + * @param {Value} source * @param {V} value * @param {boolean} [should_proxy] * @returns {V} @@ -181,7 +181,7 @@ var count_deps = 0; /** * @template V - * @param {Source} source + * @param {Value} source * @param {V} value * @param {Effect[] | null} [updated_during_traversal] * @returns {V} diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 27def05300..6212605008 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -93,7 +93,7 @@ export function set_active_effect(effect) { /** * When sources are created within a reaction, reading and writing * them within that reaction should not cause a re-run - * @type {null | Set} + * @type {null | Set} */ export let current_sources = null; @@ -121,11 +121,11 @@ export let skipped_deps = 0; /** * Tracks writes that the effect it's executed in doesn't listen to yet, * so that the dependency can be added to the effect later on if it then reads it - * @type {null | Source[]} + * @type {null | Value[]} */ export let untracked_writes = null; -/** @param {null | Source[]} value */ +/** @param {null | Value[]} value */ export function set_untracked_writes(value) { untracked_writes = value; } diff --git a/packages/svelte/src/reactivity/date.js b/packages/svelte/src/reactivity/date.js index f882c05d76..b70ba7969a 100644 --- a/packages/svelte/src/reactivity/date.js +++ b/packages/svelte/src/reactivity/date.js @@ -1,4 +1,4 @@ -/** @import { Source } from '#client' */ +/** @import { Derived } from '#client' */ import { derived } from '../internal/client/index.js'; import { set, state } from '../internal/client/reactivity/sources.js'; import { tag } from '../internal/client/dev/tracing.js'; @@ -42,7 +42,7 @@ var inited = false; export class SvelteDate extends Date { #time = state(super.getTime()); - /** @type {Map>} */ + /** @type {Map>} */ #deriveds = new Map(); #reaction = active_reaction; From da2f83343bf5e2fb183881b74ef06fac24f0e249 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 24 Sep 2026 22:47:52 +0200 Subject: [PATCH 02/14] fix: read batch-local array on each-block commit (#18879) Another extraction from #18861 which I'm pretty sure you can also construct a buggy reproduction for today, but with #18861 it's strictly necessary so we don't need to come up with one now --- .changeset/short-radios-sneeze.md | 5 +++++ packages/svelte/src/internal/client/dom/blocks/each.js | 10 ++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 .changeset/short-radios-sneeze.md diff --git a/.changeset/short-radios-sneeze.md b/.changeset/short-radios-sneeze.md new file mode 100644 index 0000000000..847c3eef17 --- /dev/null +++ b/.changeset/short-radios-sneeze.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: read batch-local array on each-block commit diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index ea828f893b..58d59a6825 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -216,9 +216,6 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f tag(each_array, '{#each ...}'); } - /** @type {V[]} */ - var array; - /** @type {Map>} */ var pending = new Map(); @@ -234,6 +231,11 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f state.pending.delete(batch); + // The effect doesn't necessarily re-run in a batch right before that batch commits + // (its view of the collection may not have changed), so we read the collection + // as the committing batch sees it rather than using the most recent block run's result + var array = get(each_array); + state.fallback = fallback; reconcile(state, array, anchor, flags, get_key); @@ -264,7 +266,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f } var effect = block(() => { - array = /** @type {V[]} */ (get(each_array)); + var array = /** @type {V[]} */ (get(each_array)); var length = array.length; /** `true` if there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */ From 98e547535d9286cfd4163fe3a6d85c7053db6c6e Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 24 Sep 2026 22:49:15 +0200 Subject: [PATCH 03/14] fix: flush anything pending before invoking flushSync callback function (#18878) no new test here but it's necessary for #18861 to work, and we might as well extract it --- .changeset/major-papayas-run.md | 5 +++++ .../svelte/src/internal/client/reactivity/batch.js | 12 ++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 .changeset/major-papayas-run.md diff --git a/.changeset/major-papayas-run.md b/.changeset/major-papayas-run.md new file mode 100644 index 0000000000..59909fb448 --- /dev/null +++ b/.changeset/major-papayas-run.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: flush anything pending before invoking flushSync callback function diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index f620c2b4c8..05f7586072 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -1032,15 +1032,15 @@ export class Batch { */ export function flushSync(fn) { var was_flushing_sync = is_flushing_sync; + var prev_previous_batch = previous_batch; + previous_batch = null; is_flushing_sync = true; try { var result; if (fn) { - if (current_batch !== null && !current_batch.is_fork) { - current_batch.flush(); - } + flushSync(); // flush anything pending through the while loop below result = fn(); } @@ -1056,6 +1056,7 @@ export function flushSync(fn) { } } finally { is_flushing_sync = was_flushing_sync; + previous_batch = prev_previous_batch; } } @@ -1406,6 +1407,8 @@ export function fork(fn) { e.fork_timing(); } + flushSync(); + var batch = Batch.ensure(); batch.is_fork = true; batch_values = new Map(); @@ -1413,7 +1416,8 @@ export function fork(fn) { var committed = false; var settled = batch.settled(); - flushSync(fn); + fn(); + flushSync(); return { commit: async () => { From f7ca0a44a5d198d600521afa44341736c9bee0c8 Mon Sep 17 00:00:00 2001 From: Dex Date: Fri, 25 Sep 2026 00:04:17 +0200 Subject: [PATCH 04/14] chore: fix typos in code comments (#18860) Fixes four typos in code comments (no behavioral change): - `packages/svelte/src/index-client.js`: `noone` -> `no one` - `packages/svelte/src/internal/client/dom/css.js`: `Therfore` -> `Therefore` - `packages/svelte/src/internal/client/runtime.js`: `loosing` -> `losing` - `packages/svelte/src/internal/server/index.js`: `aswell` -> `as well` Checklist: title prefixed with `chore:`; comment-only change so no changeset and no behavior affected. Co-authored-by: Dextheking1 --- packages/svelte/src/index-client.js | 2 +- packages/svelte/src/internal/client/dom/css.js | 2 +- packages/svelte/src/internal/client/runtime.js | 2 +- packages/svelte/src/internal/server/index.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/svelte/src/index-client.js b/packages/svelte/src/index-client.js index 0eb1b80315..3da59eb7f7 100644 --- a/packages/svelte/src/index-client.js +++ b/packages/svelte/src/index-client.js @@ -17,7 +17,7 @@ if (DEV) { if (!(rune in globalThis)) { // TODO if people start adjusting the "this can contain runes" config through v-p-s more, adjust this message /** @type {any} */ - let value; // let's hope noone modifies this global, but belts and braces + let value; // let's hope no one modifies this global, but belts and braces Object.defineProperty(globalThis, rune, { configurable: true, // eslint-disable-next-line getter-return diff --git a/packages/svelte/src/internal/client/dom/css.js b/packages/svelte/src/internal/client/dom/css.js index 23f4eff4f7..78a32901fe 100644 --- a/packages/svelte/src/internal/client/dom/css.js +++ b/packages/svelte/src/internal/client/dom/css.js @@ -12,7 +12,7 @@ export function append_styles(anchor, css) { // Use an effect to ensure `anchor` is in the DOM, otherwise getRootNode() will yield wrong results effect(() => { // Bit of a hack: branches.js/each.js use offscreen fragments with temporary text nodes that will - // never be connected to the real dom. Therfore walk up to the branch that has created the component + // never be connected to the real dom. Therefore walk up to the branch that has created the component // whose styles we want to append, and check its node instead. It will be connected by the time we get here. anchor = active_effect?.parent?.nodes?.start ?? anchor; var root = anchor.getRootNode(); diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 6212605008..76802974b7 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -410,7 +410,7 @@ function remove_reaction(signal, dependency) { // In a fork it's possible that a derived is executed and gets reactions, then commits, but is // never re-executed. This is possible when the derived is only executed once in the context // of a new branch which happens before fork.commit() runs. In this case, the derived still has - // UNINITIALIZED as its value, and then when it's loosing its reactions we need to ensure it stays + // UNINITIALIZED as its value, and then when it's losing its reactions we need to ensure it stays // DIRTY so it is reexecuted once someone wants its value again. if (derived.v !== UNINITIALIZED) { update_derived_status(derived); diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 20bef78306..6a63bd9fd6 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -168,7 +168,7 @@ export function attributes(attrs, css_hash, classes, styles, flags = 0) { // value/checked takes precedence over defaultValue/defaultChecked name = name === 'defaultvalue' ? 'value' : 'checked'; if (name in attrs) continue; - // We're checking prior entries aswell because "name in attrs" is not enough as the attributes may have different casing + // We're checking prior entries as well because "name in attrs" is not enough as the attributes may have different casing for (let j = 0; j < names.length; j++) { if (names[j].toLowerCase() === name) continue outer; } From 75aa6ba38d58c83ff012bed77c39edb814e43bc6 Mon Sep 17 00:00:00 2001 From: G Date: Fri, 25 Sep 2026 04:44:00 +0200 Subject: [PATCH 05/14] fix: don't warn about redundant `link` role on `` without `href` (#18872) Since #11737, `` without an `href` no longer triggers `a11y_no_redundant_roles`, but `` without an `href` still does. Per [ARIA in HTML](https://www.w3.org/TR/html-aria/#el-area-no-href), an `` without `href` has the implicit role `generic` and `link` is one of its allowed roles, so the role isn't redundant. This applies the existing `` exception to `` and adds test cases for `` with and without `href`. Closes #9261. The `` example in that issue was already fixed by #11737, but the issue was left open. ### 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. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs - [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`). ### Tests and linting - [x] Run the tests with `pnpm test` and lint the project with `pnpm lint` --- .changeset/quiet-areas-link.md | 5 +++++ .../phases/2-analyze/visitors/shared/a11y/index.js | 4 ++-- .../samples/a11y-no-redundant-roles/input.svelte | 5 +++++ .../samples/a11y-no-redundant-roles/warnings.json | 12 ++++++++++++ 4 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 .changeset/quiet-areas-link.md diff --git a/.changeset/quiet-areas-link.md b/.changeset/quiet-areas-link.md new file mode 100644 index 0000000000..af4e31f3cb --- /dev/null +++ b/.changeset/quiet-areas-link.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't warn about a redundant `link` role on `` elements without an `href` diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js index f06a41d088..c38b03bf69 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js @@ -173,8 +173,8 @@ export function check_element(node, context) { current_role === get_implicit_role(node.name, attribute_map) && //