Skip to content

Suggest mode 1/9: editor intent (edit/suggest/view) and experiment gate - #80427

Open
adamsilverstein wants to merge 58 commits into
trunkfrom
suggest/intent
Open

adamsilverstein wants to merge 58 commits into
trunkfrom
suggest/intent

Conversation

@adamsilverstein

@adamsilverstein adamsilverstein commented Jul 17, 2026 •

Copy link
Copy Markdown
Member

Part of #73411

What's in this PR

Adds the session-scoped editor intent state (private setEditorIntent
action / getEditorIntent selector), the Edit / Suggest / View switcher in
the editor options menu, Google Docs convention keyboard shortcuts, and a
read-only view intent via isPreviewMode. The Suggestion Mode experiment
flag and the gate helpers (isSuggestionModeEnabled / useCanSuggest) land
here so every later layer gates on the same predicate. No suggestion
capture or storage yet.

The view intent's read-only contract is enforced here too, in the layer
that promises it: preview mode is added to the memoized insertion
selectors' dependants so flipping it at runtime invalidates their cached
answers, the writing flow's cross-block input handling bails out while
the canvas is a preview, and the Block Inserter toggle is disabled (an
open inserter is closed on the way in). This was originally #81661 on top
of the stack and moved down here so it can be reviewed next to the intent
it belongs to.

Screenshot

The Edit / Suggest / View switcher in the editor Options menu, with the Google Docs style shortcuts alongside each mode:

Mode group in the editor Options menu showing Editing, Suggesting and Viewing

image

Testing

This is one layer of the stack. To exercise the whole feature, #78994 bundles every layer into one branch and builds it in Playground:

Test in WordPress Playground

👉 https://playground.wordpress.net/gutenberg.html?pr=78994

Enable Gutenberg > Experiments > Collaboration > Suggestion Mode, then follow the walkthrough in #73411, which also explains how to review the stack layer by layer.


Suggest mode stack

