fix(signals): a write is a proposal (A34) — held-node writes entangle the tick, net-equal writes propose nothing (#3494) - #3519
Conversation
Coverage Report for CI Build 35282766260Coverage remained the same at 71.304%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
Merging this PR will not alter performance
Comparing Footnotes
|
|
…schedules; drain inside try; writable-memo parity A30: a pending mark over a kept-tail link (`link._gen !== sub._depGen`) now re-derives the subscriber instead of skipping (the pushed amendment) or marking it (base). Skipped, the committed frame published stale beside its new inputs — `query=1` beside a `selected` derived from `remote(0)` (the review's tear). Marked, the node registered as reporter of a flight its held frame never reads and entangled its holder with it — the #2141 orphaned fetch, and fuzzer branches-1 #1105 (a gated reader hidden forever on a manual flight nobody awaited). Re-derived, the pass decides: reads the dep and registers through its own read; reads a held input and enters that transaction (A29 — what holds the review's case); or reads neither. A34 mechanics: setSignal schedules when it records a join (a same-value repeat leaves through the equality gate; left for a later flush the join adopted an unrelated tick), and the fast sync path defers while a join waits; the drain runs inside flush's try so a throwing comparator cannot leave `_running` set; the no-proposal drop covers an initialized writable memo (`createSignal(fn)`, REACTIVE_MANUAL_WRITE) as it covers a signal, and unstages through commitPendingNode. Pins for all four in tests/write-proposals-3494.test.ts. Differential fuzz vs next d60ec6f (4 cohorts x 2 seeds x 5,000): 124 fixed / 7 new, the same set as before the review. Core floor 25,262 -> 25,504 minified; caps ratcheted with notes; rules index regenerated. Co-authored-by: Cursor <cursoragent@cursor.com>
fba237a to
d26d979
Compare
🦋 Changeset detectedLatest commit: f5df1d2 The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 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 |
|
… the tick, net-equal writes propose nothing (#3494) A mainline write to a node a transaction holds — the same value again or another — is a second proposal for the same slot: the writer's tick joins the hold at the next flush's start (`batchJoins`, drained inside the running flush so nothing between the write and the flush is born the transaction's — the proposed nothing: the adoption loop unstages an unstamped signal staged at its committed value, and computePendingState reads the same equality as final. Fixes the four #3473 regressions from #3494: the torn `[1,0,1]` effect input, the lost hide after a coalesced toggle, the stale visible derivation (a stale reader served a held memo's committed value counts as observing the memo's flight — reporterBlocksSource follows a dep's `_pendingSources` one hop), and the delayed release after a mainline `latest()` (a companion never blocks the settle — transitionBlocked skips `_parentSource` nodes). From differential fuzzing of the ruling (4 cohorts x 2 seeds x 5,000; 124 fixed / 7 new, the new ones one O1 family that follows from the rule plus a pre-existing P1): a pending mark rides only the links a pass made (A30 amendment — notifyStatus skips STATUS_PENDING over links outside the pass's validated prefix, `link._gen !== sub._depGen`, O(1)); and the no-proposal drop applies to unstamped nodes only (a held proposal rewritten to the committed value stays its transaction's). Spec: A34 new; A15/A28/A30 amended. Size: +175 B minified core floor, +36..+93 B brotli across scenarios; caps ratcheted with notes. Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…schedules; drain inside try; writable-memo parity A30: a pending mark over a kept-tail link (`link._gen !== sub._depGen`) now re-derives the subscriber instead of skipping (the pushed amendment) or marking it (base). Skipped, the committed frame published stale beside its new inputs — `query=1` beside a `selected` derived from `remote(0)` (the review's tear). Marked, the node registered as reporter of a flight its held frame never reads and entangled its holder with it — the #2141 orphaned fetch, and fuzzer branches-1 #1105 (a gated reader hidden forever on a manual flight nobody awaited). Re-derived, the pass decides: reads the dep and registers through its own read; reads a held input and enters that transaction (A29 — what holds the review's case); or reads neither. A34 mechanics: setSignal schedules when it records a join (a same-value repeat leaves through the equality gate; left for a later flush the join adopted an unrelated tick), and the fast sync path defers while a join waits; the drain runs inside flush's try so a throwing comparator cannot leave `_running` set; the no-proposal drop covers an initialized writable memo (`createSignal(fn)`, REACTIVE_MANUAL_WRITE) as it covers a signal, and unstages through commitPendingNode. Pins for all four in tests/write-proposals-3494.test.ts. Differential fuzz vs next d60ec6f (4 cohorts x 2 seeds x 5,000): 124 fixed / 7 new, the same set as before the review. Core floor 25,262 -> 25,504 minified; caps ratcheted with notes; rules index regenerated. Co-authored-by: Cursor <cursoragent@cursor.com>
d26d979 to
f5df1d2
Compare
Fixes #3494.
Ruling (A34 — new)
v: writingvagain is nothing — stages nothing, stamps nothing, pends nothing.vagain orw— is a second proposal on a contested node. It joins T, and so does the rest of that tick's batch.This reverses one mechanism choice from #3473, which removed
setSignal's eager entry from mainline to stop theactiveTransitionleak (a memo created after the write was born the transaction's, A29) — and dropped the grouping with it. The entry is now deferred:setSignalpushes the held node's transaction tobatchJoins;flush()enters each at its start, inside the running flush, adopting the tick's ambient batch. Recorded before the equality gate, so a repeat of the held value proposes too.The four #3473 regressions (all pinned in
tests/write-proposals-3494.test.ts)[1,0,1]effect inputbatchJoins)setShow(false); setShow(true)showstamped intocount's hold with nothing to reveal; nextsetShow(false)routed to the stamp and heldcomputePendingStateequality gateCount: 1besideCopy: 0)reporterBlocksSourcefollows a dep's_pendingSourcesone hoplatest(details)latest()shadow, backfilled under the transaction, blocked the settletransitionBlockedskips companionsFrom differential fuzzing (gabbev's fuzzer, #3446)
4 cohorts × 2 seeds × 5,000 cases, base =
origin/next. 124 fixed / 7 new.branches30→5 and 26→3;optimistic0/0 both sides.Two regressions the first cut introduced, both fixed and pinned:
_transition === nullonly.thisinside jsx-template inside a constructor of a derived class causes transformation-bug #2141): a hide joined to a parked action stopped reading a memo, unchanged, so A30 kept its dep tail; the action's truth re-asked the memo and the pending mark rode the kept link, registering the hidden reader as a reporter on a fetch nobody displays. A30 amended: a pending mark rides only the links a pass made —notifyStatusskipsSTATUS_PENDINGover links outside the pass's validated prefix (link._gen !== sub._depGen, the stamplink()already maintains; O(1)). Clears and errors still ride every link. (GatingreporterBlocksSourceinstead was wrong — it runs mid-pass — and the fuzzer caught it as an S1 flood.)The 7 remaining "new":
show/mountwritten in the same tick as an action's proposal is held by the action; toggling it back from mainline is a write to a held node → joins the action → the hide waits for the action to end. Base (post-feat(signals): A28 — a write becomes visible at flush, to every channel (read-side) #3473) publishes the hide immediately; pre-feat(signals): A28 — a write becomes visible at flush, to every channel (read-side) #3473 behaved like this PR. If a mainline write that nets a held node back to its committed value should escape the hold, that's a carve-out to A34(1) needing a ruling._transition. Follow-up.Size
Core floor +175 B minified (25,249 → 25,424; per-piece:
batchJoins71, drop 64,_genskip 24, one-hop 16). Brotli vs a freshnextbuild: +56 core, +75 store, +70 isPending/latest, +45 simple app, +80 hydrating, +36 hydrating+stores, +65 CSR, +93 observe, +40 observe+attribution, 0 frames. Caps ratcheted with notes. A golf pass replaced an O(kept-tail) link walk with the existing generation stamp and dropped a redundant companion snap; the fuzzer differential re-run on the golfed tree is case-for-case identical.Docs
A34 new; A15, A28, A30 amended; INTERNALS updated (
batchJoins, no-proposal drop, one-hop liveness, companion skip, pending-mark prefix rule); rules index regenerated; changeset.Claude via Cursor