Skip to content

Conversation

Copy link

Copilot AI commented Nov 28, 2025

Before submitting the PR, please make sure you do the following

  • 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
  • Prefix your PR title with feat:, fix:, chore:, or docs:.
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.
  • If this PR changes code within packages/svelte/src, add a changeset (npx changeset).

Tests and linting

  • Run the tests with pnpm test and lint the project with pnpm lint

Fixes #17263 - A $derived property depending on a SvelteSet doesn't update when read immediately after modifying the set.

Root Cause

When a connected derived's deps change during update_derived outside of an effect update cycle (is_updating_effect === false), the update_reaction function doesn't add the derived to new deps' reactions (due to the check on line 281). This causes the derived to not be notified when those new deps change.

This manifests when:

  1. A derived depends on a SvelteSet via set.has(value)
  2. The value is deleted from the set (creating a new dependency on the set's version)
  3. The value is added back to the set
  4. Reading the derived immediately returns the stale value

The Fix

After update_derived completes in the get() function, if the derived was already connected, iterate over its current deps and ensure each one has the derived in its reactions. This handles the case where deps changed but update_reaction didn't add the reactions due to is_updating_effect being false.

Test Coverage Added

  • Proxy property deletion and re-addition
  • Signal proxy replacement
  • Property present → absent → present cycling
  • Reactions verification on new proxy sources
  • Multiple deriveds interacting
  • Derived created in effect that escapes after effect destruction
  • Derived moved between effect contexts
  • Reactions verification when derived outlives creating effect
  • SvelteSet with derived getter patterns
  • derived passed as prop - item with expanded derived from SvelteSet
  • derived with SvelteSet - disconnected then reconnected

All 156 signals tests pass, and 7007 tests in the full suite pass.

Original prompt

You're a framework author working on Svelte.
This PR #17105 has introduced a regression. Something about a derived having a dependency on an object with a property (both a source, i.e. it's a proxy), that property is deleted then added with a different value a few ticks later. As a result the newly created source doesn't have the derived in its reactions (it's null) but the derived has the source in its deps.
Analyze the PR. Then try to write a test inside https://github.com/sveltejs/svelte/blob/main/packages%2Fsvelte%2Ftests%2Fsignals%2Ftest.ts that reproduces the issue I described. Come up with suggestions what the cause of the bug could be and how to fix it And open a pr with your findings


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@changeset-bot
Copy link

changeset-bot bot commented Nov 28, 2025

🦋 Changeset detected

Latest commit: 28c6171

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
svelte Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copilot AI and others added 4 commits November 28, 2025 13:05
Co-authored-by: dummdidumm <5968653+dummdidumm@users.noreply.github.com>
…eriveds

Co-authored-by: dummdidumm <5968653+dummdidumm@users.noreply.github.com>
Co-authored-by: dummdidumm <5968653+dummdidumm@users.noreply.github.com>
…sent cycling)

Co-authored-by: dummdidumm <5968653+dummdidumm@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix derived reactions for proxy object property changes test: Add comprehensive tests for derived-proxy reconnection scenarios Nov 28, 2025
Copilot AI requested a review from dummdidumm November 28, 2025 13:17
Copilot finished work on behalf of dummdidumm November 28, 2025 13:17
@dummdidumm
Copy link
Member

@copilot It could be multiple deriveds interacting. Also try a test where the derived is created in an effect context but then subsequently moved out of that effect context. Maybe the effect it was created in even is destroyed but the derived now lives on separately

…ect contexts

Co-authored-by: dummdidumm <5968653+dummdidumm@users.noreply.github.com>
Copy link
Author

Copilot AI commented Nov 28, 2025

@copilot It could be multiple deriveds interacting. Also try a test where the derived is created in an effect context but then subsequently moved out of that effect context. Maybe the effect it was cr...

Added tests in commit 153e91d:

  • multiple deriveds interacting with proxy - Tests d1→d2→d3 chain with proxy replacement
  • derived created in effect context survives after effect destruction - Derived escapes creating effect and is used in new effect
  • derived moved between effect contexts with proxy changes - Derived read by multiple effects, effects destroyed one by one
  • derived created in destroyed effect - verify reactions - Direct verification of reactions arrays

