feat(signals,solid,web): error hooks name where an error was thrown apart from where it was met - #3518
Merged
Conversation
🦋 Changeset detectedLatest commit: 038470f The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 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 |
Coverage Report for CI Build 35260411368Coverage remained the same at 71.304%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
Merging this PR will not alter performance
Comparing Footnotes
|
…part from where it was met `ownerPath` on `ClientErrorContext`/`ServerErrorContext` is now where the error was THROWN — the labels up the owner chain of the computation that threw — with the boundary's chain as the fallback when the throw crossed nothing the runtime could name. `boundaryPath` is where it was MET: the `<Errored>` that rendered its fallback, or the `<Loading>` that shipped the rejection (`handling: "client"`). Before, both sides reported the boundary's chain, so every component under one boundary grouped into one path — too coarse for the tier the hook exists for (minified prod, no source maps). Client: the engine's `StatusError` wrapper already names the thrower (`source`, made at the first landing, kept downstream); `notify` passes it through. Server: `runWithOwner` and the memo pull's inlined twin stamp the error into a WeakMap as it escapes (first wins, object errors only, `NotReadyError` skipped); `reportServerError` reads it. The `SSR_RENDER_ERROR_CONTAINED` finding follows: `ownerPath` locates the throw, `data.boundaryPath` names the boundary beside `data.boundary`. Hydrating cap 19.85 -> 19.90 KB (+41 B over a baseline 1 B under its cap). Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Record bundle measurements after rebasing thrower and boundary paths over current next.\n\n— GPT-5.6 Sol via Cursor Co-authored-by: Cursor <cursoragent@cursor.com>
ryansolid
force-pushed
the
error-thrower-path
branch
from
September 17, 2026 18:41
7c20ab1 to
038470f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A C6 semantics refinement, surfaced while building the Sentry adapter against the linked build: both error hooks (and
SSR_RENDER_ERROR_CONTAINED) reported only the boundary's owner path, so every component under one<Errored>grouped into one path — too coarse for issue grouping in exactly the tier the hook exists for (minified production, no source maps).The two facts
ownerPath— where it was thrown. Labels root-first up the owner chain of the computation that threw: component labels and named primitives (the compiler's inner memos ride along by their default name). Falls back to the boundary's chain when the throw crossed nothing the runtime could name.boundaryPath— where it was met (new). The same labels up the chain of the<Errored>that rendered its fallback (client and server), or the<Loading>that shipped the rejection (server,handling: "client"). What broke, and what the user saw.<Loading>needed no special rule: on the client it doesn't catch (the rejection passes through to the nearest<Errored>, which is the met point); on the server it's the boundary whosehandlingthe context already names.Mechanics
StatusErrorwhosesourceis the thrower, and keeps that instance as the error propagates;CollectionQueue.notifyalready had it.reportClientErrortakes it as a third argument.runWithOwnerand the memo pull's inlined twin stamp the error into aWeakMap<object, Owner>as it escapes (first wins, object errors only,NotReadyErrorskipped).reportServerErrorreads it. Component bodies, effects and async attempts all run through those scopes, so["<App>", "<Loading>", "<Child>"]comes out for the fragment case without a per-site change.SSR_RENDER_ERROR_CONTAINEDis located at the throw;data.boundaryPathnames the boundary besidedata.boundary.The
"boundary"record is unchanged: it is about the boundary, and joins the finding by id.Verification
signals 2760 · solid 623 + 825 types · web 1001 + 185 types. Hook specs on both sides and the server finding specs assert the split (
<App> › <Errored> › <Bad>thrown /<App> › <Errored>met). One cap: hydrating 19.85 → 19.90 KB, +41 B for the second path over a baseline that sat 1 B under its cap; the other scenarios moved within noise. RFC 03/08/12 updated; changeset for@solidjs/signals,solid-js,@solidjs/web.Consumer side: the
@sentry/solid-2draft (insentry-javascript, unpublished until an rc carries this) tagssolid.ownerandsolid.boundaryfrom the pair.