Repository navigation
fix(signals): a staged error reaches the memos and derived stores that derive from it - #4456
Conversation
…-41) An error a held node's pass (or flight) found is staged with the frame (CONFIG_STAGED_ERROR), but nothing told its dependents: a staged value re-runs them (insertSubs), a staged error took the errored branch, which leaves notification to a publish that only comes at the commit. A memo over it kept its old value for the rest of the hold, so `latest()` of it returned that value where `latest()` of the source threw the proposed error (D-22), and `isPending()` of it read false (D-23 iii). The settle walk stageError already runs now marks the derivations it reaches as carrying the staging (`stageMark`), the staged twin of propagateStatus's error mark: marked, not re-run (a re-run would replace the frame a boundary's content owns, where a published error only marks), queued so the seam holds them with the frame, and walked on so their own verdict readers re-derive. A pending mark on a node carrying a staged error re-derives it through propagateStatus's verdict branch, so a source that re-asks in the same hold does not publish the old error (#4109). Only verdicts read a derivation's staging before its commit, so all of it runs with the verdict engine alone: the compiled floor is unchanged. Found by the async-gen rerun on next 5e68925 (E2: latest and isPending through an identity memo). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
The marks run with the verdict engine alone: +109 B minified on the isPending/latest bundles, 0 B on the floors. Raised the non-frozen caps that crossed at measured + 10 B rounded up to 0.01 KB, recorded minified = measured (isPending/latest, the compiled live page; the observe tier with attribution is brotli layout only, +0 B minified). The live page's frozen floor stays under its cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
🦋 Changeset detectedLatest commit: 74f3771 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 41094895847Warning 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 improve performance by 12.34%2 warnings
Performance Changes
Tip Curious why performance improved? Comment Comparing Footnotes
|
|
I don't think CodSpeed's −13.8% on The base ( Generated by Claude Code |
A derived store over a memo whose error is staged with a hold now shows the staging to its readers: latest() of the store throws the proposed error and isPending() of it is true until the hold commits, as for a plain memo (D-41). The derive is marked like any other derivation and its family is told the error status, so the family's readers re-derive from it (#4189). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
The derived-store family wake of a staged error adds 6 B minified over the memo marks; on the live server-component page it lands at 38,799 B brotli, past the frozen floor. Raised the live page's frozen floor to 38.81 KB (recorded minified 117,343) and live + router to 48.45 KB (recorded minified 142,747), at measured + 10 B rounded up to 0.01 KB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
|
On 4fce711, the "arrow in template" bench is back to unchanged. A different one is flagged now: The new Generated by Claude Code |
…-x1ore2-staged-err-derive # Conflicts: # scripts/size/scenarios.js
…ing it D-41: a derivation that reads a staged error throws it, as it would read a staged value: its own pass errors and stages with the same frame. The earlier change marked every derivation under a staged error with that error without running it, so a memo or derived-store derive that catches the error showed an error it never threw for the whole hold, and a derived store's keys that never read the source threw it too (D-30). Read only through latest(), both ended errored after the commit. The settle walk now re-runs those derivations; a pass that catches answers with its own value. A boundary's output and tree, async memos, effects and nodes that own children (a boundary's content, #3945) are left to the commit's publish as before. A projection's derive stages its pass's error like a memo. A published error no longer marks a node that holds its own pass's answer to the frame, which also fixes a catching memo ending errored when the error's flush has no hold. The silent-recovery pin counts the dependent's own errored pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
…-x1ore2-staged-err-derive
Over next @ 563b6b0: the settle walk's re-run of a staged error's derivations is +60 B minified with the verdict engine (isPending/latest 11.10 -> 11.12 KB, recorded minified 30,656); the published-error check is +2 B minified everywhere (createStore 14.95 -> 14.98 KB, recorded minified 43,926, which next already measures at 43,924). Caps at measured + 10 B rounded up to 0.01 KB. Every frozen floor stays under its cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
…-x1ore2-staged-err-derive # Conflicts: # scripts/size/scenarios.js
This reverts commit ae09a9f.
…of carrying it" This reverts commit b8af6b9.
…-x1ore2-staged-err-derive
The marks run with the verdict engine alone: +115 B minified on the isPending/latest bundles, 0 B on the floors. isPending/latest 11.12 -> 11.16 KB, the live server-component page's frozen floor 38.85 -> 38.90 KB, compiled live 42.73 -> 42.81 KB, each at measured + 10 B rounded up to 0.01 KB with recorded minified = measured; live + router re-records its minified only (still under its cap). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
…-x1ore2-staged-err-derive # Conflicts: # scripts/size/scenarios.js
isPending/latest 11.12 -> 11.17 KB (30,771), compiled live 42.75 -> 42.79 KB (125,655), live + router 48.44 -> 48.51 KB (142,918); the live server-components floor keeps its 38.85 KB cap and re-records minified 117,376 -> 117,514 (Size-Exception in the PR body). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
#4519) * fix(signals): link a store reader to its derive's staged error (#4517) A derive marked with a staged error (`stageMark`, #4456) carries no STATUS_ERROR until the commit, so `pullFamily` took it for a settled derive and pulled it untracked. The pull threw the staged error before the reader read any leaf, so the reader subscribed to nothing in the family: when the derive's source re-asked and landed, neither the wake nor the settle reached it. An `<Errored>` mounted in the change that errored its content stayed on its fallback over a keyed `createProjection`, while the same read of the memo recovered. A reader now links to a derive carrying a staged error as it does to a pending or errored one, and re-runs at the landing. The render-effect early return in that branch is for a derive with a flight up only: a staged error is an answer the frame reader has to meet (D-41; the #3887 pin). Pins: packages/signals/tests/errored-born-staged-error-projection-4517.test.ts, packages/web/test/errored-born-staged-error-projection-4517.spec.tsx (the issue's repro). Size: +8 to +10 B minified on the store scenarios, every one under its brotli cap; the three over-cap warnings are +0 B over next. * fix: address review findings The pullFamily comment block keeps its original lines, with one line on the staged-error pull in place of the three that narrated the issue. The web spec's header keeps only the jsxImportSource and vitest-environment pragmas, as the neighbouring specs do. The changeset states the behavior that changed in two sentences.
Summary
Before: a held pass or flight that found an error staged it with the frame (D-41), but nothing deriving from it was told: no memo, no derived store. A memo over the errored source kept its old value for the whole hold.
latest()of the source threw the proposed error, yetlatest()of the memo returned the old value, andisPending()of the memo readfalse.The async generator found two cases on next 5e68925 (E2, E3):
m0throw.latest(m0)read in a leaf showed[ROOT-E], but the same read through an identity memo or a derived store showedlm0:3.isPendingof the memo readtrue, butisPendingof an identity memo or derived store over it readfalse.After: a derivation of a staged error carries that error, staged with the same frame (D-41). This holds through any depth of memos and through a derived store's readers.
latest()of the derivation throws the proposed error, andisPending()of it istrueuntil the hold commits, the same as for the source memo.How
All the changes are in
packages/signals/src/core/async.ts. A derivation is marked, not re-run.stageErroralready runs the settle walk over the node's dependents. When that walk meets a derivation of a staged error,stageMarknow gives the derivation the staging and queues it.fam?.st(STATUS_ERROR)).propagateStatusre-derives a marked node on a pending mark. When the source asks again, the error the node carried is no longer the answer ([2.0 / next] A visible button's condition throws in its click handler during a held update #4109).Marking adds no derivation runs. Re-running each derivation would add one run per derivation per staged error, and a whole chain would re-run. Ryan chose the mark for that reason. Catching an error with try/catch in a derivation is not a pattern Solid supports, so this PR doesn't try to make a catching memo answer differently during the hold.
Size vs next @ b773146 (after #4464 and #4472), measured locally (linux). Minified deltas are exact; brotli deltas are against the CI report for next and are approximate to ±5 B:
page: live server components(frozen floor)signals: + isPending/latestpage: compiled live server componentspage: live + routerlive + router's brotli delta is layout noise from the same +116 B of minified code (it was +30 brotli over next @ 131ceb8). Every brotli delta here is over the 20 B flag.Size-Exception: the live server-components page carries the D-41 staged-error mark that lets a derivation of a staged error read like its source (+116 B minified, about +49 B brotli). Its cap is unchanged at 38.85 KB, and its recorded minified rises 117,376 → 117,514 so the floor gates later PRs on their own bytes. Correctness.
How did you test this change?
New
packages/signals/tests/staged-error-derivation.test.tscoverslatestandisPendingof a derivation in a hold, a two-level errored chain, and the [2.0 / next] A visible button's condition throws in its click handler during a held update #4109 supersede through a memo.packages/web/test/async-gen/e2-staged-error-derivation.spec.tsande3-store-staged-error.spec.tsreplay the minimized generator scenarios. [2.0 / next] A keyedcreateProjectionover a memo stops reporting pending and swallows later errors #4271 stays green.Suite results:
Async generator, seeds 3289 and 1158, 200 cases each: 0 findings, and traces match next in all 400 cases. The seat shadow adds no mismatch classes over next.
Found by the async-gen rerun on rc.20 (findings #6/#15, #8).
🤖 Generated with Claude Code
https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2