Fix clearBound corrupting prevBound.extent on singleton SharedBound removal (#143)#144
Merged
Merged
Conversation
…emoval (issue #143) When the removed region was the sole member of its SharedBound (extentToIx == selfIx), clearBound unconditionally wrote the live extentToEff STRef into prevBound.extent. After pushIx(-1) fired on the removed region that reference resolved to -1, corrupting the preceding SharedBound's extent. Subsequent bumps on newly-allocated regions then failed to propagate end to the parent span, breaking useDynAtEndWith after an add/remove/add cycle. Fix: only write extentToEff into prevBound.extent when extentToIx > selfIx (i.e. there are surviving regions beyond the removed one that now belong to prevBound). Adds a regression test in index.test.js covering the exact failure sequence from the issue report. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The earlier approach (guarding the extent write in clearBound) was too broad and broke sendTo: bump(Nothing) keeps the region in the children array and later moves (via sendTo) rely on the live ixRef being tracked by prevBound.extent so that fixManaged can propagate the updated index. Root cause revisited: clearBound correctly writes the cleared region's live ixRef to prevBound.extent even when the region is the sole member of its SharedBound (extentToIx == selfIx). The corruption happens specifically in remove, where pushIx(-1) is called after clearBound, making the live ref resolve to -1. Fix: in remove, after bump(Nothing) (which invokes clearBound) and before pushIx(-1), check whether prevBound.extent now resolves to our soon-to-be- invalidated index; if so, replace it with the preceding region's ix (a live ref that won't be invalidated). This preserves the correct tracking behaviour for all subsequent operations. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root cause
clearBoundinDeku.Internal.Regionhas two branches — the first branch is taken when the preceding SharedBound is larger (or we're at the first region), and it extendsprevBoundto cover the nextBound:extentToEffis a liveSTRef— it'sST.read managed.ixRefon the removed region. When the removed region is the sole member of its SharedBound (extentToIx == selfIx),fixManagedTois a no-op (backwards range), and thenpushIx(-1)fires on the removed region, making that ref resolve to-1. From that point on,prevBound.extentreturns-1, soisLastBoundnever fires for survivors, and a subsequent bump fails to propagate end to the parent span.The exact failure sequence from the issue (add C0, add C1, undo C1, undo C0, add C0 again, observe broken placement) maps to: r1.bump, r2.bump, r2.remove (singleton clear → corrupts prevBound.extent), r1.remove (isLastBound broken, updateParent never fires), r3.bump (end not notified).
Fix
Only write
extentToEffintoprevBound.extentwhenextentToIx > selfIx— i.e. when there are actually surviving regions beyond the removed one that now belong toprevBound:Tests
Added a regression test in
index.test.jscovering the exact sequence: add, add, remove-last, remove-first, add — verifying that end propagates correctly to the parent span after the full cycle.Note: the pre-existing
sendTotest failure is unrelated to this PR.🤖 Generated with Claude Code