Repository navigation
fix(signals): serve an inherited posture the hold's write from node-less store keys - #4682
Conversation
🦋 Changeset detectedLatest commit: c8e6ab2 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 |
Merging this PR will not alter performance1 warning
Comparing Footnotes
|
Five rows, +111 B minified each from #4677 and +7 B from #4682 (not re-recorded yet): signals + isPending/latest, app hydrating + every store family, the live page (a frozen floor, Size-Exception in the PR body), the compiled live page and live + router. Measured locally over next @ 5208944 with the in-repo compiler build; caps are measured brotli + 10 B. Co-authored-by: tembo[bot] <208362400+tembo-io[bot]@users.noreply.github.com>
The same five rows, now +527-529 B minified from #4677 (both cases) and +7 B from #4682: signals + isPending/latest, app hydrating + every store family, the live page (a frozen floor, Size-Exception in the PR body), the compiled live page and live + router. Measured locally over next @ 5208944 with the in-repo compiler build and re-verified after merging next @ f6b6737 (#4688 is size-neutral on every row); caps are measured brotli + 10 B. Co-authored-by: tembo[bot] <208362400+tembo-io[bot]@users.noreply.github.com>
Summary
Builds on #4681 by @GabbeV. His commit (
68be911d, unchanged, authored by him) fixes theTypeErrorthat halted reactivity when an untracked staged-store read sat besidelatest()on a store with no reactive node. This PR adds one commit on top. It extends that guard so node-less store keys also follow the #4544 ruling, instead of skipping it.Problem with the guard alone. #4625 added the ruling in RULES-DERIVED D-25 ("an inherited posture is not a probe", #4544): a pass whose verdict posture is only inherited is served the hold's own sync write. INTERNALS-STORE-STATE R2 says stores follow signal rules by default. The guard skips
holdStagingwheneverstagedScreengets no node. So on a store key with no node, an inherited-posture pass was served the committed value instead, while the same read on a signal, or on a key with a node, was served the staging. Whether the store had a node, an internal detail, changed what users saw:isPending: at the landing, a render effect saw a torn frame (datalanded,countstill 0), then the corrected one in the same flush. The signal goes straight to the corrected frame.latest: the pass derived0/0mid-hold instead of0/1, and re-ran an extra time after the landing.Fix.
holdStagingaccepts a missing node. With no node it applies only the reader-posture checks: no probe of its own (CONFIG_OWN_PROBE), not a frame, no open window or unanswered probe, and the lane condition. It skips the node-specific checks, because a node-less staging is by construction this flush's own plain sync store write. D-26 atomicity ([latest(x), x()]through a memo) is unaffected: a key that alatest()window asked about always gets a leaf node, so it reads through the node path, where_askedInstill decides. I traced this, and the D-26 case below pins it.How did you test this change?
tests/posture-store-parity.test.ts(23 tests). Six sources: signal, untracked signal, tracked store, and untracked store with a container node, with a leaf node only, or with no node. Each runs under an inheritedisPending, an inheritedlatest, and an own-latestposture, and must match an exact trace. Each test also asserts which nodes exist, so the labels can't drift. A D-26 case pins store/signal parity forlatestof the same key through a memo.isPendingshows the tornscreen 1 1/0, and inheritedlatestshows the0/0pass. All 23 pass with this commit.visibility-oracle-posture.test.tsgains amemoInheritedUntrackedreader kind (discovery coverage). Its readers are built after the write rather than re-run in the write's flush, so it does not reproduce this bug: all 160 new cells matchmemoUntracked, and the report is identical before and after the fix. S9 is the pin.docs/RULES-INDEX.mdis regenerated (rules-index.test.tspasses). The changeset is updated.build:jsandtypespass. Full suite after the build: 479 files, 6,689 passed, 14 expected failures, 2 skipped. The focused files (S9,nodeless-staged-verdict,verdict-memo-held-branch-4544,latest-memo-inline-parity) also pass underSIGNALS_TIER=prodandobserve(102 each).test/consistency,c11-trace-equals-oracle): 758 passed, no failures.verdict-memo-held-branch-4544.spec.tsx: passes.dist-nested-manifests: web type declarations were not built locally.server-functions-adapter-request. It fails identically without this commit (local Node 22.20).+isPending/latest+4 B minified / +7 B brotli (12,061 B vs a 12,070 B cap). The other three scenarios are unchanged. All within caps; no caps changed.git diff --check, andnode scripts/check-release-invariants.mjspass.