1101 content planner create the content suggestions blocks#23117
Conversation
Coverage Report for CI Build 2Coverage increased (+0.6%) to 53.397%Details
Uncovered ChangesCoverage RegressionsNo coverage regressions found. Coverage Stats💛 - Coveralls |
There was a problem hiding this comment.
Pull request overview
This PR lays the groundwork for the AI Content Planner “content suggestions” flow by adding new store slices for suggestions/outline, introducing a new “Content Suggestion” block, and wiring up “apply outline” behavior from the suggestions modal into the editor.
Changes:
- Added Redux store slices (state/selectors/actions/controls) for content suggestions and content outline, and registered them in the Content Planner store.
- Registered a new hidden-in-inserter Gutenberg block (
yoast-seo/content-suggestion) and helper utilities to build editor blocks + apply post/Yoast meta from an outline. - Wired suggestion selection in the modal to fetch an outline and apply it to the editor, plus updated unit test mocks.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/js/tests/ai-content-planner/components/feature-modal.test.js | Extends mocks to cover new dispatch/select usage when applying outlines. |
| packages/js/src/ai-content-planner/store/index.js | Registers new suggestions/outline slices (actions/selectors/controls/reducers/initialState). |
| packages/js/src/ai-content-planner/store/content-suggestions.js | New slice + generator action/control for fetching suggestions. |
| packages/js/src/ai-content-planner/store/content-outline.js | New slice + generator action/control for fetching/holding an outline (currently stubbed). |
| packages/js/src/ai-content-planner/initialize.js | Registers the new yoast-seo/content-suggestion block and transform. |
| packages/js/src/ai-content-planner/helpers/build-blocks-from-outline.js | Creates Gutenberg blocks from an outline (heading/paragraph/notes/FAQ). |
| packages/js/src/ai-content-planner/helpers/apply-post-meta-from-outline.js | Applies outline meta to core editor + Yoast editor stores (title/meta/keyphrase/category). |
| packages/js/src/ai-content-planner/components/feature-modal.js | Wires “select suggestion → fetch outline → apply meta + blocks” into the modal. |
| packages/js/src/ai-content-planner/components/content-suggestions-modal.js | Adds onSuggestionSelect callback support for suggestion buttons. |
| packages/js/src/ai-content-planner/components/content-suggestion-block.js | Adds UI component used as the new content-notes block’s edit view. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| blocks.push( createBlock( "yoast-seo/content-suggestion", { suggestions: outline[ contentNotes ] } ) ); | ||
| blocks.push( createBlock( "core/paragraph" ) ); | ||
| } | ||
|
|
||
| blocks.push( createBlock( "yoast-seo/content-suggestion", { suggestions: outline.faqContentNotes } ) ); | ||
| blocks.push( createBlock( "yoast/faq-block" ) ); |
There was a problem hiding this comment.
The block insertion order here doesn’t match the function docblock (and likely the intended UX of having the notes under their corresponding paragraph/FAQ). Currently each section inserts heading → content-suggestion → paragraph, and FAQ inserts content-suggestion → FAQ block. Consider swapping the order to heading → paragraph → content-suggestion, and FAQ block → FAQ content-suggestion, so the notes appear beneath the content they relate to.
| blocks.push( createBlock( "yoast-seo/content-suggestion", { suggestions: outline[ contentNotes ] } ) ); | |
| blocks.push( createBlock( "core/paragraph" ) ); | |
| } | |
| blocks.push( createBlock( "yoast-seo/content-suggestion", { suggestions: outline.faqContentNotes } ) ); | |
| blocks.push( createBlock( "yoast/faq-block" ) ); | |
| blocks.push( createBlock( "core/paragraph" ) ); | |
| blocks.push( createBlock( "yoast-seo/content-suggestion", { suggestions: outline[ contentNotes ] } ) ); | |
| } | |
| blocks.push( createBlock( "yoast/faq-block" ) ); | |
| blocks.push( createBlock( "yoast-seo/content-suggestion", { suggestions: outline.faqContentNotes } ) ); |
There was a problem hiding this comment.
heading → content-suggestion → paragraph and content-suggestion → FAQ block is correct ✅ as per the design
| /** | ||
| * @param {string} endpoint The endpoint to fetch content suggestions from. | ||
| * @returns {Object} Success or error action object. | ||
| */ | ||
| export function* getContentSuggestions( { endpoint } ) { | ||
| yield{ type: `${ FETCH_CONTENT_SUGGESTIONS_ACTION_NAME }/${ ASYNC_ACTION_NAMES.request }` }; |
There was a problem hiding this comment.
The JSDoc doesn’t match the actual function signature: getContentSuggestions takes a single object argument ({ endpoint }), not a string endpoint. Updating the JSDoc will prevent confusion for callers and tooling.
There was a problem hiding this comment.
Still work in progress, it will be updated once we have the real endpoint.
| key={ suggestion.title } | ||
| { ...suggestion } | ||
| onClick={ noop } | ||
| onClick={ onSuggestionSelect } |
There was a problem hiding this comment.
SuggestionButton uses onClick as a normal button handler, so passing onSuggestionSelect directly means it will receive the click event (not the suggestion object). That will break handleApplyOutline (it expects suggestion fields). Wrap the handler to pass the suggestion (e.g., onClick={ () => onSuggestionSelect( suggestion ) }) or change SuggestionButton to call onClick with the suggestion data.
| onClick={ onSuggestionSelect } | |
| onClick={ () => onSuggestionSelect( suggestion ) } |
Code Review — PR #23117OverviewThis PR adds the groundwork for the content suggestions flow: Redux store slices for suggestions and outline, a new Architecture & Data Flow
Store Slices
Block Registration
Content Suggestion Block Component
Tests
CI StatusAll checks passing ✅ Summary
|
|
I think most of the Copilot comments can be ignored since it's still work in progress. (2,3,4,10,11) |
… and content notes. Update block building logic to accommodate new structure.
…ng in FeatureModal, and yield actions in content outline and suggestions store functions
|
TLDR of my changes:
|
|
A merge conflict has been detected for the proposed code changes in this PR. Please resolve the conflict by either rebasing the PR or merging in changes from the base branch. |
Resolve conflicts: keep base branch's UI flow (SuggestionsPanel, ContentOutlineModal, skipTransitions) while preserving feature branch's store integration, content-suggestion block, and outline application logic. Remove deleted banner store slice references. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
A merge conflict has been detected for the proposed code changes in this PR. Please resolve the conflict by either rebasing the PR or merging in changes from the base branch. |
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… both empty and non-empty posts.
leonidasmi
left a comment
There was a problem hiding this comment.
CR: ✅ for the commits added after @JorPV original CR.
Acceptance: 🏗️
Sorry if the below points were intentional omissions from this PR for the sake of velocity! I even ignored some issues that looked like they were supposed to be fixed in later PRs, but even so, I thought it might be useful to flag some of them here, so we can have them documented to fix them later on, I hope that's alright.
Feel free to create separate tasks for the below instead of fixing them here, for the sake of velocity (apologies if tasks are already created for those, I couldn't find any from a quick glance):
- Clicking the
Add outline to postbutton inserts different hardcoded data in the post title, SEO title, meta description, focus keyphrase and category than the hardcoded data that exist in the content outline view- Maybe this is intentional, but I would assume that it would be responsibility of this PR to do this specific wiring. If not, ignore
- Clicking the
Add outline to postbutton in a post with existing content and then clickingcancelon the "Replace existing content with this outline?" confirmation modal indeed returns you to the content outline view 👍 BUT, if you then click to go back to the Content Suggestion modal, all modals close - A greater question about the behavior of the content note blocks that are being added:
- Once they are added, should they persist in the post content even after the post is saved? What about when the post is published?
- I'm thinking that we need to doublecheck with product on what happens with those blocks, because I'm under the impression they must be temporary and be cleaned out at some point (I guess upon publishing the post)
- Clicking the
Add outline to postbutton whilecategoryis toggled off, has no effect, the category is still being applied to the post - Clicking the
Add outline to postbutton has a weird UX flow:- You click the button but the modal stays open for some seconds
- While the modal is open, in the background the outline is getting added in the post content
- After a while the modal closes
- I find this as something that needs to be definitely improved, maybe with a loading state while the outline is getting added in the post?
- The same UX thing is true when you click to replace the existing content, in the "Replace existing content with this outline?" confirmation
- When the content outline view is still loading data and skeletons loaders are still shown, clicking the
Add outline to postbutton should be disabled, as per the designs. - About the "Click the
undobutton in the Gutenberg editor and verify the content is reverted" test instruction, I'm not sure what's the expected behavior, but for me I have to click multiple times the undo button until I see my original content fully reverted. I think that each undo button click reverts a single addition of the content note block - might be ok, but wanted to flag that too - Not sure if intended (the designs have this in one case, but dont have it in other cases) but I see that for between every section (h2+content note block) we add, a blank paragraph is also added:
- Similarly, I'm not sure why we are always adding a blank paragraph before the FAQ block as well, I dont see the same behavior in the designs. In fact there are a lot of things that I am not sure they are as intended, in the way we're dealing with FAQ blocks in
buildBlocksFromOutline().- We currently hardcode a
yoast/faq-blockto always be created, regardless of whether it exists in the suggested outline - We use
outline.faqContentNotesin the paragraph above the FAQ instead of using it in the FAQ block itself - We might need some logic to not do anything with
yoast/faq-blocks, if this has been unregistered from the site (although I do see a gracefulYour site doesn’t include support for the "yoast/faq-block" block. You can leave it as-is or remove it.block warning if that's the case)
- We currently hardcode a
- Not sure what we expect when we convert the content note to a paragraph, but I see that the listed suggestions:
become separate paragraphs instead of listed items, which is what I would expect:

If we think it's ok to merge with this issues documented here and dealt with later on, by all means.
|
Thank you @leonidasmi for pointing out all the issues. Below is an update on each of your points:
Are tackled with this commit . Now the data from the Content Outline is wired with the Outline blocks in the editor. What remains is to wire it with the real API for which I added a Temorary comment.
I will address those in the other discrepancies issues as they kind of fall under the scope Fix loading state discrepancies in Content Planner modals
I've created this issue to tackle that point ☝️ The remaining points need some Product/UX feedback before we can address them:
|
They should ideally persist if the post is saved, and I'd argue also when the post is published. However they should only be visible in the editor, but never be output on the front-end. Is that possible?
Ideally the whole addition of the post, but I'm not sure that's possible (I think we had a similar issue with AI Optimize). If the only option is to revert single additions, that's also fine.
I think those are intended so that users can easily add content, right @ux-tom ?
I think for that we don't need a blank paragraph (since users aren't expected to add anything) but also checking with @ux-tom .
List items would make more sense, but if that's difficult to achieve we can also make it a nice-to-have. |
|
Yes it is, in fact that's already the current behavior. And the user can delete them one by one same as with other Gutenberg blocks.
Indeed, that's a tricky one. We dispatch different entries for the post but WordPress doesn't seem to have an API to group multiple dispatches into a single undo step. I'm creating a new issue #1152 to address it in a later phase.
Alright, then the current implementation already adds the paragraph after each H2 + Content note.
Thanks for the clarification! That's also the current behavior.
I'm applying that change in my last commit. |
…dren and update tests to validate the new transformation behavior.
This fixes a problem where stylesheet would be missing after page load.
|
CR and AC ✅
|
Context
Summary
This PR can be summarized in the following changelog entry:
Relevant technical choices:
Test instructions
Test instructions for the acceptance test before the PR gets merged
Scenario 1: Empty post (no confirmation modal)
Get content suggestionsbutton in the Yoast sidebar.Add outline to postbutton.Scenario 2: Post with existing content (confirmation modal)
Get content suggestionsbutton in the Yoast sidebar.Add outline to postbutton.Canceland verify you return to the content outline view.Add outline to postagain.Replace contentin the confirmation modal.undobutton in the Gutenberg editor and verify the content is reverted. (After a few times, it's a known limitation addressed by this PR).Scenario 3: Content suggestion blocks
Relevant test scenarios
Test instructions for QA when the code is in the RC
QA can test this PR by following these steps:
Impact check
This PR affects the following parts of the plugin, which may require extra testing:
Other environments
[shopify-seo], added test instructions for Shopify and attached theShopifylabel to this PR.[yoast-doc-extension], added test instructions for Yoast SEO for Google Docs and attached theGoogle Docs Add-onlabel to this PR.Documentation
Quality assurance
grunt build:imagesand commited the results, if my PR introduces new images or SVGs.Innovation
innovationlabel.Fixes https://github.com/Yoast/reserved-tasks/issues/1101