Repository navigation
fix(signals): createOptimisticStore(fn) passes an upstream memo's error to its readers (#4437) - #4457
Conversation
…rror (#4437) A derived optimistic store built its family by hand and left out the two links `createProjection` sets: the family's status hook (`fam.st`) and the derive's back-pointer (`node.fam`). A failed memo the derive reads reaches the derive by status propagation, not by a re-run of its pass, so without them the leaves' readers never learned the error (F-12). The store now builds its family through `createProjectionInternal`, with an `opt` flag and an optional derive, so both forms share one constructor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
🦋 Changeset detectedLatest commit: 2146f5c The changes in this PR will be included in the next version bump. This PR includes changesets to release 15 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)
|
Coverage Report for CI Build 41056290847Warning Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes. Coverage remained the same at 78.1%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
Merging this PR will regress 1 benchmark2 warnings
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
|
I don't think either of CodSpeed's two regressions comes from this PR.
The three Generated by Claude Code |
…-x1ore2-4437 # Conflicts: # scripts/size/scenarios.js
… once errorFamily re-ran every live reader of the family on each errored pass of the derive. A second pass meeting the same upstream error object - a held error's commit dirtying the derive under an optimistic store's action guess (the regression #4457 review found), or a reader's pull retrying an errored async derive beside an isPending probe (pre-existing in createStore(fn)) - re-ran readers already showing that error, so an effect's error arm ran twice (D-41: the arm runs once, at the reveal). A reader already errored with the very error being handed off is now skipped, as core propagateStatus skips one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
|
CodSpeed's one regression, Generated by Claude Code |
createStore and the compiled CSR app were over their caps on next after #4454 and #4457 landed together; raise both by the cap rule and re-record the minified of every non-frozen scenario that moved. Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 Co-authored-by: Claude <noreply@anthropic.com>
…once (#4463) * fix(signals): a derived store's propagated error reaches its readers once propagateStatus handed the derive's family a status without its error, so errorFamily's same-error skip could not apply and a sync derive over a sync memo re-ran its reader's error arm twice in one flush (#4457 review). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * chore(size): re-measure over next @ 131ceb8; type the family status error Raise the base + router and compiled hydrating caps (brotli layout from +2 B minified) and give StoreFamily.st its error parameter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 --------- Co-authored-by: Claude <noreply@anthropic.com>
…ad; seat rows D1''/L1''/V3'' (#4472) * fix(signals): a netted write read in a strict untrack is no staged read; seat rows D1''/L1''/V3'' #4452 made a signal's staging equal to its committed value (writes that netted, D-8) a read of the screen in `read`'s fast block. The reader-seat table still raised Fl on that read (rows D1 / L1 / D7, and V3 for a verdict reader), so the shadow's seat parted from the flags and every later read of the pass reported too ("world after the read", "verdict seat after the read", B2 "born held"): 8 INV-seat-3 classes (x59) in the signals suite, 7 in web, and 60 in the generator (37 / 35 / 43 at 2,000 cases x seeds 3289 / 91501 / 7, every pair). Bisected over every merge 7135a77..131ceb8: 0 before #4452 (66c6f56), 60 at it, none added by #4453, #4458, #4460, #4454, #4457 or #4455. The runtime follows the rule (D-8: a tick whose writes net to the committed value made no proposal), so the table was stale: rows D1''/L1'' (D7'' in a lane) and V3'' serve P and raise nothing. Shadow: 0 classes in signals, web and the generator. With the rows in, the shadow found the path #4452 missed: in dev a labelled `untrack` read (`strictRead`) takes the slow path, where `serve` still called `stagedRead`, so dev held the errored re-run that prod (fast block) does not. `serve` now skips it for a netted signal in dev; prod folds the test away (0 B on every scenario). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): gate serve's netted skip on the strict read, not on dev Only the strict untrack read skips the fast block in dev; a hydration snapshot or a promoted write reaches serve in dev and prod alike and keeps the staged-read mark. 0 B in prod. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 --------- Co-authored-by: Claude <noreply@anthropic.com>
Summary
Before:
createOptimisticStore(fn)built its derived-store family by hand and left out the projection's status hook (st: familyStatus,node.fam). When the derive read a memo that threw, the error never reached the store's readers. They kept showing the old value, and noErroredcaught the error (F-12).Separately, a derived store,
createStore(fn)included, could deliver one error to an effect twice. It happened when an async derive was read besideisPending, and, once the fix above is in, when an optimistic store's derive failed while an action's guess was in flight.After: the optimistic store builds its family through
createProjectionInternal, the same pathcreateStore(fn)uses. An upstream memo's error or pending state now reaches its readers the same way it does for a plain derived store. Each reader gets an error once, at the reveal (D-41).How
createProjectionInternalis exported and takes anoptflag. It also accepts a nullfn, for an optimistic store that isn't derived.createOptimisticStorecalls it instead of duplicating the family setup.errorFamily(fam, error?)skips a reader that is already errored with this exact error. This is the same check core'spropagateStatusmakes. The derive's own throw and its async rejection pass the error in.refresh()waiter's pull after the commit published the staged error; the other from a retried async pass rejecting again.undefined.Size vs next @ 5f07d04 (brotli / minified):
app: hydrating + every store primitive familysignals: + createStoreapp: compiled CSRapp: compiled hydratingNo caps change.
How did you test this change?
New
packages/signals/tests/optimistic-derive-memo-error-4437.test.ts. It fails onnext.New
packages/signals/tests/derived-store-error-delivered-once.test.tscovers three cases, each expecting the error once:isPending;createStore(fn)async derive read besideisPending.It also has four guard cases: a sync or an async derive rejecting with
undefinedstill reaches its reader, for both store kinds. The first three cases fail without the fix.Suites: signals 6,021 passed, web 195 files passed. Hydrate passes except
c09-no-phantom, which also fails onnext.Fixes #4437
Generated by Claude Code