Skip to content

1106 content planner create spark limit modal notification#23173

Merged
leonidasmi merged 42 commits intofeature/content-plannerfrom
1106-content-planner-create-spark-limit-modal-notification
Apr 27, 2026
Merged

1106 content planner create spark limit modal notification#23173
leonidasmi merged 42 commits intofeature/content-plannerfrom
1106-content-planner-create-spark-limit-modal-notification

Conversation

@vraja-pro
Copy link
Copy Markdown
Contributor

@vraja-pro vraja-pro commented Apr 17, 2026

Context

Summary

This PR can be summarized in the following changelog entry:

  • Adds the spark notification to the content planner feature.
  • Change the spark notification to trigger when user reaches 5 sparks before the limit and triggers every time untill limit is reached both for AI generator and content planner.
  • Fixes the replace content modal to be rendered over the content outline modal.
  • Refactors the content suggestions list and button components to separate files and fix hover style for suggestion button.
  • Changes the approve modal to function as a modal and not a modal panel and by that drops the transition.
  • Adds transition to the content outline loading states and fix category loading style.
  • Adds changing description for the suggestion modal when loading.

Relevant technical choices:

  • I split the code that was in the suggestions-content-modal.js to several files to reduce complexity and improve readability.
  • I removed the suggestions-content-modal.js
  • I separated the different modals that are in different sizes to different components and added App to control them:
    • Approve modal
    • Feature modal
    • Replace content modal
    • Consent Modal
  • Seperating the modal means we lose some of the trantision between them.
  • When getting the content suggestion we also fetching the usage count, this request also takes time and I added the fetch usage count request time to the fetch content suggestion loading state in the frontend.

Test instructions

Test instructions for the acceptance test before the PR gets merged

This PR can be acceptance tested by following these steps:

  1. Install and activate Yoast SEO
  2. Create or edit a new post, click on get suggesstion button in the inline banner.
  3. Check you get the consent modal and grant consent.
  4. Check you are in the content suggestion modal and it is loading.
  5. Check the usage counter is also loading.
  6. When loading is inp place, check the sentence under the Yoast logo is changing from "Analyzing your site content…" to "Composing your content suggestions…" to "Writing compelling headlines…".
  7. While loading check you don't see notification.
  8. Once suggestion are loaded, hover over the usage counter and check the tooltip is above it.
  9. Hover over the different suggestion and check the title get underline and the suggestion title has a darker color class(yst-text-slate-900), the background color changes (yst-bg-slate-50).
  10. Close the modal and open it from the metabox or sidebar. Repeat untill you reach 5 sparks.
  11. In the fifth time, after loading, check you see the upsell notification with "5 free sparks left!" title.
  12. Dismiss the notification and check content suggestion modal is still open.
  13. Check the sparks counter and the beta badge are aligned vertically to the center of the header with the rest of the header elements.
  14. Select one suggestion and check in the content outline modal that the sparks counter and the beta badge are aligned to the center of the header with the rest of the element and the usage count hasn't changed.
  15. Check the outline loading state transitions with animation to the success stage.
  16. Check the loading category is the same as the design (80px width and 24px space under).
  17. Go back to the suggestion and check you don't see the notification again.
  18. Close the modal and get new suggestions.
  19. Check you see the notification again with the title 4 free sparks left!.
  20. Do not dismiss the notification and select one suggestion. Check the notification is still there.
  21. Apply the outline and check it is applied to the content.
  22. Regenerate suggestions and apply the outline again, check you get an replace content modal on top of the outline modal.
  23. Apply the new outine and check it is applied.
  24. Repead and dismiss the replace content modal, check outline modal is not dismissed.
  25. Regenerate suggestions untill you reach the 10th spark, check the tite in the notification is You're out of free sparks!.
  26. Try to regenerate more suggestions and check you get the approve modal with the upsell button.

Test AI generator:

  • Reset sparks with new site.

  • Without focus keyphrase try to use the AI generator

  • Check focus is switched to the focus keyphrase field.

  • Use AI generator untill you get to 5 sparks

  • Check you see the same notifications 5 free sparks left!.

  • Keep generating and check the number changes in the notification.

  • When you reach the 10th spark check you get You're out of free sparks! notification.

  • With premium, change the usage count via the console to reach 100.

wp.data.dispatch( "yoast-seo/ai-generator").setUsageCount(100)
  • Check you get the notification that says "You've used 100 sparks this month."

Relevant test scenarios

  • 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 editors (Default Block/Gutenberg/Classic/Elementor/other)
  • 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.

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:

  • The spark notification new behaviour of rendering every time in the last 5 sparks is also applies to the AI generator.

Other environments

  • This PR also affects Shopify. I have added a changelog entry starting with [shopify-seo], added test instructions for Shopify and attached the Shopify label to this PR.
  • This PR also affects Yoast SEO for Google Docs. I have added a changelog entry starting with [yoast-doc-extension], added test instructions for Yoast SEO for Google Docs and attached the Google Docs Add-on label to this PR.

