Skip to content

fix(signals): superseded-transition verdicts ignore parked memo reads (#3884) - #3934

Merged
ryansolid merged 6 commits into
nextfrom
fix/3884-superseded-transition-verdicts
Oct 9, 2026
Merged

ryansolid merged 6 commits into
nextfrom
fix/3884-superseded-transition-verdicts

Conversation

@ryansolid

@ryansolid ryansolid commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

Summary

A probe (isPending / latest, tracked or untracked) inside an async memo stamps that memo as a verdict reader. On the next pass the stamp makes it a frame reader: it sees the committed screen instead of the superseding write, the flight settles, and the transition lands while the new write is still parked. Other readers then observe a different pending answer solely because the parked computation also probed.

A pending flight's re-run drops that stamp before it reads, so it stays on the transition's write and the hold holds. A probe in the body re-stamps the memo only after those plain reads, so [isPending(x), x()] in a real verdict reader is unchanged. The "nothing" baseline is untouched: the effect shows /slow1 when A parks, /slow2 at B's flush, and - when B lands, and an outside isPending(source) stays true until B lands.

Fixes #3884

Public API

None

How did you test this change?

  • New packages/signals/tests/superseded-transition-verdicts-3884.test.ts: the narrowed repro and the original repro, each with all five extras (nothing, untracked latest, untracked isPending, tracked latest, tracked isPending). Checkpoints are A parked, B flushed, B flushed +tick, A resolved, and B resolved. Confirmed the four divergent rows failed on current next and nothing already passed, then that every row matches nothing.
  • Neighbor verdict / pending / latest tests (verdict contract, consistency, probe order, lane hold, boundaries).
  • pnpm --filter @solidjs/signals test: 283 files, 5095 passed, 28 expected fail, 2 skipped.
  • Size: signals: core floor brotli 7.44 KB (cap 7.45 KB) and signals: + isPending/latest brotli 9.64 KB (cap 9.65 KB). No cap change.

Made with Cursor

Size-Exception: #3884 is accepted. Hello world measured at 9,936 B brotli (still under 10KB) and the hydrating floor at 17,922 B. Those frozen caps, and the inline scenarios this PR pushed over, are set to the CI measurement.

…#3884)

A probe inside an async memo stamps that memo as a verdict reader. The next pass then frame-reads the source, sees the committed screen instead of the superseding write, and the transition lands while the new write is still parked. Other readers therefore observe a different isPending/latest answer solely because the parked computation also probed. A pending flight's re-run drops that stamp so it stays on the transition's write; a probe in the body re-stamps only after those plain reads.

Co-authored-by: Grok via Cursor <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@changeset-bot

changeset-bot Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 4dabb14

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

This PR includes changesets to release 12 packages
Name Type
@solidjs/signals Patch
test-integration Patch
@solidjs/web Patch
@solidjs/babel-plugin Patch
@solidjs/compiler Patch
@solidjs/diagnostics Patch
@solidjs/element Patch
@solidjs/h Patch
@solidjs/html Patch
solid-js Patch
@solidjs/universal Patch
todos-server-example 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

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Size (brotli, eager entry chunk)

scenario head vs base minified vs base minified vs recorded cap lazy chunks (not counted)
signals: core floor (createSignal/Memo/Effect/Root/flush) 7.45 KB +10 B (+0.1%) +21 B +23 B 7.45 KB ✅
signals: + createStore 14.69 KB +9 B (+0.1%) +21 B 0 B 14.71 KB ✅
signals: + isPending/latest 9.67 KB 0 B +21 B +21 B 9.68 KB ✅
app: render + one signal (the simple-app floor) 9.93 KB −4 B (−0.0%) +21 B 0 B 9.94 KB ✅
app: hydrating (no stores) with Show/For/Loading/Errored/lazy 17.89 KB −1 B (−0.0%) +21 B 0 B 17.93 KB ✅ lazy-page.js 0.04 KB
app: hydrating + every store primitive family 29.20 KB −50 B (−0.2%) +21 B +21 B 29.26 KB ✅ lazy-page.js 0.04 KB
app: CSR with Show/For/Loading/Errored/lazy 12.95 KB 0 B +21 B +21 B 12.96 KB ✅ lazy-page.js 0.04 KB
app: CSR, observe tier (same app on the observe artifacts) 14.55 KB +36 B (+0.2%) +21 B 0 B 14.56 KB ✅ lazy-page.js 0.04 KB
app: CSR, observe tier + attribution engine enabled 28.87 KB +65 B (+0.2%) +21 B +20 B 28.89 KB ✅ lazy-page.js 0.04 KB
app: compiled floor (one template, one text hole, one delegated click) 10.13 KB +16 B (+0.2%) +21 B +23 B 10.13 KB ✅
app: compiled CSR (JSX todo app: spread/merge/omit, events, class/style, keyed For, Show, Loading + lazy, store) 25.29 KB +30 B (+0.1%) +21 B 0 B 25.30 KB ✅ stats.js 0.18 KB
app: compiled hydrating (the same JSX todo app through hydrate(), compiled hydratable) 31.18 KB +8 B (+0.0%) +21 B 0 B 31.20 KB ✅ stats.js 0.20 KB
frames: eager client consumer (frames client + transport, lazy codec) 11.41 KB 0 B 0 B 0 B 11.41 KB ✅
page: base server components (hydrating + dynamic + frames + sf reference) 34.14 KB −16 B (−0.0%) +21 B +16 B 34.22 KB ✅ assets.js 0.78 KB, bind.js 1.83 KB, decode.js 6.23 KB, lazy-page.js 0.04 KB, regions.js 0.80 KB, trace.js 8.25 KB, wire.js 0.93 KB
page: live server components (base + live/GET + action + isPending/latest) 37.91 KB −15 B (−0.0%) +21 B +95 B 37.94 KB ✅ assets.js 0.78 KB, bind.js 1.83 KB, decode.js 6.23 KB, lazy-page.js 0.04 KB, regions.js 0.80 KB, trace.js 8.24 KB, wire.js 0.93 KB
page: compiled base server components (the base page as JSX: templates with class/style/attributes/events, For/Show; no spread) 35.48 KB +80 B (+0.2%) +21 B +16 B 35.47 KB ⚠️ over by 11 B, 4 B minified headroom assets.js 0.78 KB, bind.js 1.83 KB, decode.js 6.23 KB, regions.js 0.80 KB, sc-comments.js 0.20 KB, trace.js 8.24 KB, wire.js 0.93 KB
page: compiled live server components (the compiled base page + live/GET + action + isPending/latest) 41.06 KB +10 B (+0.0%) +21 B 0 B 41.06 KB ✅ eager (counted): web.js 22.05 KB; assets.js 0.78 KB, bind.js 1.83 KB, decode.js 6.23 KB, regions.js 0.79 KB, sc-comments.js 0.19 KB, trace.js 8.25 KB, wire.js 0.93 KB
page: base + router (base page + @solidjs/router: createRouter, two routes, preload, useNavigate) 41.64 KB +55 B (+0.1%) +21 B +16 B 41.56 KB ⚠️ over by 81 B, 4 B minified headroom assets.js 0.78 KB, bind.js 1.85 KB, decode.js 6.23 KB, lazy-page.js 0.04 KB, regions.js 0.80 KB, server.js 1.02 KB, serverForms.js 3.58 KB, trace.js 8.28 KB, wire.js 0.94 KB
page: live + router (live page + @solidjs/router: createRouter, two routes, preload, useNavigate) 47.31 KB +26 B (+0.1%) +21 B 0 B 47.31 KB ✅ eager (counted): client.js 27.84 KB; assets.js 0.78 KB, bind.js 1.85 KB, decode.js 6.23 KB, lazy-page.js 0.04 KB, regions.js 0.81 KB, server.js 1.02 KB, serverForms.js 3.30 KB, trace.js 8.23 KB, wire.js 0.94 KB
server: floor (getRequestEvent + isServer) 1.33 KB 0 B 0 B 0 B 1.34 KB ✅
server: renderToString (the server-render floor) 20.40 KB 0 B 0 B +4 B 20.42 KB ✅

⚠️ Over the brotli cap within the minified allowance (passes)

  • page: compiled base server components (the base page as JSX: templates with class/style/attributes/events, For/Show; no spread): over brotli cap by 11 B; minified 110,171 B vs 110,155 B recorded with the cap (+16 B) — 4 B of the 20 B minified allowance left; +21 B minified over this PR's base
  • page: base + router (base page + @solidjs/router: createRouter, two routes, preload, useNavigate): over brotli cap by 81 B; minified 129,885 B vs 129,869 B recorded with the cap (+16 B) — 4 B of the 20 B minified allowance left; +21 B minified over this PR's base

Bundled with Rolldown (what Vite ships), brotli q11, decimal KB. A scenario fails only when it is over its brotli cap and its minified size is more than 20 B over the minified recorded with the cap; over the cap within that allowance is brotli layout noise and passes with a warning. Caps and their recorded minified in scripts/size/scenarios.js; the floor and page caps in floor-caps.json are frozen (lower only, or Size-Exception: in the PR body). npm run ratchet lowers caps per RC; it never raises one (scripts/size/README.md).

@coveralls

coveralls commented Oct 8, 2026 •

Copy link
Copy Markdown

Coverage Report for CI Build 37877708549

Coverage remained the same at 76.014%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 1312
Covered Lines: 1061
Line Coverage: 80.87%
Relevant Branches: 1031
Covered Branches: 720
Branch Coverage: 69.84%
Branches in Coverage %: Yes
Coverage Strength: 26.86 hits per line

💛 - Coveralls

@codspeed

codspeed Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 195 untouched benchmarks


Comparing fix/3884-superseded-transition-verdicts (8b2a841) with next (20db6a2)

Open in CodSpeed

ryansolid and others added 4 commits October 8, 2026 19:10
Hello world is 9,936 B brotli, still under 10KB. The floors and inline
scenarios this change pushed over their caps are set to that measurement.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Measured on next after the loading-wake landing. Caps match that
brotli size, rounded up to 0.01 KB. Hello world stays under 10KB.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ryansolid
ryansolid merged commit f6905dc into next Oct 9, 2026
7 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