Repository navigation
@solidjs/signals@2.0.0-rc.20
Pre-releasePatch Changes
-
c9d1eeb:
refresh(memo)inside an action no longer loops forever when a render
effect readsisPending(memo)(#4349). The action's transaction landed
beneath the re-ask's flight, so the reader observed the flight and held a
frame of its own; the lane seam then re-derived that reader on the committed
world every round, and each re-run re-entered the same transaction and parked
it again until the dev guard threw "Potential Infinite Loop Detected". A
reader a parked transaction holds that read none of its stagings now runs at
the landing instead. The same loop hit[isPending(x), x()]in one render
effect after an action's write tox's input. A bare refresh stays quiet
(v41, thenv42);affects()+refresh()showspending, thenv42.
An action'saffects()mark on a node whose own refetch is still in flight
when the action's transaction lands now stays until that refetch lands, as a
mark declared outside an action already did: an action that ends right after
affects(x); refresh(x)showspendingwhether theisPendingread is inline
or in its own memo, as JSX compiles a condition. -
178605c:
isPendingof a derived store's key no longer readstruefor one pass at the store's first landing. A first answer is loading, not pending, as it is for the memo the store copies. Before, a<Show when={isPending(...)}>over such a key could open for that pass and mount content that was never meant to show. -
17ec571: A derived store whose derive reads an async memo through
latest()no longer shows its seed for one pass after it first resolves: a memo (or a second derived store) reading it now sees what the derive wrote, so a render effect downstream commits only the real value, the same as one reading the store directly (#4355). -
178605c: A render effect reading a derived store that errored, then recovered through
latest()into a new flight, now re-runs when that flight lands. Before, it never re-ran: anErroredaround it went back to its content with the value missing, or aLoadingaround it stayed on its fallback. -
957f065: An
<Errored>heals when the last reader that caught its error is disposed. The boundary waits on what threw only while a reader holding the error is alive, so a<Show when={isPending(m)}>that closes overm()once the action that mademthrow settles no longer leaves the fallback up (#4373). -
3b7ea81: An
<Errored>boundary no longer loops ("Potential Infinite Loop Detected") after a memo it caught is re-asked: a reader born in the flush that read a memo with a staged new error throws that error, not the committed one (D-41, #4404); a pass replacing an error the memo staged is a change even when it computes the committed value, so thelatest()readers that threw the staged error re-derive (D-41, #4403); and at an action's landing an errored boundary tree re-derives, so it links the content the landing remounted and that content's recovery reaches it (D-3, D-39, #4405). -
2861ab8: An uninitialized async memo whose re-run throws from a dependency now reaches the wrapping
<Errored>(#4364). The dependency's error was staged for the commit (D-41), but a reader threw it at once: a render effect reading a memo born staged, or the memo itself throughlatest(). The error reached the boundary naming that source, and the boundary's next pass took the source as settled because its error was only staged. It dropped the source and showed the content, soLoadingrevealed an empty hole that never recovered. An error staged on its source now keeps the boundary waiting until the commit publishes it. -
0f5b150: Smaller core for apps without
isPending/latestor optimistic lanes: the verdict engine's async-memo bookkeeping, its re-derive checks and the pass-lane seat now sit behind the engines' hooks, so a bundle that never installs them folds that code away (hello world −92 B brotli). No behavior change. -
e82efb0: A computed's error that halts the reactive system is rethrown as the error the user threw, not Solid's internal status wrapper. The wrapper carried the reactive node, so an error reporter (or a test runner) serializing the uncaught error walked the whole graph.
-
2ba8a02: A memo that threw and then recovers under an open action now keeps its error on screen until the action commits. Before, a reader served the committed value, such as
<Errored>content beside alatest()read ("X" + latest(s) + m()), readundefinedin place of the error and showedX1undefinedwhile the action was still open. The recovery's staging now keeps the error as the shown error until it commits (D-39, D-41). Frame and verdict readers are served that error, as an untracked read already was, and they re-derive when the action lands.The same holds for a memo the content created on its first pass and kept in a closure (
(cached ||= createMemo(...))()). That memo is disposed with the pass that created it. The error boundary now waits on the reader when the source that threw is disposed. Before, it waited on the dead memo, which settled at once and looped the flush. -
910abc1:
isPending(s)read through a memo (<Show when={isPending(s)}>, a compiled ternary's condition) no longer staystrueafter an action completes whensis also written outside the action (#4367). The outside write that matches the action's value ends the verdict, and the memo's pass leaves the verdict lane on a value equal to its committed one, so the equality gate notified nobody; the lane seam woke the reader that still showed the lane'strueonly when the lane's frame was not held, and the action held it. A pass outside any transaction's flush is the screen's: its readers now re-derive at the seam, as the inlineisPending(s)does. -
cddc7c3: A section a change mounts now commits once its async memos land when two boundaries read
isPending()of the same memo and that memo lands last (#4338). The nested reader's pending held theisPendinglane, so the sibling boundary's output became a lane node before the lane had shown; its render effect, reading that node from the frame, had no committed value to show and went pending on no flight — and was registered to re-derive at the lane's reveal only when it did not throw. It held the change, the lane ends only when the change lands, and the old content stayed on screen for good. The reader is now registered for the reveal before it waits, so it re-derives there and the change commits whole. -
81a12d8: A memo or render effect reading
latest(a)besideb()no longer computes a torn pass on a batched write (#4365). Thelatest()read of a's staging opened a transaction for the flush, and the plainb()read after it took that transaction for a hold, so it paired a's new value with b's old one. Nothing held the flush, so that pass was shown: a render effect behind a memo ran[2,1]before[2,2], and a throw from the pass stuck at the error boundary.latest()no longer opens a transaction. Its reader's pass belongs to the frame, and if the flush parks it runs once more in the verdict lane, ahead of the hold. -
81a12d8: An async memo that reads
latest()no longer asks the frame's question twice when its flush parks. Its first pass belongs to the frame and asks that frame's question. When the flush parks, the memo re-derives in the verdict lane for the display-ahead view, and that re-derive used to discard the frame's flight, so the transaction's landing asked the same question again. The landing now adopts the frame's flight, whether it has already landed or is still in flight. -
5f22701:
latest(x)read inline and the same read through a memo (createMemo(() => latest(x))) show the same screen. A render effect re-run by a verdict memo's change now tears beside a held optimistic lane through the memo as it does inline (1 x1 / 5, #4284). A plain write beside an async memo shows the screen beside the proposal (v:0 l:1) in either read order and either form. The committed frame's effect beside one a held pass created tears during the hold inline as through a memo.latest()of an async derivation's first load still in flight in a verdict lane is loading, neverundefined, including after the lane dissolves. The same holds through an identity memo or a derived store, andisPending()of such a load is loading too. A plain synchronous write besidelatest()computes once. An<Errored>keeps its error on screen while the async memo the error came through re-asks after the source that threw recovers, instead of revealing content whose holes are still loading. -
81a12d8: Two
latest()async readers under one async memo, with the second input written while the first one's flush is parked, ask each question once. Alatest()reader whose frame lands at the seam no longer re-derives in the verdict lane, so its frame question isn't asked a second time. A verdict-lane pass that read a staging re-derives only while the transaction holding that staging stays parked, judged on that transaction rather than the lane's parent and not counting the pass's own render effect. Before, the memo asked a question twice and could show a value derived from the input's old answer next to its new one.A
latest()reader's lane answer now reaches the screen whatever the grouping. AnisPending()effect beside the reader no longer makes it wait for an unrelated hold, and an async memo overlatest()that only its own flight holds shows the lane's answer before the final one. A[isPending(m), m()]reader of a synclatest()memo no longer shows a pending pair with the old value before the lane's. -
9baaba3:
latest(),isPending()and plain reads of an uninitialized async memo now throw NotReady (A7) while the memo is re-asked as a verdict lane's work, for example when its compute readslatest(s)andschanges. Before, they returnedundefined, and the lane's end committed thatundefinedas the memo's first answer, which tripped the flush loop guard (#4362, #4363). During hydration, a chain of memos overlatest()adopts the streamed answers again instead of askingfetch(NaN)(#4376).A memo that reads
latest()and throws over the committed value of an async input it was served while that input was in flight now re-derives when the input lands, so an<Errored>it tripped recovers (D-39). Before, the error stuck until some other input changed. -
ffe0c1c: Dev: warn
LOADING_ON_ACCESSORwhen a Loading boundary'sonreceives a function (on={route}oron={() => route()}). The boundary never calls it, so it never re-arms; pass the value,on={route()}. Dev builds only. -
b68942c: A
<Loading on>boundary no longer shows its fallback when a heldonchange commits under an action's ownrefresh()(#4379). When anonchange is held with an action (an input changed under its optimistic guess, #4336) and the content's new answer lands inside that hold a flush after the change, the boundary now folds the staged fallback back, as it already did when the content was ready at the change. The action's commit shows the content, and the quietrefresh()that follows keeps it on screen until it answers. -
7cacef2: A
<Loading>armed byon={latest(id)}no longer stays on its fallback for good when another boundary's flight over the sameidstill holds the frame as the armed boundary's own flight lands. The boundary's output pass left the verdict lane inside the held transaction's flush with an answer equal to its committed value, so the equality gate notified nobody, and the lane seam wakes such a pass only when the frame is not held: when the frame landed, nothing re-ran the reader. A pass that leaves a lane the screen has shown while its frame is still held now hands the shown value to_value(what the screen shows stays the screen until the landing), so its answer is a change and lands with the frame. -
ac28157: A per-key guess on
createOptimisticStorestays on screen through a re-ask that lands while its action is pending (D-34, #4329). When the derive writes a keyed list through its draft (d.length = 0; d.push(...rows), ord.issues = rows) under a row that carries a guess, the new object at that row's slot is reconciled into the guessed row, as a returned landing is: the row keeps its proxy and the screen shows the guess until the action commits.On both landing paths, a guessed key is judged against its guess, so
latest()answers the truth even when the truth equals the value the guess covered. A correction or confirmation no longer joins the whole landing to the guess's hold. When one re-ask answers guesses from two actions, they stay independent (D-33): each row reverts or commits when its own action settles, and rows with no guess show their new truth at the landing. -
1e115b8:
createOptimisticStore: when a guess puts a different row object at an index,<For>(keyed and non-keyed) now picks up the new row at once and goes back at the settle, the same as direct reads. Before,<For>kept the old row if the new one had the sameid, or, for rows without anid, the same contents or any contents in a reorder (#4344). An edit to that row then showed in direct reads but not on screen. A row the store already serves is now compared by the row it is, not by its key or contents. A server echo still confirms the guess. -
6b7e34a: An input changed outside an optimistic guess's action no longer tears the screen (#4336). When a
createOptimisticorcreateOptimisticStoreguess made in a pending action (a post'smine (sending)oversel() = 3) is re-asked because a plain write changed its input (setSel(1)), the write is held with that action (D-34). The screen keeps#3: dana | mine (sending)until the action commits, then shows#1: ...with its answer as one frame, whether the reads share one render effect or are split across several.isPendingis true while the new question is in flight or held, including through the action's ownrefresh(), andlatest()answers the newest truth. Arefresh()of the same question still keeps its own frame. For a store, the hold applies to a frame that still reads a guess: a keyed store's other keys (comments[1]while the guess is oncomments[3]) wait only for their own answer. An input set through its own optimistic write in another action now shows in its own lane once its answer lands, without the other action's guess beside it. Also fixed: a held write read in the same flush before the action's first lane read no longer shows before the commit. If that input's action ends without writing the truth, the other action's guess comes back in the revert's frame (#1:then#3: dana | mine (sending)) and lasts until its own action settles; the split-effect form waits with the input's lane instead of showing#1:beside the guess. A<Loading on>boundary whose fallback readslatest()no longer swaps to its fallback ahead of a held change: the swap lands with the change's frame (#4337). -
822fa01: A memo whose own error came from a pass over a held write no longer shows the Errored fallback for a moment when the hold lands. The landing re-derives the memo before the held effects run, as it already did for a render effect reading the same thing inline (#4407).
-
2c63f41: A derived store whose derive peeks (untracked, catching
NotReadyError) at a memo built from its own keys settles instead of re-running without bound (#4342). A derive's synchronous pass no longer re-runs the readers left linked to it from a flight that has already landed. A held frame keeps those links, and re-running them on every pass made a loop: the reader, then the peeked memo, then the derive, all in one flush, untilqueuePendingNodethrew aRangeError. This is the query-hook shape: a keyed list of rows, each row with its own hook, and a parent key switched while the list's answer is in flight. Landings still re-run the readers that observed the flight (#4070). -
5f60713: A keyed
createProjectionwhose function returned another store's proxy and then a plain array (for example a memo that returnsissuesorissues.filter(…)) keeps updating. Switching away from the proxy compared the new array against the proxy itself, which already showed the source store's new rows, so the projection's readers never heard the change and the frame stalled. The switch now tells every leaf and the container, and each node's own equality check decides whether its readers re-run (#4361). -
425a713: A render effect's first run goes through when its subtree mounts, whatever boundary above is still on its fallback in that flush (D-19). Under
<Loading> > <Show when={ready()}> > <Loading>, a render effect in the inner content ran at once while the inner fallback showed whenreadywas a signal, but waited for the inner data whenreadywas an async memo settling: the outer boundary had not swapped to its content yet when the effect was created, and its first run was held like a queued one. Both gates now run it with the mount (#4343). User effects still wait for the reveal. -
38e0c49:
<For>/mapArrayover a shallow store diffs the store's backing array instead of reading every slot through the proxy. A one-row write on a 1,000-row shallow store spent ~1.3 ms in per-slot trap reads; the list's diff now costs what it does over a plain array (~0.05 ms). Optimistic shallow stores, accessor slots and reads inside a setter keep the per-slot path. -
56a982c: The first draft write to a plain store array clones it with
slice()instead of copying one property descriptor per element. A push into a 10,000-row store drops from ~10.7 ms to ~0.15 ms per write. Arrays with accessors, named keys or a non-Array prototype keep the descriptor copy. -
1b32ff2: A reader created in the flush that clears an upstream error, under
<Errored>and<Loading>, now shows the async memo's error once it rejects, instead of an empty hole. The rejection re-ran the error boundary outside the hold that had staged its content, and the boundary went back to the fallback it had already committed. Because that was no change, the boundary's reader did not re-run, and the hold's landing applied its stale run of the content. A held frame node that re-runs outside its hold now re-derives the readers of the staging it replaces (#4378). -
4d8195d: An unowned memo with an
isPending()reader recomputes correctly (#4366). A module-level memo left with no reader goes dormant, and the next read wakes it with a recompute. When that read was anisPending(m)probe, the wake ran inside the probe's window, so the memo read its sources as the probe does, at their committed values: after an async source landed it derived from the old value (undefined + 1, soNaN), and after a source recovered from an error it kept the value from before the error. The wake is the memo's own pass (A31), the same as a pull, so it now runs outside the window. -
7ebd4a3: A memo of a verdict (
createMemo(() => latest(s))orcreateMemo(() => isPending(s))) no longer shows a held change early. Before, a pass that read an action's staged write and then read that memo moved into the verdict lane, and the lane showed what the pass had read while the action was still open. For example,<Errored>showed the branch that the action's write recovers in place of its error, and[s(), L()]in one render effect showedv:1 l:1. Now a derivation in that position belongs to the frame, as it does when it callslatest()inline, so its result appears when the action lands. The same holds for a pass created inside such a pass, such as the condition of a<Show>nested in a branch that read the staging; before, that case could leave the screen empty. A render effect in that position re-derives on the committed world when the flush parks, so it showsv:0 l:1, the same as the inline[s(), latest(s)](D-26). The async-app generator found this through its E9 pair (seed 3289). -
f94dcd5: An async memo that reads a memo using
latest()orisPending()no longer asks the same question twice. Reading such a memo makes the async memo a verdict reader too (D-25). It was re-derived each time one of its verdict sources went pending, served a committed value it had already answered, and asked that question again; with two verdict sources it could ask a stale question after both had landed. Such a memo, one that calls neitherlatest()norisPending()itself, now waits on a verdict source going pending like any derivation, and a re-run its hold makes reads plainly as the hold's work. It asks each question once. An async memo that callslatest()orisPending()itself is unchanged: it stays a verdict reader and shows the lane's answer ahead (F-6).A reader that groups
isPending()with the value over a held write, such as[isPending(m1), m1()], now shows the lane's answer as a plain value reader does. A pass served a held memo's committed value no longer waits on that memo's flight, and a lane's reveal leaves the stale-reader mark the held transaction's landing needs to re-run it. -
409f893: A
<Loading>that a verdict reader (<Show when={latest(x) ?? x()}>) mounts reveals its content when a component inside it writes a signal during its mount and a memo that stays pending with no reader reads it through other memos (gate = createMemo(() => version()), then a memo overgate), at any depth: that memo holds nothing and reaches no boundary. #4203's fix covered only a memo reading the written signal directly (#4340). -
85ad399: A memo or render effect born in a branch that a staged or held condition just opened no longer reads the screen when it calls
latest()orisPending()(#4341). Its first pass runs inside the pass that opened the branch (or inside passes that branch is still creating, such as a<Show>or a component inside it), and the branch exists only in that world, so the pass reads that world like any derivation of it, and likebornIntodoes once the node is held. It becomes a verdict reader from its next pass. Before, the reader read the screen, where its branch was still closed: a<Match>callback's narrowed accessor saw its own branch as unmounted and threw "Attempting to access a stale value from ", which halted the reactive system. The order of reads inside the reader and whether a memo sits between the read and the binding no longer change this. Apps that do not uselatest()orisPending()ship no new bytes.