Documentation

  • I have written documentation for this change. For example, comments in the Relevant technical choices, comments in the code, documentation on Confluence / shared Google Drive / Yoast developer portal, or other.

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 #https://github.com/Yoast/reserved-tasks/issues/1106

@vraja-pro vraja-pro added this to the feature/content-planner milestone Apr 17, 2026
@vraja-pro vraja-pro added the changelog: non-user-facing Needs to be included in the 'Non-userfacing' category in the changelog label Apr 17, 2026
@coveralls
Copy link
Copy Markdown

coveralls commented Apr 17, 2026

Coverage Report for CI Build 6785228

Coverage decreased (-0.09%) to 53.466%

Details

  • Coverage decreased (-0.09%) from the base build.
  • Patch coverage: 48 uncovered changes across 9 files (48 of 96 lines covered, 50.0%).
  • 16 coverage regressions across 2 files.

Uncovered Changes

File Changed Covered %
packages/js/src/ai-content-planner/components/app.js 22 0 0.0%
packages/js/src/ai-generator/components/sparks-limit-notification.js 8 0 0.0%
packages/js/src/ai-content-planner/containers/approve-modal.js 7 0 0.0%
packages/js/src/ai-content-planner/components/suggestions-modal-content.js 22 18 81.82%
packages/js/src/ai-content-planner/containers/feature-modal.js 3 0 0.0%
packages/js/src/ai-content-planner/blocks/banner-block.js 1 0 0.0%
packages/js/src/ai-content-planner/components/content-planner-editor-item.js 1 0 0.0%
packages/js/src/ai-content-planner/containers/content-planner-editor-item.js 1 0 0.0%
packages/js/src/ai-content-planner/store/modal.js 2 1 50.0%

Coverage Regressions

16 previously-covered lines in 2 files lost coverage.

File Lines Losing Coverage Coverage
packages/js/src/ai-content-planner/hooks/use-apply-outline.js 15 3.45%
packages/js/src/ai-generator/components/sparks-limit-notification.js 1 5.56%

Coverage Stats

Coverage Status
Relevant Lines: 65986
Covered Lines: 35141
Line Coverage: 53.26%
Relevant Branches: 16922
Covered Branches: 9187
Branch Coverage: 54.29%
Branches in Coverage %: Yes
Coverage Strength: 45587.05 hits per line

💛 - Coveralls

@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.

@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.

…06-content-planner-create-spark-limit-modal-notification
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

Adds/adjusts the “sparks limit” notification behavior and integrates it into the AI Content Planner’s content suggestions modal, alongside a refactor that splits the modal UI into smaller components.

Changes:

  • Integrates SparksLimitNotification into the content planner’s content suggestions modal and updates related tests.
  • Updates SparksLimitNotification title/visibility logic for free vs. unlimited sparks.
  • Refactors the content suggestions modal by extracting modal content, suggestion list, and suggestion button into separate components.

Reviewed changes

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

Show a summary per file
File Description
packages/js/tests/ai-content-planner/components/feature-modal.test.js Mocks SparksLimitNotification to keep planner tests isolated.
packages/js/tests/ai-content-planner/components/content-suggestions-modal.test.js Mocks SparksLimitNotification for the modal unit tests.
packages/js/src/ai-generator/components/sparks-limit-notification.js Changes notification display threshold + introduces dynamic “sparks left” title logic.
packages/js/src/ai-content-planner/containers/content-suggestions-modal.js Renames mapped prop to usageCountStatus.
packages/js/src/ai-content-planner/components/suggestion-list.js New extracted suggestions list UI.
packages/js/src/ai-content-planner/components/suggestion-button.js New extracted suggestion button + skeleton loader.
packages/js/src/ai-content-planner/components/content-suggestions-modal.js Refactors modal to use extracted content component and adds notifications container + sparks toast.
packages/js/src/ai-content-planner/components/content-suggestions-modal-content.js New extracted loading/success modal content with (optional) transitions.
Comments suppressed due to low confidence (1)

packages/js/src/ai-generator/components/sparks-limit-notification.js:176

  • SparksLimitUpsellContent expects an isWooProductEntity prop, but the call site passes isWooUpsell. As a result the component will always use the default false and display the non-Woo copy even for Woo upsells. Align the prop name (or update the component signature) so the correct upsell label is shown.
			{ hasUnlimitedSparks
				? <SparksLimitContent onClose={ hideNotification } />
				: <SparksLimitUpsellContent onClose={ hideNotification } upsellLink={ upsellLink } isWooUpsell={ isWooUpsell } />
			}

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

Comment thread packages/js/src/ai-generator/components/sparks-limit-notification.js Outdated
Comment thread packages/js/src/ai-content-planner/components/content-suggestions-modal.js Outdated
@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.

…06-content-planner-create-spark-limit-modal-notification
@FAMarfuaty FAMarfuaty added the innovation Innovative issue. Relating to performance, memory or data-flow. label Apr 21, 2026
Copy link
Copy Markdown
Contributor

@FAMarfuaty FAMarfuaty left a comment

Choose a reason for hiding this comment

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

LGTM! ✨

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

Copilot reviewed 27 out of 27 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (2)

