Skip to content

Add confirmation modal before replacing post content with outline#23108

Merged
pls78 merged 10 commits intofeature/content-plannerfrom
content-planner-confirmation-modal-before-applying-suggestions
Apr 9, 2026
Merged

Add confirmation modal before replacing post content with outline#23108
pls78 merged 10 commits intofeature/content-plannerfrom
content-planner-confirmation-modal-before-applying-suggestions

Conversation

@JorPV
Copy link
Copy Markdown
Contributor

@JorPV JorPV commented Mar 26, 2026

Context

This PR adds a confirmation modal that appears when the user clicks "Add outline to post" in the Content Outline modal. It warns the user that their existing post content will be replaced and gives them the option to cancel or proceed.

Summary

  • Adds the ReplaceContentModal confirmation component, shown before applying the generated outline to the post.
  • Integrates the confirmation as a transition state inside the existing FeatureModal Dialog, ensuring proper focus trap and screen reader support.
  • Adds a11y unit tests for the ReplaceContentModal component and integration tests for the full confirmation flow in FeatureModal.

Relevant technical choices

  • The ReplaceContentModal renders as a Modal.Panel inside the FeatureModal's single HeadlessUI Dialog, following the same pattern as ContentSuggestionsModal and ContentOutlineModal. This avoids nested Dialogs which break focus trapping and screen reader announcements.
  • Confirmation modal uses Modal.CloseButton with ref + useEffect focus to ensure screen readers announce the dialog context on mount.
  • "Replace content" uses the error button variant matching the destructive action pattern.
  • Buttons are always side-by-side and right-aligned (no responsive breakpoints), matching the Figma design.

Test instructions for the acceptance test before the PR gets merged

Important

Please note that the copy of the modal has been updated (see slack conversation)

Functional tests

  1. Go to Posts > Add Post in the WordPress admin.
  2. In the Yoast SEO metabox, click Get content suggestions → approve → select a suggestion.
  3. In the Content Outline modal, click Add outline to post.
  4. Verify a confirmation modal appears with:
    • Red warning icon in a pink circle
    • Title: "Replace existing content with this outline?"
    • Description mentioning the undo button in the Gutenberg editor
    • "Cancel" and "Replace content" (red) buttons, side-by-side and right-aligned
  5. Click Cancel — verify it returns to the Content Outline view.
  6. Click Add outline to post again, then click Replace content — verify the modal closes.
  7. Click the X close button — verify it closes the entire modal.

Accessibility tests

  1. Open the confirmation modal and verify focus moves to the X close button automatically.
  2. Press Tab — verify focus cycles through the modal elements (close button → Cancel → Replace content) and does not escape to the page behind.
  3. Press Escape — verify the modal closes.
  4. Using a screen reader (e.g. VoiceOver), open the confirmation modal and verify:
    • The dialog title "Replace existing content with this outline?" is announced.
    • All buttons are properly labeled.

Unit tests

  1. Run yarn jest --no-coverage --config packages/js/jest.config.js packages/js/tests/ai-content-planner/components/replace-content-modal.test.js — verify all 9 tests pass.
  2. Run yarn jest --no-coverage --config packages/js/jest.config.js packages/js/tests/ai-content-planner/components/feature-modal.test.js — verify all 7 tests pass.

Relevant test scenarios

  • Changes should be tested on different editors (Default Block/Gutenberg/Classic/Elementor/other)
  • Changes should be tested with the browser console open
  • Changes should be tested on different posts/pages/taxonomies/custom post types/custom taxonomies
  • Changes should be tested on different browsers
  • Changes should be tested on multisite

Test instructions for QA when the code is in the RC

  • QA should use the same steps as above.

Impact check

  • New ReplaceContentModal component in packages/js/src/ai-content-planner/components/
  • Changes to FeatureModal (added replace-content transition state)
  • Simplified ContentPlannerEditorItem (confirmation flow now managed by FeatureModal)
  • New test file packages/js/tests/ai-content-planner/components/replace-content-modal.test.js
  • Updated test file packages/js/tests/ai-content-planner/components/feature-modal.test.js

