From 737de2e4f3020493474d509e4cda799cd4fd7a16 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 13 Oct 2026 12:07:21 -0400 Subject: [PATCH] restore removal of batch_values.delete(derived) in mark_reactions A derived's batch-local value can be a later batch's previous value; deleting it makes the next read fall back to the real (later batch's) value. --- .../src/internal/client/reactivity/sources.js | 1 - .../_config.js | 33 +++++++++++++++++++ .../main.svelte | 32 ++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 packages/svelte/tests/runtime-runes/samples/async-effect-write-keeps-batch-derived/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-effect-write-keeps-batch-derived/main.svelte diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 09b6d3bb97..f2ed02a23e 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -411,7 +411,6 @@ function mark_reactions(signal, status, updated_during_traversal) { } else if ((flags & DERIVED) !== 0) { var derived = /** @type {Derived} */ (reaction); - batch_values?.delete(derived); mark_reactions(derived, MAYBE_DIRTY, updated_during_traversal); } else if (not_dirty) { var effect = /** @type {Effect} */ (reaction); diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-write-keeps-batch-derived/_config.js b/packages/svelte/tests/runtime-runes/samples/async-effect-write-keeps-batch-derived/_config.js new file mode 100644 index 0000000000..fb0b6e8a4f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-effect-write-keeps-batch-derived/_config.js @@ -0,0 +1,33 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +const buttons = ''; + +// While batch A (x) commits, a pre effect writes to `s`. That marks `label` (via `big`) as +// maybe dirty, but it doesn't actually change. `label` must still show the value from A's +// point of view, not the one from the later, still-pending batch B (t). +export default test({ + async test({ assert, target }) { + await tick(); + const [x, t, shift] = target.querySelectorAll('button'); + + // A + x.click(); + await tick(); + + // B + t.click(); + await tick(); + assert.htmlEqual(target.innerHTML, `${buttons}

0

0

0 false-0

`); + + // A commits, B is still pending + shift.click(); + await tick(); + assert.htmlEqual(target.innerHTML, `${buttons}

1

0

1 false-0

`); + + // B commits + shift.click(); + await tick(); + assert.htmlEqual(target.innerHTML, `${buttons}

1

1

1 false-1

`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-write-keeps-batch-derived/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-effect-write-keeps-batch-derived/main.svelte new file mode 100644 index 0000000000..1791827eae --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-effect-write-keeps-batch-derived/main.svelte @@ -0,0 +1,32 @@ + + + + + + +

{await push(x)}

+

{await push(t)}

+ +{#key label}{/key} + +

{x} {label}