packages/js/src/ai-content-planner/store/modal.js:21

  • openModal was removed from the modal slice actions, but there are still callers expecting it on the content planner store (e.g. packages/js/src/ai-content-planner/blocks/banner-block.js calls const { openModal } = useDispatch( CONTENT_PLANNER_STORE ); openModal();). That will make openModal undefined and throw at runtime when the inline banner is clicked. Either reintroduce an openModal action (and keep isOpen meaningful) or update remaining consumers to rely only on featureModalStatus to control visibility.
	initialState: {
		isOpen: false,
		featureModalStatus: null,
	},
	reducers: {
		closeModal: () => slice.getInitialState(),
		setFeatureModalStatus: ( state, { payload } ) => {
			state.featureModalStatus = payload;
		},
	},

packages/js/src/ai-content-planner/components/outline-modal-content.js:280

  • In the outline error state, the "Try again" button will effectively be a no-op because onRetry is expected as a prop but isn't provided by the connected container (containers/outline-modal-content.js only injects onBackToSuggestions). As a result, ContentPlannerError falls back to its default noop retry handler. Consider wiring retry up here (e.g. call useFetchContentOutline() with the current suggestion) or have the container supply an onRetry callback.

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

Comment thread packages/js/src/ai-content-planner/components/feature-modal.js Outdated
Comment thread packages/js/src/ai-content-planner/store/modal.js Outdated
Comment thread packages/js/src/ai-content-planner/components/app.js Outdated
FeatureModal and App were both instantiating useApplyOutline with the same editedOutlineRef, resulting in two hook instances for the same action. Move the single instance to App (which already owns the ref)  and pass handleApplyOutline down as a prop to FeatureModal.
Cleanup of tests
@leonidasmi
Copy link
Copy Markdown
Contributor

Looser CR + acceptance test than usual, as discussed in the standup, to unblock the numerous moving parts. Merging for now, I'll share a comment with some acceptance-related notes after that.

@leonidasmi leonidasmi merged commit 9889422 into feature/content-planner Apr 27, 2026
19 checks passed
@leonidasmi leonidasmi deleted the 1106-content-planner-create-spark-limit-modal-notification branch April 27, 2026 07:58
useEffect( () => {
const showNotificationPremium = hasUnlimitedSparks && usageCount === usageCountLimit;
const showNotificationFree = ! hasUnlimitedSparks && isUsageCountLimitReached;
const showNotificationFree = ! hasUnlimitedSparks && remainingSparks <= 5;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Shouldn't we add remainingSparks to the dependency list?


return {
onClose: closeModal,
setStatus: setFeatureModalStatus,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is not used in the approve modal component, so maybe it's safe to remove?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

to be more precise: the setStatus seems that it's not being used by the respective component

@@ -120,9 +120,11 @@ export const SparksLimitNotification = ( { className = "" } ) => {

const [ showNotification, , setShowNotification, , hideNotification ] = useToggleState( usageCount === usageCountLimit );
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The usageCount === usageCountLimit check is now obsolete in non Premium setups, so we have a mismatch here with the useEffect below.

Maybe consider making it into
const [ showNotification, , setShowNotification, , hideNotification ] = useToggleState( false );
and let the effect own visibility?

@leonidasmi
Copy link
Copy Markdown
Contributor

As per the above, I noticed a couple of visual discrepancies @vraja-pro , let's discuss which ones are expected, which ones can be fixed later and which ones are blockers:

Out of free sparks copy never shown in modal

  • According to the designs, there's a modal meant to be displayed when there are no more free sparks (on empty content and on existing content)
  • I never got to see those modals with those designs.
  • When you click the get content suggestions on an empty post and on a post with content, I get some very different variations of those modals (notice the different copies, missing icons in the CTA, etc.)
image

Modal header elements not vertically aligned

Check the sparks counter and the beta badge are aligned vertically to the center of the header with the rest of the header elements
That's not what I'm seeing, I'm adding a screenshot with an added outline to illustrate the problem:

image

Upsell modal shown unexpectedly on Premium

What I did to replicate, not sure which step exactly was the culprit:

  • Have Premium not enabled and use your 10 free sparks
  • Enable Premium and go to the post again
  • Run the wp.data.dispatch( "yoast-seo/ai-generator").setUsageCount(100) as per instructions to simulate the usage count to reach 100.
  • Use the AI Generate feature with no issues
  • Go to use the Content Planner feature, and even though you expect to be able to use it with no issues, you get the upsell modal:
image

Sparks notification positioning

  • The x sparks left notification is displayed in a weird spot at the bottom left corner, not exactly at the bottom but also not exactly at the level of the suggestions modal. I would expect at least the latter.
image

And some things to note going forward:

  • We need some regression steps for the rest of the AI features (AI optimize, AI summarize) because I think they get impacted as well
  • The exact transitions throughout the entirety of the feature are still TBD
  • Also left some CR comments inline after merging

@vraja-pro vraja-pro mentioned this pull request Apr 28, 2026
19 tasks
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 innovation Innovative issue. Relating to performance, memory or data-flow.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants