Repository navigation
fix(signals): an Errored heals when nothing reads the failed source any more (#4644) - #4653
Conversation
…ny more (#4644) An error boundary waits on the source that threw. When the reader it caught was its own tree (the error reached it only through memos, e.g. a Show/Match condition), the tree re-ran clean after navigating away but prune kept it because the source itself was still errored. A source read by nothing now settles: no live reader holds the error (D-39, F-3, F-12). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117BoWNSSgtemsSYsaX2hXE
🦋 Changeset detectedLatest commit: 3f8be1d 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)
❌ Fails the gate
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 |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117BoWNSSgtemsSYsaX2hXE
Coverage Report for CI Build 41606735688Coverage remained the same at 78.205%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
Merging this PR will improve performance by 9.43%2 warnings
Performance Changes
Tip Curious why performance improved? Comment Comparing Footnotes
|
prune kept an error boundary's reader while the source that threw was errored and subscribed by anything at all. A memo hoisted outside the boundary, a derived store or projection, or a reader under another Errored kept the source subscribed, so the fallback stayed up after every reader under the boundary was gone. D-39: the boundary waits on what threw only while a reader that caught the error is alive. The source now keeps the reader only while that reader still reads it, directly or through derivations (#4644). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117BoWNSSgtemsSYsaX2hXE
…4644) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117BoWNSSgtemsSYsaX2hXE
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117BoWNSSgtemsSYsaX2hXE
What was wrong
An error boundary waits on the source that threw (
StatusError.source), andprunekept a reader while that source was still errored. When the error reached the boundary only through memos (a<Show when>/<Match when>condition, a memo of the condition, a keyedShow), the reader the boundary caught was its own tree. Navigating to another branch disposed every memo that read the failed source and the tree re-ran clean, but the source (owned outside the boundary) was still errored, soprunekept the tree forever and the fallback stayed up. A render-effect reader recovered only because it was disposed (#4373's path).The first version of this PR asked whether anything still subscribed to the source. That left the same bug when something outside the boundary still reads it: a memo hoisted above the
<Errored>, a derived store orcreateProjectionbuilt from it, or a reader under a different<Errored>(a header count). None of those caught this boundary's error, but each kept the fallback up.nextbehaves the same way.Rule
D-39's heal clause: "A fallback that no live reader holds heals: the boundary waits on what threw only while a reader that caught the error is alive, so when the last one is disposed … the content shows again — what a fresh render with the same inputs shows, since nothing on it reads the error." F-3: a reader that is no longer the display holds nothing. F-12: the error fallback is a fallback. The neighbouring D-39 cases are unchanged: a re-ask of the errored source, or a pending read directly under the
<Errored>, keeps the error up until its answer, and a recovery held in an action reveals with its hold (D-41).Change
prune(packages/signals/src/boundaries.ts): the errored or pending source keeps an error boundary's reader only while that reader still reads it, directly or through derivations wherever they live (a hoisted memo, a derived store's derive, a loading boundary's output). A smallreads(r, s)walks the reader's deps. The reader's own status still keeps it, as before. A reader under another boundary, or a derivation that only something outside this boundary reads, no longer counts. For loading boundaries the reader and the source are the same node, so the walk never runs there.Size: the first version was +8 B minified (not +12 as this body said). This version adds the deps walk, an estimated ~+60–80 B minified in the boundaries chunk (well above 20 B; see the CI size report for the real numbers). The walk is only in the boundaries chunk, so the core path and hello world should not change. It runs only for an error boundary's reader whose own status has cleared while its source is still errored or pending, and it stops at the first path to the source.
Tests
packages/signals/tests/errored-heal-memo-reader-4644.test.ts: memo-only reader, effect reader, and a "kept" row where another live reader under the boundary keeps the error up. New rows read the failed source through a hoisted memo, a derived store, a projection, a reader under another error boundary, and a sync throw through a hoisted memo. Each keeps the error while the list is mounted, including after another reader settles and triggers a prune, and heals when you navigate away. The other-boundary row keeps its own fallback, and the sync row shows the error again when you navigate back. All five new rows fail on the previous head.packages/web/test/errored-heal-memo-reader-4644.spec.tsx: the issue's matrix (Show condition, Show of rows, keyed Show, Switch/Match, memo of the condition, text, For), with and without<Loading>. New rows: a derived memo outside the<Errored>, a derived store outside it, and a header reader under another<Errored>.Erroredheal, recovery and retry specs.Fixes #4644.
https://claude.ai/code/session_0117BoWNSSgtemsSYsaX2hXE
Generated by Claude Code