From 2ffb8065b17fef737e5a1112ce39d263eb5551f7 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Oct 2026 08:45:34 +0000 Subject: [PATCH 1/6] fix(signals): close the verdict window's state for a memo it pulls (#4429) `verdictValue` pulls a dirty memo with the dispatch closed (`setVerdict(null)`, A31: the pulled memo's pass is its own) but left `probing` and `latestActive` set. A `latest()` / `isPending()` the pulled memo made itself re-entered the window with the outer probe's state: - `latest()` of a born-held node took the probe's `joinPass` arm, so the memo's pass joined the hold and its value was held with it; - a flight its `latest()` read set `probeFound`, so the outer `isPending` answered for the memo's reads, not for the memo; - inside `latest(fn)`, the memo's own `isPending` of a held write read as a probe inside a `latest` window and answered final. Each depended on who woke the memo. Save and clear both flags around the pull, as the dispatch already is. `probeFound` needs no save: it is only set while `probing`. Fixes #4429 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 --- .changeset/verdict-pulled-pass-window-4429.md | 5 + packages/signals/docs/RULES-INDEX.md | 2 +- packages/signals/src/core/verdict.ts | 11 +- .../tests/probe-pulled-pass-4429.test.ts | 180 ++++++++++++++++++ 4 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 .changeset/verdict-pulled-pass-window-4429.md create mode 100644 packages/signals/tests/probe-pulled-pass-4429.test.ts diff --git a/.changeset/verdict-pulled-pass-window-4429.md b/.changeset/verdict-pulled-pass-window-4429.md new file mode 100644 index 0000000000..98fc0b1990 --- /dev/null +++ b/.changeset/verdict-pulled-pass-window-4429.md @@ -0,0 +1,5 @@ +--- +"@solidjs/signals": patch +--- + +A memo pulled inside `isPending()` or `latest()` now computes as its own pass (A31): the window's state no longer leaks into it, so a `latest()` it calls doesn't join the hold through the probe, its reads don't answer the outer `isPending`, and its own `isPending` isn't read as inside the outer `latest` (#4429). diff --git a/packages/signals/docs/RULES-INDEX.md b/packages/signals/docs/RULES-INDEX.md index 714fdb838f..e9797c7441 100644 --- a/packages/signals/docs/RULES-INDEX.md +++ b/packages/signals/docs/RULES-INDEX.md @@ -75,7 +75,7 @@ Status legend: **live** stated and standing · **ruled** carries an explicit rul | A28 | ruled, mechanism landed | `docs/SPEC-ASYNC-SEMANTICS.md:95` | constants.ts×1 core.ts×12 lanes.ts×2 scheduler.ts×3 types.ts×1 verdict.ts×2 store.ts×5 | continuation-sibling-reader-4146.test.ts×1 createOptimistic.test.ts×5 fuzz-findings-l2.test.ts×6 held-derivation-not-a-proposal-3612.test.ts×1 issue-3800-repro.test.ts×1 latest-held-till-flush.test.ts×1 latest-memo-inline-parity.test.ts×1 posture-store-parity.test.ts×5 question-scoped-pending.test.ts×3 snapshot-derived-store-rows.test.ts×1 createOptimisticStore.test.ts×10 optimistic-draft-visibility-3665.test.ts×5 optimistic-list-mutation-matrix.harness.ts×1 optimistic-list-mutation-matrix.test.ts×2 shallow.test.ts×1 woken-transaction-adopts-staged-bump.test.ts×1 treeshake.test.ts×2 verdict-contract.test.ts×1 visibility-oracle-store.states.ts×8 visibility-oracle.states.ts×8 | [ruled, mechanism landed 2026-09-15] A write becomes visible at flush — to every channel — (**ruled 2026-09-08**; supersedes the #2922 mid-tick pull) **A write becomes visible at flush — to every chan… | | A29 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:119` | boundaries.ts×1 action.ts×1 constants.ts×1 core.ts×14 effect.ts×1 lanes.ts×1 scheduler.ts×4 signals.ts×1 store.ts×1 | adoption-unchanged-key-read-3706.test.ts×9 body-end-supersession-visibility.test.ts×1 born-held.test.ts×3 boundary-not-born-held-3540.test.ts×4 createProjection.draft-lifetime-3585.test.ts×1 direct-commit-readers-posture.test.ts×1 fuzz-findings-l2.test.ts×6 held-branch-verdict-reader-4010.test.ts×1 held-conditional-memo.test.ts×1 held-frame-dependencies.test.ts×2 held-truth-lane-only.test.ts×3 l2-contract.test.ts×1 latest-held-till-flush.test.ts×2 loading-fallback-in-flush-3540.test.ts×6 loading-on-frame-following-3540.test.ts×1 mount-over-foreign-hold-3761.test.ts×1 optimistic-mount-nested-memo-3835.test.ts×1 optimistic-read-lane-not-transaction-3698.test.ts×3 posture-born-held-and-observation.test.ts×1 posture-store-parity.test.ts×6 derived-presence-async-3726.test.ts×3 optimistic-untracked-reads-f3-f5.test.ts×1 store-unchanged-read-independent-write-3688.test.ts×1 tick-scoped-pass-transaction.test.ts×2 treeshake.test.ts×3 verdict-mount-first-pass-3851.test.ts×2 verdict-mount-loading-flush-loop.test.ts×2 visibility-oracle-store.states.ts×5 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×9 visibility-oracle.test.ts×1 write-proposals-3494.test.ts×1 | [ruled, amended in place 2026-09-13 (#3408)] A tracked read served a live transaction's staged value enters that transaction — A tracked computation served a node's staged `_pendingValue` — a value a … | | A30 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:259` | async.ts×3 attribution.ts×1 constants.ts×1 core.ts×1 effect.ts×1 lanes.ts×1 scheduler.ts×5 projection.ts×1 | async-landing-deps-3461.test.ts×3 fuzz-findings-l2.test.ts×3 held-conditional-effect.test.ts×1 held-conditional-memo.test.ts×1 held-frame-dependencies.test.ts×2 ispending-in-boundary-on-3528.test.ts×1 lane-frame-deferred-run-3662.test.ts×1 lane-frame-held-lane-3662.test.ts×1 posture-born-held-and-observation.test.ts×1 projection-peek-loop-4342.test.ts×1 treeshake.test.ts×2 write-proposals-3494.test.ts×2 zombie-rerun-after-commit-3546.test.ts×2 | [ruled 2026-09-13 (#3410)] A memo's dependencies are the committed frame's until the frame is replaced — A pass that _staged_ its value has not replaced the committed frame, so the committed value sti… | -| A31 | live | `docs/SPEC-ASYNC-SEMANTICS.md:133` | boundaries.ts×1 core.ts×1 lanes.ts×1 verdict.ts×2 | fuzz-findings-l2.test.ts×6 ispending-combined-atomic-3442.test.ts×1 unowned-memo-ispending-reader-4366.test.ts×1 | [live 2026-09-14 (#3442)] A memo computes under its own lane posture, never its puller's — A memo's value is one shared slot every reader sees, so its pass runs under the lane posture the memo itself … | +| A31 | live | `docs/SPEC-ASYNC-SEMANTICS.md:133` | boundaries.ts×1 core.ts×1 lanes.ts×1 verdict.ts×2 | fuzz-findings-l2.test.ts×6 ispending-combined-atomic-3442.test.ts×1 probe-pulled-pass-4429.test.ts×1 unowned-memo-ispending-reader-4366.test.ts×1 | [live 2026-09-14 (#3442)] A memo computes under its own lane posture, never its puller's — A memo's value is one shared slot every reader sees, so its pass runs under the lane posture the memo itself … | | A32 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:141` | core.ts×4 lanes.ts×1 store.ts×1 | visibility-oracle-store.states.ts×8 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×9 visibility-oracle.test.ts×1 | [ruled 2026-09-14] Children-forbidden readers see the frame, not the graph — `createTrackedEffect` and `onSettled` callbacks are effect-phase code that runs after the frame is decided. They read the f… | | A33 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:275` | boundaries.ts×1 scheduler.ts×2 | async-chain-supersession.test.ts×2 boundary-not-born-held-3540.test.ts×2 create-deferred-3710.test.ts×1 fuzz-findings-l2.test.ts×4 ispending-in-boundary-on-3528.test.ts×2 loading-reset-collects-forwarded-3459.test.ts×3 | [ruled 2026-09-12 (#3375)] A fallback-caught flight holds no transaction; a Loading reset moves the hold onto the boundary — A `` boundary showing its fallback is the display of everything un… | | A34 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:285` | constants.ts×1 core.ts×6 lanes.ts×2 scheduler.ts×2 store.ts×6 | a34-writes-then-derivations.test.ts×2 continuation-sibling-reader-4146.test.ts×1 createMemo.test.ts×1 derived-write-then-derivation-3733.test.ts×1 finalize-reentry.test.ts×2 fuzz-findings-l2.test.ts×5 held-derivation-not-a-proposal-3612.test.ts×6 optimistic-guess-over-held-slot.test.ts×2 optimistic-list-mutation-matrix.test.ts×1 unchanged-presence-no-hold-3743.test.ts×3 woken-transaction-adopts-staged-bump.test.ts×1 transition-corpse-revival.test.ts×1 treeshake.test.ts×2 visibility-oracle.states.ts×2 write-proposals-3494.test.ts×5 | [ruled 2026-09-16 (#3494)] A write is a proposal: one on a held node entangles its tick; one that nets to the committed value is none — A write proposes a value for a node. **Held, both are suggestion… | diff --git a/packages/signals/src/core/verdict.ts b/packages/signals/src/core/verdict.ts index 7c05b7d032..8cded680f5 100644 --- a/packages/signals/src/core/verdict.ts +++ b/packages/signals/src/core/verdict.ts @@ -393,7 +393,14 @@ function verdictValue(el: Signal | Computed, c: Computed | null): // produced. So is a lazy or dormant memo's wake (an unowned memo with no // reader left, #4366): woken inside the window, it read its sources as the // probe does, the committed value under a landing. (A signal has no - // `_flags`: the wake is a no-op for it.) + // `_flags`: the wake is a no-op for it.) The window's state closes with it + // (#4429): a `latest()` or `isPending()` the pass makes itself opens its + // own window — it does not join a hold through the probe's arm, answer + // the outer probe (`probeFound` is only set while `probing`), or read as + // inside the outer `latest`. + const prevProbing = probing, + prevLatest = latestActive; + probing = latestActive = false; setVerdict(null); prepareComputed(owner, false); if (c && tracking) { @@ -401,6 +408,8 @@ function verdictValue(el: Signal | Computed, c: Computed | null): if ((el as any)._fn) pullComputed(owner, c); } setVerdict(verdictValue); + probing = prevProbing; + latestActive = prevLatest; // A live `affects()` mark on the node or a dependency (affects.ts): not // final by declaration — the value itself is read as below. The verdict is // display-ahead like any: in a transaction's flush (the declaring action's diff --git a/packages/signals/tests/probe-pulled-pass-4429.test.ts b/packages/signals/tests/probe-pulled-pass-4429.test.ts new file mode 100644 index 0000000000..c388295bfb --- /dev/null +++ b/packages/signals/tests/probe-pulled-pass-4429.test.ts @@ -0,0 +1,180 @@ +/** + * #4429 — a memo's pass pulled inside a verdict window is its own (A31), + * whoever woke it. + * + * `verdictValue` pulls a dirty memo with the dispatch closed + * (`setVerdict(null)`), so the memo's plain reads are its own. It left the + * window's state set, though: `probing` (and `latestActive`) stayed on for + * the pulled pass, so a `latest()` / `isPending()` the memo calls itself + * re-entered the window with the outer probe's state: + * + * - `latest()` of a node born held (no committed value, staged) took the + * probe's `joinPass` arm: the pulled memo joined the hold, and a memo it + * created during that pass was born held (D-27) — where the same pass + * pulled by a plain read leaves it mainline (F-vworld: a verdict read + * raises no world); + * - `probeFound` was set from inside the pulled pass (a flight its + * `latest()` read), so the outer `isPending` answered for the memo's + * reads rather than for the memo; + * - inside `latest(fn)`, a pulled memo's own `isPending` of a held write + * answered as a probe inside a `latest` window (final), so the memo's + * shared value depended on who pulled it. + * + * Each case compares the pull inside the window with the same pass woken by + * a plain read: the memo's pass, its value and what it creates are the same. + */ +import { describe, expect, it } from "vitest"; +import { + action, + createMemo, + createRenderEffect, + createRoot, + createSignal, + flush, + isPending, + latest, + untrack +} from "../src/index.js"; + +const peek = (fn: () => unknown) => { + try { + return untrack(fn); + } catch (e) { + return (e as Error).constructor.name; + } +}; + +const settle = async () => { + await Promise.resolve(); + await Promise.resolve(); + flush(); +}; + +describe("#4429: a memo pulled inside a verdict window computes as its own pass", () => { + for (const pull of ["isPending", "plain"] as const) + it(`latest() of a born-held node in the pulled pass joins nothing (pulled ${pull})`, async () => { + const [x, setX] = createSignal(0); + const [s, setS] = createSignal(0); + const [on, setOn] = createSignal(false); + let born: (() => number) | null = null; + let child!: () => number; + const m1 = createRoot(() => { + createRenderEffect(x, () => {}); + // Deeper than the reader below: the reader's pass pulls it. + const sa = createMemo(() => s()); + const m1 = createMemo(() => { + const v = sa(); + const l = born ? latest(born) : -1; + // Reads nothing held: born held only if its creator joined. + child = createMemo(() => v * 100); + return l * 10 + v; + }); + createRenderEffect(m1, () => {}); + createRenderEffect( + () => on() && (pull === "isPending" ? isPending(() => m1()) : m1()), + () => {} + ); + return m1; + }); + flush(); + let release!: () => void; + action(function* () { + setX(1); + yield new Promise(r => (release = r)); + })(); + flush(); + + // Born held: no committed value, its staging (1) its only value. + createRoot(() => { + born = createMemo(() => x()); + }); + expect(peek(born!)).toBe("NotReadyError"); + setS(1); + setOn(true); + flush(); + // The pulled pass read `latest(born)` = 1 (D-22) and made its child + // from the screen's s: mainline — not the hold's. + expect(peek(m1)).toBe(11); + expect(peek(child)).toBe(100); + release(); + await settle(); + expect(peek(m1)).toBe(11); + expect(peek(child)).toBe(100); + }); + + for (const pull of ["isPending", "plain"] as const) + it(`a flight the pulled pass's latest() reads is not the probe's answer (pulled ${pull})`, async () => { + const [q, setQ] = createSignal(1); + const [s, setS] = createSignal(0); + const [on, setOn] = createSignal(false); + let resolve!: () => void; + const seen: unknown[] = []; + const m1 = createRoot(() => { + const a = createMemo(async () => { + const v = q(); + if (v > 1) await new Promise(r => (resolve = r)); + return v; + }); + createRenderEffect(a, () => {}); + const sa = createMemo(() => s()); + const m1 = createMemo(() => sa() + latest(a) * 10); + createRenderEffect(m1, () => {}); + createRenderEffect( + () => on() && (pull === "isPending" ? isPending(() => m1()) : (m1(), isPending(m1))), + v => { + seen.push(v); + } + ); + return m1; + }); + await settle(); + expect(peek(m1)).toBe(10); + setQ(2); + flush(); + // a is in flight over its committed 1. + setS(1); + setOn(true); + flush(); + // m1 is a settled sync memo of `latest(a)` — the committed 1 while the + // flight is up: nothing m1 shows is waiting on it. + expect(peek(m1)).toBe(11); + expect(seen).toEqual([false, false]); + resolve(); + await settle(); + expect(peek(m1)).toBe(21); + }); + + for (const pull of ["latest", "plain"] as const) + it(`isPending() in the pulled pass answers for the memo, not the outer latest window (pulled ${pull})`, async () => { + const [x, setX] = createSignal(0); + const [s, setS] = createSignal(0); + const [on, setOn] = createSignal(false); + const m1 = createRoot(() => { + createRenderEffect(x, () => {}); + const sa = createMemo(() => s()); + const m1 = createMemo(() => (sa(), isPending(x))); + createRenderEffect(m1, () => {}); + createRenderEffect( + () => on() && (pull === "latest" ? latest(() => m1()) : m1()), + () => {} + ); + return m1; + }); + flush(); + let release!: () => void; + action(function* () { + setX(1); + yield new Promise(r => (release = r)); + })(); + flush(); + expect(peek(m1)).toBe(true); + setS(1); + setOn(true); + flush(); + // x's held write has not landed: the memo's own probe says so. + expect(peek(m1)).toBe(true); + release(); + await settle(); + expect(peek(m1)).toBe(false); + }); +}); From 2ae67a40df93fd7692bd666b0ce63b72b061fbd9 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Oct 2026 09:28:17 +0000 Subject: [PATCH 2/6] fix(signals): close only the probe's state around a pulled memo (#4429) Narrow the fix to `probing`, the state #4429 names: +9 B brotli / +13 B minified on `+ isPending/latest`, where clearing `latestActive` too cost +25 / +25. `latestActive` leaks the same way (inside `latest(fn)`, a pulled memo's own `isPending` of a held write answers final); that is left for its own issue and decision. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 --- .changeset/verdict-pulled-pass-window-4429.md | 2 +- packages/signals/src/core/verdict.ts | 15 +++---- .../tests/probe-pulled-pass-4429.test.ts | 44 ++----------------- 3 files changed, 10 insertions(+), 51 deletions(-) diff --git a/.changeset/verdict-pulled-pass-window-4429.md b/.changeset/verdict-pulled-pass-window-4429.md index 98fc0b1990..0d38628fdb 100644 --- a/.changeset/verdict-pulled-pass-window-4429.md +++ b/.changeset/verdict-pulled-pass-window-4429.md @@ -2,4 +2,4 @@ "@solidjs/signals": patch --- -A memo pulled inside `isPending()` or `latest()` now computes as its own pass (A31): the window's state no longer leaks into it, so a `latest()` it calls doesn't join the hold through the probe, its reads don't answer the outer `isPending`, and its own `isPending` isn't read as inside the outer `latest` (#4429). +A memo pulled inside `isPending()` now computes as its own pass (A31): the probe's state no longer leaks into it, so a `latest()` it calls doesn't join the hold through the probe, and its reads don't answer the outer `isPending` (#4429). diff --git a/packages/signals/src/core/verdict.ts b/packages/signals/src/core/verdict.ts index 6c3fc99859..b761ad3017 100644 --- a/packages/signals/src/core/verdict.ts +++ b/packages/signals/src/core/verdict.ts @@ -403,14 +403,12 @@ function verdictValue(el: Signal | Computed, c: Computed | null): // produced. So is a lazy or dormant memo's wake (an unowned memo with no // reader left, #4366): woken inside the window, it read its sources as the // probe does, the committed value under a landing. (A signal has no - // `_flags`: the wake is a no-op for it.) The window's state closes with it - // (#4429): a `latest()` or `isPending()` the pass makes itself opens its - // own window — it does not join a hold through the probe's arm, answer - // the outer probe (`probeFound` is only set while `probing`), or read as - // inside the outer `latest`. - const prevProbing = probing, - prevLatest = latestActive; - probing = latestActive = false; + // `_flags`: the wake is a no-op for it.) So is the probe's state (#4429): + // a `latest()` the pass makes itself does not join a hold through the + // probe's arm, and what the pass reads does not answer the outer probe + // (`probeFound` is only set while `probing`). + const prevProbing = probing; + probing = false; setVerdict(null); prepareComputed(owner, false); if (c && tracking) { @@ -419,7 +417,6 @@ function verdictValue(el: Signal | Computed, c: Computed | null): } setVerdict(verdictValue); probing = prevProbing; - latestActive = prevLatest; // Shadow (S0): the window's decision point — the rows Vl*/Vp*. if (__TEST__) shadowExpect(c, el, tracking, latestActive ? 2 : 1, spectating, false); // A live `affects()` mark on the node or a dependency (affects.ts): not diff --git a/packages/signals/tests/probe-pulled-pass-4429.test.ts b/packages/signals/tests/probe-pulled-pass-4429.test.ts index c388295bfb..e928d6c5a1 100644 --- a/packages/signals/tests/probe-pulled-pass-4429.test.ts +++ b/packages/signals/tests/probe-pulled-pass-4429.test.ts @@ -4,9 +4,8 @@ * * `verdictValue` pulls a dirty memo with the dispatch closed * (`setVerdict(null)`), so the memo's plain reads are its own. It left the - * window's state set, though: `probing` (and `latestActive`) stayed on for - * the pulled pass, so a `latest()` / `isPending()` the memo calls itself - * re-entered the window with the outer probe's state: + * probe's state set, though: `probing` stayed on for the pulled pass, so a + * `latest()` the memo calls itself re-entered the window as the outer probe: * * - `latest()` of a node born held (no committed value, staged) took the * probe's `joinPass` arm: the pulled memo joined the hold, and a memo it @@ -15,10 +14,7 @@ * raises no world); * - `probeFound` was set from inside the pulled pass (a flight its * `latest()` read), so the outer `isPending` answered for the memo's - * reads rather than for the memo; - * - inside `latest(fn)`, a pulled memo's own `isPending` of a held write - * answered as a probe inside a `latest` window (final), so the memo's - * shared value depended on who pulled it. + * reads rather than for the memo. * * Each case compares the pull inside the window with the same pass woken by * a plain read: the memo's pass, its value and what it creates are the same. @@ -143,38 +139,4 @@ describe("#4429: a memo pulled inside a verdict window computes as its own pass" await settle(); expect(peek(m1)).toBe(21); }); - - for (const pull of ["latest", "plain"] as const) - it(`isPending() in the pulled pass answers for the memo, not the outer latest window (pulled ${pull})`, async () => { - const [x, setX] = createSignal(0); - const [s, setS] = createSignal(0); - const [on, setOn] = createSignal(false); - const m1 = createRoot(() => { - createRenderEffect(x, () => {}); - const sa = createMemo(() => s()); - const m1 = createMemo(() => (sa(), isPending(x))); - createRenderEffect(m1, () => {}); - createRenderEffect( - () => on() && (pull === "latest" ? latest(() => m1()) : m1()), - () => {} - ); - return m1; - }); - flush(); - let release!: () => void; - action(function* () { - setX(1); - yield new Promise(r => (release = r)); - })(); - flush(); - expect(peek(m1)).toBe(true); - setS(1); - setOn(true); - flush(); - // x's held write has not landed: the memo's own probe says so. - expect(peek(m1)).toBe(true); - release(); - await settle(); - expect(peek(m1)).toBe(false); - }); }); From d47a5dcc4c5a3124ff590aa628d0d5e29aef1e73 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Oct 2026 09:28:32 +0000 Subject: [PATCH 3/6] chore: keep the #4425 spec as next has it (undo the merge's formatter pass) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 --- .../errored-recovery-nested-loading-4425.spec.tsx | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/web/test/errored-recovery-nested-loading-4425.spec.tsx b/packages/web/test/errored-recovery-nested-loading-4425.spec.tsx index 34a3f5f267..a5d8ce6dbe 100644 --- a/packages/web/test/errored-recovery-nested-loading-4425.spec.tsx +++ b/packages/web/test/errored-recovery-nested-loading-4425.spec.tsx @@ -14,16 +14,7 @@ * refresh landing is the new answer (D-39). */ import { expect, test } from "vitest"; -import { - Errored, - For, - Loading, - Show, - createMemo, - flush, - refresh, - type Refreshable -} from "solid-js"; +import { Errored, For, Loading, Show, createMemo, flush, refresh, type Refreshable } from "solid-js"; import { render } from "../src/index.js"; const tick = async () => { From caf08049882b2a95048eb1e70482007766e6a60f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Oct 2026 10:33:55 +0000 Subject: [PATCH 4/6] chore(size): raise the compiled live page's cap for #4429 (+13 B minified) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 --- scripts/size/scenarios.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/size/scenarios.js b/scripts/size/scenarios.js index ff93f301a5..db19a37d92 100644 --- a/scripts/size/scenarios.js +++ b/scripts/size/scenarios.js @@ -5904,11 +5904,12 @@ module.exports = [ // Re-record after the 2026-10-29 afternoon batch (#4418, #4414; 2026-10-29, over next @ 5d57e12a): 42.62 KB -> 42.65 KB, recorded minified 125,165 -> 125,266 B (+101 B), measured in CI at 42,640 B brotli. Growth is #4414 (a truncated fragment's fresh render computes what the server never sent: the truncation sweep deletes unsettled records at DOMContentLoaded, +~100 B minified in solid's hydration client); the maintainer said merge (2026-10-29). Cap at measured + 10 B rounded up to 0.01 KB; recorded minified = measured. // Batch re-record (2026-10-30, next @ 32163a9, size job 123956613767): cap unchanged, recorded minified 125,266 -> 125,285 B, measured in CI at 42,636 B brotli. Growth is the 2026-10-30 merges (#4424, #4436, #4416, #4442); #4416's truncation sweep is the +19 B minified on the hydrating scenarios. Cap at measured + 10 B rounded up to 0.01 KB where over; recorded minified = measured. // A held memo's parked reader re-derives at the seam (#4439, 2026-10-30, over next @ b249ad96): 42.65 KB -> 42.67 KB, recorded minified 125,285 -> 125,374 B (+89 B), measured locally (linux) at 42,655 B brotli. `laneSeams` also re-derives a reader whose pass read a held node, so `isPending` of a memo parked behind a held one turns true. Correctness. Cap at measured + 10 B rounded up to 0.01 KB; recorded minified = measured. - limit: "42.67 KB", + // A memo pulled inside isPending() computes as its own pass (#4429, 2026-10-30, over next @ 7135a77a): 42.67 KB -> 42.71 KB, recorded minified 125,374 -> 125,387 B (+13 B), measured locally (linux) at 42,696 B brotli. `verdictValue` closes the probe's `probing` flag around the memo it pulls, as it closes the dispatch, so a `latest()` in the pulled pass does not join the hold through the probe. Correctness. Cap at measured + 10 B rounded up to 0.01 KB; recorded minified = measured. + limit: "42.71 KB", // An Errored heals when the last reader that caught its error is disposed (#4373, #4384, 2026-10-28, re-recorded over next @ cbc0eebd): recorded minified 123,191 -> 123,249 B (+58 B minified over next's record), measured locally (linux) at 41,899 B brotli. Recorded minified = measured; CI re-bases. // Boundary readers keyed by the source they wait on (#4373), with enterLane's tail folded into one else-chain as an offset (-29 B on lane pages) (#4384, 2026-10-28, re-recorded over next @ 1b32ff2e): recorded minified 123,203 -> 123,224 B (+21 B minified over next's record), measured locally (linux) at 41,966 B brotli. Recorded minified = measured; CI re-bases. // latest() read inline and through a memo show the same screen (#4284 through a memo; a plain write beside an async memo), 2026-10-29, over next @ 7ebd4a32: recorded minified 123,224 -> 123,733 B (+403 B minified over next), measured locally (linux) at 42,119 B brotli (+169 B over next). A render effect a verdict memo's change re-runs reads as its verdict reader for that pass (`laneDirty`), a pass that read a staging before the frame had a transaction re-derives once as a verdict reader only if the frame parks, `latest()` of a first load in flight in a verdict lane is loading (also through an identity memo, a derived store and `isPending`), a lane's dissolution leaves such a load and the nodes pending on it uninitialized instead of committing `undefined`, a verdict lane born at a seam is judged at it, a pass born in a staged reader stays the frame's on a verdict-memo read (C4, #4010), a plain write beside `latest()` computes once (the seam re-derives a pre-transaction staged reader only if the frame parks), a verdict reader's plain read of a staging in a transaction only a `latest()` opened is the frame's read, so a memo of `latest()` computes once too (D-22; not a pass with no value yet, D-6), and an error stays the display while the memo it came through re-asks, a Loading fallback behind it included (D-39, async-gen 91501 #1831). recompute's CONFIG_VERDICT clear now sits behind the lane engine: -21 B minified on every floor. Correctness; the maintainer: "only flag me if brotli goes more than 20kb" (2026-10-29). Cap unchanged; recorded minified = measured. - capMinified: 125374, + capMinified: 125387, alias: pageAlias }, { From af436b1a6fa62977b0487b978240e8ee60805ac3 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Oct 2026 12:03:56 +0000 Subject: [PATCH 5/6] chore(size): raise the live server components floor for #4429 (+13 B minified) Frozen floor 38.76 KB -> 38.80 KB, recorded minified 117,227 -> 117,328 B (+13 B this fix; the rest is next's growth since the floor was recorded). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 --- scripts/size/floor-caps.json | 4 ++-- scripts/size/scenarios.js | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/size/floor-caps.json b/scripts/size/floor-caps.json index 8cc82c36de..62a999faed 100644 --- a/scripts/size/floor-caps.json +++ b/scripts/size/floor-caps.json @@ -16,8 +16,8 @@ "minified": 101605 }, "page: live server components (base + live/GET + action + isPending/latest)": { - "cap": "38.76 KB", - "minified": 117227 + "cap": "38.80 KB", + "minified": 117328 }, "server: floor (getRequestEvent + isServer)": { "cap": "1.29 KB", diff --git a/scripts/size/scenarios.js b/scripts/size/scenarios.js index 191f875eee..5cb5498444 100644 --- a/scripts/size/scenarios.js +++ b/scripts/size/scenarios.js @@ -5598,6 +5598,7 @@ module.exports = [ // (117,143 B minified). Lower only: cap at measured + 10 B rounded up to // 0.01 KB; recorded minified never raised. // A held memo's parked reader re-derives at the seam (#4439, 2026-10-30, over next @ b249ad96): 38.74 KB -> 38.76 KB, recorded minified 117,143 -> 117,227 B (+84 B), measured locally (linux) at 38,741 B brotli. `laneSeams` also re-derives a reader whose pass read a held node, so `isPending` of a memo parked behind a held one turns true. Correctness; a frozen floor, +15 B brotli is under the maintainer's 20 B flag line (2026-10-29), Size-Exception in the PR body. Cap at measured + 10 B rounded up to 0.01 KB; recorded minified = measured. + // A memo pulled inside isPending() computes as its own pass (#4429, 2026-10-30, over next @ 5f07d049): frozen floor 38.76 KB -> 38.80 KB, recorded minified 117,227 -> 117,328 B (+101 B: +13 B this fix, +88 B next's own growth since the floor was recorded), measured locally (linux) at 38,782 B brotli. `verdictValue` clears the probe's state for the pass it pulls, so a `latest()` there joins no hold through the probe and its reads do not answer the outer `isPending` (A31, F-vworld). Correctness; Size-Exception in the PR body. Cap at measured + 10 B rounded up to 0.01 KB; recorded minified = measured. limit: floorCaps["page: live server components (base + live/GET + action + isPending/latest)"], capMinified: floorMinified["page: live server components (base + live/GET + action + isPending/latest)"], From 0bc0adb496b6f1041b7a36030966c08ac4c334c9 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Oct 2026 14:45:46 +0000 Subject: [PATCH 6/6] test(signals): drop the seat shadow's known entry for #4429 The probe now restores `probing` around a pulled pass, so the entry can only hide a regression. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 --- packages/signals/docs/RULES-INDEX.md | 2 +- packages/signals/src/core/seat.ts | 19 ------------------- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/packages/signals/docs/RULES-INDEX.md b/packages/signals/docs/RULES-INDEX.md index 049cac301d..ad0dee89ac 100644 --- a/packages/signals/docs/RULES-INDEX.md +++ b/packages/signals/docs/RULES-INDEX.md @@ -75,7 +75,7 @@ Status legend: **live** stated and standing · **ruled** carries an explicit rul | A28 | ruled, mechanism landed | `docs/SPEC-ASYNC-SEMANTICS.md:95` | constants.ts×1 core.ts×12 lanes.ts×2 scheduler.ts×3 seat.ts×3 types.ts×1 verdict.ts×2 store.ts×5 | continuation-sibling-reader-4146.test.ts×1 createOptimistic.test.ts×5 fuzz-findings-l2.test.ts×6 held-derivation-not-a-proposal-3612.test.ts×1 issue-3800-repro.test.ts×1 latest-held-till-flush.test.ts×1 latest-memo-inline-parity.test.ts×1 posture-store-parity.test.ts×5 question-scoped-pending.test.ts×3 snapshot-derived-store-rows.test.ts×1 createOptimisticStore.test.ts×10 optimistic-draft-visibility-3665.test.ts×5 optimistic-list-mutation-matrix.harness.ts×1 optimistic-list-mutation-matrix.test.ts×2 shallow.test.ts×1 woken-transaction-adopts-staged-bump.test.ts×1 treeshake.test.ts×2 verdict-contract.test.ts×1 visibility-oracle-store.states.ts×8 visibility-oracle.states.ts×8 | [ruled, mechanism landed 2026-09-15] A write becomes visible at flush — to every channel — (**ruled 2026-09-08**; supersedes the #2922 mid-tick pull) **A write becomes visible at flush — to every chan… | | A29 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:119` | boundaries.ts×1 action.ts×1 constants.ts×1 core.ts×14 effect.ts×1 lanes.ts×1 scheduler.ts×4 seat.ts×1 signals.ts×1 store.ts×1 | adoption-unchanged-key-read-3706.test.ts×9 body-end-supersession-visibility.test.ts×1 born-held.test.ts×3 boundary-not-born-held-3540.test.ts×4 createProjection.draft-lifetime-3585.test.ts×1 direct-commit-readers-posture.test.ts×1 fuzz-findings-l2.test.ts×6 held-branch-verdict-reader-4010.test.ts×1 held-conditional-memo.test.ts×1 held-frame-dependencies.test.ts×2 held-truth-lane-only.test.ts×3 l2-contract.test.ts×1 latest-held-till-flush.test.ts×2 loading-fallback-in-flush-3540.test.ts×6 loading-on-frame-following-3540.test.ts×1 mount-over-foreign-hold-3761.test.ts×1 optimistic-mount-nested-memo-3835.test.ts×1 optimistic-read-lane-not-transaction-3698.test.ts×3 posture-born-held-and-observation.test.ts×1 posture-store-parity.test.ts×6 derived-presence-async-3726.test.ts×3 optimistic-untracked-reads-f3-f5.test.ts×1 store-unchanged-read-independent-write-3688.test.ts×1 tick-scoped-pass-transaction.test.ts×2 treeshake.test.ts×3 verdict-born-into-other-flush-4426.test.ts×1 verdict-mount-first-pass-3851.test.ts×2 verdict-mount-loading-flush-loop.test.ts×2 visibility-oracle-store.states.ts×5 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×9 visibility-oracle.test.ts×1 write-proposals-3494.test.ts×1 | [ruled, amended in place 2026-09-13 (#3408)] A tracked read served a live transaction's staged value enters that transaction — A tracked computation served a node's staged `_pendingValue` — a value a … | | A30 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:259` | async.ts×3 attribution.ts×1 constants.ts×1 core.ts×1 effect.ts×1 lanes.ts×1 scheduler.ts×5 projection.ts×1 | async-landing-deps-3461.test.ts×3 fuzz-findings-l2.test.ts×3 held-conditional-effect.test.ts×1 held-conditional-memo.test.ts×1 held-frame-dependencies.test.ts×2 ispending-in-boundary-on-3528.test.ts×1 lane-frame-deferred-run-3662.test.ts×1 lane-frame-held-lane-3662.test.ts×1 posture-born-held-and-observation.test.ts×1 projection-peek-loop-4342.test.ts×1 treeshake.test.ts×2 write-proposals-3494.test.ts×2 zombie-rerun-after-commit-3546.test.ts×2 | [ruled 2026-09-13 (#3410)] A memo's dependencies are the committed frame's until the frame is replaced — A pass that _staged_ its value has not replaced the committed frame, so the committed value sti… | -| A31 | live | `docs/SPEC-ASYNC-SEMANTICS.md:133` | boundaries.ts×1 core.ts×1 lanes.ts×1 seat.ts×1 verdict.ts×2 | fuzz-findings-l2.test.ts×6 ispending-combined-atomic-3442.test.ts×1 probe-pulled-pass-4429.test.ts×1 unowned-memo-ispending-reader-4366.test.ts×1 | [live 2026-09-14 (#3442)] A memo computes under its own lane posture, never its puller's — A memo's value is one shared slot every reader sees, so its pass runs under the lane posture the memo itself … | +| A31 | live | `docs/SPEC-ASYNC-SEMANTICS.md:133` | boundaries.ts×1 core.ts×1 lanes.ts×1 verdict.ts×2 | fuzz-findings-l2.test.ts×6 ispending-combined-atomic-3442.test.ts×1 probe-pulled-pass-4429.test.ts×1 unowned-memo-ispending-reader-4366.test.ts×1 | [live 2026-09-14 (#3442)] A memo computes under its own lane posture, never its puller's — A memo's value is one shared slot every reader sees, so its pass runs under the lane posture the memo itself … | | A32 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:141` | core.ts×4 lanes.ts×1 seat.ts×1 store.ts×1 | visibility-oracle-store.states.ts×8 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×9 visibility-oracle.test.ts×1 | [ruled 2026-09-14] Children-forbidden readers see the frame, not the graph — `createTrackedEffect` and `onSettled` callbacks are effect-phase code that runs after the frame is decided. They read the f… | | A33 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:275` | boundaries.ts×1 scheduler.ts×2 | async-chain-supersession.test.ts×2 boundary-not-born-held-3540.test.ts×2 create-deferred-3710.test.ts×1 fuzz-findings-l2.test.ts×4 ispending-in-boundary-on-3528.test.ts×2 loading-reset-collects-forwarded-3459.test.ts×3 | [ruled 2026-09-12 (#3375)] A fallback-caught flight holds no transaction; a Loading reset moves the hold onto the boundary — A `` boundary showing its fallback is the display of everything un… | | A34 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:285` | constants.ts×1 core.ts×6 lanes.ts×2 scheduler.ts×2 store.ts×6 | a34-writes-then-derivations.test.ts×2 continuation-sibling-reader-4146.test.ts×1 createMemo.test.ts×1 derived-write-then-derivation-3733.test.ts×1 finalize-reentry.test.ts×2 fuzz-findings-l2.test.ts×5 held-derivation-not-a-proposal-3612.test.ts×6 optimistic-guess-over-held-slot.test.ts×2 optimistic-list-mutation-matrix.test.ts×1 unchanged-presence-no-hold-3743.test.ts×3 woken-transaction-adopts-staged-bump.test.ts×1 transition-corpse-revival.test.ts×1 treeshake.test.ts×2 visibility-oracle.states.ts×2 write-proposals-3494.test.ts×5 | [ruled 2026-09-16 (#3494)] A write is a proposal: one on a held node entangles its tick; one that nets to the committed value is none — A write proposes a value for a node. **Held, both are suggestion… | diff --git a/packages/signals/src/core/seat.ts b/packages/signals/src/core/seat.ts index 8b0100f45e..876876265d 100644 --- a/packages/signals/src/core/seat.ts +++ b/packages/signals/src/core/seat.ts @@ -1314,25 +1314,6 @@ const known: KnownDifference[] = [ seat.creatorsWorld === WORLD_H ); } - }, - { - // `isPending`'s `probing` flag is left set around a memo's pass the - // probe pulled (`verdictValue` closes only the dispatch, A31): a - // `latest()` of a born-into-the-future node inside that pass takes the - // probe's `joinPass` arm and the pulled pass joins the hold. A verdict - // read raises no world (F-vworld); the slot — the staging — agrees. - issue: 4429, - kind: "world", - row: "Vl4", - expected: WORLD_S, - actual: WORLD_H, - when: (c, el, seat) => - (el!._config & CONFIG_HELD) !== 0 && - ((el as Computed)._statusFlags & STATUS_UNINITIALIZED) !== 0 && - el!._pendingValue !== NOT_PENDING && - !((el as Computed)._statusFlags & STATUS_PENDING) && - (c._flags & REACTIVE_JOINED) !== 0 && - seat.ownWorld < WORLD_H } ]; const knownHits = new Map();