Repository navigation
Description
Reproduced on next at 2a0da2f (2026-10-24), @solidjs/signals built from source. Run: node --conditions=browser --conditions=development dv4-signals.mjs [store|memo] [boundary|none].
Rule. RULES-DERIVED D-30: "a derived store's leaves report the
derive's new-question refetch like any async memo"; SCENARIOS S-37 row 2:
after setId(2), isPending(() => state.a) is true.
Shape. createStore(async draft => { Object.assign(draft, await fetch(id())) }, {}),
a render effect showing state.a and isPending(() => state.a), under a
createLoadingBoundary that has shown content. setId(2).
Observed.
store, boundary memo, boundary store, no boundary
setId(2) "1 -" ✗ "1 pending" ✓ "1 pending" ✓
The async memo under the same boundary reports pending; the same store
outside a boundary reports pending. Only the store-under-boundary
combination reads false. It shows when the verdict is read in the same computation as the value; with the verdict in its own {…} hole (as compiled JSX reads it) it reads true.
Repro
dv4-signals.mjs
// a derived store's per-key verdicts under a boundary that has shown content.
// Expected (RULES-DERIVED D-30, SCENARIOS S-37 row 2): after setId(2), while the derive re-asks, isPending(() => state.a) is true.
import { createRoot, createSignal, createMemo, createStore, createRenderEffect, createLoadingBoundary, isPending, flush, untrack } from "@solidjs/signals";
const flights = new Map();
const fetchKey = key => { if (!flights.has(key)) { let resolve; const p = new Promise(r => (resolve = r)); flights.set(key, { p, resolve }); } return flights.get(key).p; };
const tick = async () => { await new Promise(r => setTimeout(r, 0)); flush(); await new Promise(r => setTimeout(r, 0)); flush(); };
const land = async (key, v) => { flights.get(key).resolve(v); await tick(); };
const [kind, where] = [process.argv[2] ?? "store", process.argv[3] ?? "boundary"];
let screen = "";
const [id, setId] = createSignal(1);
createRoot(() => {
let a;
if (kind === "store") { const [state] = createStore(async draft => { const v = await fetchKey("s:" + id()); Object.assign(draft, v); }, {}); a = () => state.a; }
else { const m = createMemo(() => fetchKey("s:" + id())); a = () => m().a; }
const view = () => `${a()} ${isPending(a) ? "pending" : "-"}`;
const out = where === "boundary" ? untrack(() => createLoadingBoundary(view, () => "loading")) : view;
createRenderEffect(() => out(), v => { screen = v; });
});
flush();
const show = label => console.log(label.padEnd(18), JSON.stringify(screen));
await tick(); show("mount");
await land("s:1", { a: 1, b: 1 }); show("s(1) lands");
setId(2); flush(); await tick(); show("setId(2)"); // expected: "1 pending"
await land("s:2", { a: 2, b: 2 }); show("s(2) lands");Found by ReactiveScript's conformance suite, which diffs its spec against Solid's SCENARIOS.md.
Activity
fix(signals): clear a derived store's stale inputs-published mark at …
ryansolid commentedon Oct 26, 2026
Fixed on
nextby #4278 (f811097); it ships in the next RC.Generated by Claude Code