Other environments

  • This PR also affects Shopify.
  • This PR also affects Yoast SEO for Google Docs.

Documentation

  • I have written documentation for this change.

Quality assurance

  • I have tested this code to the best of my abilities.
  • During testing, I had activated all plugins that Yoast SEO provides integrations for.
  • I have added unit tests to verify the code works as intended.
  • If any part of the code is behind a feature flag, my test instructions also cover cases where the feature flag is switched off.
  • I have written this PR in accordance with my team's definition of done.
  • I have checked that the base branch is correctly set.
  • I have run grunt build:images and commited the results, if my PR introduces new images or SVGs.

Innovation

  • No innovation project is applicable for this PR.
  • This PR falls under an innovation project. I have attached the innovation label.
  • I have added my hours to the WBSO document.

Fixes Yoast/reserved-tasks#1104

…rEditorItem for content replacement functionality
@JorPV JorPV added the changelog: non-user-facing Needs to be included in the 'Non-userfacing' category in the changelog label Mar 26, 2026
Copy link
Copy Markdown
Contributor

@vraja-pro vraja-pro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should wait for Content Planner: Create the modal for the content suggestions to be merged and add that modal to the FeatureModal

We will also need some focus managment like I did there with the content suggestions modal.

@github-actions
Copy link
Copy Markdown

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.

…press-seo into content-planner-confirmation-modal-before-applying-suggestions
JorPV added 3 commits March 31, 2026 14:34
- Introduced new tests for ReplaceContentModal, covering accessibility, content display, and action handling.
- Expanded FeatureModal tests to include scenarios for transitioning to the replace content confirmation and handling user interactions.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 2, 2026

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.

Base automatically changed from content-planner-outline-modal to feature/content-planner April 3, 2026 09:29
…seo into content-planner-confirmation-modal-before-applying-suggestions
The merge from feature/content-planner introduced duplicate declarations:
- content-outline-modal.js: duplicate `withIds` function and stale JSDoc block
- feature-modal.js: duplicate `Transition` import, missing `hasVisitedReplace`
  state, incomplete state resets, and missing ReplaceContentModal rendering

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coveralls
Copy link
Copy Markdown

coveralls commented Apr 7, 2026

Coverage Report for CI Build 0

Coverage increased (+0.02%) to 53.542%

Details

  • Coverage increased (+0.02%) from the base build.
  • Patch coverage: 2 uncovered changes across 2 files (31 of 33 lines covered, 93.94%).
  • 1 coverage regression across 1 file.

Uncovered Changes

File Changed Covered %
packages/js/src/ai-content-planner/components/content-planner-editor-item.js 1 0 0.0%
packages/js/src/ai-content-planner/components/feature-modal.js 21 20 95.24%

Coverage Regressions

1 previously-covered line in 1 file lost coverage.

File Lines Losing Coverage Coverage
packages/js/src/ai-content-planner/components/content-planner-editor-item.js 1 0.0%

Coverage Stats

Coverage Status
Relevant Lines: 65228
Covered Lines: 34760
Line Coverage: 53.29%
Relevant Branches: 16808
Covered Branches: 9164
Branch Coverage: 54.52%
Branches in Coverage %: Yes
Coverage Strength: 46116.87 hits per line

💛 - Coveralls


