Skip to content

fix: getPrevious after array push restores the previous array length (undo after push) - #668

Open
huytdps13400 wants to merge 1 commit into
LegendApp:mainfrom
huytdps13400:fix/undo-array-push
Open

huytdps13400 wants to merge 1 commit into
LegendApp:mainfrom
huytdps13400:fix/undo-array-push

Conversation

@huytdps13400

Copy link
Copy Markdown

Fixes #397

Root cause

push() with a single item takes the fast path in collectionSetter, so the change is recorded at the new index with prevAtPath: undefined. When applyChanges(..., applyPrevious = true) reverts that change, setAtPath writes undefined to the index and leaves it in the array. As a result getPrevious() returns [...prev, undefined]. undoRedo clones that value through JSON and gets [...prev, null], which is why undoing a push leaves a null behind.

Change

In src/helpers.ts:

  • applyChange: when reverting a change whose previous value is undefined and whose target is the last index of an array, the element is removed instead of set to undefined.
  • applyChanges: when applyPrevious is set, changes are reverted in reverse order. Without this, two pushes in one batch still left one undefined. Reversing also fixes a batch that changes a child and then its parent (b.c.set(1); b.set({ c: 5 })). Before this change, getPrevious() there returned { b: { c: 1 } } instead of { b: { c: 0 } }.

getPrevious, revertChanges and the crud update diff are the only callers of applyChanges(..., true). The forward (non-previous) path is unchanged.

Verification

New tests:

  • tests/history.test.ts: "Undo/Redo with array push" and "Undo/Redo with array push in a batch"
  • tests/tests.test.ts: "Array push getPrevious has the previous length" and "getPrevious with child and parent changes in a batch"

The new tests use toStrictEqual on purpose. Jest's toEqual treats [a, undefined] as equal to [a], which is why the existing "Array objects push getPrevious" test missed this bug.

  • Before the fix, all 4 new tests fail. For example, undo after a push gives [null] instead of [].
  • After the fix, they pass. The full npx jest suite passes (1085 tests), along with tsc --noEmit, lint:check and format:check. The perf.test.ts timing tests flake on my machine under load, and they flake the same way on a clean main.

One edge case: if an array really ended in an undefined element that was then set to a value, the reverted array is now one element shorter. Objects already behave this way, since a reverted key comes back as undefined.

This fix was prepared with AI assistance (Claude Code) and verified locally with the tests above.

🤖 Generated with Claude Code

Reverting a change that appended an element to an array (like push) set the
new index to undefined instead of removing it, so getPrevious() returned an
array with a trailing undefined. undoRedo cloned that into a trailing null.
Remove the appended element instead, and revert changes in reverse order so
multiple pushes in one batch and child-then-parent changes revert correctly.

Fixes LegendApp#397

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Undo issue with array.push

1 participant