Post Revisions: Upgrade diff from v4 to v8#77992
Post Revisions: Upgrade diff from v4 to v8#77992manzoorwanijk wants to merge 3 commits intotrunkfrom
diff from v4 to v8#77992Conversation
Aligns `packages/editor` and `packages/block-editor` with `packages/sync` on `diff@^8.0.3` (needed for the Syncpack alignment work in #77950 / #77954). The bump exposes two unrelated upstream changes that would regress the post-revisions UI: 1. v6+ adds a "deletions before insertions" tie-breaker, so for inputs with multiple equal-length LCSes (whitespace-block pivots, paragraph swaps), `diffArrays` selects a different match than v4 did. The downstream `pairSimilarBlocks` step then mis-pairs blocks and shows two confusing inline diffs instead of a clean modified+unchanged pair. 2. v6+ stops treating whitespace as a token in `diffWords`, coalescing adjacent word changes into one removed/added pair and losing per-word precision in inline rich-text diffs. Fix on the consumer side so existing tests pass without touching any assertion: - Replace the imported `diffArrays` in `block-diff.js` with a local v4-compatible port of `Diff.prototype.diff` (Myers, array+strict-eq), including v4's `(added, removed)` -> `(removed, added)` swap in `buildValues` so condensed sections still render in the right order. - Switch `diffWords` -> `diffWordsWithSpace` for the inline rich-text diff, the `changedAttributes` panel diff, and the `Meta` field diff in `revision-fields-diff`. `preserve-client-ids.js` and `block-compare` (uses `diffChars`) need no changes -- neither hits the affected v6+ behaviours and their tests pass unmodified under v8. 41/41 revision-related unit tests pass; full `npm run test:unit` is green. Closes #77976
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
|
Size Change: +657 B (+0.01%) Total Size: 7.95 MB 📦 View Changed
ℹ️ View Unchanged
|
|
Flaky tests detected in deddbd0. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/25426573501
|
|
This feels like something that @ellatrix should take a look at — I don't personally have much context to understand the full implications of changing / keeping the previous diff behavior. In the meantime, I'm sharing some notes from an initial round of AI-assisted reviewPR Review: #77992 — Post Revisions: Upgrade
|
Addresses review feedback on #77992 (findings 1, 5, 6, 7, 8, 9). The previous commit inlined ~150 LoC of v4's Myers algorithm to keep `diffArrays`'s LCS pivot stable across the v4 -> v8 bump. That preserved all existing test assertions but came at a real maintenance cost. The cleaner approach (the issue's original "Class 1" fix): just drop freeform/whitespace pseudo-blocks from both arrays before LCS. Without the `\n\n` blocks competing as a match anchor, v8's "deletions before insertions" tie-breaker picks the same content-block pivot v4 did for every input that was previously failing, and the inlined algorithm becomes unnecessary. Two follow-ups to make that approach work end-to-end: 1. Adjust `pairSimilarBlocks`'s placement heuristic. The original heuristic looked for *added* blocks between the removed and added positions to decide where to anchor a paired modification. With whitespace pseudo-blocks no longer in the result list, an unchanged content block between the two positions is now the only "the modified block crosses current-revision content" signal -- so the heuristic now also fires on unchanged blocks (but still ignores removed blocks, which don't exist in the current revision and so don't count as crossing). 2. Relax the `'handles two blocks that swapped positions'` assertion to the user-facing invariant (one unmarked + one removed/added pair with same content) rather than which side of the swap gets matched. For a pure swap the two LCS choices are equally valid -- both v4 and v8 produce semantically-correct output, they just disagree on which block reads as "unchanged" -- so asserting one is testing the implementation, not the behaviour. Net: -191 +56 LoC in `block-diff.js`. All 33 block-diff unit tests pass and the broader revision-related suites stay green.
Addresses review feedback on #77992 (findings 2, 3, 4, 10). - `preserve-client-ids.js` and `block-compare/index.js` now import `diffArrays` / `diffChars` from the top-level `'diff'` package instead of the deep `'diff/lib/diff/<name>'` paths. v8's `package.json` `exports` map only wildcards `./lib/*.js` (with extension); the bare-folder `./lib/` mapping requires a trailing slash. The deep paths only resolve here because the bundler/Jest resolver fills in `.js` -- a future tooling change could break them. v8 also marks the package `sideEffects: false`, so the historical tree-shaking reason for the deep imports no longer applies. The "diff doesn't tree-shake correctly" comment in `block-compare/index.js` is now stale and gets removed. - Add `### Internal` entries to `packages/editor/CHANGELOG.md` and `packages/block-editor/CHANGELOG.md` recording the major dependency bump.
Addresses review feedback on #77992 (findings 1, 5, 6, 7, 8, 9). The previous commit inlined ~150 LoC of v4's Myers algorithm to keep `diffArrays`'s LCS pivot stable across the v4 -> v8 bump. That preserved all existing test assertions but came at a real maintenance cost. The cleaner approach (the issue's original "Class 1" fix): just drop freeform/whitespace pseudo-blocks from both arrays before LCS. Without the `\n\n` blocks competing as a match anchor, v8's "deletions before insertions" tie-breaker picks the same content-block pivot v4 did for every input that was previously failing, and the inlined algorithm becomes unnecessary. Two follow-ups to make that approach work end-to-end: 1. Adjust `pairSimilarBlocks`'s placement heuristic. The original heuristic looked for *added* blocks between the removed and added positions to decide where to anchor a paired modification. With whitespace pseudo-blocks no longer in the result list, an unchanged content block between the two positions is now the only "the modified block crosses current-revision content" signal -- so the heuristic now also fires on unchanged blocks (but still ignores removed blocks, which don't exist in the current revision and so don't count as crossing). 2. Relax the `'handles two blocks that swapped positions'` assertion to the user-facing invariant (one unmarked + one removed/added pair with same content) rather than which side of the swap gets matched. For a pure swap the two LCS choices are equally valid -- both v4 and v8 produce semantically-correct output, they just disagree on which block reads as "unchanged" -- so asserting one is testing the implementation, not the behaviour. Net: -191 +56 LoC in `block-diff.js`. All 33 block-diff unit tests pass and the broader revision-related suites stay green.
Addresses review feedback on #77992 (findings 2, 3, 4, 10). - `preserve-client-ids.js` and `block-compare/index.js` now import `diffArrays` / `diffChars` from the top-level `'diff'` package instead of the deep `'diff/lib/diff/<name>'` paths. v8's `package.json` `exports` map only wildcards `./lib/*.js` (with extension); the bare-folder `./lib/` mapping requires a trailing slash. The deep paths only resolve here because the bundler/Jest resolver fills in `.js` -- a future tooling change could break them. v8 also marks the package `sideEffects: false`, so the historical tree-shaking reason for the deep imports no longer applies. The "diff doesn't tree-shake correctly" comment in `block-compare/index.js` is now stale and gets removed. - Add `### Internal` entries to `packages/editor/CHANGELOG.md` and `packages/block-editor/CHANGELOG.md` recording the major dependency bump.
37057b0 to
deddbd0
Compare
What?
Closes #77976
Upgrades the
difflibrary from^4.0.2to^8.0.3inpackages/editorandpackages/block-editor, and updates the post-revisions consumer code so the four-major bump is invisible to users — the in-editor revisions UI keeps the same block pairing and inline word-level diff output it produced under v4.Why?
The
difflibrary was declared at^4.0.2ineditor/block-editorand^8.0.3insync, blocking the cross-workspace Syncpack alignment work (#77950, #77954). Bumpingeditorto^8.0.3is a four-major jump that, on its own, breaks four unit tests inblock-diff.jsand surfaces real UX regressions in the post-revisions feature. The failures fall into two unrelated upstream behaviour changes:prev=[paragraph, whitespace, paragraph]versuscurr=[paragraph_modified, whitespace, paragraph]: v8 picks the whitespace pseudo-block as the LCS anchor, sopairSimilarBlocksthen sees two removed and two added paragraphs and similarity-matches dissimilar pairs — producing two confusing inline diffs instead of a clean modified+unchanged pair.diffWordssemantics change (v6+). v6 stopped treating whitespace as a token, so adjacent word changes coalesce into one removed/added pair. Inline rich-text diffs lose precision:Visit <a><del>our</del><ins>the</ins> <del>site</del><ins>website</ins></a> today(per-word) becomesVisit <a><del>our site</del><ins>the website</ins></a> today(one coarser diff).Both are real user-visible regressions, not snapshot drift.
How?
1. Bump the dependency
packages/editor/package.jsonandpackages/block-editor/package.json:diff→^8.0.3. (packages/syncwas already on v8.)2. Filter whitespace pseudo-blocks before LCS
packages/editor/src/components/post-revisions-preview/block-diff.js—diffRawBlocksnow strips freeform/whitespace pseudo-blocks (the\n\nbetween block markers) from both arrays before passing them todiffArrays. With those out of the picture, v8's tie-breaker selects the same content-block LCS anchor v4 did for every previously-failing case. Whitespace pseudo-blocks aren't rendered (parseRawBlockreturns undefined for them), so dropping them before the diff has no user-visible effect.pairSimilarBlocks's placement heuristic was extended to match: it previously decided where to anchor a paired modification by checking whether any added block sat between the removed and added positions. Now that whitespace pseudo-blocks are no longer in the result list, an unchanged content block between the two positions is the only "the modified block crosses current-revision content" signal — so the heuristic also fires on unchanged blocks (but still ignores removed blocks, which don't exist in the current revision and so don't count as crossing).3. Switch rich-text and field diffs to
diffWordsWithSpaceblock-diff.js:applyRichTextDiffandapplyDiffToBlock(sidebarchangedAttributes) now calldiffWordsWithSpacefrom the top-leveldiffimport. v8'sdiffWordsWithSpacekeeps whitespace as its own token, matching v4'sdiffWordssemantics.revision-fields-diff/index.js: meta field diffs likewise usediffWordsWithSpace.4. Migrate remaining
diffimports to top-levelpreserve-client-ids.jsandblock-compare/index.jsused deep paths like'diff/lib/diff/array'. v8'spackage.jsonexportsmap only wildcards./lib/*.js(with extension); the bare-folder./lib/mapping requires a trailing slash. The deep paths only resolve here because the bundler/Jest resolver fills in.js— a future tooling change could break them. v8 also marks the packagesideEffects: false, so the historical tree-shaking reason for the deep imports no longer applies. The "diff doesn't tree-shake correctly" comment inblock-compare/index.jswas true for v4 but is now stale and gets removed.5. Relax one test assertion
The
'handles two blocks that swapped positions'test was asserting which block stayed unmarked vs. which became removed/added. For a pure swap, both LCS choices are equally valid — both v4 and v8 produce semantically-correct output, they just disagree on which block reads as "unchanged." The test now asserts the user-facing invariant (one unmarked + one removed/added pair with same content) instead of the implementation choice.6. CHANGELOG entries
Added
### Internalentries topackages/editor/CHANGELOG.mdandpackages/block-editor/CHANGELOG.mdrecording the major dependency bump.Testing Instructions
Automated:
npm run test:unit -- -- packages/editor/src/components/post-revisions-preview/ \ packages/editor/src/components/revision-fields-diff/ \ packages/editor/src/components/revision-diff-panel/ \ packages/block-editor/src/components/block-compare/Expected: 42/42 pass. Full
npm run test:unitis also green (32538/32545, 7 unrelated skips).Manual (golden path for the revisions UI):
wp-env(npm run wp-env start) and create a post with several paragraphs.our site→the website).<strong>/<em>formatting to part of a paragraph.trunkbefore the bump:<del>…</del><ins>…</ins>diff, not two confusing pair-ups.our → theandsite → websiteas two separate diffs), not coalesced into one.revision-diff-format-*spans on just the affected range.Testing Instructions for Keyboard
No UI changes; existing keyboard navigation through the revisions panel is unaffected.
Screenshots or screencast
<del>First</del><ins>Second</ins> block content <ins>modified</ins>+ a second confusing inline diff for the unchanged siblingSecond block content<ins> modified</ins>; the unchanged sibling stays unmarkedVisit <a><del>our site</del><ins>the website</ins></a> today(one coarse diff)Visit <a><del>our</del><ins>the</ins> <del>site</del><ins>website</ins></a> today(per-word)Use of AI Tools
This PR was authored with the assistance of Claude Code (Opus 4.7). All edits, the test results, and the manual testing steps were reviewed and verified by me.