Skip to content

fix(signals): affects() as an action's first statement never lights isPending - #2888

Merged
ryansolid merged 2 commits into
solidjs:nextfrom
brenelz:fix/affects-first-statement-lane
Jul 15, 2026
Merged

fix(signals): affects() as an action's first statement never lights isPending#2888
ryansolid merged 2 commits into
solidjs:nextfrom
brenelz:fix/affects-first-statement-lane

Conversation

@brenelz

@brenelz brenelz commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #2887affects(store) declared as the first statement of an action (before any optimistic write) never read as pending through tracked isPending probes for the duration of the action; the badge blipped true for a single tick at settle instead. Moving the same affects call after the first write worked, exactly as the issue describes.

Root cause

affects() on a lane-less node writes true into the node's isPending companion signal, which (being the first optimistic write) creates a lane for the companion. Companion lanes must stay children of their owner's lane so their effects flush immediately instead of waiting on the transaction's async — but the parent link was captured only at lane creation from parentSource._optimisticLane. With affects first, the owner has no lane yet, so the companion lane was born parentless. The action's optimistic write then reached the shared subscriber (the badge memo), found no parent-child relation in assignOrMergeLane, and merged the companion's lane into the store's async-carrying lane. runLaneEffects skips lanes with pending async, so every tracked reader of the verdict deferred to settle.

The mark itself was held correctly the whole time (refcount, untracked probes read true) — only tracked/effect-driven readers were starved.

Fix

When an owner's lane is created in getOrCreateLane, adopt any lane its companions (_pendingSignal, _latestValueComputed) already created as a child of the new lane — the parent-child relation becomes a property of the nodes rather than of write order. Adoption is guarded to the companion's own unmerged, still-parentless root, so a lane group that has absorbed unrelated work is never re-parented.

Testing

  • New regression test in affects-propagation.test.ts reproducing the issue's setup (derived optimistic store + live mapArray subscriber + tracked badge memo): fails without the fix (badge log stays empty during the action), passes with it.
  • Full suites pass: solid-signals, solid, solid-web; package typecheck clean.

🤖 Generated with Claude Code

@changeset-bot

changeset-bot Bot commented Jul 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 06d821d

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

This PR includes changesets to release 9 packages
Name Type
@solidjs/signals Patch
test-integration Patch
solid-js Patch
babel-preset-solid Patch
@solidjs/web Patch
@solidjs/html Patch
@solidjs/h Patch
@solidjs/universal Patch
@solidjs/element 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

@codspeed-hq

codspeed-hq Bot commented Jul 15, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 7 untouched benchmarks
⏩ 113 skipped benchmarks1


Comparing brenelz:fix/affects-first-statement-lane (06d821d) with next (587cf48)

Open in CodSpeed

Footnotes

  1. 113 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

brenelz and others added 2 commits July 14, 2026 22:06
…() precedes the first optimistic write

A companion (isPending/latest) written before its owner's first optimistic
write — affects(store) as an action's first statement — created its lane
with no parent link, so the owner's subsequent write merged the companion's
subscribers into the store's async-carrying lane and their effects deferred
to settle: tracked badges read false for the whole action, then blipped
true for one tick.

Adopt pre-existing companion lanes as children when the owner's lane is
created, making the parent-child relation a property of the nodes rather
than of write order.

Fixes solidjs#2887

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@ryansolid
ryansolid force-pushed the fix/affects-first-statement-lane branch from 41ad421 to 06d821d Compare July 15, 2026 05:23
@ryansolid
ryansolid merged commit 442ad9a into solidjs:next Jul 15, 2026
3 checks passed
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.

2 participants