Phase 3: Add suggestion diff preview, Apply, and Reject#77405
Phase 3: Add suggestion diff preview, Apply, and Reject#77405adamsilverstein wants to merge 15 commits intosuggest-mode-phase-2from
Conversation
|
Size Change: +2.06 kB (+0.03%) Total Size: 7.91 MB 📦 View Changed
ℹ️ View Unchanged
|
edf102e to
fa199f5
Compare
7e2fe59 to
9debb13
Compare
fa199f5 to
36fc92e
Compare
f63fe8c to
66f2542
Compare
36fc92e to
8043af8
Compare
66f2542 to
5f968da
Compare
b1af20a to
6ae1fa6
Compare
5f968da to
8e41010
Compare
6ae1fa6 to
b09c96d
Compare
8e41010 to
31a0ce8
Compare
|
Flaky tests detected in a143a3c. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/25348424933
|
|
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. |
Phase 3 of the Suggest mode effort. Implements the Apply/Reject lifecycle and inline diff preview for suggestion notes. New in provider.js: - applySuggestion: applies operations to block attributes via updateBlockAttributes, marks note resolved with status 'applied'. Emits a warning snackbar when baseRevision differs from current post modified date (stale suggestion). - rejectSuggestion: marks note resolved with status 'rejected' without changing content. - applyOperations: pure function running attribute-set ops against current block attributes. - parseSuggestionPayload: safe JSON parser for _wp_suggestion meta. New SuggestionDiff component: - Word-level LCS-based diff for text attributes (insertions green underline, deletions red strikethrough). - Attribute label for non-text changes. UI integration in collab-sidebar/comments.js: - SuggestionActions component rendered inside CommentBoard when thread has _wp_suggestion meta. Shows diff + Apply/Reject buttons for pending suggestions, or Applied/Rejected label for resolved. Tests: applyOperations, parseSuggestionPayload, wordDiff (25 total). Refs #73411
- applySuggestion's catch path now restores exactly the keys the apply touched, with their original values (or undefined when the key was added by this apply). updateBlockAttributes is a partial merge — passing currentAttributes alone left newly-added keys stuck on the block at their applied value when the server rejected the status update. - MAX_DIFF_LENGTH dropped from 5000 → 2000. Two 2KB strings already produce a 4M-cell DP table; 5000² is too costly for an interactive sidebar render. - Add a wordDiff segmentation test covering the in-place replacement case.
31a0ce8 to
00f4458
Compare
Trunk refactored the collab-sidebar (#77614, etc.), splitting comments.js into note.js / note-thread.js / note-card.js. Re-port the phase 3 hooks - SuggestionActions in the body and SuggestionActionButtons in the header, plus the Resolve-button suppression for suggestion threads - into the new note.js.
The merge of trunk's Notes refactor (#77614) into phase-3 ported a SuggestionActionButtons reference into the new note.js, but that named export does not exist on phase-3 — only `SuggestionActions` (default) ships in this phase. Header-slot icon buttons are a downstream refinement and will arrive in a later phase. Removes the unused named import and the header-slot usage so the package build no longer fails with 'No matching export'.
Trunk's new `@wordpress/use-recommended-components` lint rule flags `__experimentalHStack`/`__experimentalVStack` and (via `@wordpress/use-import-as`) requires the `__experimentalText` import to use the `WCText` alias. Switch HStack/VStack/VisuallyHidden to the `@wordpress/ui` Stack and VisuallyHidden, rename `Text` → `WCText`, and add the two suggestion files to the lint suppressions baseline alongside existing entries (e.g. pagination/index.js) so the deferred `Text → @wordpress/ui` migration does not block this PR.
…estion In Suggest mode, `applySuggestion` calls `updateBlockAttributes` to write the proposed attribute values onto the live block. The store interceptor was reverting that dispatch the same way it reverts a user edit, so the comment status flipped to "applied" but the block content never actually changed. Reject worked because it only saves the comment status. Add a ref-based bypass set on the overlay context that the apply flow can opt into. The interceptor checks the set before diffing, adopts the current attributes as its new snapshot baseline, and skips the revert. Clearing the overlay entry resets the per-block suggestion tracking so a subsequent edit re-baselines from the post-apply attributes. Outside Suggest mode the interceptor isn't running and the calls are no-ops.
…tData diffs are detected Two equality helpers — `isAttributeEqual` in provider.js and `shallowAttributeEquals` in store-interceptor.js — short-circuited on `RichTextData` (and similar wrapper) values. RichTextData holds its content in private class fields, so `Object.keys()` returns an empty array and the structural compare was vacuously equal regardless of the underlying text or formatting. The string/object branch also returned false outright when one side was a plain string (the JSON-deserialized 'before' from a stored suggestion) and the other a RichTextData instance from the live store, so content-attribute diffs registered as "changed" even when nothing had moved. Fix: when both sides have zero enumerable keys, or when only one side is an object, fall back to `String(a) === String(b)`. This preserves equality across the wrapper/string boundary and across RichTextData instances, so content suggestions are detected and applied correctly.
…terceptor In collaborative editing, an applied suggestion lands on the live block on the accepter's tab and propagates through the sync layer to the suggester. Without a complement to the accepter-side interceptor bypass, the suggester's store interceptor sees the incoming attributes as a drift from its snapshot, reverts them back to baseline, and that revert round-trips back through sync — undoing the apply on the accepter's screen a moment after they clicked. Detect the case explicitly: when every changed attribute on a block linked to one or more notes matches the after value declared in those notes' suggestion payloads, adopt the change as the new baseline rather than reverting. The check is purely local (no comment-status watcher, no race with the apply dispatch) and only fires when the live store exactly matches a known suggestion payload. Tighten shallowAttributeEquals to compare across the wrapper/primitive boundary so a JSON-decoded after string and a live RichTextData wrapper read as equal — without it, content suggestions never match and the new branch never fires. The suppression entry mirrors the pattern from the broader react-hooks sweep on add-suggestion-mode and covers four pre-existing ref-write violations in this file.
# Conflicts: # tools/eslint/suppressions.json
…gestionDiff Document the public action arguments and surface the suggestion-diff component's input shape, edge cases (MAX_DIFF_LENGTH fallback, wrapper values), and rendering semantics so reviewers don't have to trace the helper functions to understand the contract.
Overview
This is one of 5 stacked PRs implementing Suggest mode for the WordPress editor — a Google Docs–style workflow where reviewers can propose changes that the post author can Accept or Reject. Tracked in #73411, with design direction from #73410 and jasmussen's mockups.
This PR (Phase 3)
Adds the persistence layer and review UI:
_wp_suggestioncomment meta — registered onnotecomments as a JSON payload (schemaVersion,blockName,baseRevision,operations). Payload size capped at 64 KB via asanitize_callback._wp_suggestion_statuscomment meta — lifecycle flag (pending/applied/rejected) with a REST-exposed enum.SuggestionsProviderinterface (useSuggestionsProvider()):createSuggestion,updateSuggestion,deleteSuggestion,applySuggestion,rejectSuggestion. Implementations can swap in without touching the UI — the Yjs-backed provider drops in with the same methods.applyOperations— pure function that applies an ordered list ofattribute-setoperations to a block's current attributes.SuggestionDiff— word-level LCS diff rendered in the notes sidebar with green underlined insertions and red strikethrough deletions. Falls back to a compact before→after label for non-text attributes.What's NOT in this PR
Phase 3 test plan
createSuggestion→ confirm aPOST /wp/v2/commentswithtype=noteand a_wp_suggestionmeta JSON body_wp_suggestion_statusis set toapplied_wp_suggestion_statusis set torejectednpm run test:unit -- packages/editor/src/components/suggestion-mode/test/provider.jsnpm run test:unit -- packages/editor/src/components/suggestion-mode/test/suggestion-diff.js🗺️ PR Stack Navigation
_wp_suggestionmeta, provider, sidebar actions📋 Tracking issue: #73411