Skip to content

Release RC packages (rc) - #3098

Open
github-actions[bot] wants to merge 1 commit into
nextfrom
changeset-release/next
Open

Release RC packages (rc)#3098
github-actions[bot] wants to merge 1 commit into
nextfrom
changeset-release/next

Conversation

@github-actions

@github-actions github-actions Bot commented Aug 29, 2026

Copy link
Copy Markdown

This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to next, this PR will be updated.

⚠️⚠️⚠️⚠️⚠️⚠️

next is currently in pre mode so this branch has prereleases rather than normal releases. If you want to exit prereleases, run changeset pre exit on next.

⚠️⚠️⚠️⚠️⚠️⚠️

Releases

@solidjs/compiler@2.0.0-rc.5

Minor Changes

  • 7c551ae: Key server-function ids on identity instead of position (A server function's id encodes its position in the file, so appending one re-points existing addresses #3109). Production ids were <xxhash32(path)>-<ordinal>, so appending a server function to a file renumbered the others and every client holding the old numbering silently dispatched to different code with a 200. Ids are now <name>-<xxhash32(root-relative path)>, with a trailing ordinal only when the same descriptive name recurs within one file — appends, deletes, reorders, and body edits no longer move any address, and a removed or renamed function becomes a clean 404 instead of a wrong call. Development and production now share the exact same id format.

Patch Changes

  • 320f1f5: Universal text is text (babel-plugin: universal renderer HTML-escapes static text (< becomes &lt;) and leaves JSX entities undecoded #3127). The DOM and SSR generators splice static
    text into an HTML template that a parser later unescapes, so they escape
    static values and keep JSX entities as written. The universal generator
    hands strings straight to the host — createTextNode, setProp — with no
    parser downstream, so the escaping rendered literally ({"<b>"} showed as
    &lt;b>) and entities never decoded (&lt; showed as &lt;), leaving no
    spelling that produced a literal < in static text under a custom
    renderer. Universal-rendered element children now pass static values
    through unescaped and decode JSX entities in text and string attributes,
    matching what component children and fragment text always did. The flag
    rides on the element, not the config, so generate: "dynamic" decides per
    renderer. Applied to both compilers; the attribute half closed ten pinned
    cross-mode parity divergences between them. Reported with the fix mapped
    out by @antoinevanwel.
  • 5230666: Fix hydration ids drifting after a reactive lone spread (fix(compiler): align lone spread hydration ids #3105). A lone spread now passes its accessor straight to spread() on the client — no mergeProps, no memo, no hydration id — matching the server's existing pass-through fast path. The runtime resolves a function props source inside its own tracking scopes.

@solidjs/web@2.0.0-rc.5

Minor Changes

  • 51392f3: Bound what a server-function call may send (A server function accepts a body of any size and any number of arguments #3115). The argument payload is buffered and decoded before dispatch, so its cost was paid before application code could decline it: a 32 MB body was accepted and decoded, and a modest argument list forced a range error out of any function when spread into the call. bodySizeLimit (default 1 MiB, matching the neighbours' server-action ceilings) now refuses an oversized POST body or ?args= encoding with 413 before any decoding — a declared Content-Length is checked up front, a chunked body is buffered under the cap — and maxArguments (default 1000) refuses an oversized argument list with 400. Both are configurable through configureServerFunctionsServer and per-handler options; Infinity removes a bound. The decode depth cap also now holds whichever body format the caller selects (The decode depth cap is opt-out: the caller picks the format that skips it #3119): the plain-JSON format walked into a bare JSON.parse with no ceiling, where the framed codec enforced 64 levels — the same ceiling now applies to both, and a non-array argument encoding in either body format answers 400 instead of surfacing as the function's own failure.
  • 2f18c56: Deliver a server-function encode failure as a failure, not an empty success (A result the codec cannot encode reaches the caller as undefined #3117). When the codec could not encode a result, the head was already committed — status spent, no error tag possible — and the body simply stopped; a truncated body decodes to undefined, the same answer a void function gives, so a mutation that ran and committed its side effects was indistinguishable from one that returned nothing, and a data layer might retry it. The failure now travels in band: a terminal error-trailer frame (a !-prefixed payload on the existing chunk framing, unambiguous because codec frames always open with {) that the decoder throws — as the call's failure when it is the first frame, and into every still-pending async value when a later value's encoding fails mid-stream, with the delivered head keeping its data. The trailer is sanitized like any thrown error (generic in production, cause preserved in dev via Server function result could not be encoded: …). Version skew degrades safely: an old client reading a trailer fails the call with a decode error rather than resolving undefined.
  • fc5d079: Name the contract a GET() declaration signs, and add the opt-out of its trade (A GET-declared read is invocable cross-site, and nothing enforces that it is a read #3114). The origin gate is skipped for GET-declared reads by design — same-origin policy already keeps a cross-site caller from reading the response, and the gate's Vary fragments the shared-cache entries the helper exists to enable — which makes declaring GET a safety assertion, not only a transport choice: the function becomes executable from any origin, with caller-chosen arguments, carrying the user's ambient cookies. That contract is now stated on GET()'s documentation on both entries (declare GET only for reads that are safe in the RFC 9110 §9.2.1 sense), and csrf: { protectDeclaredReads: true } lets a deployment that does not rely on shared caches apply the origin gate to its reads as well. Both halves are pinned by tests: the default skip, and the opt-in gate.
  • 2f6d8cc: Label the unknown-id 404 so version skew is recoverable (A stale server-function id is an undistinguishable 404, so version skew cannot be recovered #3110). A call whose well-formed address is not registered in the answering deployment — a tab holding the previous build's ids across a deploy, or a genuinely removed function — now answers with an X-Server-Function-Unknown header, and the client stamps unknownFunction: true (plus a directed message) on the rejection. Integrations can act on it — typically by reloading the document onto the current build — instead of surfacing a generic failed call. A 404 for a path the address scheme gives no meaning to stays unlabelled.
  • 006a115: Carry masked redirects in a dedicated header and retire the RC transition shims. Scripted callers now receive redirects as X-Server-Function-Redirect: <status> <url> with the target resolved server-side against the request URL (A masked redirect keeps its target in Location on a 200 #3102) — Location never rides a masked 200, so an authored Location on a forwarding status (a 201's created-at) stays data, and integrations compare origins on a real URL instead of guessing navigation strategy from the author's spelling (navigate() from flight-data consumer can silently drop a relative-Location redirect (reliable with absolute URLs) #3107). decodeRedirectHeaderValue is exported for readers. Removed the transitional instance-header scripted fallback at the bare address and its forced no-store (A cacheable raw Response is shaped by a request header nothing keys on #3094): the answer shape is now a function of the URL alone, with the data address as the only scripted path.
  • 9522945: Scripted server-function calls now go to their own data address, <endpoint>/data/<id>, leaving the bare <endpoint>/<id> address to plain HTTP (A cacheable raw Response is shaped by a request header nothing keys on #3094). The two caller kinds get differently shaped answers — codec encodings for the client transport, verbatim responses / form-convention handling for everyone else — and shared caches key on the URL, so a header-driven shape meant one caller kind's cached answer could be replayed to the other (a GET-declared function returning a raw Response with a public cache policy could serve its codec encoding to a browser navigation, or its raw body to the app's own transport). The answer's shape is now a function of the URL alone. A reference's .url and rendered action urls stay on the bare address; reconstructed callables splice the data segment in ahead of the id for their own calls. Transitional: the instance header still summons the scripted shape at the bare address so already-loaded tabs survive a server deploy, with those answers forced no-store.
  • 21a5122: Single-flight always folds the keyed envelope — the raw legacy payload shape is gone with the other RC shims. The unnamed registration's slice rides under its reserved id "true" like any named source, so { value, data } has one shape, not two; the client always delivers data[source] to each consumer. The unrecognized-opt-in courtesy (arbitrary truthy header values reaching the unnamed hook) is also removed: only exact source ids run collection.

Patch Changes

  • ec52360: Contain flight-data collector errors per source: a throwing collector no longer fails the mutation response (the client received an error for a mutation that succeeded) or drop the other sources' slices — the failing source is simply omitted and logged.

  • da50a36: Warn in dev when a scripted server function call is answered with 304 Not Modified (A 304 resolves the call to undefined, so "not modified" arrives as "no data" #3101). The scripted transport sends no conditional headers, so a hand-rolled 304 resolves the call to undefined rather than "unchanged" — the warning points at GET-declared reads with ETag/Cache-Control, where the browser owns the conditional exchange and replays its cached answer.

  • 817b4d1: Bound the composed redirect and revalidate response headers (The redirect and revalidate headers are unbounded, so a successful mutation can answer with a network error #3131, the
    An unbounded error message in the response header makes the response unreadable #3093 class). A 20K-character redirect target or a few hundred
    revalidation keys produced a header past receivers' limits — undici's
    16 KiB default, nginx's one-page proxy buffer for the whole header block —
    so the response died at the socket (HPE_HEADER_OVERFLOW) after the
    mutation committed. Truncation is not an option for these values the way
    it was for An unbounded error message in the response header makes the response unreadable #3093's error label: a trimmed target is a different address
    and a trimmed key list is a silently stale cache. So redirect() and the
    revalidate option now refuse past 4096 characters with a legible error
    naming the remedy (carry the state server-side; split the invalidation or
    use coarser keys). The bound sits in the producing helpers, which run
    inside the function body, so both the returned and thrown spellings land
    on the ordinary error path — what leaves dispatch is the error shape,
    attributable and parseable. A raw Response built by hand with an
    oversized Location remains the author's own; only the helpers are
    bounded.

  • ecfee20: Two cookie fixes. The no-JS flash cookie now degrades instead of vanishing
    when an outcome exceeds the browser's 4 KB cookie ceiling (The no-JS flash cookie has no size bound — an outcome past the browser's ceiling vanishes silently #3137): past it
    the whole Set-Cookie was silently discarded — no error anywhere, and the
    page after the redirect looked like nothing was submitted, inviting the
    retry that writes twice. The encoder drops the input echo first, then
    bounds the value itself (a string keeps the longest prefix that fits,
    structured results reduce to the outcome flag), and the submission arrives
    with truncated set so integrations can say "succeeded, result too large
    to display". And serializeCookie now refuses in dev the shapes every
    browser silently rejects on arrival (serializeCookie emits cookie shapes every browser silently rejects #3138): __Host-/__Secure- prefix
    requirements and SameSite=None/Partitioned without Secure — each one
    attribute away from a cookie that never comes back, with login-shaped
    consequences. The validation compiles out of production builds. CHIPS
    partitioned is also supported now, so partitioned third-party cookies no
    longer require hand-building the header string.

  • af4cfc8: Two server-function grant fixes (A GET() declaration outlives the function it was made about #3129, The X-Single-Flight request header reshapes a cacheable GET body, and nothing names the variance #3128). A GET() declaration now
    dies with the binding it was made about: registerServerFunction revokes
    the id's declared method when it rebinds the id to a different function,
    so a mutation registered onto a once-declared id (an id collision, a
    module re-evaluated in a live process after an edit dropped the wrapper)
    no longer inherits GET dispatch and the origin-gate exemption — a function
    that still declares GET re-runs GET() right after re-registering, which
    re-arms the grant exactly when it is still meant. And the single-flight
    request header is now honored on POST only, the server half of the
    client's own rule: folding on a GET would put a second body — an envelope
    carrying data computed from that caller's request — at a cacheable url
    under whatever public Cache-Control the author wrote, with nothing naming
    the variance, one curl away from a shared-cache poisoning.

  • be7bcd2: The bare server-function address no longer decides its answer shape by the
    absence of a header (At the bare address the no-JS answer shape is decided by the absence of a header #3139). The no-JS redirect convention (303, outcome
    in the flash cookie) engaged on shape alone — form content type, no format
    tag — which a page script's fetch(url, { body: new URLSearchParams(...) })
    also matches: the script followed the 303 to the referrer's HTML, read
    response.ok === true, and its answer disappeared into a cookie it would
    never look at. Dispatch now reads the browser's own word for the caller
    kind: Sec-Fetch-Mode: navigate (or no fetch metadata, for older
    browsers) keeps the convention, while a script's form-shaped post is
    refused 400 before dispatch — before the mutation runs — pointing at the
    data address and the format tag, the two spellings that work. Tagged
    direct-HTTP callers keep the plain response as documented.

  • 93adc02: Three transport-correctness fixes on the server-function HTTP surface. A
    POST whose body-format tag names no decoding this runtime has — an unknown
    tag, a duplicated format header comma-joined by Headers, an untagged
    non-form body — is refused 400 before dispatch instead of calling the
    function with a substituted undefined argument that let the mutation
    commit and answer 200 (An unusable body-format header drops the body and calls the function with undefined #3130). The transport's defaulted
    Cache-Control: no-store is no longer written onto a 304, which is a
    cache UPDATE rather than a stored response — the default was instructing
    caches to evict the very entry the conditional request had just confirmed
    (A defaulted Cache-Control: no-store on a 304 evicts the entry the revalidation just confirmed #3134). And redirect() percent-encodes non-ASCII code points in its
    target before the value touches the latin1 Location header: targets
    above U+00FF used to throw (masked as a sanitized 500) and latin1-range
    characters rode as raw bytes a client decoded to U+FFFD, redirecting
    /café to /caf%EF%BF%BD (redirect() to a non-ASCII path answers 500, and a latin1 one redirects somewhere else #3135). ASCII passes through untouched, so
    already-encoded targets are not double-encoded.

  • 19fa8b0: Fix two server-function transport encoding issues:

    • Bound the error response header value (An unbounded error message in the response header makes the response unreadable #3093). The header is a classification label — the structured error travels in the body — so long thrown messages (nine-fold inflated by percent-encoding for non-latin1 text) no longer blow past receiver header limits and turn the application error into an unreadable response.
    • Support null-body statuses (204, 205, 304) (A null-body status turns into a 200 carrying an unexplained Internal Server Error #3095). respond(undefined, { status: 204 }) and raw null-body Responses now answer with a real bodiless response at the declared status instead of a TypeError from the Response constructor that dispatch sanitized into a phantom generic error at 200. A value-carrying result on a null-body status is reported as a legible authoring error naming the status, in every build.
  • 1a95943: Server-function failure is now signaled by the protocol's error tag alone, and thrown errors answer a real 500 (Failure is classified by HTTP status instead of the protocol's own error tag #3097). The client no longer treats status >= 500 as failure on responses the runtime encoded — respond(value, { status: 500 }) resolves with its value like any other returned value, and only a thrown outcome rejects. A peer's own 5xx (proxy, load balancer) carries no body-format header and is still refused before decoding. On the server, a plain thrown error now answers 500 instead of 200-with-tag, so intermediaries — CDN metrics, load-balancer health, log alerts — see what the tag tells the client; thrown envelopes keep the author's status as before.

  • 5be07a8: Forward an author's 3xx status consistently (Whether an author's 3xx survives depends on return vs throw and on whether the value is empty #3096). The scripted redirect mask now covers exactly the statuses fetch follows (301, 302, 303, 307, 308) — a 304, the natural answer for a conditional read, forwards untouched for every caller. Returned envelopes keep their status for unscripted callers (the returned path used to hardcode 200 where the thrown path forwarded it), and the no-JS form convention honors a returned redirect envelope's Location the way it already honored a thrown one.

  • 8d34af1: Answer the labelled version-skew 404 before the CSRF origin gate (The version-skew label is unreachable for a caller that carries no origin proof: 403, not the labelled 404 #3136).
    A removed id is no longer in METHODS, so it could not be recognised as a
    declared read and the gate fired on it: every caller without origin proof
    — a CDN revalidating a GET-declared read, an uptime monitor, a
    server-to-server client (Node's fetch sends none of the headers the gate
    reads) — got a bare 403 instead of the X-Server-Function-Unknown 404,
    so a deploy that removed a function read as an auth/WAF failure in the
    edge logs and the A stale server-function id is an undistinguishable 404, so version skew cannot be recovered #3110 recovery signal was invisible. Nothing is
    registered at an unknown id, so the gate had nothing there to protect,
    and the ids were never secret — the compiler ships them in the client
    bundle. The hoisted lookup is a side-effect-free Map read, the labelled
    404 no longer carries the CSRF Vary (its answer does not depend on
    origin proof, so it must not fragment shared-cache entries on it), and
    the meaningless-path 404 stays bare and stays gated. Diagnosed, measured,
    and drafted by @frenzzy.

  • fe4bfa0: Type the client live() reference truthfully: calling it returns the reconnecting iterable itself, synchronously — not a Promise of one. The declaration previously routed through ServerFunction, whose call signature promises Promise<T>; the mismatch was masked by the dangling declaration references this release also fixes. Isomorphic consumers are unaffected: they await the call, and awaiting the client's plain iterable is identity.

  • 02f87fe: live() reconnects through the 4xx statuses that say "retry" and honors Retry-After (live() treats every 4xx reconnect as permanent, including 408 and 429 #3100). The reconnect loop treated the whole 4xx band as a definite rejection, so a rate limiter's 429 — or a gateway's 408 — permanently closed a healthy stream. 408 (RFC 9110 §15.5.9), 425 (RFC 8470) and 429 (RFC 6585 §4) now reconnect like a 5xx, as does any failure whose response carries Retry-After — the peer inviting the retry in as many words. A named Retry-After wait (seconds or HTTP-date, stamped on the error as retryAfter in seconds for policy layers) replaces the exponential backoff guess for that attempt, capped at 60s so a misconfigured header cannot end the stream in all but name.

  • 5230666: Fix hydration ids drifting after a reactive lone spread (fix(compiler): align lone spread hydration ids #3105). A lone spread now passes its accessor straight to spread() on the client — no mergeProps, no memo, no hydration id — matching the server's existing pass-through fast path. The runtime resolves a function props source inside its own tracking scopes.

  • 653dd41: Multi-source single-flight: named flight-data sources alongside the unnamed hook

    The single-flight channel assumed exactly one data-owning integration — one
    collectFlightData hook on the server, one subscribeFlightData consumer on
    the client, later registrations displacing earlier ones. An app running two
    caches (a router's route data and a query library's client) had no way to
    refresh both from one mutation response: whichever library registered last
    silently won.

    The channel now multiplexes named sources over the same round trip:

    • registerFlightDataSource(id, hook) (server) registers a collector
      additively next to the unnamed collectFlightData slot, which remains the
      data-owning integration's (a router's).
    • subscribeFlightData(id, consumer) (client) subscribes a consumer to its
      source's slice; the bare legacy signature keeps meaning the unnamed source.
    • The request-leg X-Single-Flight header now carries the subscribed source
      ids, so the server only runs collectors the client can consume; the
      response leg echoes the ids actually folded, making the payload shape
      self-describing. With named sources in play, data is the keyed envelope
      { [source]: slice, ... } and each slice is delivered to its consumer,
      awaited, before the mutation's promise resolves.

    Fully wire-compatible in every cross-version pairing: a lone unnamed
    registration still sends and echoes the literal true with the raw payload
    shape, byte-identical to the previous protocol, and unrecognized opt-in
    values from hand-tagged requests still reach the unnamed hook. Existing
    integrations (Solid Router, TanStack Solid Start) keep working unchanged; the
    keyed envelope only materializes when a named source registers on both ends.

  • f739ec3: Sanitize a failure that escapes through a server function's result graph.
    sanitizeServerError guarded the one road a thrown error takes out of
    dispatch; a rejected promise, an async iterable that throws, or a stream
    that errors reaches the codec as a value to encode instead, and shipped
    its message and every own-property to the client verbatim — a driver
    error's failing query, connection string and bound params included —
    under a 200 carrying no error tag, because the head was already
    committed. Those channels are now wrapped before either serializer sees
    them: the response encoder and the frames flight sink, which encodes its
    outcome with a serializer of its own.

    The walk covers plain objects, arrays, Map and Set. A channel held by
    a class instance or behind an accessor is left alone — rebuilding one and
    invoking the other are not the runtime's to do. markSafeError remains
    the escape hatch, an Error that is a returned value is untouched, and
    the wire format is unchanged, cycles and shared references included.

  • fe4bfa0: Fix server function references typing as any: the emitted server-functions declarations referenced ServerFunction/ServerFunctionMetadata without importing them (the export type blocks only re-export the names), so under skipLibCheck every GET/live/createServerReference return type silently collapsed to any for consumers.

  • f06f7b1: Pull a streamed server-function result behind a demand gate (A streamed result has no backpressure: one slow consumer buffers it all in server memory #3118). The
    response stream was built with no pull and no queuing strategy, and
    every codec node is enqueued the moment it is parsed, so the producer ran
    as fast as it could resolve whether or not anyone was reading: one slow
    consumer buffered the whole result in server memory, unbounded and
    invisible to application code. The consumer's reads now drive pull,
    which releases one source pull at a time, so an unread stream stays near
    the queue size instead of running away.

    Scope: the gate sits on the source the runtime wraps, which is the
    result itself. An async iterable nested inside the result — { items: rows() } — is pumped by the codec directly and is not yet gated. Ending
    the stream releases a parked pull, so an aborted, cancelled or failed
    stream still closes its source; a consumer that abandons a stream without
    cancelling it now leaves the producer parked rather than running it to
    completion.

  • Updated dependencies [51ffcb9]

  • Updated dependencies [00d1d5d]

  • Updated dependencies [0c02d42]

    • solid-js@2.0.0-rc.5

@solidjs/babel-plugin@2.0.0-rc.5

Patch Changes

  • 320f1f5: Universal text is text (babel-plugin: universal renderer HTML-escapes static text (< becomes &lt;) and leaves JSX entities undecoded #3127). The DOM and SSR generators splice static
    text into an HTML template that a parser later unescapes, so they escape
    static values and keep JSX entities as written. The universal generator
    hands strings straight to the host — createTextNode, setProp — with no
    parser downstream, so the escaping rendered literally ({"<b>"} showed as
    &lt;b>) and entities never decoded (&lt; showed as &lt;), leaving no
    spelling that produced a literal < in static text under a custom
    renderer. Universal-rendered element children now pass static values
    through unescaped and decode JSX entities in text and string attributes,
    matching what component children and fragment text always did. The flag
    rides on the element, not the config, so generate: "dynamic" decides per
    renderer. Applied to both compilers; the attribute half closed ten pinned
    cross-mode parity divergences between them. Reported with the fix mapped
    out by @antoinevanwel.
  • 5230666: Fix hydration ids drifting after a reactive lone spread (fix(compiler): align lone spread hydration ids #3105). A lone spread now passes its accessor straight to spread() on the client — no mergeProps, no memo, no hydration id — matching the server's existing pass-through fast path. The runtime resolves a function props source inside its own tracking scopes.

@solidjs/diagnostics@2.0.0-rc.5

Patch Changes

@solidjs/element@2.0.0-rc.5

Patch Changes

@solidjs/h@2.0.0-rc.5

Patch Changes

@solidjs/html@2.0.0-rc.5

Patch Changes

@solidjs/signals@2.0.0-rc.5

Patch Changes

  • 51ffcb9: refresh(target) now returns a promise for the target's next QUIESCENT state — the re-ask (and anything that supersedes it) has settled. Accessor targets resolve with the settled value; store targets resolve with the store node passed (nested targets re-ask the whole family but resolve with the node the caller was looking at). A failed re-ask rejects, so yield refresh(x) in an action throws back at the yield point and reverts like any failed step; an ignored promise never surfaces an unhandled rejection, keeping fire-and-forget refresh unchanged. Semantics are quiescence, not flight identity: a superseding refresh folds every waiter onto whatever finally lands. Inside actions, truth landing into the held transaction is staged; the promise settles then (matching resolve()/until() delivery) and delivers the staged value — never the caller's optimistic override. The re-ask stays verdict-quiet (isPending unchanged).

    Implementation is pay-for-use and shares resolve()/until()'s effect machinery: the waiter is a microtask-delivered, direct-commit, authoritative-read effect over the marked node, deferred one microtask so same-tick refreshes still coalesce into a single re-ask. One new reader bit (CONFIG_FRESH_READ) makes the waiter's read pull a still-dirty source through recompute inline — it then parks on the re-ask's pending window (woken by the settle walk, which runs on every landing including equal-value ones) or serves the sync answer, instead of misreading the pre-re-ask value as settled. updateIfNecessary now also refuses disposed nodes outright (Dependency-free computations can be resurrected after disposal #2983's bug class), and a disposed or non-derived target resolves immediately with its last value.

  • 28a1eaf: Clear a node's transition stamp when its pending value commits. _transition
    was only ever cleared for optimistic nodes and in one async settle;
    everything else relied on reassignPendingTransition, which the completing
    branch runs over batch._pendingNodes. commitPendingNodes drains that list
    without clearing anything, so a node committed by an earlier drain kept
    pointing at a transition that later finished. setSignal re-enters
    el._transition before it discovers a write changes nothing, and a loading
    boundary rewrites the same flag on every drain pass, so a finished transition
    was re-armed forever and flush() never ended: dev threw "Potential Infinite
    Loop Detected", production has no counter on that loop and hung. The
    re-entered transition also aliases the ambient batch's containers, so
    initTransition's adoption pass could push into the array it was iterating
    until RangeError: Invalid array length.

  • ca16891: Harden the transaction-stamp lifecycle around 2.0.0-rc.4: flush() guard trips from the asyncWrite write-back on a screen with several async memos (no minimal reproduction) #3140 (companion to fix(signals): clear a node's transition stamp when its pending value commits #3143, which clears _transition stamps when pending values commit). initTransition now refuses a transaction whose _done chain ends in true — the belt for dead references that survive outside the commit path (merged forwarding chains, async settles racing completion), since setSignal re-opens a node's stamped transaction before the value-equal bail and re-activating a corpse spins the flush drain loop (dev threw the loop guard; production hung). The dev loop guard also now reports what kept the loop alive — transition done-state, queue counts, and the last staged node — instead of only that it happened.

  • ed2fb43: Fix flattenArray overwriting its needsUnwrap flag with a nested call's result instead of OR-ing it (flattenArray drops needsUnwrap when a fragment follows an accessor, universal renderers receive a raw memo in insertNode #3133). Under doNotUnwrap, an accessor child (a <For>/<Repeat>/memo) followed at the same level by a fragment containing no functions reset the flag, so flatten returned a plain array with the raw accessor still inside instead of the resolving wrapper. Every renderer crashed on the raw function: universal hosts received it in insertNode (as reported), and the DOM renderer threw insertBefore … parameter 1 is not of type 'Node' — the protective function branch remembered from 1.x dom-expressions does not exist in 2.0. Reported with the fix by @antoinevanwel; also submitted by @nickshiro.

  • 2023daa: Fix unrelated async work being captured by a lingering ambient transaction (2.0.0-rc.4 | optimistic store, read in an effect, changes the visible rendering. and has inconsistent value when logged and when rendered. #3141). Parking is flush-driven, but a transaction opened without any writes — an action whose first statements only await — scheduled nothing, so activeTransition and the adopted batch stayed armed across the async gap. The next unrelated work to arrive was adopted into a transaction it had nothing to do with: an optimistic store's authoritative landing would not render until the stranger action settled, an unowned optimistic write rode that transaction instead of reverting at the flush, and deep()/per-key readers disagreed about the committed value in the meantime. initTransition now guarantees a flush, so the ambient window closes in one flush regardless of whether the transaction wrote anything — enforcing the A26 containment ruling.

  • 09bbe24: Fix a type error in refresh()'s quiescence waiter that broke declaration emit (pnpm types): the waiter captured inside the effect's own compute is the effect node, so it is typed Computed rather than Owner, matching what dispose() takes. Type-only; no runtime change.

  • 88fa9d6: Fix derived optimistic stores permanently corrupting committed state when the source's draft writes ran while a caller's optimistic override was active (2.0.0-rc.5 until breaks optimistic store #3108). The source is the truth author: its draft reads — sync body and post-await/yield continuations alike — now serve the authoritative view instead of composing the caller's tentative overlay. Before, a generator continuation's store.push read length through an action's optimistic row and landed truth at the wrong index, committing [null, row]. Values, array length, membership, keys, and descriptors all leave the authoritative view together, gated on the same authoritative-write posture ensurePB already used to seed authoritative drafts from committed truth. User setter drafts are unchanged and keep composing on the optimistic view (2.0.0-beta.26 | optimistic store unexpected pending state for store or dependency, while having an optimistic override #2951). Not an until() bug, despite the report's shape — the corruption reproduced with a plain yielded promise.

  • 0c02d42: Add until(fn, options?) — the acknowledgment primitive for mutations confirmed on a live data channel (sockets, subscriptions, live queries) rather than by the mutation's own response. Resolves the first time the reactive predicate settles truthy (falsy and pending both mean "not yet"); yield until(...) from an action holds the transaction — and its optimistic state — open until the world confirms, with { timeout } (TimeoutError) and { signal } rejections throwing back at the yield point so failed holds revert like any failed action.

    The predicate reads the AUTHORITATIVE view, and the carve-out is exactly one layer deep: the caller's own optimistic overrides (values and structure) are invisible, so a tentative write can never satisfy its own ack — including on the single-primitive shape where the optimistic store is the live-fed store. Everything else reads normally, including uncommitted transition-staged data: truth landing into the open transaction (e.g. a refresh() the action issued) stages and cannot commit until the hold releases, so refusing staged reads would deadlock the hold on its own data plane. The A17-silent "landing equals the override" paths wake authoritative readers only (CONFIG_AUTHORITATIVE_OBSERVED); the wakeup machinery is hook-installed at first until() call so unused apps tree-shake it.

    Also fixes resolve() (and until()) delivering stale values when their source settles into a held transaction: promise-delivery effects apply on a microtask (2.0.0-beta.21: resolve() created inside action() never settles, deadlocking the action #2930) but their computed value staged with the transition, so the immediate apply read stale mainline state — resolve could report pre-refresh data and until could deadlock. Such effects now commit their value directly (CONFIG_DIRECT_COMMIT), keeping value and delivery on the same schedule; safe because effects are private leaves (no subscriber reads an effect's value).

solid-js@2.0.0-rc.5

Patch Changes

  • 51ffcb9: refresh(target) now returns a promise for the target's next QUIESCENT state — the re-ask (and anything that supersedes it) has settled. Accessor targets resolve with the settled value; store targets resolve with the store node passed (nested targets re-ask the whole family but resolve with the node the caller was looking at). A failed re-ask rejects, so yield refresh(x) in an action throws back at the yield point and reverts like any failed step; an ignored promise never surfaces an unhandled rejection, keeping fire-and-forget refresh unchanged. Semantics are quiescence, not flight identity: a superseding refresh folds every waiter onto whatever finally lands. Inside actions, truth landing into the held transaction is staged; the promise settles then (matching resolve()/until() delivery) and delivers the staged value — never the caller's optimistic override. The re-ask stays verdict-quiet (isPending unchanged).

    Implementation is pay-for-use and shares resolve()/until()'s effect machinery: the waiter is a microtask-delivered, direct-commit, authoritative-read effect over the marked node, deferred one microtask so same-tick refreshes still coalesce into a single re-ask. One new reader bit (CONFIG_FRESH_READ) makes the waiter's read pull a still-dirty source through recompute inline — it then parks on the re-ask's pending window (woken by the settle walk, which runs on every landing including equal-value ones) or serves the sync answer, instead of misreading the pre-re-ask value as settled. updateIfNecessary now also refuses disposed nodes outright (Dependency-free computations can be resurrected after disposal #2983's bug class), and a disposed or non-derived target resolves immediately with its last value.

  • 00d1d5d: Stop exposing generated declaration files through solid-js/types/*. Public values and types remain available from solid-js; keeping implementation declarations private also prevents TypeScript from suggesting invalid runtime imports such as solid-js/types/server/signals.js.

  • 0c02d42: Add until(fn, options?) — the acknowledgment primitive for mutations confirmed on a live data channel (sockets, subscriptions, live queries) rather than by the mutation's own response. Resolves the first time the reactive predicate settles truthy (falsy and pending both mean "not yet"); yield until(...) from an action holds the transaction — and its optimistic state — open until the world confirms, with { timeout } (TimeoutError) and { signal } rejections throwing back at the yield point so failed holds revert like any failed action.

    The predicate reads the AUTHORITATIVE view, and the carve-out is exactly one layer deep: the caller's own optimistic overrides (values and structure) are invisible, so a tentative write can never satisfy its own ack — including on the single-primitive shape where the optimistic store is the live-fed store. Everything else reads normally, including uncommitted transition-staged data: truth landing into the open transaction (e.g. a refresh() the action issued) stages and cannot commit until the hold releases, so refusing staged reads would deadlock the hold on its own data plane. The A17-silent "landing equals the override" paths wake authoritative readers only (CONFIG_AUTHORITATIVE_OBSERVED); the wakeup machinery is hook-installed at first until() call so unused apps tree-shake it.

    Also fixes resolve() (and until()) delivering stale values when their source settles into a held transaction: promise-delivery effects apply on a microtask (2.0.0-beta.21: resolve() created inside action() never settles, deadlocking the action #2930) but their computed value staged with the transition, so the immediate apply read stale mainline state — resolve could report pre-refresh data and until could deadlock. Such effects now commit their value directly (CONFIG_DIRECT_COMMIT), keeping value and delivery on the same schedule; safe because effects are private leaves (no subscriber reads an effect's value).

  • Updated dependencies [51ffcb9]

  • Updated dependencies [28a1eaf]

  • Updated dependencies [ca16891]

  • Updated dependencies [ed2fb43]

  • Updated dependencies [2023daa]

  • Updated dependencies [09bbe24]

  • Updated dependencies [88fa9d6]

  • Updated dependencies [0c02d42]

    • @solidjs/signals@2.0.0-rc.5

@solidjs/universal@2.0.0-rc.5

Patch Changes

  • Updated dependencies [51ffcb9]
  • Updated dependencies [00d1d5d]
  • Updated dependencies [0c02d42]
    • solid-js@2.0.0-rc.5

test-integration@2.0.0-rc.5

Patch Changes

  • Updated dependencies [51ffcb9]
  • Updated dependencies [51392f3]
  • Updated dependencies [28a1eaf]
  • Updated dependencies [ec52360]
  • Updated dependencies [da50a36]
  • Updated dependencies [2f18c56]
  • Updated dependencies [817b4d1]
  • Updated dependencies [ecfee20]
  • Updated dependencies [ca16891]
  • Updated dependencies [ed2fb43]
  • Updated dependencies [af4cfc8]
  • Updated dependencies [2023daa]
  • Updated dependencies [be7bcd2]
  • Updated dependencies [09bbe24]
  • Updated dependencies [93adc02]
  • Updated dependencies [19fa8b0]
  • Updated dependencies [1a95943]
  • Updated dependencies [5be07a8]
  • Updated dependencies [88fa9d6]
  • Updated dependencies [320f1f5]
  • Updated dependencies [8d34af1]
  • Updated dependencies [fc5d079]
  • Updated dependencies [00d1d5d]
  • Updated dependencies [2f6d8cc]
  • Updated dependencies [fe4bfa0]
  • Updated dependencies [02f87fe]
  • Updated dependencies [5230666]
  • Updated dependencies [653dd41]
  • Updated dependencies [006a115]
  • Updated dependencies [f739ec3]
  • Updated dependencies [9522945]
  • Updated dependencies [fe4bfa0]
  • Updated dependencies [21a5122]
  • Updated dependencies [f06f7b1]
  • Updated dependencies [0c02d42]
    • @solidjs/signals@2.0.0-rc.5
    • solid-js@2.0.0-rc.5
    • @solidjs/web@2.0.0-rc.5
    • @solidjs/babel-plugin@2.0.0-rc.5
    • @solidjs/h@2.0.0-rc.5
    • @solidjs/html@2.0.0-rc.5
    • @solidjs/universal@2.0.0-rc.5

@github-actions
github-actions Bot force-pushed the changeset-release/next branch 30 times, most recently from c71fd00 to 39cc6b8 Compare August 31, 2026 08:43
@github-actions
github-actions Bot force-pushed the changeset-release/next branch 8 times, most recently from dac2814 to 4a8ca4a Compare August 31, 2026 10:21
@github-actions
github-actions Bot force-pushed the changeset-release/next branch from 4a8ca4a to c1f5154 Compare August 31, 2026 10:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants