You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[2.0 / next] A first-load <Loading> reveals with isPending() === true when the memo only holds an answer to an older question (F-9) #4569
a2 awaits a0, and a0 depends on a signal s. On the first load under <Loading>, a0:0 lands and a2 asks a2:11. Then s changes, so a0 asks a0:1. When a2:11 lands, a2 holds an answer to the old question while the current one is still in flight. At that point the boundary reveals its content with isPending(() => a2()) reading true, and keeps showing pending until a2:20 lands.
a2 has never committed, so per F-9 it has no verdict: "a first load is loading, not pending", and isPending "cannot be true on creation". Inside an owner, isPending of such a node should do what the read does and hold the boundary until the answer to the current question lands.
The vitest spec below (in packages/web/test/), on next @ 62745f6.
Steps to Reproduce the Bug or Issue
import{render}from"@solidjs/web";import{createMemo,createSignal,flush,isPending,Loading}from"solid-js";import{expect,it}from"vitest";consttick=async()=>{for(leti=0;i<2;i++){awaitnewPromise(r=>setTimeout(r,0));flush();}};it("a first load does not reveal with isPending() === true",async()=>{constflights=newMap<string,(v: number)=>void>();constask=(key: string)=>newPromise<number>(resolve=>flights.set(key,resolve));const[s,setS]=createSignal(0);constel=document.createElement("div");constframes: string[]=[];constdispose=render(()=>{consta0=createMemo(()=>ask("a0:"+s()));consta2=createMemo(async()=>ask("a2:"+(awaita0())));return(<Loadingfallback={<p>loading</p>}><b>{isPending(()=>a2()) ? "pending" : "settled"}</b></Loading>);},el);awaittick();frames.push(el.textContent!);flights.get("a0:0")!(11);awaittick();frames.push(el.textContent!);// asks a2:11setS(1);flush();awaittick();frames.push(el.textContent!);// asks a0:1flights.get("a2:11")!(16);awaittick();frames.push(el.textContent!);// an answer to the old questionflights.get("a0:1")!(20);awaittick();frames.push(el.textContent!);// asks a2:20flights.get("a2:20")!(21);awaittick();frames.push(el.textContent!);dispose();expect(frames).toEqual(["loading","loading","loading","loading","loading","settled"]);});
npx vitest run in packages/web. Received frames: loading, loading, loading, pending, pending, settled.
Expected behavior
Frames loading, loading, loading, loading, loading, settled: the boundary first reveals when a2 answers the current question, and its first frame shows no pending verdict.
Thanks for this one — it's a genuine edge the differential found, and it needed a ruling rather than a fix.
Ruling (2026-10-31): today's frames stand. A node's question changes only when the node itself re-runs (#3373: "nothing superseded it"). setS(1) re-asks a0 and marks a2 pending by propagation — that changes a0's status, not a2's inputs — so when a2:11 lands it is the current answer to a2's current question. It initializes a2, which is then committed and pending on a0's flight. A <Loading> whose content reads only isPending(() => a2()) reveals when the verdict exists (S-40: a fresh boundary reading only a display-ahead channel shows what it has), so the first frame is pending. F-9's "cannot be true on creation" is about the node at creation, not a boundary's first frame; #4459 already pins a true first reveal for a node answered-then-re-asked.
The crux, as ruled: does an upstream re-ask that marks a node pending by propagation change the node's question before the node re-runs? No. The alternative would need a "supersession by propagation" no rule has, a new resting state (staged, uninitialized, pending, unheld), would treat an uninitialized node more strictly than a committed one (#3373 writes the same landing to a committed node), and would contradict #4459/#4495's first reveals.
Control worth knowing: any plain read of a2 beside the probe — {a2()} alone, or D-23's canonical <p>{a2()}</p><span>{isPending(a2) …}</span> — already gives the frames you expected (loading ×5, settled): a reader of the node itself waits on it (F-2, no screen to keep). Only verdict-only content reveals early, and that asymmetry is the rule.
Pinned with the ruling, the control, a 3-deep chain and the no-boundary form, plus D-23/F-9/S-40 doc clauses, in #4571 — this issue will close with that PR.
Activity
ryansolid commentedon Nov 1, 2026
test(signals): pin #4569 — a first load answered while pending by pro…
ryansolid commentedon Nov 1, 2026
Ruled and pinned on
nextby #4571 (77931d6): a first load answered while pending by propagation is committed and pending (D-23).Generated by Claude Code