All tests pass. The reconnection logic handles these scenarios correctly.

@dummdidumm
Copy link
Member

@copilot Look at this issue #17263 it contains a link to a reproduction which contains the following:

@dummdidumm
Copy link
Member

dummdidumm commented Nov 28, 2025

@copilot This seems to be a reproduction:

<script>import {SvelteSet} from "svelte/reactivity"import Item from "./Item.svelte"const expanded_ids = new SvelteSet()function on_expand(id) {expanded_ids.add(id)}function on_collapse(id) {expanded_ids.delete(id)}</script><Itemitem={{id: 1,name: "Item 1",expanded: expanded_ids.has(1),}}{on_expand}{on_collapse}/> 
<script>let {item,on_expand,on_collapse,on_toggle_expansion,} = $props()function buggy_on_toggle_expansion() {if (item.expanded) {on_collapse(item.id)}else {on_expand(item.id)}// This triggers the bugconsole.log('on_toggle_expansion', 'after', item.expanded)}function working_on_toggle_expansion(item) {if (item.expanded) {on_collapse(item.id)}else {on_expand(item.id)}// This triggers the bugconsole.log('on_toggle_expansion', 'after', item.expanded)}function handle_click() {const BUGGY = trueif (BUGGY) {buggy_on_toggle_expansion()}else {working_on_toggle_expansion(item)}}</script><button onclick={handle_click}>{item.expanded ? "Expanded" : "Collapsed"}</button>

…t reconnection bug

Co-authored-by: dummdidumm <5968653+dummdidumm@users.noreply.github.com>
Copy link
Author

Copilot AI commented Nov 28, 2025

@copilot This seems to be a reproduction:

<script>import {SvelteSet} from "svelte/reactivity"import Item from "./Item.svelte"const expanded_ids = new SvelteSet()function on_expand(id) {expanded_...

Reproduced the bug in commit a51580b. Two tests now fail:

  1. derived passed as prop - item with expanded derived from SvelteSet - Uses actual derived() with SvelteSet, fails on second toggle
  2. derived with SvelteSet - disconnected then reconnected - Derived doesn't see changes after reconnection

The key difference from passing tests: these use actual derived() signals rather than plain getters. When a derived is disconnected and reconnected, immediate reads after SvelteSet modifications return stale values.

Copilot AI changed the title test: Add comprehensive tests for derived-proxy reconnection scenarios test: Add comprehensive tests for derived-proxy reconnection scenarios (reproduces #17263) Nov 28, 2025
Copilot finished work on behalf of dummdidumm November 28, 2025 18:36
@dummdidumm
Copy link
Member

@copilot Now fix the bug

…ide effect context

Co-authored-by: dummdidumm <5968653+dummdidumm@users.noreply.github.com>
Copy link
Author

Copilot AI commented Nov 28, 2025

@copilot Now fix the bug

Fixed in commit 28c6171.

Root cause: When a connected derived's deps change during update_derived outside of an effect update cycle (is_updating_effect === false), the update_reaction function skips adding the derived to new deps' reactions. This caused the derived to miss notifications when those new deps changed (e.g., after SvelteSet.delete() followed by add()).

Fix: After update_derived completes, if the derived was already connected, ensure each dep has the derived in its reactions.

Copilot AI changed the title test: Add comprehensive tests for derived-proxy reconnection scenarios (reproduces #17263) fix: ensure derived signals maintain reactions after deps change outside effect context Nov 28, 2025
Copilot finished work on behalf of dummdidumm November 28, 2025 19:36
@dummdidumm
Copy link
Member

dummdidumm commented Nov 28, 2025

Have a feeling this is wrong. I wonder if the real fix is to not set deriveds to clean if they are interacted with outside effect_tracking - PR #17105 removed that condition from "set status back to clean when ..." inside is_dirty. Update: nope that's not it

@abdel-17
Copy link

Hey, thanks for working on this bug! Just wondering if there are any blockers merging this because it's breaking a lot of stuff for our app. One of the toggles gets stuck in a true state forever and can't toggle back.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A $derived property depending on a SvelteSet doesn't update

3 participants