const handleRequestAddOutline = useCallback( () => {
setHasVisitedReplace( true );
setStatus( "replace-content" );
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe declare all the possible status strings as constant as we do in other implementations (see, e.g. packages/js/src/ai-generator/constants/index.js), ASYNC_ACTION_STATUS)

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a destructive-action confirmation step to the AI Content Planner flow so users must explicitly confirm before replacing their post content with a generated outline, while keeping everything within a single HeadlessUI Dialog for correct focus trapping and screen reader behavior.

Changes:

  • Added a new ReplaceContentModal panel and a new replace-content transition state in FeatureModal.
  • Updated the outline panel focus behavior to support panel-to-panel transitions without nested dialogs.
  • Added unit/integration tests covering the confirmation modal accessibility and the end-to-end confirmation flow.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/js/src/ai-content-planner/components/replace-content-modal.js Adds the new confirmation panel UI and focus management for the destructive action.
packages/js/src/ai-content-planner/components/feature-modal.js Introduces a new flow state and mounts/swaps outline vs confirmation panels within one dialog.
packages/js/src/ai-content-planner/constants.js Adds centralized FEATURE_MODAL_STATUS constants for the modal flow.
packages/js/src/ai-content-planner/components/content-outline-modal.js Adds isActive-driven focus behavior to support being kept mounted while hidden.
packages/js/src/ai-content-planner/components/content-planner-editor-item.js Wires the updated FeatureModal API into the editor item.
packages/js/tests/ai-content-planner/components/replace-content-modal.test.js Adds a11y-focused unit tests for the new confirmation panel.
packages/js/tests/ai-content-planner/components/feature-modal.test.js Adds integration tests validating transitions to/from the confirmation panel and confirmation behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +21 to +22
closeFeatureModal();
}, [ closeFeatureModal ] );
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onAddOutline is wired to closeFeatureModal(), but FeatureModal already calls onClose() after confirming replacement. In the current integration this results in the same close handler being invoked twice on confirm, which can cause duplicate side effects (analytics, state resets, etc.). Consider making onAddOutline responsible only for applying/inserting the outline, and let FeatureModal handle closing; i.e., remove the extra closeFeatureModal() from this callback (or alternatively, stop calling onClose() in FeatureModal and make onAddOutline the single close path).

Suggested change
closeFeatureModal();
}, [ closeFeatureModal ] );
// Intentionally left empty: FeatureModal already handles closing via onClose.
}, [] );

Copilot uses AI. Check for mistakes.
Comment on lines 283 to +287
* @param {string} category Optional. If provided, show the suggest category section.
*
* @returns {JSX.Element} The ContentOutlineModal component.
*/
export const ContentOutlineModal = ( { onBack, onAddOutline, suggestion, sparksLimit, sparksUsage, category } ) => {
export const ContentOutlineModal = ( { onBack, onAddOutline, suggestion, sparksLimit, sparksUsage, category, isActive } ) => {
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSDoc for ContentOutlineModal no longer matches the function signature: isActive was added but isn’t documented. Please add an @param entry explaining isActive (used for focus management) so consumers know when to pass it.

Copilot uses AI. Check for mistakes.
*
* @returns {JSX.Element} The ReplaceContentModal component.
*/
export const ReplaceContentModal = ( { onClose, onConfirm, isActive } ) => {
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prop name onClose is ambiguous here: it’s used for the “Cancel/back to outline” action, while the “X” close button triggers the parent Modal’s onClose instead. Renaming this prop to something like onCancel/onBack would make the API clearer and reduce the risk of wiring the wrong handler.

Copilot uses AI. Check for mistakes.
Comment on lines +195 to +197
* Once the outline has been visited, keep both outline and confirmation panels
* mounted and toggle via display:none to avoid a one-frame empty container
* between panel swaps.
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment says “Once the outline has been visited” but the mounting behavior is actually gated by hasVisitedReplace (i.e., after entering the replace confirmation). Updating the wording would avoid confusion when maintaining the flow.

Suggested change
* Once the outline has been visited, keep both outline and confirmation panels
* mounted and toggle via display:none to avoid a one-frame empty container
* between panel swaps.
* Once the replace confirmation has been visited, keep both outline and
* confirmation panels mounted and toggle via display:none to avoid a
* one-frame empty container between panel swaps.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

@pls78 pls78 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CR && Acc: ✅

@pls78 pls78 added this to the feature/content-planner milestone Apr 9, 2026
@pls78 pls78 merged commit 823de2d into feature/content-planner Apr 9, 2026
20 checks passed
@pls78 pls78 deleted the content-planner-confirmation-modal-before-applying-suggestions branch April 9, 2026 08:24
@vraja-pro vraja-pro mentioned this pull request Apr 28, 2026
19 tasks
@vraja-pro vraja-pro removed this from the feature/content-planner milestone Apr 28, 2026
@vraja-pro vraja-pro added this to the 27.6 milestone Apr 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog: non-user-facing Needs to be included in the 'Non-userfacing' category in the changelog

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants