Skip to content

fix: reset effects inside skipped branches#17581

Merged
Rich-Harris merged 40 commits intomainfrom
17335-alt
Jan 29, 2026
Merged

fix: reset effects inside skipped branches#17581
Rich-Harris merged 40 commits intomainfrom
17335-alt

Conversation

@Rich-Harris
Copy link
Copy Markdown
Member

@Rich-Harris Rich-Harris commented Jan 28, 2026

Alternative to #17335. When traversing, we skip any branches that will be destroyed when the batch commits, so that we don't (for example) try to evaluate object.property when object is undefined within a batch:

{#if object}
  {object.property}
{/if}

Because of this, however, it's possible for dirty effects inside skipped branches to be left dirty. If they are later rescheduled, schedule_effect will bail out if it hits an already-dirty branch, which can prevent state changes from having an effect. This is made more obvious by forks, but doesn't require them.

This PR fixes it, by traversing skipped branches when a batch is deferred.

Closes #17197.

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

hmnd and others added 30 commits December 9, 2025 02:08
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jan 28, 2026

🦋 Changeset detected

Latest commit: 2e52a4c

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

@github-actions
Copy link
Copy Markdown
Contributor

Playground

pnpm add https://pkg.pr.new/svelte@17581

@Rich-Harris Rich-Harris merged commit 1c131f1 into main Jan 29, 2026
18 checks passed
@Rich-Harris Rich-Harris deleted the 17335-alt branch January 29, 2026 00:00
@github-actions github-actions Bot mentioned this pull request Jan 29, 2026
dummdidumm added a commit that referenced this pull request Feb 2, 2026
When a branch is speculatively marked for destruction (condition temporarily falsy), its child effects are reset to `CLEAN` to prevent them running in a doomed branch (as of #17581). However, if the branch survives (condition becomes truthy again), those effects remain `CLEAN` and never run - the source was already marked dirty before the reset, so no new dirty marking occurs.

The fix is to change `skipped_effects` from a `Set` to a `Map` that tracks which child effects were dirty/maybe_dirty before being reset. When a branch is unskipped (survives), restore their status and reschedule them.
Rich-Harris added a commit that referenced this pull request Feb 4, 2026
* fix: reschedule effects inside unskipped branches

When a branch is speculatively marked for destruction (condition temporarily falsy), its child effects are reset to `CLEAN` to prevent them running in a doomed branch (as of #17581). However, if the branch survives (condition becomes truthy again), those effects remain `CLEAN` and never run - the source was already marked dirty before the reset, so no new dirty marking occurs.

The fix is to change `skipped_effects` from a `Set` to a `Map` that tracks which child effects were dirty/maybe_dirty before being reset. When a branch is unskipped (survives), restore their status and reschedule them.

* update comment

* lint

* make skipped_effects private

* actually while we're at it let's use a more descriptive name

* prettier

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
dummdidumm added a commit that referenced this pull request Feb 4, 2026
fixes #17595

When an if/key/etc block has an expression that depends on an async blocker (e.g., is inside a component with top level `await`), the compiler incorrectly treats the expression as async - even when the expression itself contains no `await`.

This causes the expression to be added to `$.async`'s `expressions` array, which wraps it in an `async_derived`. This is not only unnecessary but also buggy: it breaks the direct reactive connection between the source and its dependent effects, causing inconsistent effect executions.

The fix is to only add expressions to `$.async`'s `expressions` array when they actually contain an `await`.

When a branch is speculatively marked for destruction (condition temporarily falsy), its child effects are reset to `CLEAN` to prevent them running in a doomed branch (as of #17581). However, if the branch survives (condition becomes truthy again), those effects remain `CLEAN` and never run - the source was already marked dirty before the reset, so no new dirty marking occurs.

The fix is to change `skipped_effects` from a `Set` to a `Map` that tracks which child effects were dirty/maybe_dirty before being reset. When a branch is unskipped (survives), restore their status and reschedule them.

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
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.

Async: breaking bug when hovering a link that has +layout.svelte

5 participants