Skip to content

Order index-based array removes to preserve correct application; add test and bump version#407

Merged
ltwlf merged 7 commits into
masterfrom
codex/check-open-issue
Apr 22, 2026
Merged

Order index-based array removes to preserve correct application; add test and bump version#407
ltwlf merged 7 commits into
masterfrom
codex/check-open-issue

Conversation

@ltwlf
Copy link
Copy Markdown
Owner

@ltwlf ltwlf commented Apr 22, 2026

Motivation

Description

  • Add orderArrayChildChanges which, for embeddedKey === '$index', moves remove operations to the end and sorts them in descending numeric index order so later removes don't shift earlier targets.
  • Use orderArrayChildChanges inside walkChanges before emitting child array operations.
  • Add a unit test applies multiple index-based removes correctly without identity keys (#404) in tests/jsonAtom.test.ts that asserts the emitted operations and that applying the atom yields the expected object.
  • Bump package version from 5.0.0-alpha.8 to 5.0.0-alpha.9 in package.json and package-lock.json.

Testing

  • Ran the test suite with npm test (Jest) and the suite passed, including the new index-removal test.
  • The new test also validates that applyAtom produces the expected final object when the atom is applied, and it succeeded.

Codex Task

Copilot AI review requested due to automatic review settings April 22, 2026 06:57
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 22, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@ltwlf ltwlf removed the codex label Apr 22, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d5de1f4525

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/jsonAtom.ts Outdated
Comment on lines +254 to +256
const removes = changes.filter((c) => c.type === Operation.REMOVE).sort((a, b) => Number(b.key) - Number(a.key));
const rest = changes.filter((c) => c.type !== Operation.REMOVE);
return [...rest, ...removes];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve REMOVE+ADD pairs when ordering index-based changes

For $index arrays, this reordering moves every REMOVE behind non-remove operations, but array child changes can include adjacent REMOVE+ADD at the same index for type changes (e.g. old [1] to new [[2]]). In walkChanges, those child-level pairs are not merged before ordering, so the new order becomes ADD then REMOVE, which applies as insert-then-delete and produces the wrong final array when applyAtom runs sequentially. Please keep same-index remove/add replacement pairs intact (or merge them before reordering) and only reorder independent tail removes.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes incorrect application of multiple index-based array removals when arrays are diffed by index (embeddedKey === '$index') by reordering emitted JSON Atom operations so removals are applied in descending index order, and adds a regression test for issue #404. It also bumps the package alpha version.

Changes:

  • Add orderArrayChildChanges and use it in walkChanges to reorder $index array child changes (descending removes).
  • Add a Jest unit test asserting correct operation ordering and successful applyAtom round-trip for multiple removes (#404).
  • Bump version 5.0.0-alpha.85.0.0-alpha.9 in package.json / package-lock.json.

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 2 comments.

File Description
src/jsonAtom.ts Reorders $index array child changes before emitting atom operations to avoid index-shift issues during sequential apply.
tests/jsonAtom.test.ts Adds regression test ensuring multiple index-based removes are emitted/apply correctly (#404).
package.json Version bump to 5.0.0-alpha.9.
package-lock.json Lockfile version bump to match package.json.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/jsonAtom.ts Outdated
Comment on lines +254 to +256
const removes = changes.filter((c) => c.type === Operation.REMOVE).sort((a, b) => Number(b.key) - Number(a.key));
const rest = changes.filter((c) => c.type !== Operation.REMOVE);
return [...rest, ...removes];
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For embeddedKey === '$index', this reorders all REMOVE child changes after non-REMOVE changes. That breaks REMOVE+ADD pairs emitted by jsonDiff when treatTypeChangeAsReplace is true (type changes are represented as adjacent REMOVE then ADD at the same index). After this reorder, the ADD will run before the corresponding REMOVE, so applying the atom will delete the newly added value and leave the old value (e.g., old [1] → new ['x'] results in add[0] then remove[0]). Fix by preserving/merging same-index REMOVE+ADD pairs before ordering (e.g., convert them to a single replace/update op), and only reorder “pure” index deletions.

Suggested change
const removes = changes.filter((c) => c.type === Operation.REMOVE).sort((a, b) => Number(b.key) - Number(a.key));
const rest = changes.filter((c) => c.type !== Operation.REMOVE);
return [...rest, ...removes];
type OrderedGroup =
| { kind: 'pure-remove' }
| { kind: 'preserved'; changes: IChange[] };
const groups: OrderedGroup[] = [];
const pureRemoves: IChange[] = [];
for (let i = 0; i < changes.length; i++) {
const current = changes[i];
const next = changes[i + 1];
if (
current.type === Operation.REMOVE &&
next &&
next.type === Operation.ADD &&
String(current.key) === String(next.key)
) {
groups.push({ kind: 'preserved', changes: [current, next] });
i++;
continue;
}
if (current.type === Operation.REMOVE) {
pureRemoves.push(current);
groups.push({ kind: 'pure-remove' });
continue;
}
groups.push({ kind: 'preserved', changes: [current] });
}
pureRemoves.sort((a, b) => Number(b.key) - Number(a.key));
const ordered: IChange[] = [];
let removeIndex = 0;
for (const group of groups) {
if (group.kind === 'pure-remove') {
ordered.push(pureRemoves[removeIndex++]);
} else {
ordered.push(...group.changes);
}
}
return ordered;

Copilot uses AI. Check for mistakes.
Comment thread tests/jsonAtom.test.ts
const applied = applyAtom(structuredClone(oldObj), atom);
expect(applied).toEqual(newObj);
});

Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test covers multiple removes (#404), but it doesn’t cover the new ordering behavior’s interaction with type-change pairs inside $index arrays (which diff() represents as REMOVE+ADD at the same index when treatTypeChangeAsReplace is enabled). Consider adding a regression test where an element’s type changes at a given index (e.g. [1]→['x']) to ensure diffAtom/applyAtom still produces the correct final array.

Suggested change
it('applies same-index type changes correctly in $index arrays', () => {
const oldObj = { items: [1] };
const newObj = { items: ['x'] };
const atom = diffAtom(oldObj, newObj);
expect(atom.operations.some((op) => op.path === '$.items[0]')).toBe(true);
const applied = applyAtom(structuredClone(oldObj), atom);
expect(applied).toEqual(newObj);
});

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 31178d2dab

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/jsonAtom.ts Outdated
Comment on lines +274 to +278
removes.sort((a, b) => Number(b.key) - Number(a.key));

const ordered = [...changes];
for (let i = 0; i < removeSlots.length; i++) {
ordered[removeSlots[i]] = removes[i];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep index replace pairs intact when sorting removals

Sorting every $index REMOVE by numeric key and writing them back into the original remove slots can separate a same-index REMOVE+ADD pair that represents a type change, so the emitted atom no longer applies correctly. For example, diffing {a:[1,2]} to {a:[[1],[2]]} now emits remove[1], add[0], remove[0], add[1], and applyAtom produces {a:[1,[2]]} instead of the target. This happens whenever there are at least two removes (e.g., a type-change pair plus another removal), so the reordering should avoid moving paired replacement removes across other operations.

Useful? React with 👍 / 👎.

@sonarqubecloud
Copy link
Copy Markdown

@ltwlf ltwlf merged commit abf5dac into master Apr 22, 2026
8 checks passed
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.

2 participants