github solidjs/solid solid-js@2.0.0-rc.9

pre-release5 hours ago

Patch Changes

  • 8bf04ea: New dev diagnostic ASYNC_STORE_SETTER: a store setter callback that returns a Promise now throws in dev (through the diagnostics channel) instead of being silently ignored. A store setter is a synchronous transaction — the draft closes when the callback returns — so setStore(async d => …) committed only the writes before its first await and lost the rest. Covers every store family with a user setter (createStore, createOptimisticStore, the derived store's manual setter); a derived store's own async compute is not affected. Store-specific: a signal may legitimately hold a promise, so setSignal has no such rule. Production is unchanged.

    Docs: the OBSERVE.exclude guidance in RFC 08 no longer suggests runWithOwner(panelRoot, …) for panel writes (that is a write in an owned scope, see #3512); the store's own nodes carry the excluded owner.

  • d826cd3: The client error hook — configureClientErrors({ onError }) from solid-js, and render/hydrate's onError option in @solidjs/web: the prod-tier seam through which an app, or an error monitor, hears the one failure nothing else can see — an <Errored> / createErrorBoundary collected it and renders its fallback. The twin of the server's configureServerErrors. An uncaught error is not this hook's: the halt (REACTIVITY_HALTED) hands its cause to the platform's reportError, the channel every monitor already listens on.

    Once per error object (a reset() re-collecting the same failure says nothing new; a primitive is reported per sight); ownerPath carries the component labels where the runtime keeps owner names; no return — the client has no wire to map for; a throwing hook is reported and ignored. A root's own hook wins over the ambient one for failures under it.

    Pay-for-use: the hook machinery (core/error-hooks.ts) is retained by createErrorBoundary or the app's own configureClientErrors import; a root's hook is parked on the root owner under the registered ROOT_ERROR_HOOK symbol (defined in the scheduler), so render retains nothing for an app that passes none. Core floor unchanged; apps with a boundary +~200 B.

    The server surface consolidates on the same name. onError on renderToStream/renderToString and on handleServerFunctionRequest is the server error hook ((error, context) => wire | void); onServerError is removed. A one-argument onError written for the old shape keeps working and now hears every handled failure — filter on context.handling === "failed" for the request-failing ones alone. New handling: "serialize" for a hydration value that would not serialize (what seroval's onError reported, for a render that passed one). With no hook anywhere, a failure that fails the request still reaches console.error.

  • cc0396b: _parent joins _name as a field signals' property mangling reserves — the two cross-package owner fields.

    Signals' prod and observe artifacts rename every _-prefixed property except a reserved list; the dev artifact (which the test suites run against) is unmangled. Two things read _parent across the package boundary and only worked in dev:

    • solid-js's client hydration walks owner._parent to the root to mark the hydration snapshot scope. In the built prod and observe artifacts the walk found nothing and marked the current owner instead, so computations created outside that owner's subtree during hydration read live values rather than the server snapshot.
    • The core's owner walks — ownerPath and OBSERVE.exclude/isExcluded — over solid-js's server owners. ownerPath had a server-side shim (located(), now removed); OBSERVE.exclude was a silent no-op for a server owner outside dev.

    Cost: ~40 B brotli on the prod app scenarios; the observe scenarios did not grow. Pinned from both ends: packages/solid/test/cross-package-fields.spec.ts checks the reserved fields survive in the mangled artifacts and scans the built client artifacts of solid-js, @solidjs/web and @solidjs/universal for any signals _ field that is not reserved; packages/web/test/server/server-owner-walks.spec.tsx runs ownerPath and OBSERVE.exclude over server owners against the built observe and development artifacts.

    Also: RFC 08 gains "Values in records — the PII surface", the complete list of record and finding fields that carry user data (value previews, interaction target text, navigation paths/params, data.error on the server error findings) for exporters that leave the process.

  • 53280e7: The error hooks and SSR_RENDER_ERROR_CONTAINED tell where an error was thrown apart from where it was met.

    ownerPath on ClientErrorContext and ServerErrorContext is now where the error was thrown: the labels root-first up the owner chain of the computation that threw — the component that broke — falling back to the boundary's chain when the throw crossed nothing the runtime could name. A new boundaryPath is where it was met: 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"). Before, ownerPath was the boundary's on both sides, so every component under one boundary grouped into one path. On the client the engine's status wrapper already named the thrower (StatusError.source); on the server the owner scopes stamp it as the error escapes. The SSR_RENDER_ERROR_CONTAINED finding follows: ownerPath locates the throw, data.boundary / data.boundaryPath the boundary.

  • 3154ed6: A signal written from onSettled or createEffect during hydration no longer strands the DOM it reveals (#3504). Two halves: a root created while hydrating marks the hydration snapshot scope itself, so a write during the root pass is held until the pass completes and then replays — previously the scope was marked lazily by the first hydration-aware primitive, so a <Show> condition created before that sat outside it and the write cascaded mid-claim. And a streamed boundary's resume window now claims only the subtree under that boundary: a write from the resumed content that reaches a signal above it (a route's onSettled adding a toast to a provider) re-renders that already-hydrated region as a client render — fresh nodes, live inserts — instead of claiming against the server registry, missing with a "Hydration key miss" warning, and rendering detached.

  • b298154: Move the runtimes' seams off the solid-js surface and behind solid-js/internal

    merge()/omit() returning lazy views (#3454) gave spread() and
    ssrElement() a protocol for reading props leaf by leaf instead of trapping
    through the proxy per key. Because @solidjs/web and @solidjs/universal
    depend on solid-js alone — never on @solidjs/signals directly, so an app
    holds exactly one reactive engine — every piece of that protocol went out
    through solid-js's main export: eleven names, mergeSources before them in
    #3325, on the public surface with no marking. None of it is API.

    The protocol (viewOf, mergeView/omitView, MergeView/OmitView,
    sourceKeys/sourceHas/sourceGet, hasStaticKeys, resolvedTable, the
    SOURCE_* kinds, SourceKind) now lives on a solid-js/internal subpath,
    along with the server-scope seams that were already @internal in JSDoc and
    consumed only by @solidjs/web (ssrHandleError, ssrScope,
    runInServerComponentScope, inServerComponentScope, creationStamp,
    getProjectionTrace, materializeContainerTrace). The names stay exported
    from the main entries at runtime, so the subpath shares one module state and
    the single-engine guarantee is untouched; stripInternal keeps them out of
    the generated declarations, so TypeScript no longer offers or types them.
    internal-surface.spec.ts pins the boundary in both directions.

    Also dropped from the entries: storeIsShallow, storeHasFamily,
    storeHasOptimisticFamily (leftovers of the gutted patch channel),
    storePath and $REFRESH (referenced only by @solidjs/signals's own
    internals), and NoHydrateContext (@internal, used only by solid-js's
    server code). Nothing in the repo consumed them.

    No runtime behavior change. merge, omit, and the rest of the reactive
    surface are unaffected.

  • 899c2c4: merge() and omit() are always lazy views, and props consumers read their leaves

    omit(props, ...keys) returns a live view of props for every input — a plain object included — instead of copying it with a getOwnPropertyDescriptor + defineProperty per prop. A predicate form hides keys by rule without enumerating first: omit(props, k => k[0] === "$"). merge() no longer builds an eager copy when its sources are plain objects: under Proxy it always returns an O(1) view over the flattened sources (a single non-function source is returned as is).

    The two compose flat. An omit() over a merge() carries one filtered view per flattened merge source, a merge() over an omit() takes the view record as a leaf, and nested omits fold their filters into one record. A component chain of merge(defaults) → omit(consumed) → merge(statics) → omit("as") — the shape headless-UI libraries render every element through — collapses to leaf views over the original objects, each with its accumulated filter, with no proxy layer left between the outermost spread and the author's props. merge() keeps the omitted keys hidden by construction (#3014) rather than by treating the omit as opaque. Construction cost drops 3–7× at depth 1–7; the SSR polymorphic-chain bench (#3448) runs ~2.4× faster.

    Reads stay cheap: a view over plain objects resolves a key → owning-leaf table once, on first read, and every get/has/descriptor is one lookup after that. spread() (DOM and universal) and ssrElement() read the leaves directly — never through the proxies' traps — and walk that table when there is one, so an effect rerun costs one read per key, as it did over the copy. Both proxies use a class target and one shared handler (no per-instance closures).

    A view over a store asks the store nothing but the read. Each source's kind (plain object, omit record, proxy, memo) is decided once, when the view is built, and carried beside it — every brand check on a Proxy is a trap (instanceof is a getPrototypeOf trap, as expensive as a store read), and store detection goes through $TARGET, a symbol the store's get trap answers on its fast path, never its generic tracked-read path. merge(defaults, store) constructs ~30% faster than the copy did and reads ~15% faster; omit(store) reads at parity.

    The views tell the truth: Object.getOwnPropertyDescriptor(view, key) reports a data descriptor only when the key is a data property of a plain leaf (the compiler's encoding of a static prop) and an accessor for a getter, a store key, or a memo source. Together with the new internal hasStaticKeys(), spread() now skips the children effect for static children behind omit/merge layers (#3388 through views).

    Behavior changes:

    • Writes to a merge() or omit() result are no-ops (they already were for the proxy forms). A caller that needs its own object copies it ({ ...merged }), and the copy carries no sources (#3384). @solidjs/html now collects its own props and spreads into one merge() at the end instead of assigning onto the result.
    • A data property on a source is read live through the view rather than snapshotted at merge()/omit() time.
    • Key order of a merged view is the merged order — every key at the position of the last source that carries it — matching ssrElement's array form.
    • Sources are treated as own-keyed; a key added to a plain source after merging is not seen (the copy did not see it either).
    • Enumerating a view through its traps (for…in, Object.keys, { ...view }) costs a trap per key, as any proxy does; the internal consumers avoid it. Environments without Proxy keep the copy paths.

    Internal helpers for consumers, exported from solid-js: viewOf(o), mergeView(o), omitView(o), sourceKeys(entry, kind), sourceHas(entry, kind, key), sourceGet(entry, kind, key), hasStaticKeys(o), resolvedTable(o), the SOURCE_* kinds.

  • 3ae9e92: OBSERVE.records — one records channel on both platforms (observe/dev tiers); frame records from both ends; the client "call" record; observeServerFunctionCalls removed

    • @solidjs/signals: OBSERVE.recordssubscribe(type, listener), observed(type), emit(type, event, live) — the channel every runtime record rides, created once per process and registered on globalThis under Symbol.for("@solidjs/signals/observe/records") so a second copy of the core (a bundled server build instrumented through --import) and wire layers bundled without a framework import reach the same listener sets. Listeners are snapshotted per emit; a throwing listener is reported and the rest run. Types: Records, RecordTypes (extends HostRecordTypes; both declared empty, for the runtimes to augment — one augmenter per interface), RecordType, RecordEvent, RecordLive, RecordListener. Folds out of prod. New OBSERVE.attribution.currentOrigin() (and the currentOrigin hook on AttributionHooks): the provenance a root write performed now would be stamped with — the interaction whose handler is running, the navigation/effect/action frame open, or inside a recompute the origin of the change that caused it — as the engine's own ChangeOrigin object, undefined when external or with no engine; for a runtime stamping a record of its own. The installed hooks are also registered on globalThis under Symbol.for("@solidjs/signals/observe/attribution"), the same reach-without-an-import the channel has.
    • solid-js: the "boundary" record moves from OBSERVE.server.records to OBSERVE.records (augmenting the core's RecordTypes). OBSERVE.server keeps only the trace slot; ServerRecords is gone.
    • @solidjs/web: the "invocation" and "frame" records move to OBSERVE.records (augmenting HostRecordTypes through solid-js). New "call" record (CallEvent, CallLive, CallListener): one per server-function call made from the browser, at the caller's settle — { id, at, durationMs, method: "GET" | "POST", outcome, status?, origin?, deferred? } with { args, response?, result? | error? } beside it; joins the server's "invocation" by id, and — through origin, the engine's own interaction/navigation object read at dispatch via currentOrigin() — the attribution engine's InteractionEvent / NavigationEvent / HoldEvent by identity, so an observer files the call under the click that made it without a time join. The "frame" record now has a client half: FrameEvent is FrameProducedEvent | FrameAppliedEvent, discriminated by side, same census on both; the client half (applyFrameResponse, one per stream in a response) adds address (the as remap) and outcome: "truncated" for a body that ended before complete, with live.response. Server census fix: regions counts html chunks addressed to a child frame id (the former count read a chunk type that does not exist), and shellMs is set by the stream's own shell only. The emitters and their wrappers fold out of the prod client artifacts behind the observe literal (prod applyFrameResponse and the server-function dispatch are the pre-existing functions, no extra frame or promise hop). The server-functions and frames client entries gain observe and development builds and export conditions (server-functions/dist/client.{observe,dev}.js, frames/dist/client.observe.js); the server-functions client is now built with its flags replaced in every tier (before, _SOLID_DEV_ there was an unreplaced truthy string).
    • Removed: observeServerFunctionCalls and the ServerFunctionCall / ServerFunctionRequestCall / ServerFunctionResponseCall types, from both server-function entries. Subscribe to OBSERVE.records "call" (client) or "invocation" (server) instead.
    • @solidjs/diagnostics (format v6): artifact.server is replaced by artifact.records: { boundary, invocation, frame, call } — always present, captured on both platforms including the browser bridge; types BoundaryRecord, InvocationRecord, FrameRecord (FrameProducedRecord | FrameAppliedRecord), CallRecord (with origin?: ChangeOrigin), ArtifactRecords replace the Server*Record / ArtifactServer names. JSONL: one line per record with type naming its table; the meta line's boundaryCount/invocationCount/frameCount become recordCounts: { boundary, invocation, frame, call }.
  • 328580f: omit() over a merge() is one record that holds the merge's record, not one leaf view per merge source

    An omit over a merge used to flatten at construction: one OmitView plus one combined hidden-key list per flattened leaf, and the next merge() copied those entries into its own arrays. On a component chain of defaults + omit + spread (Kobalte-shaped: merge(omit(merge(omit(props)))), four layers) that was ~19 records and as many list copies per element — the largest allocation of the render. The omit now holds the MergeView record itself (a new source kind, SOURCE_MERGE) and is one record however many leaves the merge has; a later merge() carries it as one entry, and a later omit() folds into it. Nothing is read through a proxy trap along the way: the entry helpers (sourceKeys/sourceHas/sourceGet, hasStaticKeys, descriptors, the resolved table) recurse into the record by function call.

    • Reads through the nesting are one walk per read (a nested entry answers presence and value together), and a nested record counts no reads of its own toward the table threshold — the view that was asked decides for the whole tree, and its table is collected in one pass over the leaves rather than one table per layer.
    • New @internal sourceOwners(source, keys, owners): every key of a props source — a plain object, a store, or a merge/omit view — appended in merged order with the object that owns it, in one pass, later sources moving a key to the end. ssrElement collects any spread that is not plain objects only (a view, a store, the array form with one among them) this way, so each attribute is one direct read of its owner — no in walk per key through the layers, no key list per leaf, no table, and no per-entry classification (pushEntry is gone).
    • An omit's $SOURCES never answers anything now (previously its filtered leaf views); consumers reach the record through viewOf and walk it as one filtered entry.

    Measured against next (interleaved, min of N, quiet machine): the tier-1 polymorphic-chain SSR harness allocates 12% less per row (14.6 → 12.8 KB) and is 2–6% faster across the interpreter, Sparkplug, Maglev and TurboFan tiers; the props-chain microbench builds 6–65% faster and builds+consumes 7–31% faster by depth and tier; the omit/merge micro-suite is flat or better in every shape. On the yak-bench SSR lanes with every yak piece on Solid primitives: +7% geomean, +20–36% on the component-composition cases (polymorphic-chain, tabs, multifile-composition), which brings those to parity with yak's hand-rolled runtime.

  • 61a114c: Server boundary records on OBSERVE.server.records (observe/dev tiers)

    • New "boundary" record: one per <Loading> boundary that waited during a server render, delivered when it settles — { id, at, durationMs, heldMs, passes, outcome: "settled" | "fallback" | "client" | "error", streamed, revealGroup?, ownerPath? }, with the thrown error beside it. id pairs it with SSR_RENDER_ERROR_CONTAINED; passes counts render passes (a sequential chain reads as 3+); under a <Reveal> group the record waits for the group's swap so heldMs measures how long finished content was held for its siblings. No clock is read without a listener, and the emitter folds out of prod.
    • The server records channel is OBSERVE.server.records.subscribe(type, listener) — the server twin of OBSERVE.attribution.subscribe(type, …). OBSERVE.server.invocations (unreleased) is renamed onto it: subscribe("invocation", …). The InvocationChannel type is gone; ServerRecords is the channel's interface.
    • Types now layer one augmenter per interface: @solidjs/signals declares ServerObserve empty; solid-js augments it with records: ServerRecords and trace: ServerTrace, declaring both; @solidjs/web augments those two through "solid-js". TraceSlot in @solidjs/web is now an alias of solid-js's ServerTrace. (Two augmentations of one re-exported interface through different module aliases merge order-dependently in TypeScript — one set was silently lost.)
    • @solidjs/signals exports ownerPath(subject) — the root-first component-label walk its diagnostics already make — so a record's ownerPath and the finding it pairs with come from the one walk.
  • 0d8347a: Server records reach the diagnostics artifact and the dev checks (server-dev-build-plan P4)

    • @solidjs/diagnostics artifact format v5: artifact.server: { boundaries, invocations } | null folds OBSERVE.server.records when the scenario runs under the server runtime — captureArtifact(() => renderToStream(…)) — one row per <Loading> boundary that waited and per server-function execution; null for client captures and the browser bridge. New exported types ArtifactServer, ServerBoundaryRecord, ServerInvocationRecord (mirrors of the runtime's BoundaryEvent/InvocationEvent; the package still depends on @solidjs/signals alone). JSONL egress adds boundary and invocation lines and the header counts.
    • InvocationEvent.boundary: a direct server-function call made during a <Loading> boundary's render pass carries that boundary's hydration id, the "boundary" record's id — the join between a boundary's wait and the calls under it.
    • Two dev checks derived from the boundary facts in ssrLoadingBoundary: ASYNC_WATERFALL with data.side: "server" (passes - 1 sequential flights; 2 → info, structured only; 3+ → console warn) and a new code SSR_CLIENT_CONTENT_MASKED (warn, ssr) for client-only content that surfaced only after a real server wait — the server's work discarded, the fallback shown for the wait. Dev tier only; the boundary clock now runs in dev without a listener.
    • solid-js's server emitFinding keeps info findings off the console (structured channel only), matching the core.
  • 7623ce1: Server diagnostics on OBSERVE.diagnostics; OBSERVE.server owned by solid-js's server entry

    The server runtime now reports on the same structured channel as the client. Findings — facts about a render, present in observe and dev builds — SSR_RENDER_ERROR_CONTAINED (a render error a boundary routed; data.handling is fallback, client, or failed — the structured face of what renderToStream's onError receives, on the process-wide channel), SSR_SUBTREE_ABANDONED (a failed fragment's pending descendants discarded), SSR_STREAM_ABANDONED (consumer cancelled or sink failed mid-render), LATE_HEADER_WRITE (recorded beside the existing dev throw / prod log), SERVER_FN_ERROR_SANITIZED (the original error the production wire replaced), and FRAME_MARKER_CORRUPTED from the frames client. Checks — dev-only guidance — convert every server console.warn to a code: SERVER_WRITE, REVEAL_IN_RENDER_TO_STRING, LAZY_ASSET_UNMAPPED, PRELOAD_DESCRIPTOR_INVALID, HEAD_TAG_INVALID, BEHAVIOR_CLAIM_DROPPED, and UNRECOGNIZED_INSERT_VALUE (now one code and a render kind on both platforms); ASYNC_OUTSIDE_LOADING_BOUNDARY on the server records with data.side: "server" before it throws. Server components are labelled for ownerPath (createComponent runs the body under a transparent <Name> owner in observe/dev — no hydration id consumed), so in <App> › <Page> reads the same on both sides, and the server entry installs the same repair-guide console footer as the client. Prod artifacts carry none of it; DiagnosticKind gains ssr, head, render.

    OBSERVE.server's objects (the invocation listener set, the trace-provider slot) are now created by solid-js's server entry, once per process under Symbol.for("solid-js/observe/server") on globalThis, rather than by @solidjs/web's module init: an observer's init() that imports only solid-js can subscribe and provide before the web runtime has loaded, and a host that bundles the runtime into its server build and instruments through a --imported module finds one listener set and one provider across both copies. The core keeps server: {}; the client pays nothing.

  • 56858e7: The server error hook — configureServerErrors({ onError }) from @solidjs/web, onServerError on renderToStream/renderToString and on handleServerFunctionRequest: the prod-tier seam through which a server integration hears every failure the runtime handles and may say what the client receives instead (sentry-integration-plan C6, #3468 part 3).

    Until now a production build reported only the failure that fails a request (renderToStream's onError); an <Errored> fallback rendered, a <Loading> fragment rejected and re-rendered by the client, and a server-function throw sanitized onto the wire were observe-tier findings only. The hook is called once per error object, at first sight, with where the failure was met — kind: "render" (fallback / client / failed, with the boundary's hydration id and the component labels when compiled in) or kind: "server-function" (thrown / channel, with functionId and direct for an in-process call during SSR) — and the request event. Its return is the wire value: rendered into the fallback, serialized for hydration, sent as the RPC error; undefined keeps the default policy (generic outside dev, fidelity in dev); ignored for failed. A direct server-function call that throws during SSR is reported once as the function's failure, and the boundary that contains it reuses the verdict. Two tiers as wrapInvocation has: ambient (registered on globalThis under Symbol.for("solid-js/server/errors"), shared by a bundled build and an --imported module) and per request, the latter winning. A throwing hook is reported and treated as silent. The hook fires in every tier; onError keeps its meaning.

    Internals: solid-js's server entry owns the verdict engine (reportServerError / ssrSanitizeError through solid-js/internal, one cache per error object); the SSR context gains errorPolicy and flushed; the _fr rejection reads its verdict at delivery. A rejected async source's own serialized rejection is encoded in the rejection's microtask, ahead of the boundary, and carries the default policy's value — the mapping reaches the boundary's record, which the hydrating client renders from; no tick is added to the error path. SSR_ERROR_SANITIZED carries data.wire.

  • af94f67: Server observe surface: OBSERVE.server and the invocation channel

    OBSERVE gains a server slot — an augmentable ServerObserve interface declared empty in @solidjs/signals (re-exported by solid-js), typed and emitted into by @solidjs/web's server runtime, so server-side observability consumers subscribe on the one OBSERVE object they already know from the client. The first channel is OBSERVE.server.invocations: subscribe("invocation", (event, live) => …) delivers one { id, direct, at, durationMs, outcome, deferred? } record per server-function execution — HTTP dispatch and direct SSR calls alike — when it settles, with the request event, request, args, and the result or the error as thrown beside it. Observers, not policy: any number of listeners, none able to alter the call; wrapInvocation remains the single policy hook.

    @solidjs/web now publishes observe-tier server artifacts (dist/server.observe.js, server-functions/dist/server.observe.js, frames/dist/server.observe.js) under the observe export condition, alongside the existing dev/prod pairs. The surface and every emit site fold out of the prod artifacts.

  • e87d694: { shallow: true } computed stores keep their leaves raw on every path (#3498). The projection draft no longer wraps a nested value in a draft proxy — leaf identity holds and a frozen leaf can no longer trip a Proxy invariant — and the loading shadow, its commit copy, the SSR draft and snapshots, and the hydration replay shadow copy only the root container instead of JSON-cloning the tree, which turned Date into a string, NaN into null, dropped undefined properties, and could not represent BigInt or cycles. Deep stores are unchanged.

  • 5426ffb: Fix a server-rendered <Errored> fallback hydrating dead (#3414). When the boundary's children threw synchronously on the server and the fallback was a zero-arg thunk (fallback={() => <Fallback />}), the thunk was handed back unresolved and unwrapped by the enclosing boundary — the client does that inside the boundary's flatten computed, the server did it inline under the boundary owner — so the fallback's hydration keys disagreed, its root element failed to claim the server node, and its event handlers and effects never attached. The server Errored and Loading boundaries now resolve their children's result in a virtual id scope mirroring the client's second computed; the error record stays at the boundary id.

  • 63560a1: componentNames now applies to SSR output. Under the option both compilers keep the createComponent call they otherwise inline to Comp(props) and pass the source tag name — createComponent(Comp, props, "Comp") — so the server runtime's observe/dev createComponent labels the owner and a server finding's ownerPath reads <App> › <Page> like the client's. Without the option (prod builds) SSR output is unchanged. @solidjs/vite-plugin already passes the option for its dev and observe postures, so app server builds pick this up with no config change.

    Fixes ssrScope under transparent owners: the virtual hole scope swapped the current owner's id counter, but content inside a hole resolves ids by walking past transparent owners, so with one in between (the server-component scope owner; now the labelled component owner) the hole's content took ids from the enclosing counter and disagreed with the client. The scope now swaps the nearest id-bearing owner.

  • 34287d8: SSR render failures reach the client sanitized (#3468): the wire policy the server-function handler has applied since #3113/#3116 now covers every SSR road a failure takes — the error an <Errored> serializes for hydration, a rejected async source serialized into the stream, a <Loading> fragment's _fr rejection, a frame stream's error chunks (the fragment's, a live hole's, the root's). Outside the dev build a plain thrown value is replaced with a generic Error ("Internal Server Error"); message, cause and own properties stay on the server. A "use server" function called in-process during SSR never touches the RPC wire, so before this the production page load shipped what that wire withholds.

    • solid-js (server): <Errored> sanitizes before rendering its fallback and serializes the same replacement, so fallback markup and the hydration record agree. A fallback printing err().message shows the generic message in production, as for a server-function failure. markSafeError (Symbol.for("solid.SafeError")) passes through with own properties; an Error reached as a value is data and passes as written (#3113's ruling). One replacement per original, however many roads it takes. New finding SSR_ERROR_SANITIZED (info, observe + dev; data.error the original), beside SSR_RENDER_ERROR_CONTAINED which carries the failure itself. ssrSanitizeError is exposed to the runtimes through solid-js/internal. Server findings now carry their ownerPath in the observe artifact too — the core's walk reads _parent under its own build's property mangling and found nothing on a server owner there, so the server entry locates its findings itself.
    • @solidjs/web (server): the hydration serialize funnel guards every channel it writes (a promise's rejection, an async iterable's thrown step); a fragment's terminal error reaches the _fr rejection and a transport sink's error chunk sanitized while the abandonment ledger keeps the original; the frame sink's root error chunk and the live-hole/live-attribute error chunks carry the replacement's message.
    • @solidjs/signals: the SSR_ERROR_SANITIZED code.

    The dev/prod line is the build variant: the development server artifacts keep full fidelity; production and observe sanitize.

  • Updated dependencies [8ff4803]

  • Updated dependencies [e9c464b]

  • Updated dependencies [0da94f9]

  • Updated dependencies [8bf04ea]

  • Updated dependencies [eaa7e33]

  • Updated dependencies [ebedb44]

  • Updated dependencies [a8a8949]

  • Updated dependencies [d80cd1f]

  • Updated dependencies [d826cd3]

  • Updated dependencies [cc0396b]

  • Updated dependencies [d2a36f5]

  • Updated dependencies [50323b4]

  • Updated dependencies [53280e7]

  • Updated dependencies [d7cb456]

  • Updated dependencies [a0d6dd2]

  • Updated dependencies [c3ae310]

  • Updated dependencies [6095955]

  • Updated dependencies [e80f241]

  • Updated dependencies [84adf0b]

  • Updated dependencies [5c1f01f]

  • Updated dependencies [a5d8eae]

  • Updated dependencies [14ded24]

  • Updated dependencies [25c5064]

  • Updated dependencies [1ce0f85]

  • Updated dependencies [05c7e21]

  • Updated dependencies [9da7f0a]

  • Updated dependencies [62b0a22]

  • Updated dependencies [27b24aa]

  • Updated dependencies [549f482]

  • Updated dependencies [d80cd1f]

  • Updated dependencies [76230f9]

  • Updated dependencies [347a5ca]

  • Updated dependencies [a8a8949]

  • Updated dependencies [632e45c]

  • Updated dependencies [75c5113]

  • Updated dependencies [899c2c4]

  • Updated dependencies [ca05917]

  • Updated dependencies [3ae9e92]

  • Updated dependencies [328580f]

  • Updated dependencies [0bffee2]

  • Updated dependencies [f329a26]

  • Updated dependencies [c827758]

  • Updated dependencies [6e9243c]

  • Updated dependencies [7a09cd9]

  • Updated dependencies [61a114c]

  • Updated dependencies [0d8347a]

  • Updated dependencies [7623ce1]

  • Updated dependencies [af94f67]

  • Updated dependencies [e87d694]

  • Updated dependencies [dd19e9e]

  • Updated dependencies [c245532]

  • Updated dependencies [34287d8]

  • Updated dependencies [64f9266]

  • Updated dependencies [0148d58]

  • Updated dependencies [765a656]

  • Updated dependencies [9db33cf]

  • Updated dependencies [bfd6f6c]

  • Updated dependencies [2054045]

  • Updated dependencies [c410709]

  • Updated dependencies [f555ec2]

  • Updated dependencies [d8e35a3]

  • Updated dependencies [5f7da9d]

  • Updated dependencies [7f5f902]

  • Updated dependencies [31adfce]

    • @solidjs/signals@2.0.0-rc.9

Don't miss a new solid release

NewReleases is sending notifications on new releases.