diff --git a/.changeset/ispending-async-derivation-lane-4677.md b/.changeset/ispending-async-derivation-lane-4677.md new file mode 100644 index 000000000..289c92f1b --- /dev/null +++ b/.changeset/ispending-async-derivation-lane-4677.md @@ -0,0 +1,8 @@ +--- +"@solidjs/signals": patch +--- + +An async memo that reads `isPending(x)` no longer shows its verdict beside an `isPending(x)` binding that disagrees with it, and nothing derived from the write it reads ahead of shows before that write does (#4677). + +- When the memo's lane landed in the same seam as the write it reads ahead of, the landing voided the binding's lane run, so the memo's `pending: true` showed while the binding stayed `false`. The lane now reveals whole (F-6), and the landing's re-derivation follows next round. +- When the lane landed first, the write's landing re-derived the memo onto a new flight and held its own runs for it, but that flush's runs of effects deriving plainly from the landing still ran: `details: Item 1` showed beside `id: 0`. Those runs now wait with the landing's held runs (F-1, F-7); what derives from a verdict reader still shows now (F-6, D-25). diff --git a/packages/signals/docs/RULES-INDEX.md b/packages/signals/docs/RULES-INDEX.md index 21476611b..b0ec4f9ee 100644 --- a/packages/signals/docs/RULES-INDEX.md +++ b/packages/signals/docs/RULES-INDEX.md @@ -59,7 +59,7 @@ Status legend: **live** stated and standing · **ruled** carries an explicit rul | A12 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:199` | — | createOptimistic.test.ts×2 spec-async-semantics.test.ts×1 | [ruled, amended in place] Resting optimistic nodes report pending like a plain memo — A resting optimistic node reports pending via exactly the causes a plain async memo does (A19) — a reverting optim… | | A13 | ruled | `docs/SPEC-ASYNC-SEMANTICS.md:207` | — | spec-async-semantics.test.ts×7 | [ruled 2026-07-06 (promoted from B1)] Resting optimistic ≡ plain async memo at every checkpoint — (was B1) A resting optimistic node (no active override) is observationally identical to a plain async … | | A14 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:215` | — | spec-async-semantics.test.ts×1 | [ruled, amended in place 2026-07-06 (promoted from B2)] Companion nodes get child lanes that do not merge with the owner — (was B2) `isPending`/`latest` companion nodes get child lanes that do not mer… | -| A15 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:251` | async.ts×2 constants.ts×1 core.ts×4 effect.ts×1 lanes.ts×6 scheduler.ts×18 seat.ts×1 verdict.ts×5 store.ts×1 | async-chain-supersession.test.ts×1 boundary-output-frame-reader.test.ts×1 create-deferred-3710.test.ts×2 first-observer-stale-reader.test.ts×1 fuzz-findings-l2.test.ts×17 held-restore.test.ts×1 hold-recovers-landing-reruns-4469.test.ts×1 l2-contract.test.ts×7 l2-fuzz-existing-rules.test.ts×2 lane-contract.test.ts×1 lane-hold-on-observation.test.ts×1 lane-outside-view.test.ts×1 lane-pass-stamped-effect-3662.test.ts×1 latest-memo-inline-parity.test.ts×1 latest-stale-branch-hold-4661.test.ts×1 latest-uninitialized-over-latest-4362.test.ts×1 loading-fallback-in-flush-3540.test.ts×4 mount-over-foreign-hold-3761.test.ts×2 optimistic-lane-seam-loop-4643.test.ts×1 overlapping-flights.test.ts×3 posture-born-held-and-observation.test.ts×4 posture-store-parity.test.ts×2 reveal-carve-out.test.ts×2 shared-effect-no-entangle.test.ts×1 show-stale-child-in-action-4137.test.ts×1 spec-async-semantics.test.ts×2 stale-read-uninitialized-cross-transition.test.ts×1 superseded-source-blocks-3462.test.ts×2 tick-scoped-pass-transaction.test.ts×1 tracked-effect-staged-dep-4606-4607.test.ts×1 treeshake.test.ts×4 unread-derive-midflight-mount-4491.test.ts×1 verdict-memo-flight-no-reask.test.ts×1 verdict-memo-held-branch-4544.test.ts×1 verdict-probe-committed-4648.test.ts×2 verdict-shortcut-pending-source-liveness.test.ts×1 visibility-oracle-store.states.ts×6 visibility-oracle.states.ts×7 visibility-oracle.test.ts×1 write-proposals-3494.test.ts×1 | [ruled, amended in place 2026-07-06 (promoted from B3)] Transition entanglement is graph-driven; lanes settle as one reveal — (was B3) Transition entanglement is graph-driven: writes whose async work … | +| A15 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:251` | async.ts×2 constants.ts×1 core.ts×4 effect.ts×1 lanes.ts×7 scheduler.ts×18 seat.ts×1 verdict.ts×5 store.ts×1 | async-chain-supersession.test.ts×1 boundary-output-frame-reader.test.ts×1 create-deferred-3710.test.ts×2 first-observer-stale-reader.test.ts×1 fuzz-findings-l2.test.ts×17 held-restore.test.ts×1 hold-recovers-landing-reruns-4469.test.ts×1 l2-contract.test.ts×7 l2-fuzz-existing-rules.test.ts×2 lane-contract.test.ts×1 lane-hold-on-observation.test.ts×1 lane-outside-view.test.ts×1 lane-pass-stamped-effect-3662.test.ts×1 latest-memo-inline-parity.test.ts×1 latest-stale-branch-hold-4661.test.ts×1 latest-uninitialized-over-latest-4362.test.ts×1 loading-fallback-in-flush-3540.test.ts×4 mount-over-foreign-hold-3761.test.ts×2 optimistic-lane-seam-loop-4643.test.ts×1 overlapping-flights.test.ts×3 posture-born-held-and-observation.test.ts×4 posture-store-parity.test.ts×2 reveal-carve-out.test.ts×2 shared-effect-no-entangle.test.ts×1 show-stale-child-in-action-4137.test.ts×1 spec-async-semantics.test.ts×2 stale-read-uninitialized-cross-transition.test.ts×1 superseded-source-blocks-3462.test.ts×2 tick-scoped-pass-transaction.test.ts×1 tracked-effect-staged-dep-4606-4607.test.ts×1 treeshake.test.ts×4 unread-derive-midflight-mount-4491.test.ts×1 verdict-memo-flight-no-reask.test.ts×1 verdict-memo-held-branch-4544.test.ts×1 verdict-probe-committed-4648.test.ts×2 verdict-shortcut-pending-source-liveness.test.ts×1 visibility-oracle-store.states.ts×6 visibility-oracle.states.ts×7 visibility-oracle.test.ts×1 write-proposals-3494.test.ts×1 | [ruled, amended in place 2026-07-06 (promoted from B3)] Transition entanglement is graph-driven; lanes settle as one reveal — (was B3) Transition entanglement is graph-driven: writes whose async work … | | A16 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:223` | scheduler.ts×1 | caught-reactive-read-diagnostic.test.ts×1 latest-uninitialized-over-latest-4362.test.ts×2 spec-async-semantics.test.ts×1 strict-read-pending-store.test.ts×2 uninitialized-visibility.test.ts×1 visibility-oracle-store.states.ts×2 visibility-oracle.states.ts×2 visibility-oracle.test.ts×1 | [ruled, amended in place 2026-07-06 (promoted from B5)] `isPending` never throws in untracked contexts — (was B5) `isPending` never throws in untracked contexts — thunks that throw real errors or read… | | A17 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:71` | async.ts×3 constants.ts×1 lanes.ts×7 scheduler.ts×2 seat.ts×1 verdict.ts×1 map.ts×1 optimistic.ts×2 store.ts×3 | create-deferred-3710.test.ts×1 fuzz-findings-l2.test.ts×6 lane-uninitialized-landing-3648.test.ts×5 optimistic-over-held-row-3796.test.ts×1 optimistic-read-lane-not-transaction-3698.test.ts×2 optimistic-undefined-override.test.ts×1 posture-store-parity.test.ts×1 refresh-await.test.ts×1 reveal-gating-contract.test.ts×3 spec-async-semantics.test.ts×10 createOptimisticStore.test.ts×2 kanban-a17-fixture.test.ts×3 optimistic-list-mutation-matrix.test.ts×1 optimistic-maparray-index-frame-f1.test.ts×1 optimistic-untracked-reads-f3-f5.test.ts×1 signal-store-twins-qd.test.ts×1 treeshake.test.ts×1 until.test.ts×1 visibility-oracle-store.states.ts×24 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×20 visibility-oracle.test.ts×1 | [ruled, amended in place 2026-07-06 (promoted from C4)] An active override is the displayed value until its transaction commits, and the graph's value until its own source answers — **Statement (curre… | | A18 | amended | `docs/SPEC-ASYNC-SEMANTICS.md:81` | action.ts×1 async.ts×2 core.ts×3 lanes.ts×8 scheduler.ts×2 types.ts×1 verdict.ts×1 map.ts×1 optimistic.ts×1 projection.ts×1 | body-end-supersession-visibility.test.ts×4 createOptimistic.test.ts×3 l2-fuzz-existing-rules.test.ts×2 lane-contract.test.ts×1 lane-frame-deferred-run-3662.test.ts×1 lane-outside-view.test.ts×1 lane-uninitialized-landing-3648.test.ts×5 optimistic-move-duplicate-3548.test.ts×2 optimistic-read-lane-not-transaction-3698.test.ts×4 posture-store-parity.test.ts×5 spec-async-semantics.test.ts×5 flight-owned-transaction.test.ts×1 lane-authority-twins.test.ts×1 optimistic-list-mutation-matrix.test.ts×1 optimistic-untracked-reads-f3-f5.test.ts×1 signal-store-twins-qd.test.ts×1 unchanged-presence-no-hold-3743.test.ts×2 superseded-before-first-commit.test.ts×4 visibility-oracle-store.states.ts×8 visibility-oracle-store.test.ts×1 visibility-oracle.states.ts×17 visibility-oracle.test.ts×1 | [ruled, amended in place 2026-07-07 (promoted from B4)] An override lives exactly as long as its own transaction; a newer truth from the source supersedes it in the graph immediately, on screen at com… | diff --git a/packages/signals/src/core/lanes.ts b/packages/signals/src/core/lanes.ts index 23b09f65d..2140011b1 100644 --- a/packages/signals/src/core/lanes.ts +++ b/packages/signals/src/core/lanes.ts @@ -102,6 +102,7 @@ import { setPassLane, txOf, type Transaction, + globalQueue, installLaneHooks, armGuessing } from "./scheduler.js"; @@ -880,7 +881,17 @@ function laneSeam(l: Transaction, leaked: boolean): void { } l._held = false; l._shown = true; - if (reruns(l)) revealing.push(resolveTx(l._parent!)); + const p = resolveTx(l._parent!), + v = p._reruns.filter(r => (r as any)._modified && r._x?._transaction === l); + if (reruns(l)) revealing.push(p); + // The parent lands this seam and re-derives readers whose runs the lane + // stashed, voiding them (`reruns`: they are for the frame being replaced). + // Beside other runs of the lane they show first: the lane's readers show + // its answer together (F-6), and the re-derivation follows next round + // (#4677: `pending: true` on an async memo over `isPending(id)` beside a + // binding still `false`). Restoring a run `reruns` skipped changes nothing. + else if (l._queues[0].length > v.length) + l._queues[0].unshift(() => v.forEach(r => ((r as any)._modified = true))); for (let i = 0; i < l._nodes.length; i++) { const n = l._nodes[i] as Computed; // (Not once a pass since has notified them — D-37: a lane does not @@ -1074,6 +1085,41 @@ function heldBy(t: Transaction, r: Computed): boolean { } return true; } +/** The effects that derive plainly from a landing this round that + * re-derives a stale reader (A15) reading it as committed: its held runs + * wait for the re-derivation (`land`), and these runs are that frame too — + * they wait with them (verdict.ts `attachWaits`; F-1, F-7, #4677). The walk + * stops at verdict readers: what derives from a display-ahead read shows + * now (F-6, D-25, #4398). Dropped at the end of the round's effect phase. */ +export let waiting: Set | null = null; +/** A transaction lands: its lanes end, nested first (`endLanes`). */ +function landing(u: Transaction): void { + walk(u); + endLanes(u); +} +/** Read before the lanes end: a lane's runs are its reveal's (F-6). */ +function walk(u: Transaction): void { + // As `reruns` judges it: a stale-reader memo re-derives onto a question of + // its own, and the landing's held runs wait for a re-derivation. + if ( + !u._reruns.some(r => !(r as any)._type && r._flags & REACTIVE_FRAME_READ) || + !u._reruns.some( + r => + (r._flags & REACTIVE_FRAME_READ || r._config & CONFIG_VERDICT) && + ((r as any)._type || r._config & CONFIG_HELD) + ) + ) + return; + if (!waiting) ((waiting = new Set()), globalQueue._queues[1].push(() => (waiting = null))); + const q: any[] = u._nodes.filter(n => !(n._config & CONFIG_VERDICT)); + for (let n; (n = q.pop()); ) + for (let s = n._subs; s; s = s._nextSub) { + const c: any = s._sub; + if (waiting.has(c) || c._config & CONFIG_VERDICT || c._x?._transaction?._lane) continue; + waiting.add(c); + if (!c._type) q.push(c); + } +} /** `r`'s pass read a node a transaction holds (#4439). */ function readsHeld(r: Computed): boolean { for (let d = r._deps; d; d = d._nextDep) if (d._dep._config & CONFIG_HELD) return true; @@ -1260,7 +1306,7 @@ installLaneHooks( applyGuessesAt, laneSeams, () => lanes.length !== 0 && laneCorrections(), - endLanes, + landing, lanesBlocked, adoptParked, laneServed diff --git a/packages/signals/src/core/verdict.ts b/packages/signals/src/core/verdict.ts index f65716faf..87439befa 100644 --- a/packages/signals/src/core/verdict.ts +++ b/packages/signals/src/core/verdict.ts @@ -67,7 +67,7 @@ import { settlePendingSource } from "./async.js"; import { forgetThrownRead, warnStrictReadUntracked } from "./dev.js"; import { NotReadyError } from "./error.js"; import { link } from "./graph.js"; -import { ahead, display, laneValueOf, verdictLane } from "./lanes.js"; +import { ahead, display, laneValueOf, verdictLane, waiting } from "./lanes.js"; import { deleteFromHeap, enqueueSub } from "./heap.js"; import { blocked, @@ -843,6 +843,16 @@ function valueInserts(v: unknown): boolean { * a value it reads) holds such a hole waits for that hold — the attach runs * at the landing with its content, never as an empty shell now. */ function attachWaits(node: Computed): boolean { + // A run of the frame a landing holds for its re-derivation (lanes.ts + // `waiting`) runs next round, with the landing's held runs. + if (waiting?.has(node)) + return ( + globalQueue.enqueue( + (node as any)._type, + ((node as any)._boundRunEffect ??= GlobalQueue._runEffect.bind(null, node)) + ), + true + ); if ( (node as any)._type !== EFFECT_RENDER || !(node._config & CONFIG_VERDICT || node._x?._transaction?._lane) || diff --git a/packages/signals/tests/ispending-async-derivation-4677.test.ts b/packages/signals/tests/ispending-async-derivation-4677.test.ts new file mode 100644 index 000000000..34802cdd0 --- /dev/null +++ b/packages/signals/tests/ispending-async-derivation-4677.test.ts @@ -0,0 +1,144 @@ +// #4677: an async memo that reads `id()` and `isPending(id)` is a verdict +// reader (D-25: the probe decides the pass's posture), so its async opens a +// lane of its own (F-6). While its flight is up, the verdict binding and +// everything rendered from it wait together; at the lane's landing they +// reveal together, ahead of the parent when the lane lands first. The parent +// (the write to `id`) waits for the lane's flight, and when its landing has +// the badge re-derive onto a new flight, the landing's other runs park with +// that flight and show with it (U-5, D-9). F-1 holds throughout: no +// derivation ahead of its input, no verdict-derived value beside a verdict +// that disagrees with it. +// +// Two orders: +// 1. The issue's repro: details (the parent's own async) lands before the +// badge's lane flight. The lane lands in the same seam the parent commits +// in, whose landing voided the verdict binding's lane run: `pending: true` +// showed on the badge while the binding stayed `false` (the lane split: +// F-6, F-1). The lane reveals whole (`Pending true` beside the badge's +// `true`), then everything at the badge's re-derived landing (lanes.ts +// `laneSeam`). +// 2. The badge's lane flight lands first (it reveals whole); then details +// lands, the parent commits and the badge re-derives onto a new flight. +// The landing flush's own run (details) showed `Item 1` beside `id: 0` +// (F-1, F-7) while the held runs parked with the re-derivation (U-5). The +// flush's runs of effects that derive plainly from the landing wait with +// the held runs (lanes.ts `landing`); what derives from a verdict reader +// still shows now (#4398, `latest-first-one-pass-4402.test.ts`). +import { describe, expect, it } from "vitest"; +import { + createMemo, + createRenderEffect, + createRoot, + createSignal, + flush, + isPending +} from "../src/index.js"; + +const tick = async () => { + for (let i = 0; i < 8; i++) await Promise.resolve(); + flush(); +}; + +function setup(between = false) { + const [id, setId] = createSignal(0); + const details: Array<() => void> = []; + const badges: Array<() => void> = []; + const screen = { id: "", details: "", pending: "", badge: "" }; + const frames: string[] = []; + const dispose = createRoot(dispose => { + const detailsMemo = createMemo(() => { + const i = id(); + if (i === 0) return "Item 0"; + return new Promise(r => details.push(() => r(`Item ${i}`))); + }); + const badge = createMemo(() => { + const i = id(); + const p = isPending(id); + const text = `ID: ${i}, pending: ${p}`; + if (i === 0 && !p) return text; + return new Promise(r => badges.push(() => r(text))); + }); + const bind = (k: keyof typeof screen, fn: () => unknown) => + createRenderEffect(fn, v => { + screen[k] = String(v); + }); + bind("id", id); + // A plain memo between the landed node and its binding: still the + // landing's frame. + bind("details", between ? createMemo(() => `${detailsMemo()}`) : detailsMemo); + bind("pending", () => isPending(id)); + bind("badge", badge); + return dispose; + }); + flush(); + const frame = () => + `id ${screen.id} | ${screen.details} | pending ${screen.pending} | ${screen.badge}`; + const snap = () => frames.push(frame()); + return { setId, details, badges, frames, snap, dispose }; +} + +describe("#4677 isPending beside an async memo derived from it", () => { + it("baseline: the badge's lane flight lands first and reveals whole", async () => { + const t = setup(); + t.snap(); + t.setId(1); + flush(); + t.snap(); + t.badges.shift()!(); + await tick(); + t.snap(); + expect(t.frames).toEqual([ + "id 0 | Item 0 | pending false | ID: 0, pending: false", + "id 0 | Item 0 | pending false | ID: 0, pending: false", + "id 0 | Item 0 | pending true | ID: 0, pending: true" + ]); + t.dispose(); + }); + + it("the issue's order: details lands, then the lane — the lane reveals whole", async () => { + const t = setup(); + t.setId(1); + flush(); + t.details.shift()!(); + await tick(); + t.snap(); + t.badges.shift()!(); + await tick(); + t.snap(); + t.badges.shift()!(); + await tick(); + t.snap(); + expect(t.frames).toEqual([ + // details landed; the parent waits for the lane's flight (F-6) + "id 0 | Item 0 | pending false | ID: 0, pending: false", + // the lane lands: the verdict and its derivation reveal together + "id 0 | Item 0 | pending true | ID: 0, pending: true", + // the badge's re-derivation lands with the parent's frame (U-5) + "id 1 | Item 1 | pending false | ID: 1, pending: false" + ]); + t.dispose(); + }); + + it.each([false, true])( + "the lane first, then details: no derivation shows ahead of `id` (memo between: %s)", + async between => { + const t = setup(between); + t.setId(1); + flush(); + t.badges.shift()!(); + await tick(); + t.details.shift()!(); + await tick(); + t.snap(); + t.badges.shift()!(); + await tick(); + t.snap(); + expect(t.frames).toEqual([ + // the parent's landing parks with the badge's re-derivation (U-5) + "id 0 | Item 0 | pending true | ID: 0, pending: true", + "id 1 | Item 1 | pending false | ID: 1, pending: false" + ]); + t.dispose(); + } + ); +}); diff --git a/scripts/size/floor-caps.json b/scripts/size/floor-caps.json index c9dc4de12..a7d0d74e0 100644 --- a/scripts/size/floor-caps.json +++ b/scripts/size/floor-caps.json @@ -16,8 +16,8 @@ "minified": 102212 }, "page: live server components (base + live/GET + action + isPending/latest)": { - "cap": "40.14 KB", - "minified": 120619 + "cap": "40.30 KB", + "minified": 121153 }, "server: floor (getRequestEvent + isServer)": { "cap": "1.29 KB", diff --git a/scripts/size/scenarios.js b/scripts/size/scenarios.js index eab3ecc7e..216d132cc 100644 --- a/scripts/size/scenarios.js +++ b/scripts/size/scenarios.js @@ -1473,9 +1473,10 @@ module.exports = [ // Batch re-record (2026-11-03, over next @ 6e569c7, size job 125766182293: #4639, #4641): 11.91 KB -> 11.92 KB, recorded minified 32,725 -> 32,772 B (+47 B), measured at 11,903 B brotli. #4639 and #4641 were accepted by the maintainer. Re-recorded once after the batch, per the batch process. // Batch re-record (2026-11-03, over next @ 061b013, size job 126059550469: #4656, #4658, #4663): 11.92 KB -> 11.99 KB, recorded minified 32,772 -> 32,951 B (+179 B), measured at 11,973 B brotli. The growth of #4656, #4658 and #4663 was accepted by the maintainer. Re-recorded once after the batch, per the batch process. // Batch re-record (2026-11-03, over next @ ab5b5cb, measured locally: #4667, #4669): 11.99 KB -> 12.07 KB, recorded minified 32,951 -> 33,081 B (+130 B), measured at 12,055 B brotli. The growth of #4667 and #4669 was accepted by the maintainer. Re-recorded once after the batch, per the batch process. - limit: "12.07 KB", + // Re-record (2026-11-05, over next @ 5208944, measured locally: #4682, #4677): 12.07 KB -> 12.25 KB, recorded minified 33,081 -> 33,615 B (+534 B: #4682 +7 B, #4677 +527 B), measured at 12,239 B brotli. Re-recorded in the fix's own change rather than in a batch, at the maintainer's request. + limit: "12.25 KB", // 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 28,693 -> 29,086 B (+343 B minified over next), measured locally (linux) at 10,565 B brotli (+135 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: 33081, + capMinified: 33615, alias }, { @@ -2797,11 +2798,12 @@ module.exports = [ // Batch re-record (2026-11-03, over next @ 061b013, size job 126059550469: #4656, #4658, #4663): 33.12 KB -> 33.15 KB, recorded minified 99,641 -> 99,885 B (+244 B), measured at 33,138 B brotli. The growth of #4656, #4658 and #4663 was accepted by the maintainer. Re-recorded once after the batch, per the batch process. // Batch re-record (2026-11-03, over next @ ab5b5cb, measured locally: #4667, #4669): 33.15 KB -> 33.26 KB, recorded minified 99,885 -> 99,898 B (+13 B), measured at 33,248 B brotli. The growth of #4667 and #4669 was accepted by the maintainer. Re-recorded once after the batch, per the batch process. // Batch re-record (2026-11-04, over next @ e704cfc, measured locally: #4674, #4676): 33.26 KB -> 33.27 KB, recorded minified 99,898 -> 99,967 B (+69 B), measured at 33,260 B brotli. The growth of #4674 and #4676 was accepted by the maintainer. Re-recorded once after the batch, per the batch process. - limit: "33.27 KB", + // Re-record (2026-11-05, over next @ 5208944, measured locally: #4682, #4677): 33.27 KB -> 33.42 KB, recorded minified 99,967 -> 100,437 B (+470 B: #4677 +470 B; #4682 is size-neutral here), measured at 33,401 B brotli. Re-recorded in the fix's own change rather than in a batch, at the maintainer's request. + limit: "33.42 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 96,989 -> 97,047 B (+58 B minified over next's record), measured locally (linux) at 31,811 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 97,024 -> 97,045 B (+21 B minified over next's record), measured locally (linux) at 31,798 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 97,045 -> 97,426 B (+240 B minified over next), measured locally (linux) at 31,976 B brotli (+109 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: 99967, + capMinified: 100437, alias }, { @@ -5754,6 +5756,7 @@ module.exports = [ // Batch re-record (2026-11-03, over next @ ccfc7ea, size job 125848841075: #4653, #4655, #4657, #4659): 39.92 KB -> 39.96 KB, recorded minified 120,104 -> 120,310 B (+206 B), measured at 39,944 B brotli. A frozen floor, Size-Exception in this PR's body. The growth of #4653, #4655 and #4659 was accepted by the maintainer; #4657 and #4654 are size-neutral on the gated rows. Re-recorded once after the batch, per the batch process. // Batch re-record (2026-11-03, over next @ 061b013, size job 126059550469: #4656, #4658, #4663): 39.96 KB -> 40.08 KB, recorded minified 120,310 -> 120,486 B (+176 B), measured at 40,066 B brotli. A frozen floor, Size-Exception in this PR's body. The growth of #4656, #4658 and #4663 was accepted by the maintainer. Re-recorded once after the batch, per the batch process. // Batch re-record (2026-11-03, over next @ ab5b5cb, measured locally: #4667, #4669): 40.08 KB -> 40.14 KB, recorded minified 120,486 -> 120,619 B (+133 B), measured at 40,124 B brotli. A frozen floor, Size-Exception in this PR's body. The growth of #4667 and #4669 was accepted by the maintainer. Re-recorded once after the batch, per the batch process. + // Re-record (2026-11-05, over next @ 5208944, measured locally: #4682, #4677): 40.14 KB -> 40.30 KB, recorded minified 120,619 -> 121,153 B (+534 B: #4682 +7 B, #4677 +527 B), measured at 40,289 B brotli. A frozen floor, Size-Exception in this PR's body. Re-recorded in the fix's own change rather than in a batch, at the maintainer's request. limit: floorCaps["page: live server components (base + live/GET + action + isPending/latest)"], capMinified: floorMinified["page: live server components (base + live/GET + action + isPending/latest)"], @@ -6106,11 +6109,12 @@ module.exports = [ // Batch re-record (2026-11-03, over next @ ccfc7ea, size job 125848841075: #4653, #4655, #4657, #4659): 43.97 KB -> 44.05 KB, recorded minified 128,356 -> 128,562 B (+206 B), measured at 44,040 B brotli. The growth of #4653, #4655 and #4659 was accepted by the maintainer; #4657 and #4654 are size-neutral on the gated rows. Re-recorded once after the batch, per the batch process. // Batch re-record (2026-11-03, over next @ 061b013, size job 126059550469: #4656, #4658, #4663): 44.05 KB -> 44.19 KB, recorded minified 128,562 -> 128,738 B (+176 B), measured at 44,177 B brotli. The growth of #4656, #4658 and #4663 was accepted by the maintainer. Re-recorded once after the batch, per the batch process. // Batch re-record (2026-11-03, over next @ ab5b5cb, measured locally: #4667, #4669): cap unchanged at 44.19 KB, recorded minified 128,738 -> 128,866 B (+128 B), measured at 44,149 B brotli. The growth of #4667 and #4669 was accepted by the maintainer. Re-recorded once after the batch, per the batch process. - limit: "44.19 KB", + // Re-record (2026-11-05, over next @ 5208944, measured locally: #4682, #4677): 44.19 KB -> 44.34 KB, recorded minified 128,866 -> 129,402 B (+536 B: #4682 +7 B, #4677 +529 B), measured at 44,324 B brotli. Re-recorded in the fix's own change rather than in a batch, at the maintainer's request. + limit: "44.34 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: 128866, + capMinified: 129402, alias: pageAlias }, { @@ -6552,11 +6556,12 @@ module.exports = [ // Batch re-record (2026-11-03, over next @ ccfc7ea, size job 125848841075: #4653, #4655, #4657, #4659): 49.63 KB -> 49.78 KB, recorded minified 145,917 -> 146,123 B (+206 B), measured at 49,770 B brotli. The growth of #4653, #4655 and #4659 was accepted by the maintainer; #4657 and #4654 are size-neutral on the gated rows. Re-recorded once after the batch, per the batch process. // Batch re-record (2026-11-03, over next @ 061b013, size job 126059550469: #4656, #4658, #4663): 49.78 KB -> 49.81 KB, recorded minified 146,123 -> 146,299 B (+176 B), measured at 49,793 B brotli. The growth of #4656, #4658 and #4663 was accepted by the maintainer. Re-recorded once after the batch, per the batch process. // Batch re-record (2026-11-03, over next @ ab5b5cb, measured locally: #4667, #4669): 49.81 KB -> 49.86 KB, recorded minified 146,299 -> 146,432 B (+133 B), measured at 49,845 B brotli. The growth of #4667 and #4669 was accepted by the maintainer. Re-recorded once after the batch, per the batch process. - limit: "49.86 KB", + // Re-record (2026-11-05, over next @ 5208944, measured locally: #4682, #4677): 49.86 KB -> 50.10 KB, recorded minified 146,432 -> 146,967 B (+535 B: #4682 +7 B, #4677 +528 B), measured at 50,082 B brotli. Re-recorded in the fix's own change rather than in a batch, at the maintainer's request. + limit: "50.10 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 140,451 -> 140,509 B (+58 B minified over next's record), measured locally (linux) at 47,516 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 140,463 -> 140,485 B (+22 B minified over next's record), measured locally (linux) at 47,547 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 140,485 -> 140,983 B (+405 B minified over next), measured locally (linux) at 47,722 B brotli (+162 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: 146432, + capMinified: 146967, alias: pageAlias, conditions: solidConditions, compile: { hydratable: true }