Add confirmation modal before replacing post content with outline#23108
Conversation
…rEditorItem for content replacement functionality
There was a problem hiding this comment.
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.
|
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
- 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.
|
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. |
…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>
Coverage Report for CI Build 0Coverage increased (+0.02%) to 53.542%Details
Uncovered Changes
Coverage Regressions1 previously-covered line in 1 file lost coverage.
Coverage Stats💛 - Coveralls |
|
|
||
| const handleRequestAddOutline = useCallback( () => { | ||
| setHasVisitedReplace( true ); | ||
| setStatus( "replace-content" ); |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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
ReplaceContentModalpanel and a newreplace-contenttransition state inFeatureModal. - 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.
| closeFeatureModal(); | ||
| }, [ closeFeatureModal ] ); |
There was a problem hiding this comment.
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).
| closeFeatureModal(); | |
| }, [ closeFeatureModal ] ); | |
| // Intentionally left empty: FeatureModal already handles closing via onClose. | |
| }, [] ); |
| * @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 } ) => { |
There was a problem hiding this comment.
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.
| * | ||
| * @returns {JSX.Element} The ReplaceContentModal component. | ||
| */ | ||
| export const ReplaceContentModal = ( { onClose, onConfirm, isActive } ) => { |
There was a problem hiding this comment.
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.
| * 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. |
There was a problem hiding this comment.
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.
| * 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. |
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
ReplaceContentModalconfirmation component, shown before applying the generated outline to the post.FeatureModalDialog, ensuring proper focus trap and screen reader support.ReplaceContentModalcomponent and integration tests for the full confirmation flow inFeatureModal.Relevant technical choices
ReplaceContentModalrenders as aModal.Panelinside theFeatureModal's single HeadlessUIDialog, following the same pattern asContentSuggestionsModalandContentOutlineModal. This avoids nested Dialogs which break focus trapping and screen reader announcements.Modal.CloseButtonwithref+useEffectfocus to ensure screen readers announce the dialog context on mount.errorbutton variant matching the destructive action pattern.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
Accessibility tests
Unit tests
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.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
Test instructions for QA when the code is in the RC
Impact check
ReplaceContentModalcomponent inpackages/js/src/ai-content-planner/components/FeatureModal(added replace-content transition state)ContentPlannerEditorItem(confirmation flow now managed by FeatureModal)packages/js/tests/ai-content-planner/components/replace-content-modal.test.jspackages/js/tests/ai-content-planner/components/feature-modal.test.jsOther environments
Documentation
Quality assurance
Innovation
innovationlabel.Fixes Yoast/reserved-tasks#1104