Repository navigation
fix(signals): an effect's error boundary keeps no superseded staged error (#4527) - #4533
Conversation
…rror (#4527) Here is the case: an action re-asks a derived store and a memo, so its transition stays open. The store's re-ask is rejected (staged, D-41), and a second re-ask answers before the commit. An effect that created `createErrorBoundary(() => store, ...)` in its compute got the fallback at the commit and never ran again. - store: handleAsync's untracked shape probe of a returned proxy pulls the derive. When that pull throws a staged error, it now links the pass, as a tracked read does (#4519). The memo carries the error and stays reachable by the re-ask and the answer that supersede it. - boundaries: prune's "fresh boundary keeps its fallback until the reader commits" clause (#3540) applies to loading boundaries only. In an error boundary, a recovery that lands in the hold that staged the error joins that hold and reveals with its commit (D-39, D-41). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
🦋 Changeset detectedLatest commit: 9f050c9 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 41185007330Warning 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.134%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
…elsewhere - store: link the pass the staged error is thrown into whether the pull is tracked or not (a tracked read links anyway, and `link` dedupes). This drops the `tracking` test, and `c &&` replaces `c !== null`. - boundaries: an error boundary is born `_initialized` (the field is loading-only: no other error-boundary path reads it), so prune's #3540 clause stays as it was. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
|
The size gate fails on two rows at 9f050c9:
On store rows the fix costs +27 B minified: +24 B for the Generated by Claude Code |
Merging this PR will degrade performance by 6.48%1 warning
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
|
CodSpeed flags one benchmark, Generated by Claude Code |
…4537) Raises the compiled hydrating cap (32.56 -> 32.61 KB; minified fell 35 B, the brotli rise is mangle layout from #4494) and re-records minified on six rows that drifted toward the 20 B allowance. Floor caps are unchanged. Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 Co-authored-by: Claude <noreply@anthropic.com>
Before: an action re-asks a store and a memo, so its transition stays open. The store's re-ask fails, then a second re-ask answers before the commit. An effect that reads the store through
createErrorBoundary(() => issues, () => null), created in its compute, getsnullat the commit. That is the fallback for the failure the second answer replaced. The effect then never runs again, while the screen shows the new rows.After: the effect gets
len2at the commit, matching the screen. Nothing sees the superseded failure.Fixes #4527.
Root cause
The bug needs two holes together:
handleAsyncasks every object a memo returns whether it is a flight, by readingSymbol.asyncIteratorandthenunderuntrack. On a store proxy, that probe goes through the get trap, which pulls the derive (pullFamily). During the held transition the derive carries the rejection as a staged error (D-41), so the pull throws it into the boundary'schildrenmemo. The memo stages the error, but the read was untracked, so there is no link. The second re-ask (a pending mark, [2.0 / next] A visible button's condition throws in its click handler during a held update #4109) and its answer never reach the memo, and the commit publishes the stale error onto it for good.prunekeeps the fallback under Loadingonshould behave as a keyed <Show> around the boundary; a boundary mounted under a hold must not be born held (A29) #3540's rule that a boundary which has not shown content waits for the reader's commit. That rule is right for a loading boundary: what a fresh boundary catches holds nothing outside it. In an error boundary it lets the commit show an error the frame no longer carries, and the content arrives a round later.How
store.tspullFamily: a pass the pull throws a staged error into is linked to the derive, tracked or not. A tracked read already linked (fix(signals): link a store reader to its derive's staged error (#4517) #4519), andlinkdedupes. The pass carries the error (D-41) and stays reachable by the re-ask or answer that supersedes it.boundaries.ts: an error boundary is born_initialized. The field is loading-only: "has shown content", and no other error-boundary path reads it. So prune's Loadingonshould behave as a keyed <Show> around the boundary; a boundary mounted under a hold must not be born held (A29) #3540 clause no longer applies to error boundaries. A recovery joins the hold it lands in and reveals with that hold's commit (D-39, D-41).I tried one alternative and backed it out: skipping the shape probe for store proxies entirely. It broke
projection-proxy-then-array-4361, which relies on the probe's pending pull.Rules
Tests
packages/signals/tests/error-boundary-superseded-staged-error-4527.test.ts: optimistic and derived store ×createEffectandcreateRenderEffect. All 4 fail onnextand pass here.packages/web/test/error-boundary-superseded-error-4527.spec.tsx: the issue's repro, with its expected frames.test-types: cleanserver-functions-adapter-request.spec.tsx. It also fails without this change.Size (local, signals rebuilt from the same tree, before vs after)
The brotli swings in both directions on +3 B minified are mangle noise.
🤖 Generated with Claude Code
https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2