Feature/bulk editor ph2 - #23552
Open
vraja-pro wants to merge 264 commits into
Open
Conversation
After a search-appearance save the bulk editor re-runs the analysis worker on the rendered fields, derives the SEO title and meta description scores, and persists them, so the needs-improvement filter reflects edits without reopening the post editor. Enqueues the worker script data on the bulk editor page. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ad of the worker Re-scoring after an edit now runs only the SEO title and meta description assessors on the main thread, instead of spinning up the analysis worker and running the full page analysis to read four results. A JS filter (yoast.bulkEditor.analysis.researcher) lets Premium return a researcher with morphology data; Free scores with base word forms, matching the Free editor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lysis is disabled The needs-improvement filter only matches on the stored per-field score while SEO analysis is enabled; when it is off the score is no longer maintained, so the search fields fall back to the empty-field check (like the social fields). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
After a search-appearance save the bulk editor re-scores the SEO title and meta description on the rendered field values and persists the result, so the needs-improvement filter reflects edits without reopening the post editor. Scoring runs the two field assessors directly on the main thread (no analysis worker, no full-page analysis); a JS filter (yoast.bulkEditor.analysis.configureResearcher) lets Premium augment the researcher with morphology data, while Free scores with base word forms. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lysis is disabled The needs-improvement filter only matches on the stored per-field score while SEO analysis is enabled; when it is off the score is no longer maintained, so the search fields fall back to the empty-field check (like the social fields). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-filter-dropdown' of github.com:Yoast/wordpress-seo into 1346-bulk-editor-add-needs-improvement-filter-option-to-filter-dropdown
Injects MyYoast_Connection_Conditional, Status_Presenter, and Connection_Permission into Bulk_Editor_Integration and exposes a myyoastConnection payload so the JS side can pick the correct "Yoast AI cannot reach your site" notification variant. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rovement meta query
Applying an AI-generated SEO title or meta description now recomputes just that field's score via createSingleFieldScorer, wired through onFieldApplied, so the needs-improvement filter reflects AI edits the same as manual ones. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…core dot Relabels the options to the plain field names and adds a decorative red (#dc3232) score dot; a visually-hidden group legend keeps the "needs improvement" meaning for assistive tech. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both collectors duplicated the 1-70 "needs improvement" band (MIN/MAX ints in the indexable collector, a [1,70] array in the post-meta fallback). Move it to Posts_Collector_Interface::NEEDS_IMPROVEMENT_MIN_SCORE / MAX_SCORE and reference it from both, so the band lives in one place. Also aligns the $needs_improvement @PARAM wording with the property doc. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduced a new property to the Post class to track whether each field requires improvement, keyed by field parameter.
The double/mock classes mirror the real classes' properties; enforcing native/@var type hints on the mock's own untyped property declarations adds no value, so exclude tests/*/Doubles/* from the PropertyTypeHint sniff, matching the existing Doubles exclusions for the comment sniffs. Lower the phpcs ERRORS threshold to 2244 to lock in the errors this exclusion removes across the test doubles. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…r args Adds mocks and assertions for MyYoast_Connection_Conditional, Status_Presenter, and Connection_Permission in the abstract base and Constructor_Test to match the expanded constructor signature. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mocks the feature-flag conditional as disabled so get_myyoast_connection_data returns null, and adds the myyoastConnection key to the expected script data. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the is_bool + boolean AND pattern with a strict === true check, and adds Get_Myyoast_Connection_Data_Test covering the flag-off, provisioned, not-provisioned, and non-boolean is_provisioned paths. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s-improvement-filter-option-to-filter-dropdown
|
📦 Plugin zip built successfully! Download it from the workflow run. |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…47-add-notification-modal-when-free-is-updated-but-premium-is-not-with-a-one-click-premium-update-option
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…modal Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… premiumUpdateUrl Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SEO Managers have wpseo_manage_options but not update_plugins, so sending them to update.php results in a wp_die permission error. The update URL is now only exposed to users who can actually complete the update, and the "Update now" button in the modal is hidden when the URL is absent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a current_user_can expectation to the existing Enqueue_Assets_Test (previously the function was never called; my prior commit added the check and left the expectation unset). Adds Get_Premium_Update_Url_Test covering the admin path (URL returned) and the non-admin path (empty string). Adds update-modal.test.js covering heading/body rendering, the Update Now link with its attributes, the SEO Manager case where the link is absent, and the close-button callback. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…_Url_Test Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ecessary store subscriptions Drop useAiUpsell from FreeBulkActions and pass contentType to UpsellModal instead, so the hook's store subscriptions are only created when the modal is actually mounted. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
UpsellModal now calls useAiUpsell internally rather than accepting the upsell values as props. Mock the hook via jest.fn() so individual tests can control its return value. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
get_script_data() now calls get_premium_update_url(), which calls current_user_can(). Stub it to return false so the method short-circuits and Brain\Monkey does not throw a MissingFunctionExpectations error. Also removed the unused wp_nonce_url stub. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Premium's notice fills (error, notice, minimal-content) all pull their data from the store via useSelect and do not consume fillProps. Remove the five dead props from BulkActionsNotices and BulkActions (contentTypeLabel, contentTypeSingularLabel, selectedIds, activeFieldSet, contentType), and the corresponding pass-through in BulkEditorContent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of threading a ref through SelectionToolbar → BulkEditorContent → DismissibleAlert, DismissibleAlert now reads the active field set from the store and focuses the Select menu button by id. SelectMenu exposes an id prop so its trigger button gets a stable, predictable id; SelectionToolbar becomes a plain function component. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…files DismissibleAlert now calls select(STORE_NAME) so any test that renders it needs the store registered. Move registerStore to a top-level beforeAll so it runs before every describe block in the file, not just the second one. Also remove the forwardRef mock from initialize.test.js since SelectionToolbar is no longer a forwardRef component. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… Premium is not active Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-free-is-updated-but-premium-is-not-with-a-one-click-premium-update-option 1347 add notification modal when free is updated but premium is not with a one click premium update option
Co-authored-by: Aida Marfuaty <48715883+FAMarfuaty@users.noreply.github.com>
…08-bulk-editor-aria-hidden-warning-when-dismissing-notification-banner
…est setup Rename the draft useActionBarFocusTrap hook to useActionBarFocusReturn (focus-trap implies preventing focus from leaving; this moves focus back). API now returns a plain (e) => void callback: focuses the Select menu button by id, falls back to e.currentTarget.blur() when not found. DismissibleAlert calls focusActionBar(e) then onDismiss(), removing its direct store and STORE_NAME imports. Fix bulk-action-bar.test.js: useSelect is fully mocked there, so the real store never needed to be registered. Remove the registerStore import and beforeAll, and mock useActionBarFocusReturn so DismissibleAlert does not try to call selectActiveFieldSet through the mocked useSelect. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix a missing hyphen in useActionBarFocusReturn: activeFieldSet is the
bare value ("search"/"social") while the button id is built with a
leading-hyphen idSuffix, so the lookup must be
yst-bulk-editor-select-menu-${activeFieldSet}-button. Without the
hyphen getElementById always returned null and focus never moved.
Clarify the select-menu id JSDoc: props.id is a prefix, not the button
id itself (the trigger gets ${id}-button).
Add a document.activeElement assertion after each Dismiss click in the
BulkEditorContent integration tests — this would have caught the id
typo before merge.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ning-when-dismissing-notification-banner Fix aria-hidden warning and clean up unused BULK_NOTICES_SLOT fillProps
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
This PR can be acceptance tested by following these steps:
This PR can be acceptance tested by following the steps per feature area below.
1. Bulk edit entry in WP admin bulk actions — #23488
Setup
The dropdown entry
Carry a small selection over (happy path)
URL cleanup (one-shot parameters)
post_idsandselected_countparameters are removed automatically shortly after the page loads (content_typestays).The "Overview selection" filter
More than 20 selected: the truncation notice
The exclusion notice (carried posts the bulk editor cannot show)
Both notices at the same time (>20 selected AND some excluded)
Permissions
wpseo_manage_optionscapability).Other content types
Hand-crafted URLs (defensive handling)
wp-admin/admin.php?page=wpseo_page_bulk_edit&content_type=bogus&post_ids=1,2,3&selected_count=3.post_ids=abc,-5,0: same clean result.(Optional) RTL
2. "Needs improvement" filter — #23438
Setup and migration
yarn build/grunt build) and activate it. On activation theAddSeoTitleAndMetaDescriptionScoresmigration runs, adding theseo_title_scoreandmeta_description_scorecolumns towp_yoast_indexable. Confirm both columns exist.On a dev checkout, force the pending migrations with WP-CLI (inside the Docker container):
Verify the columns:
Filter dropdown appearance
Open the Filters dropdown on the Search appearance tab. Each "needs improvement" option ("SEO titles", "Meta descriptions") should show a round red score bullet (#dc3232, ~12px) before its label, separated from the status filters by a divider, and with no visible "Needs improvement" heading (it exists only for screen readers).
The filter — empty fields
The filter — weak scores (search tab only)
Craft a post that NEEDS improvement (non-empty, to exercise the score path). Set a focus keyphrase (e.g.
banana bread), then:Delicious homemade loaf.Open/save the post so the scores persist. In the bulk editor, both "SEO title needs improvement" and "Meta description needs improvement" now list it.
Craft a post that does NOT need improvement. Same keyphrase, but SEO title beginning with the exact keyphrase and a meta description containing the keyphrase. Neither filter lists this post.
Updating the score from a manual edit
POST yoast/v1/bulk_editor/update_scores; refresh and confirm the row leaves or joins the filtered set according to its new score. Editing the focus keyphrase re-scores both the SEO title and the meta description.When SEO analysis is disabled — site-wide
update_scoresrequest.When SEO analysis is disabled — per-user
RTL
Switch to an RTL language and reopen the Filters dropdown. The score bullet uses logical spacing: it sits before its label on the right-hand side, and the checkbox / bullet / label order reads right-to-left.
3. Smart-select posts that need improvement — #23470
needs improvementdot: SEO titles and Meta descriptions.4. Clear "needs improvement" filter on tab switch — #23540
1.In the console,
wp.data.select( "@yoast/bulk-editor" ).selectNeedsImprovement()should return[]right after a tab switch, and the Network tab should show exactly onebulk_editor/postsrequest for that switch, without aneeds_improvement[]parameter.5. Reset search and filters when switching content types — #23514
6. Default template when SEO title/description is empty — #23538
Setup
%%title%% %%sep%% %%sitename%%.Verify the Search appearance tab
Verify the Social appearance tab
%%title%%), not empty.Verify the edit textarea and save behaviour
Verify the needs-improvement filter
Regression
7. Focus keyphrase desync fix — #23534
Comma warning
seo, ranking.Length warning
HTML warning + backend sync (the core fix)
<b>seo</b>.seo— not<b>seo</b>. The frontend must reflect what the server actually stored.seo.rgba(71, 85, 105, 1).TextareaField impact check
Organization descriptionis rendered.8. Shift+click range selection and animated notices banner — #23511
9. AI content tip on visible text — #23507
Prerequisites: Yoast SEO with AI generation available and consent granted.
10. AI prompt content collection — #23524
A. New REST route
window.wpseoBulkEditorData.nonce, then send a GET request:/wp-json/yoast/v1/bulk_editor/posts_content?ids=<id1>,<id2>Expected: returns
{ posts: [ { id, content } ] }with raw, unrenderedpost_content.posts_contentrequest is made.B. Bridge exposed to Premium
Expected:
"Wrapped wording stays.".C. Regressions to watch
11. Premium version update notification modal — #23546
Regression for the AI generator when editing a post
12. MyYoast connection data — #23461
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 committed the results, if my PR introduces or edits images or SVGs.Innovation
innovationlabel.Fixes https://github.com/Yoast/reserved-tasks/issues/1414