Repository navigation
fix(signals): a pending error-boundary output read only behind fallbacks holds nothing (#4638) - #4639
Conversation
…cks holds nothing (#4638) Two sibling `<Loading on={sel()}>` boundaries, one reading its async through an error boundary built in the component body: after a `sel` write neither showed its fallback. The error boundary's output passes the pending by (A6) and is a CONFIG_REDERIVE frame reader in `blocked`; it is created outside the Loading, so `hiddenBy` never saw the fallback its only reader is behind, and it held the transaction the sibling's forwarded pending opened. A boundary output is transparent: hidden when every reader of it is (F-4, D-14). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117BoWNSSgtemsSYsaX2hXE
🦋 Changeset detectedLatest commit: f7b13c2 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 |
…4638 spec The public entry keeps createErrorBoundary @internal, so its types live on solid-js/internal; the type-check step failed with TS2305. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117BoWNSSgtemsSYsaX2hXE
|
The red Size check comes from this fix's growth: +87 B minified, and at most +63 B brotli, on the Loading/Errored scenarios. The maintainer accepted that growth. Core and hello world are unchanged. The scenario caps get re-recorded in the ledger PR after the next merge batch, so this PR leaves scripts/size alone. The earlier Solid CI failure was this PR's own: the web spec imported Generated by Claude Code |
Merging this PR will not alter performance1 warning
Comparing Footnotes
|
Coverage Report for CI Build 41568253234Coverage remained the same at 78.205%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
…ng-on-errorboundary
|
I don't think the CodSpeed flag comes from this change. Generated by Claude Code |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117BoWNSSgtemsSYsaX2hXE
…ng-on-errorboundary
Claude-Session: https://claude.ai/code/session_0117BoWNSSgtemsSYsaX2hXE Co-authored-by: Claude <noreply@anthropic.com>
What was wrong
Two sibling
<Loading on={sel()}>boundaries. The first reads an async memo throughuntrack(() => createErrorBoundary(() => detail(), () => null)), built in the component body. The second reads a plain async memo keyed bysel. Afterselect(1)neither showed its fallback, and the old content stayed until both answers landed.Root cause: the error boundary's output passes the pending by (A6), so it is
STATUS_PENDINGwithCONFIG_REDERIVE.blockedBy(scheduler.ts) counts that as a frame reader ("a boundary showing content forwards the pending: transparent, its readers hold through it").onScreenaskshiddenBy(output), andhiddenBywalks the output's own boundary chain. The error boundary was built outside the<Loading>, so that chain had no fallback, and the output counted as on screen even though its only reader is the render effect under the re-armed Loading, which is behind the fallback. With one boundary this didn't show, because no transaction opened. The sibling's content goes pending before itsonpass re-arms it, so the pending is forwarded and opens a transaction. The error boundary's output then held that transaction, which kept both re-arm swaps off screen.Rules check
detail:1holds nothing, and both re-arms show their fallbacks withsel's frame (D-15: "now, when nothing else on the page waits on that change"). Expected frame:loading #1 loading comments.<Loading>over the same data, a header that renders it)… Only a reader whose read throws holds." Where the error boundary is created doesn't make it a reader. This PR pins the D-16 counterpart: the same page with a header render effect outside that also reads the error boundary. That frame still holds untildetaillands and the fallback is never seen.onreadssel()directly.record/prune, not byhiddenBy.Change
hiddenBy(boundaries.ts): a non-effectCONFIG_REDERIVEnode (a boundary output) is transparent. It counts as hidden when every reader of it is hidden. A boundary output read in the open, or by a memo in the open, still holds as before.Size: about +60–80 B minified and roughly +30 B brotli on rows that include boundaries.ts (the Loading/Errored rows). Hello world doesn't import boundaries, so the compiled floor is unchanged. CI will report the real numbers.
Tests
packages/signals/tests/loading-on-errorboundary-sibling-4638.test.ts. It covers plain and error boundary, with and without a sibling, plus the D-16 header counterpart. Without the fix, the error boundary + sibling case fails.packages/web/test/loading-on-errorboundary-sibling-4638.spec.tsx, which mirrors the issue. It fails without the fix withReceived: "Crash comments 1".Fixes #4638.
Generated by Claude Code