Phase 5: REST permissions + PHP tests for suggestions#77407
Draft
adamsilverstein wants to merge 11 commits intosuggest-mode-phase-4from
Draft
Phase 5: REST permissions + PHP tests for suggestions#77407adamsilverstein wants to merge 11 commits intosuggest-mode-phase-4from
adamsilverstein wants to merge 11 commits intosuggest-mode-phase-4from
Conversation
This was referenced Apr 16, 2026
|
Size Change: +2.24 kB (+0.03%) Total Size: 7.75 MB 📦 View Changed
ℹ️ View Unchanged
|
f2ea085 to
476c8eb
Compare
37bd62c to
8111615
Compare
ffd4a57 to
218f599
Compare
7368e91 to
e6e6960
Compare
218f599 to
7727f48
Compare
e6e6960 to
2a1c564
Compare
2a1c564 to
1a1f57b
Compare
324c53e to
b06f417
Compare
1a1f57b to
306e967
Compare
Override update_item_permissions_check in the Gutenberg REST comment controller so that users with edit_post on the parent can update note comments. This closes the permission gap where a post editor who did not author a suggestion could not apply or reject it (core only checks edit_comment, which requires comment authorship or moderate_comments). Also updates the _wp_suggestion and _wp_suggestion_status meta auth_callbacks to use edit_post on the parent post for note comments, matching the controller-level check. Removes the TODO added in the previous commit now that the fix is in place, and updates the architecture doc accordingly. Refs #73411
Four new tests on the REST comments controller: - test_create_note_with_suggestion_meta: round-trip _wp_suggestion JSON payload through create + read. - test_editor_can_update_note_on_own_post: verifies an editor who didn't author the note can update it (the new edit_post check). - test_subscriber_cannot_update_note: verifies a subscriber is rejected with rest_cannot_edit. - test_suggestion_status_rejects_invalid_value: confirms the enum validation on _wp_suggestion_status blocks non-enum values. Refs #73411
Replace the manual Submit/Discard toolbar with debounced auto-save: pending
overlay edits in Suggest mode are persisted as note comments after ~1.5s of
idle time, and subsequent edits update the same note instead of spawning a
new one. Track the linked comment id and a fingerprint of the last synced
operations on the overlay entry so the debounced sync can tell when a
resave is actually needed.
Announce intent transitions with a snackbar ('You're suggesting' /
'You're editing' / 'You're viewing') and an a11y live region update, so
switching modes is a visible action rather than a silent state flip.
Add a green bracket/outline treatment to blocks that have a pending suggestion overlay via a new editor.BlockListBlock filter, so an edited block is discoverable even when it isn't the active selection. Replace the verbose before→after diff in the notes sidebar with a Docs-style 'Add:', 'Delete:', 'Format:' summary, and swap the Apply / Reject text buttons for check / close icon buttons. Update the e2e spec to cover the mode snackbar and the auto-save path now that there is no manual submit.
Toggling bold/italic/etc. in Suggest mode was leaking raw markup into the note summary — e.g. 'Add: <strong>neque</strong>' — because the word diff ran over the serialized HTML content. Detect a pure inline-format change by comparing visible text (with tags stripped) and, when it matches, emit a 'Formatting:' line listing the added or removed format names (bold, italic, underline, strikethrough, code, highlight, link, sub/superscript, keyboard). Mixed edits (text change alongside formatting) still fall through to the Add/Delete path so inserted text isn't hidden.
Split the accept/reject affordance out of the comment body and slot it into the note header, to the right of the author info, so the decision controls are in view at a glance instead of stacked below a long summary. The split is done by lifting the apply/reject wiring into a shared useSuggestionDecision hook that both the header buttons and the body (summary + resolution state + staleness dialog) consume, so the behavior stays in one place. Balance the checkmark and close icons with a common width/height rule so the thin check glyph no longer looks visually smaller than the close glyph sitting next to it.
The 'post has changed since this suggestion was made' dialog was firing on nearly every accept because every auto-saved suggestion bumps the post's modified_gmt, so a post-level baseRevision compare treated practically all suggestions as stale. Switch the check to per-attribute: hasAttributeConflict() compares each op's captured 'before' to the block's current value and only flags a divergence on the attributes the suggestion actually targets. Unrelated activity on the post (or on other blocks) no longer triggers the prompt, so the common case is a silent best-guess apply. Yjs attribution will subsume this in a future iteration.
Hide the generic Resolve button on note threads that carry a suggestion payload — the Accept affordance already resolves the note, and showing a second checkmark in a circle immediately next to it is confusing. Fall back to scanning the live block tree when applySuggestion is called without a clientId. thread.blockClientId is derived from metadata.noteId on a block in the current editor, which is only present once the author has saved the post after the suggestion was auto-created. If the Accept fires before that save lands, the click was silently no-oping on an undefined clientId; look the block up by noteId instead and surface a clear notice if it can't be found. Bump the accept/reject icons to a 32px box with a 1px stroke on the check path so the thin checkmark glyph visually matches the denser close glyph sitting next to it.
Move editorIntent out of the preferences store and into the editor store's own reducer. Reloading the editor now always starts in 'edit' intent regardless of the previous session — persisting Suggest or View across reloads was surprising and almost never the user's intent. getEditorIntent reads from state.editorIntent (default 'edit'), and setEditorIntent dispatches SET_EDITOR_INTENT to the editor store instead of writing to preferences. The snackbar/a11y announcement on mode change is unchanged. Update use-block-editor-settings to derive isViewIntent from the editor store's selector, and adjust the HOC test helper to register the notices store so setEditorIntent's snackbar path doesn't throw in tests that dispatch a non-edit intent.
3bd5d4c to
ad83eb1
Compare
Drop an unused $response capture flagged by PHPCS and switch the meta round-trip and update assertions to read from get_comment_meta() rather than the REST response body. Core does not always surface custom comment-type meta on the 'note' type in the REST response, but the persistence path (meta write on create/update) is exactly what the tests need to exercise, so checking stored meta is both more accurate and insulates the tests from unrelated REST-shape changes.
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.
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.
Architecture & Yjs readiness
The eventual long-term storage primitive for suggestions is the Yjs
AttributionManagerarriving in Yjs v14 (PR #77005), which attributes CRDT document changes to specific clients — exactly what suggested edits need. However, Yjs v14 is pre-release with an unstable API, and even with Yjs, server-side persistence is still needed for non-RTC users.Rather than block on Yjs, this series builds the full feature now with a comment-meta backend behind a swappable
SuggestionsProviderinterface shaped to match Yjs attribution semantics:attribute-set) — not HTML diffs — so they map directly to whatAttributionManagerwould emit.createSuggestion,updateSuggestion,deleteSuggestion,applySuggestion,rejectSuggestion. Today's implementation reads/writes comment meta; a futureyjs-provider.jscan drop in with the same methods.When #77005 stabilizes, the migration is: implement
yjs-provider.jsagainst the same interface, useAttributionManagerfor live-session attribution, and keep comment-meta as the fallback for users without RTC.The 5 phases
editorIntentpreference (edit/suggest/view), a mode selector in the Options kebab, read-only gating for View intent, keyboard shortcuts, and a "You're suggesting/editing/viewing" snackbar on mode change.editor.BlockEditfilter divertssetAttributesinto a React context so the user sees their edit live but the block-editor store stays at the baseline. The HOC is split so non-Suggest intents only run a singleuseSelect._wp_suggestioncomment meta, theSuggestionsProviderinterface (create/update/delete/apply/reject), and the accept/reject icon buttons in the notes sidebar.This PR (Phase 5)
Hardens the lifecycle so suggestions feel automatic and editors can review them:
SuggestionAutoSavewatches the overlay and, after ~1.5 s of idle time on a given block, persists the current operations as a note comment. Subsequent edits update the same note (viaupdateSuggestion) rather than creating new ones; the overlay entry tracks the resultingcommentIdand a fingerprint of the last synced operations. Removing the pending edits (back to baseline) trashes the note. There is no manual Submit button.editor.BlockListBlockfilter tags any block with a pending overlay with anis-suggestion-pendingclass, which paints a green outline + corner brackets so edited blocks are discoverable even when another block is selected.applySuggestionnow falls back to scanning the live block tree for a block whosemetadata.noteIdmatches the comment id, covering the case where the Suggest author's post-save hasn't landed yet andthread.blockClientIdis undefined.class-gutenberg-rest-comment-controller-6-9.php) — overridesupdate_item_permissions_checkso users withedit_poston the parent can updatenotecomments, but only for suggestion-lifecycle fields (statuslimited toapproved/hold, plusmeta._wp_suggestion_status). Any other field in the update body falls back to core'sedit_commentcheck, preventing post editors from rewriting another user's note content.auth_callbacks —_wp_suggestionand_wp_suggestion_statusboth authorize onedit_postagainst the parent so editors can toggle status without needingedit_commenton someone else's note.WP_Test_REST_Comments_Controller_Gutenbergcovers meta round-trip, editor-can-apply, contributor-cannot-apply, restricted-field pass-through, and payload-size truncation.What's NOT in this PR
Phase 5 test plan
Full reviewer → author flow
POST /wp/v2/commentslands in DevTools Network.PUTs updating the same comment id.Automated tests
Phase 5 checklist
vendor/bin/phpcsclean on the new PHP files🗺️ PR Stack Navigation
_wp_suggestionmeta, provider, sidebar actions📋 Tracking issue: #73411