This rebuilds the manually-stacked Suggest mode work (#73411) as a GitHub Stack of 9 small, independently reviewable PRs, each building on the one below it:

  1. #80427 - editor intent (edit/suggest/view) + experiment gate
  2. #80428 - suggestion storage, REST controller, provider
  3. #80429 - block-level capture (attribute + structural)
  4. #80430 - inline marker primitive
  5. #80431 - inline suggestion operations
  6. #80432 - review UI (Apply/Reject sidebar + summary)
  7. #80433 - inline live wiring
  8. #82047 - architecture documentation
  9. #82048 - end-to-end test suite

Each follow up fix now sits in the layer that owns the code it changes, rather than piling onto the top of the stack. The whole feature can be exercised end-to-end via the combined testing branch #78994 (Playground). Behind the "Suggestion Mode" experiment (Gutenberg > Experiments).


AI Use

Code and description both written with 🤖 Claude Code. I will review and test.

Adds the session-scoped editor intent state (private setEditorIntent
action / getEditorIntent selector), the Edit / Suggest / View switcher in
the editor options menu, Google Docs convention keyboard shortcuts, and a
read-only view intent via isPreviewMode. The Suggestion Mode experiment
flag and the gate helpers (isSuggestionModeEnabled / useCanSuggest) land
here so every later layer gates on the same predicate. No suggestion
capture or storage yet.
@github-actions

github-actions Bot commented Jul 17, 2026 •

Copy link
Copy Markdown

Size Change: +1.92 kB (+0.02%)

Total Size: 7.91 MB

📦 View Changed
Filename Size Change
build/scripts/block-editor/index.min.js 478 kB +31 B (+0.01%)
build/scripts/editor/index.min.js 582 kB +1.89 kB (+0.33%)

compressed-size-action

@adamsilverstein adamsilverstein added No Core Sync Required Indicates that any changes do not need to be synced to WordPress Core [Type] Feature New feature to highlight in changelogs. labels Jul 17, 2026
@github-actions

github-actions Bot commented Jul 17, 2026 •

Copy link
Copy Markdown

Flaky tests detected in 5df3684.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/33116177758
📝 Reported tests:

refuses the drop and uploads nothing in /test/e2e/specs/editor/various/single-file-placeholder-drop.spec.js, passed after 1 failed attempt.
Error: expect(received).toHaveLength(expected)

Expected length: 0
Received length: 2
Received array:  [{"_links": {"about": [{"href": "http://localhost:8889/wp-json/wp/v2/types/attachment"}], "author": [{"embeddable": true, "href": "http://localhost:8889/wp-json/wp/v2/users/1"}], "collection": [{"href": "http://localhost:8889/wp-json/wp/v2/media"}], "curies": [{"href": "https://api.w.org/{rel}", "name": "wp", "templated": true}], "replies": [{"embeddable": true, "href": "http://localhost:8889/wp-json/wp/v2/comments?post=106"}], "self": [{"href": "http://localhost:8889/wp-json/wp/v2/media/106", "targetHints": {"allow": ["GET", "POST", "PUT", "PATCH", "DELETE"]}}], "wp:attached-to": [{"embeddable": true, "href": "http://localhost:8889/wp-json/wp/v2/posts/104", "id": 104, "post_type": "post"}]}, "alt_text": "", "author": 1, "caption": {"rendered": ""}, "class_list": ["post-106", "attachment", "type-attachment", "status-inherit", "hentry", "entry"], "comment_status": "open", "date": "2026-08-27T21:13:31", "date_gmt": "2026-08-27T21:13:31", "description": {"rendered": "<p class=\"attachment\"><a href='http://localhost:8889/wp-content/uploads/2026/08/10x10_e2e_test_image_z9T8jK-1.png'><img loading=\"lazy\" decoding=\"async\" width=\"10\" height=\"10\" src=\"http://localhost:8889/wp-content/uploads/2026/08/10x10_e2e_test_image_z9T8jK-1.png\" class=\"attachment-medium size-medium\" alt=\"\" style=\"width:100%;height:100%;max-width:10px;\" /></a></p>
"}, "featured_media": 0, "filename": "10x10_e2e_test_image_z9T8jK-1.png", "filesize": 80, "guid": {"rendered": "http://localhost:8889/wp-content/uploads/2026/08/10x10_e2e_test_image_z9T8jK-1.png"}, "id": 106, "link": "http://localhost:8889/?attachment_id=106", "media_details": {"file": "2026/08/10x10_e2e_test_image_z9T8jK-1.png", "filesize": 80, "height": 10, "image_meta": {"alt": "", "aperture": "0", "camera": "", "caption": "", "copyright": "", "created_timestamp": "0", "credit": "", "focal_length": "0", "iso": "0", "keywords": [], "orientation": "0", "shutter_speed": "0", "title": ""}, "sizes": {}, "width": 10}, "media_type": "image", "meta": [], "mime_type": "image/png", "modified": "2026-08-27T21:13:31", "modified_gmt": "2026-08-27T21:13:31", "ping_status": "closed", "post": 104, "slug": "10x10_e2e_test_image_z9t8jk-2", "source_url": "http://localhost:8889/wp-content/uploads/2026/08/10x10_e2e_test_image_z9T8jK-1.png", "status": "inherit", "template": "", "title": {"rendered": "10x10_e2e_test_image_z9T8jK"}, "type": "attachment"}, {"_links": {"about": [{"href": "http://localhost:8889/wp-json/wp/v2/types/attachment"}], "author": [{"embeddable": true, "href": "http://localhost:8889/wp-json/wp/v2/users/1"}], "collection": [{"href": "http://localhost:8889/wp-json/wp/v2/media"}], "curies": [{"href": "https://api.w.org/{rel}", "name": "wp", "templated": true}], "replies": [{"embeddable": true, "href": "http://localhost:8889/wp-json/wp/v2/comments?post=105"}], "self": [{"href": "http://localhost:8889/wp-json/wp/v2/media/105", "targetHints": {"allow": ["GET", "POST", "PUT", "PATCH", "DELETE"]}}], "wp:attached-to": [{"embeddable": true, "href": "http://localhost:8889/wp-json/wp/v2/posts/104", "id": 104, "post_type": "post"}]}, "alt_text": "", "author": 1, "caption": {"rendered": ""}, "class_list": ["post-105", "attachment", "type-attachment", "status-inherit", "hentry", "entry"], "comment_status": "open", "date": "2026-08-27T21:13:31", "date_gmt": "2026-08-27T21:13:31", "description": {"rendered": "<p class=\"attachment\"><a href='http://localhost:8889/wp-content/uploads/2026/08/10x10_e2e_test_image_z9T8jK.png'><img loading=\"lazy\" decoding=\"async\" width=\"10\" height=\"10\" src=\"http://localhost:8889/wp-content/uploads/2026/08/10x10_e2e_test_image_z9T8jK.png\" class=\"attachment-medium size-medium\" alt=\"\" style=\"width:100%;height:100%;max-width:10px;\" /></a></p>
"}, "featured_media": 0, "filename": "10x10_e2e_test_image_z9T8jK.png", "filesize": 80, "guid": {"rendered": "http://localhost:8889/wp-content/uploads/2026/08/10x10_e2e_test_image_z9T8jK.png"}, "id": 105, "link": "http://localhost:8889/?attachment_id=105", "media_details": {"file": "2026/08/10x10_e2e_test_image_z9T8jK.png", "filesize": 80, "height": 10, "image_meta": {"alt": "", "aperture": "0", "camera": "", "caption": "", "copyright": "", "created_timestamp": "0", "credit": "", "focal_length": "0", "iso": "0", "keywords": [], "orientation": "0", "shutter_speed": "0", "title": ""}, "sizes": {}, "width": 10}, "media_type": "image", "meta": [], "mime_type": "image/png", "modified": "2026-08-27T21:13:31", "modified_gmt": "2026-08-27T21:13:31", "ping_status": "closed", "post": 104, "slug": "10x10_e2e_test_image_z9t8jk", "source_url": "http://localhost:8889/wp-content/uploads/2026/08/10x10_e2e_test_image_z9T8jK.png", "status": "inherit", "template": "", "title": {"rendered": "10x10_e2e_test_image_z9T8jK"}, "type": "attachment"}]
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/single-file-placeholder-drop.spec.js:71:45

# Conflicts:
#	packages/editor/src/components/global-keyboard-shortcuts/index.js
#	packages/editor/src/store/private-selectors.js
@adamsilverstein
adamsilverstein marked this pull request as ready for review August 5, 2026 22:24
# Conflicts:
#	packages/editor/CHANGELOG.md
#	packages/editor/src/store/private-actions.js
#	packages/editor/src/store/private-selectors.js
…spec

The new @wordpress/dependency-group rule runs in 'never' mode, so the
header comment left in this spec still failed lint after the last pass.
The guard skipped the mode snackbar and a11y announcement when the
previous intent was undefined, but the reducer defaults the slice to
`edit` and the selector falls back to `EDITOR_INTENT_EDIT`, so that value
never occurs. The comment promised boot silence for any initial intent
while only `edit` was ever silent, which is a trap for the next reader.

Keep the equality check, which is what actually suppresses the notice, and
say so.
New React files must use .tsx and new source should be TypeScript, so the
intent switcher, the Suggest mode gate and constants, and their tests move
to .ts/.tsx with real annotations instead of JSDoc types.

MenuItemsChoice's onHover is typed as required even though the component
defaults it to a noop and its own usage example omits it; typing the first
TypeScript consumer surfaced that, so mark the prop optional.
@aduth

aduth commented Sep 18, 2026

Copy link
Copy Markdown
Member

Hey 👋 I wanted to give you a heads-up since this pull request is affected by recent validation changes for changelog files.

#83043 adds additional validation for changelog files. You'll note that this pull request is currently failing a "Required changes from trunk" check.

What you'll need to do: You will need to either rebase or merge the latest code from trunk. In addition, a cursory review of open pull requests identified this pull request as potentially failing under the new validation checks. You will want to double-check that any changes to CHANGELOG.md files follow the Maintaining Changelogs guidance, which has been improved as part of these recent changes.

adamsilverstein and others added 2 commits September 18, 2026 08:39
# Conflicts:
#	packages/editor/tsconfig.json
The 2026-09-10 package releases cut version headings above these entries, so
the new changelog structure check rejected them as citing this PR from a
published section.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vHxnB4ab5Ecriq8zcwBJd
adamsilverstein and others added 3 commits September 28, 2026 14:50
Resolve the block-editor CHANGELOG conflict by keeping trunk's new
entries and moving the branch's preview-mode entry under Unreleased,
since trunk published the section it was in as 18.0.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GqepyjmFSryBt88j6f6ubY
Trunk published editor 15.1.0, stranding four of this branch's Bug
Fixes entries under the released heading, which the changelog
structure check rejects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GqepyjmFSryBt88j6f6ubY
Viewing is offered as a read-only preview, but only the block canvas
honoured it: sidebar controls such as the excerpt still reached
editPost() and changed the post. Refuse post-level field changes there
with a snackbar, leaving block content to the canvas's own preview-mode
guards so block sync and savePost keep working.

Claude-Session: https://claude.ai/code/session_01EmSXGAGtuLnk3UEe5kXPSU
@adamsilverstein

Copy link
Copy Markdown
Member Author

Claude took the latest review pass on this layer, here is the fix:

Viewing now also covers post settings. editPost() refuses any post-level field change (excerpt, author, slug, featured image and so on) while the intent is view, and shows a "can't be changed while viewing" snackbar. blocks, content and selection still pass, because the preview-mode canvas already guards those and block sync plus savePost rely on them. Two unit tests cover it: an excerpt edit in Viewing leaves the post clean, and content edits still go through. Fixed in eaaffa0.

Core-data loads the post type entity configs in the background, and in
some shuffled orders that request reached a fetch handler another test
had installed, which rejected it as an unknown path. Vitest reports
those as unhandled errors and fails the shard even though every test
passes. Route the handlers through one fallback that answers the post
type index and rejects anything else.

Claude-Session: https://claude.ai/code/session_01EmSXGAGtuLnk3UEe5kXPSU
@adamsilverstein
adamsilverstein requested a review from ciampo October 1, 2026 22:50
@jasmussen

Copy link
Copy Markdown
Contributor

Testing just this one in isolation, I get the same error noted here. Let me know if I'm doing something wrong.

adamsilverstein and others added 2 commits October 7, 2026 14:55
Trunk's Menu now throws in development builds when a Menu.Group sits
inside a Menu.RadioGroup (#83792), which crashed the editor as soon as
the Options menu opened. Put the Mode label and radio items directly in
the RadioGroup, the same shape trunk gave ModeSwitcher, and cover it
with a jsdom test since production e2e builds never hit the guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BTRGFetxgiXPJC9a91DMTL
# Conflicts:
#	packages/editor/CHANGELOG.md
@adamsilverstein

Copy link
Copy Markdown
Member Author

Testing just this one in isolation, I get the same error noted here. Let me know if I'm doing something wrong.

this should be fixed now @jasmussen - thanks for letting me know it was broken!

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

No Core Sync Required Indicates that any changes do not need to be synced to WordPress Core [Package] Block editor /packages/block-editor [Package] Editor /packages/editor [Type] Feature New feature to highlight in changelogs.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants