Repository navigation
fix(signals): a store key's delete decrements the key count - #4132
Conversation
`kc` gates the overlay draft (an owned plain object above 32 keys drafts as an overlay over its committed backing) and caps the rebuild. The set trap incremented it for every new key; nothing decremented it when a key was deleted from a plain-clone draft, or when an overlay's own add was deleted again — only the overlay rebuild paths subtracted their recorded committed deletions. So the natural keyed-selection shape, "delete the old key, set the new one", drifted the count up by one per write: after 32 selections a three-key object crossed the overlay threshold and, since any delete makes an overlay rebuild its backing (#3689), every commit after paid a full clone of the object — 1.4 µs of a 5.5 µs select on the js-framework fixture, found while asking why Solid's keyed-store selection cost three times Vue Vapor's compiled selector. `deleteProperty` now decrements for an own key of the draft that leaves (a clone's key, an overlay's own add) and leaves an overlay's committed key to `del`, counted at the rebuild as before. Tests pin the selection shape staying at two keys and out of overlay mode over 200 writes, delete-and-re-add, delete of a never-committed key, and the overlay's two delete cases; the overlay rebuild tests (#3689) pass unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: bcd6e02 The changes in this PR will be included in the next version bump. This PR includes changesets to release 14 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 |
Size (brotli, eager entry chunk)
|
…; tighten the trap +33 B minified on the createStore graph (one hasOwn probe hoisted, the delete and the decrement share its verdict); caps at measured + 10 B with a ledger line each (signals: + createStore 14.79 -> 14.82 KB, app: hydrating + every store primitive family 30.09 -> 30.14 KB, app: compiled CSR 25.95 -> 26.03 KB). Inline caps, not frozen floors. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Coverage Report for CI Build 39104313478Coverage remained the same at 77.564%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
…CI: +2 B brotli, +33 B minified) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Merging this PR will not alter performance
|
Journal §5–§12 of the Vapor lane: the signal-favoring fixture form, the selection cost traced to the store key-count drift (#4132) and the rc.0/rc.1 comparison, swap's history back to rc.0 and the reconciler walk change (#4133), and the move-hint probe — built, measured against a back-to-back control, and parked (swap 25 -> 15.8 µs where it applies, a 3% tax where it does not, ~300 B brotli per app bundle, never for a keyed-by-function For). A pointer under the May metadata-channel deferral and the lane ledger updated. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e-delete-key-count # Conflicts: # scripts/size/scenarios.js
… together (#4138) Claude-Session: https://claude.ai/code/session_018LY7F9czoRqdMEvk2QwPr1 Co-authored-by: Claude <noreply@anthropic.com>
fix(signals): a store key's delete decrements the key count
What
deletePropertyinpackages/signals/src/store/store.tsnow decrements the store's key count (kc) when an own key of the draft leaves: a plain clone's key, or an overlay's own add. An overlay's delete of a committed key is unchanged: recorded indeland counted at the rebuild, as before.Why
kcgates the overlay draft (an owned plain object above 32 keys drafts as an overlay over its committed backing) and caps the rebuild. The set trap incremented it for every new key; nothing decremented it on a delete outside the overlay rebuild paths. So the natural keyed-selection shape — a store holdingselected[id], written as "delete the previous key, set the new one" — drifted the count up by one per write. After 32 selections a three-key object crossed the overlay threshold and, since any delete makes an overlay rebuild its backing (#3689), every commit after paid a full clone of the object. The user's code was right; the store miscounted it. Any keyed store that adds and deletes keys over time (a selection map, a cache, a set of open rows) hit the same drift.Found on the Octane js-framework fixture while asking why Solid's selection cost three times Vue Vapor's compiled selector: the profile showed
materializePB(the overlay rebuild) on a store that should never have been an overlay.Measured
Call profiler on the unminified fixture, Solid on
next, the fixture's originaldeleteform, µs per select click:materializePB1.37,ensurePB0.32,storeSetter1.69 inclstoreSetter0.89 incl,ensurePB0.22,settle0.18Octane harness, all targets rerun:
select7.2 → 6.2 µs,select_lots4.4 → 3.7 µs (Octane 5.6 / 3.7, Vapor 2.8 / 1.8); the suite's Solid ÷ Octane geomean 1.19 → 1.09. The other ops are within run-to-run noise. The remaining gap to Vapor is the transaction itself against a map lookup, which is the 2.0 selection-through-store design, not this bug.Tests
tests/store/delete-key-count.test.ts: 200 delete-then-set selections stay at two keys and never draft as an overlay; delete and re-add in one draft; delete of a never-committed key; an overlay's own add deleted again, and an overlay's committed key deleted (counted at the rebuild). The #3689 overlay rebuild tests pass unchanged. Signals (5343), solid (869), web default (1379), server (1719) and hydrate (509) suites pass.🤖 Generated with Claude Code