Repository navigation
fix(signals): a new async memo created over a hold lands its first answer into it (#3800) - #3881
Conversation
…swer into it (#3800) A memo created while a transaction holds a value, whose first pass reads that value and returns a promise, was a loading source the seam does not hold (plan sec. 15.2), so its answer landed as mainline commit #0 and its readers showed the held future beside the committed page. The same memo with a synchronous first answer is born held (A29). The first pass now records the transaction it was born into under the born-held arm's conditions (a joined read, or a verdict lane's mount reading a staging), and its first landing is staged into that transaction while it is live. The hold never waits for the first load (the direction rule, #3820). Maintainer clarification (2026-10-07): the #3869 ruling covers new memos that don't read async; a new memo that reads the held world is held, async first answer included. Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
🦋 Changeset detectedLatest commit: dfac67d The changes in this PR will be included in the next version bump. This PR includes changesets to release 12 packages
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 |
Size (brotli, eager entry chunk)
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 |
… its hold (#3800) Raises seventeen caps to CI brotli + 10 B with their recorded minified, from Size run 37684843282 against next @ dafad1d — the frozen core, simple-app, hydrating and live-page floors among them. Accepted by the maintainer 2026-10-07 on the condition hello world stays under 10 KB (CI: 9,914 B). Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Coverage Report for CI Build 37685484746Coverage remained the same at 76.43%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
Merging this PR will degrade performance by 2.73%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ❌ | memo + sync render effect only (reference) |
27.7 ms | 32.5 ms | -14.76% |
| ⚡ | createStore setter: delete + set one root key (#3044 overlay) |
2 ms | 1.8 ms | +10.99% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing fix/new-async-memo-held-3800 (dfac67d) with next (dafad1d)
Fixes #3800. Thanks to @GabbeV for the minimal primitives-only repro and the two controls (a synchronous first result, and no
flush()before the mount), which isolated the async first-result path.What happened
countis written to 2 and held by an existing async derivation. After an explicitflush(), a new memo returningPromise.resolve(count())reads the held 2. Its scheduled render effect loggedchild 2while the parent was still atparent 1 1. The same memo returningcount()synchronously stays held until the commit.Root cause
recomputeruns only when the pass returns a value. An async first answer throwsNotReadyError, so the arm never runs.setSignalas mainline commit #0. The child's render effect publishedchild 2beside the held page.The fix
recompute(core/core.ts): a first pass that goes pending under the born-held arm's conditions records the transaction it was born into on its extension (_bornIn). The conditions are: it read a held node, or it is a verdict lane's mount that read a staging of the flush (L2: mount triggered by latest() shows content beside the committed value #3851).handleAsync(core/async.ts): the first answer is staged into that transaction (holdNode) if the transaction is still live (liveTx, a new helper incore/scheduler.ts). The seam's park check reuses the helper.This is the
fix/create-time-holdsencoding (91e474506), ported to currentnextand trimmed.Maintainer clarification (2026-10-07): the #3869 ruling ("new memos created don't need to be a part of it") covers new memos that don't read async. A new memo that reads the held world is held, whether its first answer is a value or a promise.
Spec
SPEC-ASYNC-SEMANTICS.md, direction rule: the New async memo reveals a held signal update early #3800 entry ("a first load derived from a hold lands into it") now describes the shipped mechanism and its pins instead of "Pending".SPEC-ASYNC-SEMANTICS.md, A29: the maintainer's dated clarification, quoted, sits next to the fix(signals): seat a first pass in its creator's lane, never a verdict lane (#3835, #3851) #3869 amendment.RULES-INDEX.mdregenerated.Tests
packages/signals/tests/issue-3800-repro.test.tsflush()waits for the hold, thenparent 2 2andchild 2reveal together.flush()controls: the async child stays held; the sync child shows the committedchild 1(A28).fix/create-time-holds: the first load lands into the hold; under a freshLoadingthe fallback shows until the commit; a slower first load lands after the hold as its own commit; under an action's hold, the first load waits for the action and never the reverse.packages/signals/tests/verdict-mount-first-pass-3851.test.ts: "same with an async content memo" (the async twin of L2: mount triggered by latest() shows content beside the committed value #3851; onnextit showedcontent 1besidex = 0).next, 5 of these fail. With the fix: signals 5068 passed, 26 expected fail, 0 failing; solid-js 833 passed; web suites green. No existing pin flips. fix(signals): a Loading mounted under a hold shows its fallback (L2 regression of #3540) #3824'sisPending(view) === falseand fix(signals): seat a first pass in its creator's lane, never a verdict lane (#3835, #3851) #3869's "a boundary mounted by the hold" pins still pass.Size
Local harness against
next(dafad1db3); CI numbers follow in a comment.Size-Exception: approved 2026-10-07 by the maintainer on the condition hello world stays under 10 KB.
Public API changes
None.