Repository navigation
fix(signals): net staged rewrites a custom equals calls equal to the committed value (D-8, #4483) - #4488
Conversation
…committed value (D-8, #4483) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
🦋 Changeset detectedLatest commit: 8a20b58 The changes in this PR will be included in the next version bump. This PR includes changesets to release 15 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
Size (brotli, eager entry chunk)
|
Coverage Report for CI Build 41100165665Coverage remained the same at 78.1%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
Only isPending/latest read the netted test before a commit, so the write-back staging is gated on observeFlightHook and bundles without the verdict engine pay 0 B. Drops the floor re-records and Size-Exception. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
Merging this PR will regress 1 benchmark2 warnings
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
…tch PR) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
…rator A rewrite loop over a staged signal (CodSpeed propagation:avoidable, updateSignals:update1to1000) ran the uninitialized/shown-error checks and isSame on every write. Under the default comparator only the committed object is equal and `v` is already it, so one load of `_equals` decides. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
…-x1ore2-equals-netting
|
State of this PR's checks on 8a20b58 (next 64fedc7 merged in):
Generated by Claude Code |
…-x1ore2-equals-netting
… D-8 netting CodSpeed (simulation, --no-opt so setSignal runs as Sparkplug code) put `propagation:avoidable` 5.3% behind next: every staged rewrite paid a fresh `_equals` load and a strict compare for the netting, ~120 instructions a write under callgrind, on a bench that is 2,000 staged rewrites per iteration. With the verdict engine installed, `custom` (`_equals !== isEqual`) is now taken once: the gate runs the default comparator's `===` inline instead of calling `isSame`, and the netting tests the local. Without the engine `custom` is `null`, so the gate folds to the plain `isSame` call and the netting away: every store-free / non-verdict client bundle is byte for byte next's (hello world 9,973 B). callgrind, Ir per bench iteration (CodSpeed's V8 flags): next 3,294,205; PR head 3,536,042 (+7.3%); this 3,122,267 (-5.2% vs next). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
…-x1ore2-equals-netting
…4499, #4500, #4493, #4488, #4498) (#4507) next @ b5077ea is over six caps after the batch merged back to back (each fix PR carried no ledger edits, per the batch process). Re-records, measured over next @ b5077ea (linux): - signals: + createStore: 14.97 -> 14.99 KB, minified 43,924 -> 43,956 - signals: + isPending/latest: 11.29 -> 11.35 KB, minified 31,101 -> 31,245 - app: compiled CSR: 26.33 -> 26.36 KB, minified 80,035 -> 80,074 - page: live server components (frozen floor): 38.98 -> 39.10 KB, minified 117,858 -> 118,033 - page: compiled live server components: 42.95 -> 43.01 KB, minified 126,018 -> 126,181 - page: live + router: 48.58 -> 48.65 KB, minified 143,269 -> 143,439 and the recorded minified only (caps unchanged) where the batch drifted more than 20 B: hydrating + every store (98,535 -> 98,561), compiled hydrating (100,803 -> 100,836), base server components (frozen, 101,675 -> 101,706), compiled base server components (108,048 -> 108,081), base + router (125,503 -> 125,539). Hello world is 9,971 B (cap 9.98 KB). The build leaves the pinned mangle-props.json unchanged. Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 Co-authored-by: Claude <noreply@anthropic.com>
Summary
Before: take a signal with a custom
equalssuch as(a, b) => a.k === b.k. A mainline write stages{k: 2}. An action then writes a new{k: 1}back, which the comparator calls equal to the committed{k: 1}. The rewrite still pended the signal, andisPendingof a memo over it wenttrue.setSignalcompared the write only against the current value, which was the staged{k: 2}. It then staged the new object, and every netted test is identity (_pendingValue === _value). So a write that made no proposal counted as one.After: that rewrite nets to the committed value, as D-8 says: "A tick whose writes net to the slot's committed value made no proposal: the slot is not held, pends nothing".
isPendingstaysfalse. Without a customequalsnothing changes: only the committed object itself nets, and a new object is still a proposal.How
When a write lands on an existing staging and the node is neither uninitialized nor showing an error,
setSignalstages_valueitself if a custom comparator calls the write equal to_value. Every existing netted test then sees it.observeFlightHook),setSignaltakes the comparator test once (custom = el._equals !== isEqual) and shares it between the equality gate and the netting. Under the default comparator, the gate then runs===inline instead of callingisSame, and the netting is skipped on a local test. On callgrind with CodSpeed's flags this makespropagation:avoidable5.2% fewer instructions than next. Gating the netting alone, without sharing the test, measured 5–7% more.customisnull: the gate is the plainisSamecall as on next, and the netting is gone. Bundles withoutisPending/latestare byte-identical incore.js.isPendingandlatestread that test.equalsruns a second time only on writes that land on a staging. If it throws there,isSamereports the error on the node, as it already does.Trade-off: a core-only build (no
isPending/latest) still stages a custom-equals write-back as a new, non-netted staging. Nothing observes that without the verdict engine; 32 probe shapes in review found no difference on screen.Size (client, vs next 64fedc7, brotli / minified, from the CI size report; no ledger edits in this PR):
signals: + isPending/latestpage: live server componentspage: compiled live server componentspage: live + routerEvery bundle without the verdict engine is +0 minified. Hello world stays at 9,973 B.
How did you test this change?
New
packages/signals/tests/netted-equals-4483.test.tshas three cases:equals;[false, true];equals, where the committed object nets and a new object still pends.After merging next @ 64fedc7: signals 6,102 passed / 17 expected fail; web 1,579 passed / 7 expected fail. RULES-INDEX regenerates with no change.
Closes #4483.
🤖 Generated with Claude Code
https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2