From 9c6476bfa4e5341cb7972395f58a0ac59186679a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Oct 2026 21:26:28 +0000 Subject: [PATCH 1/6] fix: a throwing cleanup no longer aborts the rest of the teardown Every cleanup in the tree runs even when one throws: the ones registered before it on the same owner, unvisited siblings, ancestors and effect cleanups. The first error is rethrown once the teardown is over. Client (@solidjs/signals): one guarded call (`runCleanup`) covers both the `_disposal` list and the effect `_cleanup`, collecting the first error. Outside a flush the walk that began the teardown rethrows it; inside a flush (a commit retiring a frame, a pass replacing an uncommitted one, an effect disposing a root) `flush()` rethrows it after the drain completes, so the commit loop and the effect queues are never left half-applied and the pass that tears down a frame still drains the lazy releases it collected (#4176's follow-up case now passes without a dedicated drain). Server (solid-js): `disposeOwner` guards each child walk and each cleanup and rethrows the first error after the walk, so renderToStream's final dispose runs every cleanup and #4206 still reports the error (#4193). Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 --- .changeset/cleanup-throw-continues.md | 6 + packages/signals/src/core/core.ts | 16 +- packages/signals/src/core/dev.ts | 7 +- packages/signals/src/core/lanes.ts | 4 +- packages/signals/src/core/owner.ts | 56 ++++- packages/signals/src/core/scheduler.ts | 13 +- .../tests/cleanup-throw-continues.test.ts | 198 ++++++++++++++++++ .../deferred-release-cleanup-throw.test.ts | 41 +++- .../tests/untracked-read-after-await.test.ts | 9 +- packages/solid/src/server/signals.ts | 32 ++- packages/solid/test/server/signals.spec.ts | 27 +++ .../stream-ambient-error-hook-4193.spec.tsx | 9 +- 12 files changed, 372 insertions(+), 46 deletions(-) create mode 100644 .changeset/cleanup-throw-continues.md create mode 100644 packages/signals/tests/cleanup-throw-continues.test.ts diff --git a/.changeset/cleanup-throw-continues.md b/.changeset/cleanup-throw-continues.md new file mode 100644 index 0000000000..ac9675a6a0 --- /dev/null +++ b/.changeset/cleanup-throw-continues.md @@ -0,0 +1,6 @@ +--- +"@solidjs/signals": patch +"solid-js": patch +--- + +A cleanup (`onCleanup` or an effect's returned cleanup) that throws no longer aborts the rest of the teardown: every other cleanup in the tree still runs, and the first error is rethrown once the teardown is done. Outside a flush (a root's `dispose()`), the dispose call rethrows it. Inside a flush (a branch switch retiring a frame, a pass replacing an uncommitted one, an effect disposing a root), `flush()` rethrows it after the flush has finished, so commits and effects are never left half-applied, and lazy nodes released before the throw are still released. On the server, `disposeOwner` runs every cleanup the same way, so `renderToStream`'s final dispose still reports the error (#4193). diff --git a/packages/signals/src/core/core.ts b/packages/signals/src/core/core.ts index 4cb92f61f1..dedb7b5f27 100644 --- a/packages/signals/src/core/core.ts +++ b/packages/signals/src/core/core.ts @@ -406,14 +406,13 @@ export function recompute(el: Computed, create: boolean = false): void { // and own nothing to park. if (isEffect === EFFECT_TRACKED || el._config & CONFIG_STAGED) { // A lazy node only those children read is released when this pass - // ends, if the pass doesn't read it again (#4142). + // ends, if the pass doesn't read it again (#4142). Nested: a cleanup + // that throws here neither stops the pass nor skips that drain — its + // error is rethrown when the flush ends (owner.ts). const prev = deferredReleases; deferReleases((released = [])); - try { - disposeChildren(el); - } finally { - deferReleases(prev); - } + disposeChildren(el, false, true); + deferReleases(prev); } else if (el._firstChild !== null || el._disposal !== null) parkChildren(el); // An error a pass staged (D-41) is replaced by this pass's answer: a // value, a flight, or an error of its own (#4109, #4145). @@ -1510,8 +1509,9 @@ export function exitCallback(): void { * its owner (a list pass removing a row, a branch switching away), so its * writes are imperative like an effect callback's (#4165). Kept apart from * `callbackDepth` because its sites cannot use try/finally (the frame would - * survive into prod): a throwing cleanup leaves it raised, and dev.ts resets - * it on the next microtask, which teardown — synchronous — never spans. + * survive into prod). A throwing cleanup is caught (owner.ts `runCleanup`); + * anything else escaping a teardown leaves it raised, and dev.ts resets it on + * the next microtask, which teardown — synchronous — never spans. */ export let disposalDepth = 0; export function enterDisposal(): void { diff --git a/packages/signals/src/core/dev.ts b/packages/signals/src/core/dev.ts index 8c691dc1a4..45b6f6902b 100644 --- a/packages/signals/src/core/dev.ts +++ b/packages/signals/src/core/dev.ts @@ -1267,9 +1267,10 @@ export function checkPostAwaitRead( if (callbackDepth !== 0) return; if (disposalDepth !== 0) { // Teardown is synchronous, so a depth still raised on the next microtask - // was left behind by a cleanup that threw (owner.ts brackets it without - // try/finally, which would survive into prod). Clear it then, or every - // later check would stay silent. + // was left behind by a throw that escaped it (owner.ts brackets it + // without try/finally, which would survive into prod; a cleanup's own + // throw is caught there). Clear it then, or every later check would stay + // silent. if (!disposalHealQueued) { disposalHealQueued = true; queueMicrotask(healDisposalDepth); diff --git a/packages/signals/src/core/lanes.ts b/packages/signals/src/core/lanes.ts index 39072babda..ea358f4f13 100644 --- a/packages/signals/src/core/lanes.ts +++ b/packages/signals/src/core/lanes.ts @@ -516,7 +516,7 @@ function dissolveLane(l: Transaction, into: Transaction | null, except?: Signal< if (!effect) n._config |= CONFIG_INPUTS_PUBLISHED; } else { if (x._pendingFirstChild !== null || x._pendingDisposal !== null) - GlobalQueue._dispose(n as Computed, false, true); + GlobalQueue._dispose(n as Computed, false, false, true); n._config &= ~(CONFIG_STAGED | CONFIG_HELD | CONFIG_HELD_FRAME); } } @@ -684,7 +684,7 @@ function laneSeam(l: Transaction, leaked: boolean): void { // The frame this pass built is the screen's now; the one it replaced // goes (as `commitPendingNode` retires a committed pass's). if (x._pendingFirstChild !== null || x._pendingDisposal !== null) - GlobalQueue._dispose(n, false, true); + GlobalQueue._dispose(n, false, false, true); n._config &= ~CONFIG_STAGED; } releaseQueues(l); diff --git a/packages/signals/src/core/owner.ts b/packages/signals/src/core/owner.ts index 1fec7f9045..253c8c4e43 100644 --- a/packages/signals/src/core/owner.ts +++ b/packages/signals/src/core/owner.ts @@ -29,7 +29,7 @@ import { } from "./dev.js"; import { clearDeps, unobserved } from "./graph.js"; import { deleteFromHeap } from "./heap.js"; -import { dirtyQueue, schedule } from "./scheduler.js"; +import { dirtyQueue, globalQueue, schedule } from "./scheduler.js"; import type { Computed, Disposable, Owner, Root } from "./types.js"; // L2 (step 2): the parked frame. A pass parks the previous frame's children @@ -60,7 +60,43 @@ export function dispose(node: Computed): void { unobserved(node); } -export function disposeChildren(node: Owner, self: boolean = false, zombie?: boolean): void { +// The first error a cleanup threw, boxed so a thrown `undefined` still +// counts. A throwing cleanup stops neither its siblings nor the rest of the +// tree: every cleanup runs. The error is rethrown once the teardown is over — +// when the walk that began it ends, or, for a teardown inside a flush (a +// commit retiring a frame, a pass replacing one, an effect disposing a root), +// when the flush ends (`flush`), so the flush is never left half-applied. +let cleanupError: [unknown] | undefined; + +function runCleanup(fn: () => void): void { + try { + fn(); + } catch (e) { + cleanupError ||= [e]; + // A teardown outside a flush that does not rethrow (a pass pulled by a + // read): the flush this schedules does. + schedule(); + } +} + +/** Rethrows the error `runCleanup` collected, once. @internal */ +export function rethrowCleanupError(): void { + const error = cleanupError; + cleanupError = undefined; + if (error) throw error[0]; +} + +/** + * Tears down `node`'s children (and `node` itself when `self`), or with + * `zombie` the frame parked on it. `nested`: part of an enclosing teardown or + * pass, which rethrows a cleanup's error instead (see `cleanupError`). + */ +export function disposeChildren( + node: Owner, + self: boolean = false, + nested?: boolean, + zombie?: boolean +): void { const flags = (node as any)._flags; if (flags & REACTIVE_DISPOSED) return; // A parked frame dies with its owner (#3024): the commit that would retire @@ -74,7 +110,7 @@ export function disposeChildren(node: Owner, self: boolean = false, zombie?: boo node._x !== null && (node._x._pendingFirstChild !== null || node._x._pendingDisposal !== null) ) - disposeChildren(node, false, true); + disposeChildren(node, false, true, true); if (self) { (node as any)._flags = flags | REACTIVE_DISPOSED; if (__OBSERVE__ && node._parent === null && (node as Root)._root) unregisterRoot(node); @@ -120,7 +156,7 @@ export function disposeChildren(node: Owner, self: boolean = false, zombie?: boo // itself routes its later splice onto the detached chain, never the head, // and keeps the dev owner-chain-head invariant honest for those children. child._prevSibling = child; - disposeChildren(child, true); + disposeChildren(child, true, true); // Read after, not before: a sibling this disposal made dormant spliced // itself out of the detached chain, and a cleanup may then have linked // it at the fresh head, which rewrote the `_nextSibling` a pre-read @@ -163,9 +199,10 @@ export function disposeChildren(node: Owner, self: boolean = false, zombie?: boo const effectCleanup = node._cleanup; node._cleanup = undefined; if (__DEV__) enterDisposal(); - effectCleanup(); + runCleanup(effectCleanup); if (__DEV__) exitDisposal(); } + if (!nested && !globalQueue._running) rethrowCleanupError(); } export function linkChild(parent: Owner, node: Owner): void { @@ -188,9 +225,7 @@ function runDisposal(node: Owner, zombie?: boolean): void { if (zombie) node._x!._pendingDisposal = null; else node._disposal = null; - // No try/finally: it would survive into prod (rollup keeps the frame). A - // throw leaves the depth raised; dev.ts clears the stale count on the next - // microtask, since teardown never spans one (core.ts). + // Each cleanup is guarded (`runCleanup`), so the depth is always lowered. if (__DEV__) enterDisposal(); if (Array.isArray(disposal)) { // Unwind order (#3572, restores 1.x #1562): later registrations run @@ -198,11 +233,10 @@ function runDisposal(node: Owner, zombie?: boolean): void { // so with LIFO a body that registers cleanup before creating its children // tears down after them — the same order a per-component owner gives. for (let i = disposal.length - 1; i >= 0; i--) { - const callable = disposal[i]; - callable.call(callable); + runCleanup(disposal[i]); } } else { - disposal.call(disposal); + runCleanup(disposal); } if (__DEV__) exitDisposal(); } diff --git a/packages/signals/src/core/scheduler.ts b/packages/signals/src/core/scheduler.ts index d20b2d8441..45e8de2a3e 100644 --- a/packages/signals/src/core/scheduler.ts +++ b/packages/signals/src/core/scheduler.ts @@ -43,6 +43,7 @@ import { import { sweepDormant, trimStaleDeps } from "./graph.js"; import { deleteFromHeap, enqueueSub, runHeap, type Heap } from "./heap.js"; import { devCheckQuiescent } from "./invariants.js"; +import { rethrowCleanupError } from "./owner.js"; import type { Computed, Owner, Signal } from "./types.js"; // Optional-module hooks: module bindings, not `GlobalQueue` statics, so a @@ -782,7 +783,12 @@ export class GlobalQueue implements IQueue { _queues: [QueueCallback[], QueueCallback[]] = [[], []]; _running: boolean = false; declare static _update: (el: Computed) => void; - declare static _dispose: (el: Computed, self: boolean, zombie?: boolean) => void; + declare static _dispose: ( + el: Computed, + self: boolean, + nested?: boolean, + zombie?: boolean + ) => void; declare static _runEffect: (el: Computed) => void; /** A slot node's truth is in flight (its family's derive — store/store.ts). */ declare static _slotFlight: ((n: Signal) => boolean) | undefined; @@ -1350,7 +1356,7 @@ export function commitPendingNode(n: Signal): void { c._x != null && (c._x._pendingFirstChild !== null || c._x._pendingDisposal !== null) ) - GlobalQueue._dispose(c as Computed, false, true); + GlobalQueue._dispose(c as Computed, false, false, true); } function commitPendingNodes() { @@ -1474,6 +1480,9 @@ export function flush(fn?: () => T): T | void { // so this is the "committed and effects ran" instant for everything the // loop processed. if (__OBSERVE__ && drained && attrHooks !== null) attrHooks.flushEnd(); + // A cleanup that threw during the drain, now that the drain is complete + // (owner.ts). + rethrowCleanupError(); } function runQueue(queue: QueueCallback[], type: number): void { diff --git a/packages/signals/tests/cleanup-throw-continues.test.ts b/packages/signals/tests/cleanup-throw-continues.test.ts new file mode 100644 index 0000000000..f6429ca598 --- /dev/null +++ b/packages/signals/tests/cleanup-throw-continues.test.ts @@ -0,0 +1,198 @@ +// A cleanup that throws does not abort the rest of the teardown: every +// cleanup still runs — the ones registered before it on the same owner, the +// siblings and ancestors the walk had not reached, effect cleanups — and the +// first error is rethrown once the teardown is over. A teardown inside a flush +// (a commit retiring a frame, a pass replacing one, an effect disposing a +// root) leaves it for the end of the flush, so the flush is never left +// half-applied. +import { + createEffect, + createErrorBoundary, + createMemo, + createRenderEffect, + createRoot, + createSignal, + flush, + getOwner, + isDisposed, + onCleanup, + type Owner +} from "../src/index.js"; + +afterEach(() => flush()); + +const boom = (message = "boom") => { + throw new Error(message); +}; + +describe("root dispose", () => { + it("runs every cleanup in the tree and rethrows the first error after", () => { + const log: string[] = []; + let inner!: Owner; + let sibling!: Owner; + const dispose = createRoot(dispose => { + onCleanup(() => log.push("root")); + createRoot(() => { + inner = getOwner()!; + onCleanup(() => log.push("inner first")); + createRenderEffect( + () => 1, + () => () => log.push("effect cleanup") + ); + onCleanup(() => boom("first error")); + onCleanup(() => log.push("inner last")); + }); + createRoot(() => { + sibling = getOwner()!; + onCleanup(() => boom("second error")); + onCleanup(() => log.push("sibling")); + }); + return dispose; + }); + flush(); + + // Children go newest first, so the sibling's error is the first thrown. + expect(dispose).toThrow("second error"); + // Unwind order is kept (children, then the owner's own cleanups, latest + // first); nothing is skipped. + expect(log).toEqual(["sibling", "effect cleanup", "inner last", "inner first", "root"]); + expect(isDisposed(inner)).toBe(true); + expect(isDisposed(sibling)).toBe(true); + }); + + it("rethrows once: a later teardown does not see the error again", () => { + const first = createRoot(dispose => { + onCleanup(() => boom()); + return dispose; + }); + const second = createRoot(dispose => { + onCleanup(() => {}); + return dispose; + }); + expect(first).toThrow("boom"); + expect(second).not.toThrow(); + expect(() => flush()).not.toThrow(); + }); + + it("a throwing effect cleanup does not stop the owner's own cleanups", () => { + const log: string[] = []; + const dispose = createRoot(dispose => { + onCleanup(() => log.push("owner")); + createEffect( + () => 1, + () => () => boom() + ); + return dispose; + }); + flush(); + expect(dispose).toThrow("boom"); + expect(log).toEqual(["owner"]); + }); +}); + +describe("inside a flush", () => { + it("a branch switch runs every cleanup of the old frame, commits, then the flush throws", () => { + const log: string[] = []; + const seen: string[] = []; + const [show, setShow] = createSignal(true); + const dispose = createRoot(dispose => { + const view = createMemo(() => { + if (!show()) return "off"; + onCleanup(() => log.push("first")); + createRenderEffect( + () => 1, + () => () => log.push("effect cleanup") + ); + onCleanup(() => boom()); + return "on"; + }); + createRenderEffect(view, v => void seen.push(v)); + return dispose; + }); + flush(); + + setShow(false); + expect(() => flush()).toThrow("boom"); + expect(log).toEqual(["effect cleanup", "first"]); + // The flush completed before rethrowing: the new branch is on screen. + expect(seen).toEqual(["on", "off"]); + + // No latch, and nothing left half-applied: later flushes are clean. + expect(() => flush()).not.toThrow(); + setShow(true); + flush(); + expect(seen).toEqual(["on", "off", "on"]); + expect(dispose).toThrow("boom"); + expect(log).toEqual(["effect cleanup", "first", "effect cleanup", "first"]); + }); + + it("an error boundary's content moves on; the error surfaces from the flush", () => { + const seen: unknown[] = []; + const [show, setShow] = createSignal(true); + const dispose = createRoot(dispose => { + const out = createErrorBoundary( + () => { + const view = createMemo(() => { + if (!show()) return "off"; + onCleanup(() => boom()); + return "on"; + }); + return view(); + }, + () => "fallback" + ); + createRenderEffect(out, v => void seen.push(v)); + return dispose; + }); + flush(); + setShow(false); + expect(() => flush()).toThrow("boom"); + expect(seen).toEqual(["on", "off"]); + dispose(); + }); + + it("scheduling keeps working after the flush rethrew", async () => { + const [count, setCount] = createSignal(0); + const [show, setShow] = createSignal(true); + const seen: number[] = []; + const dispose = createRoot(dispose => { + createMemo(() => { + if (show()) onCleanup(() => boom()); + }); + createRenderEffect(count, v => void seen.push(v)); + return dispose; + }); + flush(); + setShow(false); + expect(() => flush()).toThrow("boom"); + + // The automatic (microtask) flush still runs. + setCount(1); + await Promise.resolve(); + expect(seen).toEqual([0, 1]); + dispose(); + }); + + it("a root disposed by an effect: the effect continues, the flush rethrows at its end", () => { + const log: string[] = []; + const [go, setGo] = createSignal(false); + const disposeOther = createRoot(dispose => { + onCleanup(() => log.push("other first")); + onCleanup(() => boom()); + return dispose; + }); + const dispose = createRoot(dispose => { + createEffect(go, v => { + if (!v) return; + disposeOther(); + log.push("after dispose"); + }); + return dispose; + }); + flush(); + setGo(true); + expect(() => flush()).toThrow("boom"); + expect(log).toEqual(["other first", "after dispose"]); + dispose(); + }); +}); diff --git a/packages/signals/tests/deferred-release-cleanup-throw.test.ts b/packages/signals/tests/deferred-release-cleanup-throw.test.ts index 3454f665f0..39be8d719a 100644 --- a/packages/signals/tests/deferred-release-cleanup-throw.test.ts +++ b/packages/signals/tests/deferred-release-cleanup-throw.test.ts @@ -18,9 +18,9 @@ it.each([false, true])( const [version, setVersion] = createSignal(0); const disposePending = createRoot(dispose => { createMemo(() => { - version(); + const v = version(); onCleanup(() => { - if (throws) throw failure; + if (throws && v === 0) throw failure; }); return new Promise(() => {}); }); @@ -61,3 +61,40 @@ it.each([false, true])( } } ); + +// A lazy node the dismantled frame's children released before the throw is +// still released: the throw no longer skips the drain at the end of the pass +// (the teardown is nested in the pass, and the flush rethrows when it ends). +it("a cleanup throw still releases lazy nodes collected before it", () => { + const failure = new Error("cleanup failed"); + const [version, setVersion] = createSignal(0); + let cleaned = 0; + const dispose = createRoot(dispose => { + const lazy = createMemo( + () => { + onCleanup(() => cleaned++); + return 1; + }, + { lazy: true } + ); + createMemo(() => { + if (version() === 0) { + createMemo(() => lazy()); + onCleanup(() => { + throw failure; + }); + } + return new Promise(() => {}); + }); + return dispose; + }); + try { + flush(); + setVersion(1); + expect(() => flush()).toThrow(failure); + expect(cleaned).toBe(1); + } finally { + dispose(); + flush(); + } +}); diff --git a/packages/signals/tests/untracked-read-after-await.test.ts b/packages/signals/tests/untracked-read-after-await.test.ts index 97d6d72001..d6e5ba251e 100644 --- a/packages/signals/tests/untracked-read-after-await.test.ts +++ b/packages/signals/tests/untracked-read-after-await.test.ts @@ -250,8 +250,8 @@ describe("UNTRACKED_READ_AFTER_AWAIT (dev)", () => { return 1; }, "thrower"); await settle(); - // The throw skipped the depth's exit. The first check to see the stale - // depth is muted and schedules the repair; the next continuation warns. + // The throwing cleanup is caught inside the teardown, so the depth is + // lowered on the spot: the very next continuation warns. const first = mount(async () => { await null; return x(); @@ -262,7 +262,10 @@ describe("UNTRACKED_READ_AFTER_AWAIT (dev)", () => { return x(); }, "second-after"); await settle(); - expect(stop().map(e => [e.ownerName, e.nodeName])).toEqual([["second-after", "x"]]); + expect(stop().map(e => [e.ownerName, e.nodeName])).toEqual([ + ["first-after", "x"], + ["second-after", "x"] + ]); dispose(); first.dispose(); second.dispose(); diff --git a/packages/solid/src/server/signals.ts b/packages/solid/src/server/signals.ts index e76426acc6..ee2ec672aa 100644 --- a/packages/solid/src/server/signals.ts +++ b/packages/solid/src/server/signals.ts @@ -519,7 +519,8 @@ function unlinkOwner(node: SSROwner): void { * forward-only `_firstChild` -> `_nextSibling` chain, recursively disposing * each child with `self=true`, then runs the owner's own `_disposal` queue * in unwind order (later registrations first) and resets `_firstChild` / - * `_childCount`. + * `_childCount`. A cleanup that throws does not stop the walk: every cleanup + * runs, and the first error is rethrown once the walk is done (#4193). * * `self=false` keeps `owner` itself alive (its `_disposed` flag stays clear, * future `runWithOwner(owner, ...)` keeps working) but tears down its @@ -555,10 +556,18 @@ export function disposeOwner(owner: Owner, self: boolean = true): void { return; } if (self) node._disposed = true; + // A cleanup that throws stops neither the rest of the tree nor this + // owner's other cleanups (#4193): the first error is rethrown once the + // walk is done — a child's walk rethrows its own, collected here. + let error: [unknown] | undefined; let child = node._firstChild; while (child) { const next = child._nextSibling; - disposeOwner(child as unknown as Owner, true); + try { + disposeOwner(child as unknown as Owner, true); + } catch (e) { + error ||= [e]; + } child = next; } node._firstChild = null; @@ -571,15 +580,15 @@ export function disposeOwner(owner: Owner, self: boolean = true): void { // cleanup that re-enters this owner's disposal must find nothing to re-run. const d = node._disposal; node._disposal = null; - if (d) { - if (Array.isArray(d)) { - // Unwind order, mirroring the client `runDisposal` (#3572): later - // registrations run before earlier ones. - for (let i = d.length - 1; i >= 0; i--) d[i](); - } else { - d(); - } - } + // Unwind order, mirroring the client `runDisposal` (#3572): later + // registrations run before earlier ones. + if (d) + for (const fn of ([] as (() => void)[]).concat(d).reverse()) + try { + fn(); + } catch (e) { + error ||= [e]; + } if (self) unlinkOwner(node); // Recycle the disposed owner. Skip the root case (`self=false`) and the // already-pooled case so we don't double-add. The next `createOwner` will @@ -588,6 +597,7 @@ export function disposeOwner(owner: Owner, self: boolean = true): void { node.id = undefined; ownerPool.push(node); } + if (error) throw error[0]; } /** diff --git a/packages/solid/test/server/signals.spec.ts b/packages/solid/test/server/signals.spec.ts index 8114fc2ed2..d521a519f6 100644 --- a/packages/solid/test/server/signals.spec.ts +++ b/packages/solid/test/server/signals.spec.ts @@ -681,6 +681,33 @@ describe("Server owner tree", () => { expect(order).toEqual(["C", "B", "A"]); }); + test("a throwing cleanup does not stop the walk; the first error is rethrown after (#4193)", () => { + const order: string[] = []; + let dispose!: () => void; + let child!: Owner; + createRoot( + d => { + dispose = d; + onCleanup(() => order.push("A")); + onCleanup(() => { + throw new Error("owner threw"); + }); + createRoot(() => { + child = getOwner()!; + onCleanup(() => order.push("C")); + onCleanup(() => { + throw new Error("child threw"); + }); + }); + createRoot(() => onCleanup(() => order.push("D"))); + }, + { id: "test" } + ); + expect(dispose).toThrow("child threw"); + expect(order).toEqual(["D", "C", "A"]); + expect(isDisposed(child)).toBe(true); + }); + test("a cleanup that disposes its own root during a rerun reset runs once (#3601)", () => { // Twin of the client `runDisposal` ruling: `disposeOwner` detaches the // list before running it. A rerun reset (`self=false`, the server's diff --git a/packages/web/test/server/stream-ambient-error-hook-4193.spec.tsx b/packages/web/test/server/stream-ambient-error-hook-4193.spec.tsx index c32d5376c0..bf3a18110d 100644 --- a/packages/web/test/server/stream-ambient-error-hook-4193.spec.tsx +++ b/packages/web/test/server/stream-ambient-error-hook-4193.spec.tsx @@ -12,10 +12,9 @@ * - an `onCleanup` that throws in the final dispose after a normal * completion: only `abandon()` contained a throwing dispose (#4029). * - * Not pinned here: whether the cleanups registered before a throwing one - * still run. The owner teardown stops at a throw on the server as it does on - * the client (`disposeOwner` / `runDisposal`); that is owner semantics, not - * this containment. + * The cleanups registered before a throwing one still run: the owner + * teardown (`disposeOwner`) runs every cleanup and rethrows the first error + * once it is done, which is what reaches the hook. */ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { @@ -198,6 +197,7 @@ describe("renderToStream: an onCleanup that throws in the final dispose (#4193)" expect(completed).toBe(true); expect(kinds()).toEqual(["render/failed"]); expect((heard[0].error as Error).message).toBe("cleanup threw"); + expect(ran).toEqual(["first"]); }); test("reaches the ambient hook as render/failed when piped", async () => { @@ -210,6 +210,7 @@ describe("renderToStream: an onCleanup that throws in the final dispose (#4193)" expect(escaped).toEqual([]); expect(result!.state).toBe("settled"); expect(kinds()).toEqual(["render/failed"]); + expect(ran).toEqual(["first"]); }); test("the render's own onError hears it ahead of the ambient hook", async () => { From b5be30d753201ba9716907943d4b29c7dbf632f6 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Oct 2026 21:35:28 +0000 Subject: [PATCH 2/6] chore(size): re-record caps for #4213 from CI Every scenario the change grows, at CI's measured sizes. Hello world (app: compiled floor) is 9,928 B brotli, under 10 KB. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 --- scripts/size/floor-caps.json | 24 ++++++------ scripts/size/scenarios.js | 72 +++++++++++++++++++++++------------- 2 files changed, 58 insertions(+), 38 deletions(-) diff --git a/scripts/size/floor-caps.json b/scripts/size/floor-caps.json index 61fc4c833f..fb84000dd1 100644 --- a/scripts/size/floor-caps.json +++ b/scripts/size/floor-caps.json @@ -1,30 +1,30 @@ { "signals: core floor (createSignal/Memo/Effect/Root/flush)": { - "cap": "7.39 KB", - "minified": 19991 + "cap": "7.44 KB", + "minified": 20136 }, "app: render + one signal (the simple-app floor)": { - "cap": "9.70 KB", - "minified": 26881 + "cap": "9.75 KB", + "minified": 27028 }, "app: hydrating (no stores) with Show/For/Loading/Errored/lazy": { - "cap": "18.36 KB", - "minified": 53454 + "cap": "18.40 KB", + "minified": 53597 }, "page: base server components (hydrating + dynamic + frames + sf reference)": { - "cap": "33.20 KB", - "minified": 101429 + "cap": "33.23 KB", + "minified": 101525 }, "page: live server components (base + live/GET + action + isPending/latest)": { - "cap": "37.53 KB", - "minified": 115031 + "cap": "37.58 KB", + "minified": 115015 }, "server: floor (getRequestEvent + isServer)": { "cap": "1.34 KB", "minified": 3324 }, "server: renderToString (the server-render floor)": { - "cap": "20.59 KB", - "minified": 72154 + "cap": "20.64 KB", + "minified": 72156 } } diff --git a/scripts/size/scenarios.js b/scripts/size/scenarios.js index 3794728de9..f711c6b8d6 100644 --- a/scripts/size/scenarios.js +++ b/scripts/size/scenarios.js @@ -598,6 +598,7 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 7.35 KB unchanged, recorded minified 19,934 -> 19,962 B (+28 B), measured in CI at 7,338 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 7.35 -> 7.39 KB, recorded minified 19,962 -> 20,021 B, measured locally (linux) at 7,374 B brotli / 20,021 B minified (+59 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 7.39 KB unchanged, recorded minified 20,021 -> 19,991 B (-30 B), measured in CI at 7,350 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 7.39 KB -> 7.44 KB, recorded minified 19,991 -> 20,136 B (+145 B), measured in CI at 7,422 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. limit: floorCaps["signals: core floor (createSignal/Memo/Effect/Root/flush)"], capMinified: floorMinified["signals: core floor (createSignal/Memo/Effect/Root/flush)"], alias @@ -1093,8 +1094,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 14.90 KB -> 14.94 KB, recorded minified 45,089 -> 45,115 B (+26 B), measured in CI at 14,927 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 14.94 -> 14.95 KB, recorded minified 45,115 -> 45,175 B, measured locally (linux) at 14,932 B brotli / 45,175 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 14.95 KB unchanged, recorded minified 45,175 -> 45,145 B (-30 B), measured in CI at 14,914 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - limit: "14.95 KB", - capMinified: 45145, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 14.95 KB -> 15.05 KB, recorded minified 45,145 -> 45,285 B (+140 B), measured in CI at 15,037 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "15.05 KB", + capMinified: 45285, alias }, { @@ -1402,8 +1404,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 10.12 KB -> 10.14 KB, recorded minified 28,356 -> 28,383 B (+27 B), measured in CI at 10,121 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 10.14 -> 10.16 KB, recorded minified 28,383 -> 28,443 B, measured locally (linux) at 10,144 B brotli / 28,443 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 10.16 KB -> 10.19 KB, recorded minified 28,443 -> 28,463 B (+20 B), measured in CI at 10,173 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - limit: "10.19 KB", - capMinified: 28463, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 10.19 KB -> 10.25 KB, recorded minified 28,463 -> 28,632 B (+169 B), measured in CI at 10,232 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "10.25 KB", + capMinified: 28632, alias }, { @@ -1438,8 +1441,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 7.95 KB -> 7.96 KB, recorded minified 21,745 -> 21,773 B (+28 B), measured in CI at 7,941 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 7.96 -> 7.98 KB, recorded minified 21,773 -> 21,832 B, measured locally (linux) at 7,969 B brotli / 21,832 B minified (+59 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 7.98 KB unchanged, recorded minified 21,832 -> 21,802 B (-30 B), measured in CI at 7,960 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - limit: "7.98 KB", - capMinified: 21802 + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 7.98 KB -> 8.05 KB, recorded minified 21,802 -> 21,945 B (+143 B), measured in CI at 8,035 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "8.05 KB", + capMinified: 21945 }, { name: "app: render + one signal (the simple-app floor)", @@ -1692,6 +1696,7 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 9.66 KB -> 9.67 KB, recorded minified 26,824 -> 26,852 B (+28 B), measured in CI at 9,651 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 9.67 -> 9.70 KB, recorded minified 26,852 -> 26,911 B, measured locally (linux) at 9,681 B brotli / 26,911 B minified (+59 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 9.70 KB unchanged, recorded minified 26,911 -> 26,881 B (-30 B), measured in CI at 9,681 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 9.70 KB -> 9.75 KB, recorded minified 26,881 -> 27,028 B (+147 B), measured in CI at 9,737 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. limit: floorCaps["app: render + one signal (the simple-app floor)"], capMinified: floorMinified["app: render + one signal (the simple-app floor)"], alias @@ -2084,6 +2089,7 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 18.29 KB -> 18.32 KB, recorded minified 53,401 -> 53,428 B (+27 B), measured in CI at 18,308 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 18.32 KB (unchanged), recorded minified 53,428 -> 53,488 B, measured locally (linux) at 18,309 B brotli / 53,488 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 18.32 KB -> 18.36 KB, recorded minified 53,488 -> 53,454 B (-34 B), measured in CI at 18,342 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 18.36 KB -> 18.40 KB, recorded minified 53,454 -> 53,597 B (+143 B), measured in CI at 18,381 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. limit: floorCaps["app: hydrating (no stores) with Show/For/Loading/Errored/lazy"], capMinified: floorMinified["app: hydrating (no stores) with Show/For/Loading/Errored/lazy"], alias @@ -2635,8 +2641,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 30.30 KB unchanged, recorded minified 94,999 -> 95,025 B (+26 B), measured in CI at 30,267 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 30.30 -> 30.32 KB, recorded minified 95,025 -> 95,085 B, measured locally (linux) at 30,308 B brotli / 95,085 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 30.32 KB -> 30.41 KB, recorded minified 95,085 -> 95,109 B (+24 B), measured in CI at 30,394 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - limit: "30.41 KB", - capMinified: 95109, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 30.41 KB -> 30.42 KB, recorded minified 95,109 -> 95,276 B (+167 B), measured in CI at 30,402 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "30.42 KB", + capMinified: 95276, alias }, { @@ -2925,8 +2932,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 13.24 KB unchanged, recorded minified 37,060 -> 37,087 B (+27 B), measured in CI at 13,217 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 13.24 -> 13.28 KB, recorded minified 37,087 -> 37,147 B, measured locally (linux) at 13,264 B brotli / 37,147 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 13.28 KB unchanged, recorded minified 37,147 -> 37,113 B (-34 B), measured in CI at 13,249 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - limit: "13.28 KB", - capMinified: 37113, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 13.28 KB -> 13.31 KB, recorded minified 37,113 -> 37,251 B (+138 B), measured in CI at 13,298 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "13.31 KB", + capMinified: 37251, alias }, { @@ -3268,8 +3276,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 14.99 KB -> 15.00 KB, recorded minified 42,019 -> 42,046 B (+27 B), measured in CI at 14,988 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 15.00 -> 15.03 KB, recorded minified 42,046 -> 42,106 B, measured locally (linux) at 15,016 B brotli / 42,106 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 15.03 KB -> 15.04 KB, recorded minified 42,106 -> 42,072 B (-34 B), measured in CI at 15,024 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - limit: "15.04 KB", - capMinified: 42072, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 15.04 KB -> 15.10 KB, recorded minified 42,072 -> 42,205 B (+133 B), measured in CI at 15,086 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "15.10 KB", + capMinified: 42205, alias: observeAlias }, { @@ -3762,8 +3771,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 29.59 KB unchanged, recorded minified 88,339 -> 88,365 B (+26 B), measured in CI at 29,535 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 29.59 -> 29.63 KB, recorded minified 88,365 -> 88,425 B, measured locally (linux) at 29,612 B brotli / 88,425 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 29.63 KB unchanged, recorded minified 88,425 -> 88,391 B (-34 B), measured in CI at 29,577 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - limit: "29.63 KB", - capMinified: 88391, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 29.63 KB -> 29.64 KB, recorded minified 88,391 -> 88,523 B (+132 B), measured in CI at 29,623 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "29.64 KB", + capMinified: 88523, alias: observeAlias }, // Compiled-template scenarios (2026-10-05): the four `app:` fixtures above @@ -3852,8 +3862,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 9.86 KB unchanged, recorded minified 27,356 -> 27,383 B (+27 B), measured in CI at 9,846 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 9.86 -> 9.89 KB, recorded minified 27,383 -> 27,442 B, measured locally (linux) at 9,879 B brotli / 27,442 B minified (+59 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 9.89 KB unchanged, recorded minified 27,442 -> 27,412 B (-30 B), measured in CI at 9,873 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - limit: "9.89 KB", - capMinified: 27412, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 9.89 KB -> 9.94 KB, recorded minified 27,412 -> 27,559 B (+147 B), measured in CI at 9,928 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "9.94 KB", + capMinified: 27559, alias }, { @@ -3975,8 +3986,9 @@ module.exports = [ // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 26.10 KB (unchanged), recorded minified 81,112 -> 81,172 B, measured locally (linux) at 26,087 B brotli / 81,172 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 26.10 KB -> 26.11 KB, recorded minified 81,172 -> 81,138 B (-34 B), measured in CI at 26,092 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Re-recorded on next @ 0222d684 (2026-10-21, accepted by the maintainer): 26.11 KB -> 26.14 KB, recorded minified 81,138 -> 81,159 B (+21 B), measured in CI at 26,123 B brotli. #4188 (reaction arms captured before tracking reads), #4207 (#4203, unread pending memo under a verdict mount) and #4176 (deferred releases restored after a throwing cleanup) each passed alone and together put next 1 B past the 20 B minified allowance. Cap at measured + 10 B rounded up to 0.01 KB; recorded minified = measured. - limit: "26.14 KB", - capMinified: 81159, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 26.14 KB -> 26.22 KB, recorded minified 81,159 -> 81,282 B (+123 B), measured in CI at 26,201 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "26.22 KB", + capMinified: 81282, alias }, { @@ -4131,8 +4143,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 32.13 KB unchanged, recorded minified 101,651 -> 101,677 B (+26 B), measured in CI at 32,067 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 32.13 KB (unchanged), recorded minified 101,677 -> 101,737 B, measured locally (linux) at 32,091 B brotli / 101,737 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 32.13 KB -> 32.20 KB, recorded minified 101,737 -> 101,703 B (-34 B), measured in CI at 32,182 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 32.20 KB (unchanged), recorded minified 101,703 -> 101,847 B (+144 B), measured in CI at 32,187 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. limit: "32.20 KB", - capMinified: 101703, + capMinified: 101847, alias }, { @@ -5053,6 +5066,7 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 33.16 KB -> 33.18 KB, recorded minified 101,352 -> 101,384 B (+32 B), measured in CI at 33,166 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 33.18 -> 33.20 KB, recorded minified 101,384 -> 101,444 B, measured locally (linux) at 33,190 B brotli / 101,444 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 33.20 KB unchanged, recorded minified 101,444 -> 101,429 B (-15 B), measured in CI at 33,170 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 33.20 KB -> 33.23 KB, recorded minified 101,429 -> 101,525 B (+96 B), measured in CI at 33,216 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. limit: floorCaps["page: base server components (hydrating + dynamic + frames + sf reference)"], capMinified: floorMinified["page: base server components (hydrating + dynamic + frames + sf reference)"], @@ -5393,6 +5407,7 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 37.46 KB unchanged, recorded minified 114,901 -> 114,922 B (+21 B), measured in CI at 37,410 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 37.46 -> 37.48 KB, recorded minified 114,922 -> 114,982 B, measured locally (linux) at 37,467 B brotli / 114,982 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 37.48 KB -> 37.53 KB, recorded minified 114,982 -> 115,031 B (+49 B), measured in CI at 37,516 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 37.53 KB -> 37.58 KB, recorded minified 115,031 -> 115,015 B (-16 B), measured in CI at 37,561 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; 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)"], @@ -5551,8 +5566,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 35.19 KB -> 35.23 KB, recorded minified 107,701 -> 107,725 B (+24 B), measured in CI at 35,212 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 35.23 KB (unchanged), recorded minified 107,725 -> 107,785 B, measured locally (linux) at 35,212 B brotli / 107,785 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 35.23 KB -> 35.30 KB, recorded minified 107,785 -> 107,770 B (-15 B), measured in CI at 35,285 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - limit: "35.30 KB", - capMinified: 107770, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 35.30 KB -> 35.32 KB, recorded minified 107,770 -> 107,875 B (+105 B), measured in CI at 35,310 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "35.32 KB", + capMinified: 107875, alias: pageAlias }, { @@ -5680,8 +5696,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 41.31 KB unchanged, recorded minified 122,880 -> 122,907 B (+27 B), measured in CI at 41,283 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 41.31 KB (unchanged), recorded minified 122,907 -> 122,967 B, measured locally (linux) at 41,300 B brotli / 122,967 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 41.31 KB -> 41.42 KB, recorded minified 122,967 -> 123,015 B (+48 B), measured in CI at 41,410 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 41.42 KB (unchanged), recorded minified 123,015 -> 123,012 B (-3 B), measured in CI at 41,352 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. limit: "41.42 KB", - capMinified: 123015, + capMinified: 123012, alias: pageAlias }, { @@ -5879,8 +5896,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 40.71 KB unchanged, recorded minified 125,181 -> 125,207 B (+26 B), measured in CI at 40,645 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 40.71 -> 40.72 KB, recorded minified 125,207 -> 125,267 B, measured locally (linux) at 40,706 B brotli / 125,267 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 40.72 KB unchanged, recorded minified 125,267 -> 125,252 B (-15 B), measured in CI at 40,652 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - limit: "40.72 KB", - capMinified: 125252, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 40.72 KB -> 40.82 KB, recorded minified 125,252 -> 125,363 B (+111 B), measured in CI at 40,810 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "40.82 KB", + capMinified: 125363, alias: pageAlias, conditions: solidConditions, compile: { hydratable: true } @@ -6054,8 +6072,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 46.86 KB -> 46.90 KB, recorded minified 140,165 -> 140,198 B (+33 B), measured in CI at 46,884 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 46.90 -> 46.91 KB, recorded minified 140,198 -> 140,258 B, measured locally (linux) at 46,896 B brotli / 140,258 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 46.91 KB unchanged, recorded minified 140,258 -> 140,296 B (+38 B), measured in CI at 46,884 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - limit: "46.91 KB", - capMinified: 140296, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 46.91 KB -> 46.96 KB, recorded minified 140,296 -> 140,292 B (-4 B), measured in CI at 46,945 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "46.96 KB", + capMinified: 140292, alias: pageAlias, conditions: solidConditions, compile: { hydratable: true } @@ -6176,6 +6195,7 @@ module.exports = [ // 01270641): 20.65 KB -> 20.59 KB, recorded minified 72,466 -> 72,154 B; // measured locally (linux) at 20,573 B (72,154 B minified). Lower only: cap // at measured + 10 B rounded up to 0.01 KB; recorded minified never raised. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 20.59 KB -> 20.64 KB, recorded minified 72,154 -> 72,156 B (+2 B), measured in CI at 20,623 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. limit: floorCaps["server: renderToString (the server-render floor)"], capMinified: floorMinified["server: renderToString (the server-render floor)"], alias: serverAlias, From af2f5375b40ea5aa0763cddc4e043fa06d905ad7 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Oct 2026 22:03:13 +0000 Subject: [PATCH 3/6] perf(signals): keep the cleanup rethrow off disposeChildren's hot path CodSpeed flagged createOwners at -10%: the end-of-walk rethrow check in disposeChildren slowed every disposal. Root dispose now rethrows after its walk outside a flush; every other teardown leaves it to the flush that runCleanup schedules. Drops the nested parameter too. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 --- packages/signals/src/core/core.ts | 8 +++---- packages/signals/src/core/lanes.ts | 4 ++-- packages/signals/src/core/owner.ts | 30 +++++++++----------------- packages/signals/src/core/scheduler.ts | 9 ++------ 4 files changed, 18 insertions(+), 33 deletions(-) diff --git a/packages/signals/src/core/core.ts b/packages/signals/src/core/core.ts index dedb7b5f27..0082ae3808 100644 --- a/packages/signals/src/core/core.ts +++ b/packages/signals/src/core/core.ts @@ -406,12 +406,12 @@ export function recompute(el: Computed, create: boolean = false): void { // and own nothing to park. if (isEffect === EFFECT_TRACKED || el._config & CONFIG_STAGED) { // A lazy node only those children read is released when this pass - // ends, if the pass doesn't read it again (#4142). Nested: a cleanup - // that throws here neither stops the pass nor skips that drain — its - // error is rethrown when the flush ends (owner.ts). + // ends, if the pass doesn't read it again (#4142). A cleanup that + // throws here neither stops the pass nor skips that drain: its error + // is rethrown when the flush ends (owner.ts). const prev = deferredReleases; deferReleases((released = [])); - disposeChildren(el, false, true); + disposeChildren(el); deferReleases(prev); } else if (el._firstChild !== null || el._disposal !== null) parkChildren(el); // An error a pass staged (D-41) is replaced by this pass's answer: a diff --git a/packages/signals/src/core/lanes.ts b/packages/signals/src/core/lanes.ts index ea358f4f13..39072babda 100644 --- a/packages/signals/src/core/lanes.ts +++ b/packages/signals/src/core/lanes.ts @@ -516,7 +516,7 @@ function dissolveLane(l: Transaction, into: Transaction | null, except?: Signal< if (!effect) n._config |= CONFIG_INPUTS_PUBLISHED; } else { if (x._pendingFirstChild !== null || x._pendingDisposal !== null) - GlobalQueue._dispose(n as Computed, false, false, true); + GlobalQueue._dispose(n as Computed, false, true); n._config &= ~(CONFIG_STAGED | CONFIG_HELD | CONFIG_HELD_FRAME); } } @@ -684,7 +684,7 @@ function laneSeam(l: Transaction, leaked: boolean): void { // The frame this pass built is the screen's now; the one it replaced // goes (as `commitPendingNode` retires a committed pass's). if (x._pendingFirstChild !== null || x._pendingDisposal !== null) - GlobalQueue._dispose(n, false, false, true); + GlobalQueue._dispose(n, false, true); n._config &= ~CONFIG_STAGED; } releaseQueues(l); diff --git a/packages/signals/src/core/owner.ts b/packages/signals/src/core/owner.ts index 253c8c4e43..4826bda608 100644 --- a/packages/signals/src/core/owner.ts +++ b/packages/signals/src/core/owner.ts @@ -62,10 +62,10 @@ export function dispose(node: Computed): void { // The first error a cleanup threw, boxed so a thrown `undefined` still // counts. A throwing cleanup stops neither its siblings nor the rest of the -// tree: every cleanup runs. The error is rethrown once the teardown is over — -// when the walk that began it ends, or, for a teardown inside a flush (a -// commit retiring a frame, a pass replacing one, an effect disposing a root), -// when the flush ends (`flush`), so the flush is never left half-applied. +// tree: every cleanup runs. The error is rethrown once the teardown is over: +// by a root's dispose call outside a flush (`disposeRootSelf`), otherwise when +// the flush ends (`flush`; `runCleanup` schedules one), so a flush is never +// left half-applied. let cleanupError: [unknown] | undefined; function runCleanup(fn: () => void): void { @@ -73,8 +73,6 @@ function runCleanup(fn: () => void): void { fn(); } catch (e) { cleanupError ||= [e]; - // A teardown outside a flush that does not rethrow (a pass pulled by a - // read): the flush this schedules does. schedule(); } } @@ -86,17 +84,7 @@ export function rethrowCleanupError(): void { if (error) throw error[0]; } -/** - * Tears down `node`'s children (and `node` itself when `self`), or with - * `zombie` the frame parked on it. `nested`: part of an enclosing teardown or - * pass, which rethrows a cleanup's error instead (see `cleanupError`). - */ -export function disposeChildren( - node: Owner, - self: boolean = false, - nested?: boolean, - zombie?: boolean -): void { +export function disposeChildren(node: Owner, self: boolean = false, zombie?: boolean): void { const flags = (node as any)._flags; if (flags & REACTIVE_DISPOSED) return; // A parked frame dies with its owner (#3024): the commit that would retire @@ -110,7 +98,7 @@ export function disposeChildren( node._x !== null && (node._x._pendingFirstChild !== null || node._x._pendingDisposal !== null) ) - disposeChildren(node, false, true, true); + disposeChildren(node, false, true); if (self) { (node as any)._flags = flags | REACTIVE_DISPOSED; if (__OBSERVE__ && node._parent === null && (node as Root)._root) unregisterRoot(node); @@ -156,7 +144,7 @@ export function disposeChildren( // itself routes its later splice onto the detached chain, never the head, // and keeps the dev owner-chain-head invariant honest for those children. child._prevSibling = child; - disposeChildren(child, true, true); + disposeChildren(child, true); // Read after, not before: a sibling this disposal made dormant spliced // itself out of the detached chain, and a cleanup may then have linked // it at the fresh head, which rewrote the `_nextSibling` a pre-read @@ -202,7 +190,6 @@ export function disposeChildren( runCleanup(effectCleanup); if (__DEV__) exitDisposal(); } - if (!nested && !globalQueue._running) rethrowCleanupError(); } export function linkChild(parent: Owner, node: Owner): void { @@ -369,6 +356,9 @@ export function isDisposed(node: Owner): boolean { function disposeRootSelf(this: Root, self: boolean = true): void { disposeChildren(this, self); + // Outside a flush the dispose call rethrows; inside one, the flush does. + // Kept out of `disposeChildren`, whose hot path it slows. + if (cleanupError && !globalQueue._running) rethrowCleanupError(); } /** diff --git a/packages/signals/src/core/scheduler.ts b/packages/signals/src/core/scheduler.ts index 45e8de2a3e..8f56fcd364 100644 --- a/packages/signals/src/core/scheduler.ts +++ b/packages/signals/src/core/scheduler.ts @@ -783,12 +783,7 @@ export class GlobalQueue implements IQueue { _queues: [QueueCallback[], QueueCallback[]] = [[], []]; _running: boolean = false; declare static _update: (el: Computed) => void; - declare static _dispose: ( - el: Computed, - self: boolean, - nested?: boolean, - zombie?: boolean - ) => void; + declare static _dispose: (el: Computed, self: boolean, zombie?: boolean) => void; declare static _runEffect: (el: Computed) => void; /** A slot node's truth is in flight (its family's derive — store/store.ts). */ declare static _slotFlight: ((n: Signal) => boolean) | undefined; @@ -1356,7 +1351,7 @@ export function commitPendingNode(n: Signal): void { c._x != null && (c._x._pendingFirstChild !== null || c._x._pendingDisposal !== null) ) - GlobalQueue._dispose(c as Computed, false, false, true); + GlobalQueue._dispose(c as Computed, false, true); } function commitPendingNodes() { From a8977523bc4678cea775b3a0c14073a378855657 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Oct 2026 22:06:51 +0000 Subject: [PATCH 4/6] chore(size): re-record #4213's caps from CI after the perf fix Hello world (app: compiled floor) is 9,935 B brotli, under 10 KB. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 --- scripts/size/floor-caps.json | 20 ++++---- scripts/size/scenarios.js | 92 ++++++++++++++++++------------------ 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/scripts/size/floor-caps.json b/scripts/size/floor-caps.json index fb84000dd1..af51c0df93 100644 --- a/scripts/size/floor-caps.json +++ b/scripts/size/floor-caps.json @@ -1,23 +1,23 @@ { "signals: core floor (createSignal/Memo/Effect/Root/flush)": { - "cap": "7.44 KB", - "minified": 20136 + "cap": "7.43 KB", + "minified": 20119 }, "app: render + one signal (the simple-app floor)": { - "cap": "9.75 KB", - "minified": 27028 + "cap": "9.73 KB", + "minified": 27011 }, "app: hydrating (no stores) with Show/For/Loading/Errored/lazy": { - "cap": "18.40 KB", - "minified": 53597 + "cap": "18.36 KB", + "minified": 53580 }, "page: base server components (hydrating + dynamic + frames + sf reference)": { - "cap": "33.23 KB", - "minified": 101525 + "cap": "33.30 KB", + "minified": 101508 }, "page: live server components (base + live/GET + action + isPending/latest)": { - "cap": "37.58 KB", - "minified": 115015 + "cap": "37.53 KB", + "minified": 114992 }, "server: floor (getRequestEvent + isServer)": { "cap": "1.34 KB", diff --git a/scripts/size/scenarios.js b/scripts/size/scenarios.js index f711c6b8d6..7de4dd174d 100644 --- a/scripts/size/scenarios.js +++ b/scripts/size/scenarios.js @@ -598,7 +598,7 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 7.35 KB unchanged, recorded minified 19,934 -> 19,962 B (+28 B), measured in CI at 7,338 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 7.35 -> 7.39 KB, recorded minified 19,962 -> 20,021 B, measured locally (linux) at 7,374 B brotli / 20,021 B minified (+59 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 7.39 KB unchanged, recorded minified 20,021 -> 19,991 B (-30 B), measured in CI at 7,350 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 7.39 KB -> 7.44 KB, recorded minified 19,991 -> 20,136 B (+145 B), measured in CI at 7,422 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 7.39 KB -> 7.43 KB, recorded minified 19,991 -> 20,119 B (+128 B), measured in CI at 7,412 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. limit: floorCaps["signals: core floor (createSignal/Memo/Effect/Root/flush)"], capMinified: floorMinified["signals: core floor (createSignal/Memo/Effect/Root/flush)"], alias @@ -1094,9 +1094,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 14.90 KB -> 14.94 KB, recorded minified 45,089 -> 45,115 B (+26 B), measured in CI at 14,927 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 14.94 -> 14.95 KB, recorded minified 45,115 -> 45,175 B, measured locally (linux) at 14,932 B brotli / 45,175 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 14.95 KB unchanged, recorded minified 45,175 -> 45,145 B (-30 B), measured in CI at 14,914 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 14.95 KB -> 15.05 KB, recorded minified 45,145 -> 45,285 B (+140 B), measured in CI at 15,037 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "15.05 KB", - capMinified: 45285, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 14.95 KB -> 15.00 KB, recorded minified 45,145 -> 45,268 B (+123 B), measured in CI at 14,990 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "15.00 KB", + capMinified: 45268, alias }, { @@ -1404,9 +1404,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 10.12 KB -> 10.14 KB, recorded minified 28,356 -> 28,383 B (+27 B), measured in CI at 10,121 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 10.14 -> 10.16 KB, recorded minified 28,383 -> 28,443 B, measured locally (linux) at 10,144 B brotli / 28,443 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 10.16 KB -> 10.19 KB, recorded minified 28,443 -> 28,463 B (+20 B), measured in CI at 10,173 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 10.19 KB -> 10.25 KB, recorded minified 28,463 -> 28,632 B (+169 B), measured in CI at 10,232 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "10.25 KB", - capMinified: 28632, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 10.19 KB -> 10.24 KB, recorded minified 28,463 -> 28,609 B (+146 B), measured in CI at 10,225 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "10.24 KB", + capMinified: 28609, alias }, { @@ -1441,9 +1441,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 7.95 KB -> 7.96 KB, recorded minified 21,745 -> 21,773 B (+28 B), measured in CI at 7,941 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 7.96 -> 7.98 KB, recorded minified 21,773 -> 21,832 B, measured locally (linux) at 7,969 B brotli / 21,832 B minified (+59 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 7.98 KB unchanged, recorded minified 21,832 -> 21,802 B (-30 B), measured in CI at 7,960 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 7.98 KB -> 8.05 KB, recorded minified 21,802 -> 21,945 B (+143 B), measured in CI at 8,035 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "8.05 KB", - capMinified: 21945 + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 7.98 KB -> 8.04 KB, recorded minified 21,802 -> 21,928 B (+126 B), measured in CI at 8,025 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "8.04 KB", + capMinified: 21928 }, { name: "app: render + one signal (the simple-app floor)", @@ -1696,7 +1696,7 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 9.66 KB -> 9.67 KB, recorded minified 26,824 -> 26,852 B (+28 B), measured in CI at 9,651 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 9.67 -> 9.70 KB, recorded minified 26,852 -> 26,911 B, measured locally (linux) at 9,681 B brotli / 26,911 B minified (+59 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 9.70 KB unchanged, recorded minified 26,911 -> 26,881 B (-30 B), measured in CI at 9,681 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 9.70 KB -> 9.75 KB, recorded minified 26,881 -> 27,028 B (+147 B), measured in CI at 9,737 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 9.70 KB -> 9.73 KB, recorded minified 26,881 -> 27,011 B (+130 B), measured in CI at 9,720 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. limit: floorCaps["app: render + one signal (the simple-app floor)"], capMinified: floorMinified["app: render + one signal (the simple-app floor)"], alias @@ -2089,7 +2089,7 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 18.29 KB -> 18.32 KB, recorded minified 53,401 -> 53,428 B (+27 B), measured in CI at 18,308 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 18.32 KB (unchanged), recorded minified 53,428 -> 53,488 B, measured locally (linux) at 18,309 B brotli / 53,488 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 18.32 KB -> 18.36 KB, recorded minified 53,488 -> 53,454 B (-34 B), measured in CI at 18,342 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 18.36 KB -> 18.40 KB, recorded minified 53,454 -> 53,597 B (+143 B), measured in CI at 18,381 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 18.36 KB (unchanged), recorded minified 53,454 -> 53,580 B (+126 B), measured in CI at 18,333 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. limit: floorCaps["app: hydrating (no stores) with Show/For/Loading/Errored/lazy"], capMinified: floorMinified["app: hydrating (no stores) with Show/For/Loading/Errored/lazy"], alias @@ -2641,9 +2641,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 30.30 KB unchanged, recorded minified 94,999 -> 95,025 B (+26 B), measured in CI at 30,267 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 30.30 -> 30.32 KB, recorded minified 95,025 -> 95,085 B, measured locally (linux) at 30,308 B brotli / 95,085 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 30.32 KB -> 30.41 KB, recorded minified 95,085 -> 95,109 B (+24 B), measured in CI at 30,394 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 30.41 KB -> 30.42 KB, recorded minified 95,109 -> 95,276 B (+167 B), measured in CI at 30,402 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "30.42 KB", - capMinified: 95276, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 30.41 KB (unchanged), recorded minified 95,109 -> 95,254 B (+145 B), measured in CI at 30,371 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "30.41 KB", + capMinified: 95254, alias }, { @@ -2932,9 +2932,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 13.24 KB unchanged, recorded minified 37,060 -> 37,087 B (+27 B), measured in CI at 13,217 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 13.24 -> 13.28 KB, recorded minified 37,087 -> 37,147 B, measured locally (linux) at 13,264 B brotli / 37,147 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 13.28 KB unchanged, recorded minified 37,147 -> 37,113 B (-34 B), measured in CI at 13,249 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 13.28 KB -> 13.31 KB, recorded minified 37,113 -> 37,251 B (+138 B), measured in CI at 13,298 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "13.31 KB", - capMinified: 37251, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 13.28 KB -> 13.36 KB, recorded minified 37,113 -> 37,234 B (+121 B), measured in CI at 13,348 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "13.36 KB", + capMinified: 37234, alias }, { @@ -3276,9 +3276,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 14.99 KB -> 15.00 KB, recorded minified 42,019 -> 42,046 B (+27 B), measured in CI at 14,988 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 15.00 -> 15.03 KB, recorded minified 42,046 -> 42,106 B, measured locally (linux) at 15,016 B brotli / 42,106 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 15.03 KB -> 15.04 KB, recorded minified 42,106 -> 42,072 B (-34 B), measured in CI at 15,024 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 15.04 KB -> 15.10 KB, recorded minified 42,072 -> 42,205 B (+133 B), measured in CI at 15,086 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "15.10 KB", - capMinified: 42205, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 15.04 KB -> 15.07 KB, recorded minified 42,072 -> 42,189 B (+117 B), measured in CI at 15,053 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "15.07 KB", + capMinified: 42189, alias: observeAlias }, { @@ -3771,9 +3771,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 29.59 KB unchanged, recorded minified 88,339 -> 88,365 B (+26 B), measured in CI at 29,535 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 29.59 -> 29.63 KB, recorded minified 88,365 -> 88,425 B, measured locally (linux) at 29,612 B brotli / 88,425 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 29.63 KB unchanged, recorded minified 88,425 -> 88,391 B (-34 B), measured in CI at 29,577 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 29.63 KB -> 29.64 KB, recorded minified 88,391 -> 88,523 B (+132 B), measured in CI at 29,623 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "29.64 KB", - capMinified: 88523, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 29.63 KB (unchanged), recorded minified 88,391 -> 88,507 B (+116 B), measured in CI at 29,609 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "29.63 KB", + capMinified: 88507, alias: observeAlias }, // Compiled-template scenarios (2026-10-05): the four `app:` fixtures above @@ -3862,9 +3862,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 9.86 KB unchanged, recorded minified 27,356 -> 27,383 B (+27 B), measured in CI at 9,846 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 9.86 -> 9.89 KB, recorded minified 27,383 -> 27,442 B, measured locally (linux) at 9,879 B brotli / 27,442 B minified (+59 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 9.89 KB unchanged, recorded minified 27,442 -> 27,412 B (-30 B), measured in CI at 9,873 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 9.89 KB -> 9.94 KB, recorded minified 27,412 -> 27,559 B (+147 B), measured in CI at 9,928 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "9.94 KB", - capMinified: 27559, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 9.89 KB -> 9.95 KB, recorded minified 27,412 -> 27,542 B (+130 B), measured in CI at 9,935 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "9.95 KB", + capMinified: 27542, alias }, { @@ -3986,9 +3986,9 @@ module.exports = [ // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 26.10 KB (unchanged), recorded minified 81,112 -> 81,172 B, measured locally (linux) at 26,087 B brotli / 81,172 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 26.10 KB -> 26.11 KB, recorded minified 81,172 -> 81,138 B (-34 B), measured in CI at 26,092 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Re-recorded on next @ 0222d684 (2026-10-21, accepted by the maintainer): 26.11 KB -> 26.14 KB, recorded minified 81,138 -> 81,159 B (+21 B), measured in CI at 26,123 B brotli. #4188 (reaction arms captured before tracking reads), #4207 (#4203, unread pending memo under a verdict mount) and #4176 (deferred releases restored after a throwing cleanup) each passed alone and together put next 1 B past the 20 B minified allowance. Cap at measured + 10 B rounded up to 0.01 KB; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 26.14 KB -> 26.22 KB, recorded minified 81,159 -> 81,282 B (+123 B), measured in CI at 26,201 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "26.22 KB", - capMinified: 81282, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 26.14 KB -> 26.20 KB, recorded minified 81,159 -> 81,265 B (+106 B), measured in CI at 26,185 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "26.20 KB", + capMinified: 81265, alias }, { @@ -4143,9 +4143,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 32.13 KB unchanged, recorded minified 101,651 -> 101,677 B (+26 B), measured in CI at 32,067 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 32.13 KB (unchanged), recorded minified 101,677 -> 101,737 B, measured locally (linux) at 32,091 B brotli / 101,737 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 32.13 KB -> 32.20 KB, recorded minified 101,737 -> 101,703 B (-34 B), measured in CI at 32,182 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 32.20 KB (unchanged), recorded minified 101,703 -> 101,847 B (+144 B), measured in CI at 32,187 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 32.20 KB (unchanged), recorded minified 101,703 -> 101,830 B (+127 B), measured in CI at 32,124 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. limit: "32.20 KB", - capMinified: 101847, + capMinified: 101830, alias }, { @@ -5066,7 +5066,7 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 33.16 KB -> 33.18 KB, recorded minified 101,352 -> 101,384 B (+32 B), measured in CI at 33,166 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 33.18 -> 33.20 KB, recorded minified 101,384 -> 101,444 B, measured locally (linux) at 33,190 B brotli / 101,444 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 33.20 KB unchanged, recorded minified 101,444 -> 101,429 B (-15 B), measured in CI at 33,170 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 33.20 KB -> 33.23 KB, recorded minified 101,429 -> 101,525 B (+96 B), measured in CI at 33,216 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 33.20 KB -> 33.30 KB, recorded minified 101,429 -> 101,508 B (+79 B), measured in CI at 33,287 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. limit: floorCaps["page: base server components (hydrating + dynamic + frames + sf reference)"], capMinified: floorMinified["page: base server components (hydrating + dynamic + frames + sf reference)"], @@ -5407,7 +5407,7 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 37.46 KB unchanged, recorded minified 114,901 -> 114,922 B (+21 B), measured in CI at 37,410 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 37.46 -> 37.48 KB, recorded minified 114,922 -> 114,982 B, measured locally (linux) at 37,467 B brotli / 114,982 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 37.48 KB -> 37.53 KB, recorded minified 114,982 -> 115,031 B (+49 B), measured in CI at 37,516 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 37.53 KB -> 37.58 KB, recorded minified 115,031 -> 115,015 B (-16 B), measured in CI at 37,561 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 37.53 KB (unchanged), recorded minified 115,031 -> 114,992 B (-39 B), measured in CI at 37,500 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; 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)"], @@ -5566,9 +5566,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 35.19 KB -> 35.23 KB, recorded minified 107,701 -> 107,725 B (+24 B), measured in CI at 35,212 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 35.23 KB (unchanged), recorded minified 107,725 -> 107,785 B, measured locally (linux) at 35,212 B brotli / 107,785 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 35.23 KB -> 35.30 KB, recorded minified 107,785 -> 107,770 B (-15 B), measured in CI at 35,285 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 35.30 KB -> 35.32 KB, recorded minified 107,770 -> 107,875 B (+105 B), measured in CI at 35,310 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "35.32 KB", - capMinified: 107875, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 35.30 KB -> 35.33 KB, recorded minified 107,770 -> 107,858 B (+88 B), measured in CI at 35,311 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "35.33 KB", + capMinified: 107858, alias: pageAlias }, { @@ -5696,9 +5696,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 41.31 KB unchanged, recorded minified 122,880 -> 122,907 B (+27 B), measured in CI at 41,283 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 41.31 KB (unchanged), recorded minified 122,907 -> 122,967 B, measured locally (linux) at 41,300 B brotli / 122,967 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 41.31 KB -> 41.42 KB, recorded minified 122,967 -> 123,015 B (+48 B), measured in CI at 41,410 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 41.42 KB (unchanged), recorded minified 123,015 -> 123,012 B (-3 B), measured in CI at 41,352 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 41.42 KB (unchanged), recorded minified 123,015 -> 122,990 B (-25 B), measured in CI at 41,382 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. limit: "41.42 KB", - capMinified: 123012, + capMinified: 122990, alias: pageAlias }, { @@ -5896,9 +5896,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 40.71 KB unchanged, recorded minified 125,181 -> 125,207 B (+26 B), measured in CI at 40,645 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 40.71 -> 40.72 KB, recorded minified 125,207 -> 125,267 B, measured locally (linux) at 40,706 B brotli / 125,267 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 40.72 KB unchanged, recorded minified 125,267 -> 125,252 B (-15 B), measured in CI at 40,652 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 40.72 KB -> 40.82 KB, recorded minified 125,252 -> 125,363 B (+111 B), measured in CI at 40,810 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "40.82 KB", - capMinified: 125363, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 40.72 KB -> 40.80 KB, recorded minified 125,252 -> 125,346 B (+94 B), measured in CI at 40,782 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "40.80 KB", + capMinified: 125346, alias: pageAlias, conditions: solidConditions, compile: { hydratable: true } @@ -6072,9 +6072,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 46.86 KB -> 46.90 KB, recorded minified 140,165 -> 140,198 B (+33 B), measured in CI at 46,884 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 46.90 -> 46.91 KB, recorded minified 140,198 -> 140,258 B, measured locally (linux) at 46,896 B brotli / 140,258 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 46.91 KB unchanged, recorded minified 140,258 -> 140,296 B (+38 B), measured in CI at 46,884 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 46.91 KB -> 46.96 KB, recorded minified 140,296 -> 140,292 B (-4 B), measured in CI at 46,945 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "46.96 KB", - capMinified: 140292, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 46.91 KB -> 46.92 KB, recorded minified 140,296 -> 140,270 B (-26 B), measured in CI at 46,904 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "46.92 KB", + capMinified: 140270, alias: pageAlias, conditions: solidConditions, compile: { hydratable: true } @@ -6195,7 +6195,7 @@ module.exports = [ // 01270641): 20.65 KB -> 20.59 KB, recorded minified 72,466 -> 72,154 B; // measured locally (linux) at 20,573 B (72,154 B minified). Lower only: cap // at measured + 10 B rounded up to 0.01 KB; recorded minified never raised. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 20.59 KB -> 20.64 KB, recorded minified 72,154 -> 72,156 B (+2 B), measured in CI at 20,623 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown after the walk (dispose()) or after the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 20.59 KB -> 20.64 KB, recorded minified 72,154 -> 72,156 B (+2 B), measured in CI at 20,623 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. limit: floorCaps["server: renderToString (the server-render floor)"], capMinified: floorMinified["server: renderToString (the server-render floor)"], alias: serverAlias, From 0ec8e11e6668890025e1b28e2102ae67d43a5420 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Oct 2026 22:18:39 +0000 Subject: [PATCH 5/6] fix(signals): collect unobserved-hook throws, drop cleanup errors on halt Review follow-ups for #4213: - A throwing `unobserved` hook is collected like a cleanup, so the teardown around it carries on and a pass's release collector is always removed. - A halt drops a collected cleanup error, so it never surfaces from a later, unrelated dispose(). - Server disposeOwner goes back to the indexed reverse loop. - Tests for the mid-walk release, the halt, and throwing hooks. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 --- .changeset/cleanup-throw-continues.md | 2 +- packages/signals/src/core/core.ts | 6 +- packages/signals/src/core/graph.ts | 6 +- packages/signals/src/core/owner.ts | 12 ++- packages/signals/src/core/scheduler.ts | 3 + .../tests/cleanup-throw-continues.test.ts | 96 ++++++++++++++++++- .../deferred-release-cleanup-throw.test.ts | 52 ++++++++++ packages/solid/src/server/signals.ts | 8 +- 8 files changed, 170 insertions(+), 15 deletions(-) diff --git a/.changeset/cleanup-throw-continues.md b/.changeset/cleanup-throw-continues.md index ac9675a6a0..c60fb15377 100644 --- a/.changeset/cleanup-throw-continues.md +++ b/.changeset/cleanup-throw-continues.md @@ -3,4 +3,4 @@ "solid-js": patch --- -A cleanup (`onCleanup` or an effect's returned cleanup) that throws no longer aborts the rest of the teardown: every other cleanup in the tree still runs, and the first error is rethrown once the teardown is done. Outside a flush (a root's `dispose()`), the dispose call rethrows it. Inside a flush (a branch switch retiring a frame, a pass replacing an uncommitted one, an effect disposing a root), `flush()` rethrows it after the flush has finished, so commits and effects are never left half-applied, and lazy nodes released before the throw are still released. On the server, `disposeOwner` runs every cleanup the same way, so `renderToStream`'s final dispose still reports the error (#4193). +A cleanup (`onCleanup` or an effect's returned cleanup) that throws no longer aborts the rest of the teardown: every other cleanup in the tree still runs, and the first error is rethrown once the teardown is done. Outside a flush (a root's `dispose()`), the dispose call rethrows it. Inside a flush (a branch switch retiring a frame, a pass replacing an uncommitted one, an effect disposing a root), `flush()` rethrows it after the flush has finished, so commits and effects are never left half-applied, and lazy nodes released before the throw are still released. An `unobserved` hook that throws is handled the same way. If an uncaught error halts the reactive system, a collected cleanup error is dropped rather than surfacing later from an unrelated `dispose()`. On the server, `disposeOwner` runs every cleanup the same way, so `renderToStream`'s final dispose still reports the error (#4193). diff --git a/packages/signals/src/core/core.ts b/packages/signals/src/core/core.ts index 0082ae3808..e5c43f972a 100644 --- a/packages/signals/src/core/core.ts +++ b/packages/signals/src/core/core.ts @@ -406,9 +406,9 @@ export function recompute(el: Computed, create: boolean = false): void { // and own nothing to park. if (isEffect === EFFECT_TRACKED || el._config & CONFIG_STAGED) { // A lazy node only those children read is released when this pass - // ends, if the pass doesn't read it again (#4142). A cleanup that - // throws here neither stops the pass nor skips that drain: its error - // is rethrown when the flush ends (owner.ts). + // ends, if the pass doesn't read it again (#4142). A cleanup or + // `unobserved` hook that throws here neither stops the pass nor skips + // that drain: its error is rethrown when the flush ends (owner.ts). const prev = deferredReleases; deferReleases((released = [])); disposeChildren(el); diff --git a/packages/signals/src/core/graph.ts b/packages/signals/src/core/graph.ts index 3a00bd4a91..c21fe3ef3b 100644 --- a/packages/signals/src/core/graph.ts +++ b/packages/signals/src/core/graph.ts @@ -7,7 +7,7 @@ import { STATUS_PENDING } from "./constants.js"; import { deleteFromHeap } from "./heap.js"; -import { disposeChildren } from "./owner.js"; +import { disposeChildren, runCleanup } from "./owner.js"; import { bumpNotifyEpoch, dirtyQueue } from "./scheduler.js"; import type { Computed, Link, Signal } from "./types.js"; @@ -35,8 +35,10 @@ export function unlinkSubs(link: Link): Link | null { if (nextSub === null) { // Store slot nodes dispatch to the ONE shared hook — no per-node // unobserved closure, no NodeExtension to hold it. + // A user hook that throws is collected like a cleanup (owner.ts): the + // unlink and the teardown around it carry on. if (dep._config & CONFIG_SLOT_NODE) slotUnobservedHook(dep as Signal); - else dep._x?._unobserved?.(); + else if (dep._x?._unobserved) runCleanup(dep._x._unobserved); // No more subscribers; only tear down if CONFIG_AUTO_DISPOSE is set. // A pending node is exempt: its in-flight async work is an observer — // tearing down would orphan the work and re-execute it on the next diff --git a/packages/signals/src/core/owner.ts b/packages/signals/src/core/owner.ts index 4826bda608..4767373443 100644 --- a/packages/signals/src/core/owner.ts +++ b/packages/signals/src/core/owner.ts @@ -65,10 +65,12 @@ export function dispose(node: Computed): void { // tree: every cleanup runs. The error is rethrown once the teardown is over: // by a root's dispose call outside a flush (`disposeRootSelf`), otherwise when // the flush ends (`flush`; `runCleanup` schedules one), so a flush is never -// left half-applied. +// left half-applied. A halt drops it: the error that halted is the one +// reported, and a stale one must not surface from a later, unrelated dispose. let cleanupError: [unknown] | undefined; -function runCleanup(fn: () => void): void { +/** Runs a cleanup or `unobserved` hook; a throw is collected, not raised. @internal */ +export function runCleanup(fn: () => void): void { try { fn(); } catch (e) { @@ -77,11 +79,11 @@ function runCleanup(fn: () => void): void { } } -/** Rethrows the error `runCleanup` collected, once. @internal */ -export function rethrowCleanupError(): void { +/** Rethrows the error `runCleanup` collected, once; `drop` discards it. @internal */ +export function rethrowCleanupError(drop?: boolean): void { const error = cleanupError; cleanupError = undefined; - if (error) throw error[0]; + if (error && !drop) throw error[0]; } export function disposeChildren(node: Owner, self: boolean = false, zombie?: boolean): void { diff --git a/packages/signals/src/core/scheduler.ts b/packages/signals/src/core/scheduler.ts index 8f56fcd364..4b913da2f5 100644 --- a/packages/signals/src/core/scheduler.ts +++ b/packages/signals/src/core/scheduler.ts @@ -703,6 +703,8 @@ export const ROOT_ERROR_HOOK: unique symbol = Symbol.for("solid-js/root-error-ho export function haltReactivity(cause?: unknown): void { if (halted) return; halted = true; + // A cleanup error collected before the halt is dropped (owner.ts). + rethrowCleanupError(true); let message = "[REACTIVITY_HALTED]"; if (__DEV__) { message += @@ -1460,6 +1462,7 @@ export function flush(fn?: () => T): T | void { if (__OBSERVE__ && attrHooks !== null && scheduled) attrHooks.flushStart(); while (scheduled) { if (__DEV__ && ++count === 1e5) { + rethrowCleanupError(true); // Attribution beats a bare guard (#3140): say what kept the loop alive. throw new Error( `Potential Infinite Loop Detected. Kept alive by scheduled work${ diff --git a/packages/signals/tests/cleanup-throw-continues.test.ts b/packages/signals/tests/cleanup-throw-continues.test.ts index f6429ca598..a79297e8c9 100644 --- a/packages/signals/tests/cleanup-throw-continues.test.ts +++ b/packages/signals/tests/cleanup-throw-continues.test.ts @@ -16,10 +16,14 @@ import { getOwner, isDisposed, onCleanup, + resetErrorHalt, type Owner } from "../src/index.js"; -afterEach(() => flush()); +afterEach(() => { + resetErrorHalt(); + flush(); +}); const boom = (message = "boom") => { throw new Error(message); @@ -195,4 +199,94 @@ describe("inside a flush", () => { expect(log).toEqual(["other first", "after dispose"]); dispose(); }); + + it("a lazy node released mid-walk does not rethrow before the walk is done", () => { + const log: string[] = []; + let lazy!: () => number; + const disposeSource = createRoot(dispose => { + lazy = createMemo( + () => { + onCleanup(() => log.push("lazy")); + return 1; + }, + { lazy: true } + ); + return dispose; + }); + const dispose = createRoot(dispose => { + onCleanup(() => log.push("root")); + // Disposed after the effect below (children go newest first)... + createRoot(() => { + onCleanup(() => log.push("child")); + }); + // ...and this one first, so its error is collected before the effect's + // teardown releases `lazy` through `unobserved`. + createRenderEffect(lazy, () => {}); + createRoot(() => { + onCleanup(() => boom()); + }); + return dispose; + }); + flush(); + + expect(dispose).toThrow("boom"); + expect(log).toEqual(["lazy", "child", "root"]); + disposeSource(); + }); + + it("a halt drops a collected cleanup error: a later dispose does not throw it", () => { + const [go, setGo] = createSignal(false); + const disposeOther = createRoot(dispose => { + onCleanup(() => boom("cleanup")); + return dispose; + }); + const dispose = createRoot(dispose => { + createEffect(go, v => { + if (v) disposeOther(); + }); + createEffect(go, v => { + if (v) boom("effect"); + }); + return dispose; + }); + flush(); + setGo(true); + const error = vi.spyOn(console, "error").mockImplementation(() => {}); + try { + expect(() => flush()).toThrow("effect"); + } finally { + error.mockRestore(); + } + const unrelated = createRoot(dispose => dispose); + expect(unrelated).not.toThrow(); + resetErrorHalt(); + dispose(); + }); +}); + +describe("unobserved hooks", () => { + it("a throwing hook does not stop the teardown around it", () => { + const log: string[] = []; + let source!: () => number; + const disposeSource = createRoot(dispose => { + source = createMemo(() => 1, { + lazy: true, + unobserved: () => { + log.push("hook"); + boom("hook"); + } + }); + return dispose; + }); + const dispose = createRoot(dispose => { + createRenderEffect(source, () => {}); + onCleanup(() => log.push("root")); + return dispose; + }); + flush(); + + expect(dispose).toThrow("hook"); + expect(log).toEqual(["hook", "root"]); + disposeSource(); + }); }); diff --git a/packages/signals/tests/deferred-release-cleanup-throw.test.ts b/packages/signals/tests/deferred-release-cleanup-throw.test.ts index 39be8d719a..5d23cc8361 100644 --- a/packages/signals/tests/deferred-release-cleanup-throw.test.ts +++ b/packages/signals/tests/deferred-release-cleanup-throw.test.ts @@ -62,6 +62,58 @@ it.each([false, true])( } ); +// An `unobserved` hook that throws while the frame is dismantled is collected +// like a cleanup: the pass's release collector is still removed, so a later, +// unrelated release happens. +it("a throwing unobserved hook does not leave the release collector installed", () => { + const failure = new Error("hook failed"); + const [version, setVersion] = createSignal(0); + const disposePending = createRoot(dispose => { + const hooked = createMemo(() => 1, { + lazy: true, + unobserved: () => { + throw failure; + } + }); + createMemo(() => { + if (version() === 0) createMemo(() => hooked()); + return new Promise(() => {}); + }); + return dispose; + }); + let disposeOther: (() => void) | undefined; + try { + flush(); + setVersion(1); + expect(() => flush()).toThrow(failure); + + let cleaned = 0; + let stop!: () => void; + disposeOther = createRoot(dispose => { + const value = createMemo( + () => { + onCleanup(() => cleaned++); + return 1; + }, + { lazy: true } + ); + stop = createRoot(dispose => { + createRenderEffect(value, () => {}); + return dispose; + }); + return dispose; + }); + flush(); + stop(); + flush(); + expect(cleaned).toBe(1); + } finally { + disposeOther?.(); + disposePending(); + flush(); + } +}); + // A lazy node the dismantled frame's children released before the throw is // still released: the throw no longer skips the drain at the end of the pass // (the teardown is nested in the pass, and the flush rethrows when it ends). diff --git a/packages/solid/src/server/signals.ts b/packages/solid/src/server/signals.ts index ee2ec672aa..d4f212af39 100644 --- a/packages/solid/src/server/signals.ts +++ b/packages/solid/src/server/signals.ts @@ -582,13 +582,15 @@ export function disposeOwner(owner: Owner, self: boolean = true): void { node._disposal = null; // Unwind order, mirroring the client `runDisposal` (#3572): later // registrations run before earlier ones. - if (d) - for (const fn of ([] as (() => void)[]).concat(d).reverse()) + if (d) { + const many = Array.isArray(d); + for (let i = many ? d.length - 1 : 0; i >= 0; i--) try { - fn(); + (many ? d[i] : d)(); } catch (e) { error ||= [e]; } + } if (self) unlinkOwner(node); // Recycle the disposed owner. Skip the root case (`self=false`) and the // already-pooled case so we don't double-add. The next `createOwner` will From 8e262f9085a5cf5395da40d4c4657e805aba71aa Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Oct 2026 22:22:18 +0000 Subject: [PATCH 6/6] chore(size): re-record #4213's caps from CI after the review fixes Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 --- scripts/size/floor-caps.json | 22 ++++----- scripts/size/scenarios.js | 92 ++++++++++++++++++------------------ 2 files changed, 57 insertions(+), 57 deletions(-) diff --git a/scripts/size/floor-caps.json b/scripts/size/floor-caps.json index af51c0df93..4d4107a880 100644 --- a/scripts/size/floor-caps.json +++ b/scripts/size/floor-caps.json @@ -1,30 +1,30 @@ { "signals: core floor (createSignal/Memo/Effect/Root/flush)": { - "cap": "7.43 KB", - "minified": 20119 + "cap": "7.44 KB", + "minified": 20137 }, "app: render + one signal (the simple-app floor)": { - "cap": "9.73 KB", - "minified": 27011 + "cap": "9.75 KB", + "minified": 27029 }, "app: hydrating (no stores) with Show/For/Loading/Errored/lazy": { - "cap": "18.36 KB", - "minified": 53580 + "cap": "18.38 KB", + "minified": 53598 }, "page: base server components (hydrating + dynamic + frames + sf reference)": { - "cap": "33.30 KB", - "minified": 101508 + "cap": "33.27 KB", + "minified": 101526 }, "page: live server components (base + live/GET + action + isPending/latest)": { "cap": "37.53 KB", - "minified": 114992 + "minified": 115011 }, "server: floor (getRequestEvent + isServer)": { "cap": "1.34 KB", "minified": 3324 }, "server: renderToString (the server-render floor)": { - "cap": "20.64 KB", - "minified": 72156 + "cap": "20.65 KB", + "minified": 72189 } } diff --git a/scripts/size/scenarios.js b/scripts/size/scenarios.js index 7de4dd174d..b1e189c94f 100644 --- a/scripts/size/scenarios.js +++ b/scripts/size/scenarios.js @@ -598,7 +598,7 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 7.35 KB unchanged, recorded minified 19,934 -> 19,962 B (+28 B), measured in CI at 7,338 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 7.35 -> 7.39 KB, recorded minified 19,962 -> 20,021 B, measured locally (linux) at 7,374 B brotli / 20,021 B minified (+59 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 7.39 KB unchanged, recorded minified 20,021 -> 19,991 B (-30 B), measured in CI at 7,350 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 7.39 KB -> 7.43 KB, recorded minified 19,991 -> 20,119 B (+128 B), measured in CI at 7,412 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 7.39 KB -> 7.44 KB, recorded minified 19,991 -> 20,137 B (+146 B), measured in CI at 7,426 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. limit: floorCaps["signals: core floor (createSignal/Memo/Effect/Root/flush)"], capMinified: floorMinified["signals: core floor (createSignal/Memo/Effect/Root/flush)"], alias @@ -1094,9 +1094,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 14.90 KB -> 14.94 KB, recorded minified 45,089 -> 45,115 B (+26 B), measured in CI at 14,927 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 14.94 -> 14.95 KB, recorded minified 45,115 -> 45,175 B, measured locally (linux) at 14,932 B brotli / 45,175 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 14.95 KB unchanged, recorded minified 45,175 -> 45,145 B (-30 B), measured in CI at 14,914 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 14.95 KB -> 15.00 KB, recorded minified 45,145 -> 45,268 B (+123 B), measured in CI at 14,990 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "15.00 KB", - capMinified: 45268, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 14.95 KB -> 15.01 KB, recorded minified 45,145 -> 45,286 B (+141 B), measured in CI at 14,997 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "15.01 KB", + capMinified: 45286, alias }, { @@ -1404,9 +1404,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 10.12 KB -> 10.14 KB, recorded minified 28,356 -> 28,383 B (+27 B), measured in CI at 10,121 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 10.14 -> 10.16 KB, recorded minified 28,383 -> 28,443 B, measured locally (linux) at 10,144 B brotli / 28,443 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 10.16 KB -> 10.19 KB, recorded minified 28,443 -> 28,463 B (+20 B), measured in CI at 10,173 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 10.19 KB -> 10.24 KB, recorded minified 28,463 -> 28,609 B (+146 B), measured in CI at 10,225 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "10.24 KB", - capMinified: 28609, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 10.19 KB -> 10.25 KB, recorded minified 28,463 -> 28,625 B (+162 B), measured in CI at 10,236 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "10.25 KB", + capMinified: 28625, alias }, { @@ -1441,9 +1441,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 7.95 KB -> 7.96 KB, recorded minified 21,745 -> 21,773 B (+28 B), measured in CI at 7,941 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 7.96 -> 7.98 KB, recorded minified 21,773 -> 21,832 B, measured locally (linux) at 7,969 B brotli / 21,832 B minified (+59 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 7.98 KB unchanged, recorded minified 21,832 -> 21,802 B (-30 B), measured in CI at 7,960 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 7.98 KB -> 8.04 KB, recorded minified 21,802 -> 21,928 B (+126 B), measured in CI at 8,025 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "8.04 KB", - capMinified: 21928 + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 7.98 KB -> 8.05 KB, recorded minified 21,802 -> 21,946 B (+144 B), measured in CI at 8,036 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "8.05 KB", + capMinified: 21946 }, { name: "app: render + one signal (the simple-app floor)", @@ -1696,7 +1696,7 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 9.66 KB -> 9.67 KB, recorded minified 26,824 -> 26,852 B (+28 B), measured in CI at 9,651 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 9.67 -> 9.70 KB, recorded minified 26,852 -> 26,911 B, measured locally (linux) at 9,681 B brotli / 26,911 B minified (+59 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 9.70 KB unchanged, recorded minified 26,911 -> 26,881 B (-30 B), measured in CI at 9,681 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 9.70 KB -> 9.73 KB, recorded minified 26,881 -> 27,011 B (+130 B), measured in CI at 9,720 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 9.70 KB -> 9.75 KB, recorded minified 26,881 -> 27,029 B (+148 B), measured in CI at 9,738 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. limit: floorCaps["app: render + one signal (the simple-app floor)"], capMinified: floorMinified["app: render + one signal (the simple-app floor)"], alias @@ -2089,7 +2089,7 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 18.29 KB -> 18.32 KB, recorded minified 53,401 -> 53,428 B (+27 B), measured in CI at 18,308 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 18.32 KB (unchanged), recorded minified 53,428 -> 53,488 B, measured locally (linux) at 18,309 B brotli / 53,488 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 18.32 KB -> 18.36 KB, recorded minified 53,488 -> 53,454 B (-34 B), measured in CI at 18,342 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 18.36 KB (unchanged), recorded minified 53,454 -> 53,580 B (+126 B), measured in CI at 18,333 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 18.36 KB -> 18.38 KB, recorded minified 53,454 -> 53,598 B (+144 B), measured in CI at 18,368 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. limit: floorCaps["app: hydrating (no stores) with Show/For/Loading/Errored/lazy"], capMinified: floorMinified["app: hydrating (no stores) with Show/For/Loading/Errored/lazy"], alias @@ -2641,9 +2641,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 30.30 KB unchanged, recorded minified 94,999 -> 95,025 B (+26 B), measured in CI at 30,267 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 30.30 -> 30.32 KB, recorded minified 95,025 -> 95,085 B, measured locally (linux) at 30,308 B brotli / 95,085 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 30.32 KB -> 30.41 KB, recorded minified 95,085 -> 95,109 B (+24 B), measured in CI at 30,394 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 30.41 KB (unchanged), recorded minified 95,109 -> 95,254 B (+145 B), measured in CI at 30,371 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "30.41 KB", - capMinified: 95254, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 30.41 KB -> 30.42 KB, recorded minified 95,109 -> 95,268 B (+159 B), measured in CI at 30,403 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "30.42 KB", + capMinified: 95268, alias }, { @@ -2932,9 +2932,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 13.24 KB unchanged, recorded minified 37,060 -> 37,087 B (+27 B), measured in CI at 13,217 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 13.24 -> 13.28 KB, recorded minified 37,087 -> 37,147 B, measured locally (linux) at 13,264 B brotli / 37,147 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 13.28 KB unchanged, recorded minified 37,147 -> 37,113 B (-34 B), measured in CI at 13,249 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 13.28 KB -> 13.36 KB, recorded minified 37,113 -> 37,234 B (+121 B), measured in CI at 13,348 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "13.36 KB", - capMinified: 37234, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 13.28 KB -> 13.33 KB, recorded minified 37,113 -> 37,252 B (+139 B), measured in CI at 13,312 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "13.33 KB", + capMinified: 37252, alias }, { @@ -3276,9 +3276,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 14.99 KB -> 15.00 KB, recorded minified 42,019 -> 42,046 B (+27 B), measured in CI at 14,988 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 15.00 -> 15.03 KB, recorded minified 42,046 -> 42,106 B, measured locally (linux) at 15,016 B brotli / 42,106 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 15.03 KB -> 15.04 KB, recorded minified 42,106 -> 42,072 B (-34 B), measured in CI at 15,024 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 15.04 KB -> 15.07 KB, recorded minified 42,072 -> 42,189 B (+117 B), measured in CI at 15,053 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "15.07 KB", - capMinified: 42189, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 15.04 KB -> 15.08 KB, recorded minified 42,072 -> 42,209 B (+137 B), measured in CI at 15,062 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "15.08 KB", + capMinified: 42209, alias: observeAlias }, { @@ -3771,9 +3771,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 29.59 KB unchanged, recorded minified 88,339 -> 88,365 B (+26 B), measured in CI at 29,535 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 29.59 -> 29.63 KB, recorded minified 88,365 -> 88,425 B, measured locally (linux) at 29,612 B brotli / 88,425 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 29.63 KB unchanged, recorded minified 88,425 -> 88,391 B (-34 B), measured in CI at 29,577 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 29.63 KB (unchanged), recorded minified 88,391 -> 88,507 B (+116 B), measured in CI at 29,609 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "29.63 KB", - capMinified: 88507, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 29.63 KB -> 29.64 KB, recorded minified 88,391 -> 88,527 B (+136 B), measured in CI at 29,622 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "29.64 KB", + capMinified: 88527, alias: observeAlias }, // Compiled-template scenarios (2026-10-05): the four `app:` fixtures above @@ -3862,9 +3862,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 9.86 KB unchanged, recorded minified 27,356 -> 27,383 B (+27 B), measured in CI at 9,846 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 9.86 -> 9.89 KB, recorded minified 27,383 -> 27,442 B, measured locally (linux) at 9,879 B brotli / 27,442 B minified (+59 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 9.89 KB unchanged, recorded minified 27,442 -> 27,412 B (-30 B), measured in CI at 9,873 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 9.89 KB -> 9.95 KB, recorded minified 27,412 -> 27,542 B (+130 B), measured in CI at 9,935 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "9.95 KB", - capMinified: 27542, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 9.89 KB -> 9.93 KB, recorded minified 27,412 -> 27,560 B (+148 B), measured in CI at 9,919 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "9.93 KB", + capMinified: 27560, alias }, { @@ -3986,9 +3986,9 @@ module.exports = [ // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 26.10 KB (unchanged), recorded minified 81,112 -> 81,172 B, measured locally (linux) at 26,087 B brotli / 81,172 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 26.10 KB -> 26.11 KB, recorded minified 81,172 -> 81,138 B (-34 B), measured in CI at 26,092 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Re-recorded on next @ 0222d684 (2026-10-21, accepted by the maintainer): 26.11 KB -> 26.14 KB, recorded minified 81,138 -> 81,159 B (+21 B), measured in CI at 26,123 B brotli. #4188 (reaction arms captured before tracking reads), #4207 (#4203, unread pending memo under a verdict mount) and #4176 (deferred releases restored after a throwing cleanup) each passed alone and together put next 1 B past the 20 B minified allowance. Cap at measured + 10 B rounded up to 0.01 KB; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 26.14 KB -> 26.20 KB, recorded minified 81,159 -> 81,265 B (+106 B), measured in CI at 26,185 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "26.20 KB", - capMinified: 81265, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 26.14 KB -> 26.23 KB, recorded minified 81,159 -> 81,283 B (+124 B), measured in CI at 26,214 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "26.23 KB", + capMinified: 81283, alias }, { @@ -4143,9 +4143,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 32.13 KB unchanged, recorded minified 101,651 -> 101,677 B (+26 B), measured in CI at 32,067 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 32.13 KB (unchanged), recorded minified 101,677 -> 101,737 B, measured locally (linux) at 32,091 B brotli / 101,737 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 32.13 KB -> 32.20 KB, recorded minified 101,737 -> 101,703 B (-34 B), measured in CI at 32,182 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 32.20 KB (unchanged), recorded minified 101,703 -> 101,830 B (+127 B), measured in CI at 32,124 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 32.20 KB (unchanged), recorded minified 101,703 -> 101,848 B (+145 B), measured in CI at 32,174 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. limit: "32.20 KB", - capMinified: 101830, + capMinified: 101848, alias }, { @@ -5066,7 +5066,7 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 33.16 KB -> 33.18 KB, recorded minified 101,352 -> 101,384 B (+32 B), measured in CI at 33,166 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 33.18 -> 33.20 KB, recorded minified 101,384 -> 101,444 B, measured locally (linux) at 33,190 B brotli / 101,444 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 33.20 KB unchanged, recorded minified 101,444 -> 101,429 B (-15 B), measured in CI at 33,170 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 33.20 KB -> 33.30 KB, recorded minified 101,429 -> 101,508 B (+79 B), measured in CI at 33,287 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 33.20 KB -> 33.27 KB, recorded minified 101,429 -> 101,526 B (+97 B), measured in CI at 33,255 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. limit: floorCaps["page: base server components (hydrating + dynamic + frames + sf reference)"], capMinified: floorMinified["page: base server components (hydrating + dynamic + frames + sf reference)"], @@ -5407,7 +5407,7 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 37.46 KB unchanged, recorded minified 114,901 -> 114,922 B (+21 B), measured in CI at 37,410 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 37.46 -> 37.48 KB, recorded minified 114,922 -> 114,982 B, measured locally (linux) at 37,467 B brotli / 114,982 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 37.48 KB -> 37.53 KB, recorded minified 114,982 -> 115,031 B (+49 B), measured in CI at 37,516 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 37.53 KB (unchanged), recorded minified 115,031 -> 114,992 B (-39 B), measured in CI at 37,500 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 37.53 KB (unchanged), recorded minified 115,031 -> 115,011 B (-20 B), measured in CI at 37,512 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; 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)"], @@ -5566,9 +5566,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 35.19 KB -> 35.23 KB, recorded minified 107,701 -> 107,725 B (+24 B), measured in CI at 35,212 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 35.23 KB (unchanged), recorded minified 107,725 -> 107,785 B, measured locally (linux) at 35,212 B brotli / 107,785 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 35.23 KB -> 35.30 KB, recorded minified 107,785 -> 107,770 B (-15 B), measured in CI at 35,285 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 35.30 KB -> 35.33 KB, recorded minified 107,770 -> 107,858 B (+88 B), measured in CI at 35,311 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "35.33 KB", - capMinified: 107858, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 35.30 KB (unchanged), recorded minified 107,770 -> 107,876 B (+106 B), measured in CI at 35,271 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "35.30 KB", + capMinified: 107876, alias: pageAlias }, { @@ -5696,9 +5696,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 41.31 KB unchanged, recorded minified 122,880 -> 122,907 B (+27 B), measured in CI at 41,283 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 41.31 KB (unchanged), recorded minified 122,907 -> 122,967 B, measured locally (linux) at 41,300 B brotli / 122,967 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): 41.31 KB -> 41.42 KB, recorded minified 122,967 -> 123,015 B (+48 B), measured in CI at 41,410 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 41.42 KB (unchanged), recorded minified 123,015 -> 122,990 B (-25 B), measured in CI at 41,382 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 41.42 KB (unchanged), recorded minified 123,015 -> 123,010 B (-5 B), measured in CI at 41,403 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. limit: "41.42 KB", - capMinified: 122990, + capMinified: 123010, alias: pageAlias }, { @@ -5896,9 +5896,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): cap 40.71 KB unchanged, recorded minified 125,181 -> 125,207 B (+26 B), measured in CI at 40,645 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 40.71 -> 40.72 KB, recorded minified 125,207 -> 125,267 B, measured locally (linux) at 40,706 B brotli / 125,267 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 40.72 KB unchanged, recorded minified 125,267 -> 125,252 B (-15 B), measured in CI at 40,652 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 40.72 KB -> 40.80 KB, recorded minified 125,252 -> 125,346 B (+94 B), measured in CI at 40,782 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "40.80 KB", - capMinified: 125346, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 40.72 KB -> 40.78 KB, recorded minified 125,252 -> 125,364 B (+112 B), measured in CI at 40,765 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "40.78 KB", + capMinified: 125364, alias: pageAlias, conditions: solidConditions, compile: { hydratable: true } @@ -6072,9 +6072,9 @@ module.exports = [ // Every comparator call guarded, default comparators on a fast path (#4111, 2026-10-20, over next @ 6371b910): 46.86 KB -> 46.90 KB, recorded minified 140,165 -> 140,198 B (+33 B), measured in CI at 46,884 B brotli. `isSame` is the one guarded comparator call (a NotReady read is a change, any other throw goes to the node's boundary, A3), with `isEqual` on a fast path so the propagation benches keep next's speed. Size-Exception accepted by the maintainer (2026-10-19). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. // Writable memo write clears its error without a torn read (#4110, 2026-10-20, over next @ 7014aca4, accepted by the maintainer): 46.90 -> 46.91 KB, recorded minified 140,198 -> 140,258 B, measured locally (linux) at 46,896 B brotli / 140,258 B minified (+60 B minified over the recorded size), which includes #4163's +9 B merged without re-recording: a write to an errored writable memo recovers it at commit and keeps the error shown until then, and clears an error a pass staged under the same hold. Correctness; matches CI's numbers. // Verdict-mount lane provenance for nested first passes (#4166, 2026-10-20, over next @ 5a4b2e85): cap 46.91 KB unchanged, recorded minified 140,258 -> 140,296 B (+38 B), measured in CI at 46,884 B brotli. A module-local laneRoot carries the mount's lane through nested first passes, gated on the lane engine, so bundles without it shrink. Size-Exception accepted by the maintainer (2026-10-20). Cap at measured + 10 B rounded up to 0.01 KB, never lowered here; recorded minified = measured. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 46.91 KB -> 46.92 KB, recorded minified 140,296 -> 140,270 B (-26 B), measured in CI at 46,904 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. - limit: "46.92 KB", - capMinified: 140270, + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 46.91 KB -> 46.96 KB, recorded minified 140,296 -> 140,292 B (-4 B), measured in CI at 46,942 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + limit: "46.96 KB", + capMinified: 140292, alias: pageAlias, conditions: solidConditions, compile: { hydratable: true } @@ -6195,7 +6195,7 @@ module.exports = [ // 01270641): 20.65 KB -> 20.59 KB, recorded minified 72,466 -> 72,154 B; // measured locally (linux) at 20,573 B (72,154 B minified). Lower only: cap // at measured + 10 B rounded up to 0.01 KB; recorded minified never raised. - // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 20.59 KB -> 20.64 KB, recorded minified 72,154 -> 72,156 B (+2 B), measured in CI at 20,623 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. + // Throwing cleanups no longer abort teardown (#4213, 2026-10-21, over next @ a2a5b6f8): 20.59 KB -> 20.65 KB, recorded minified 72,154 -> 72,189 B (+35 B), measured in CI at 20,632 B brotli. Every onCleanup and effect cleanup runs when one throws; the first error is rethrown by a root's dispose() or at the end of the flush. Size-Exception accepted by the maintainer (2026-10-21). Cap at measured + 10 B rounded up to 0.01 KB, never lowered; recorded minified = measured. limit: floorCaps["server: renderToString (the server-render floor)"], capMinified: floorMinified["server: renderToString (the server-render floor)"], alias: serverAlias,