Bulk editor: add in-app tour for first-time users - #23509
Conversation
…ask_list' key in favor of 'bulk_editor_tour'
…-editor-in-app-tour
Removes the AI Brand Insights modals/classes/tests and image that #23439 deleted from trunk, plus stray wordpress-urls (class-wpseo-utils) and yarn.lock edits, so this PR only contains the guided-tour work. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Coverage Report for CI Build 71Coverage increased (+0.2%) to 55.582%Details
Uncovered Changes
Coverage Regressions1 previously-covered line in 1 file lost coverage.
Coverage Stats💛 - Coveralls |
There was a problem hiding this comment.
Pull request overview
Adds a first-run guided tour to the Bulk Editor and a one-time entry notification on the General page, using the existing opt-in REST route + shared opt-in store slice to persist a per-user “seen” flag.
Changes:
- Adds a multi-step, spotlight-style in-app tour to the Bulk Editor (including AI-disabled step gating).
- Replaces the old task-list opt-in notification with a Bulk Editor tour entry notification on the General page.
- Extends server + client script data and tests to support the new
bulk_editor_touropt-in key.
Reviewed changes
Copilot reviewed 36 out of 36 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/WP/General/User_Interface/Set_Opt_In_Seen_Test.php | Updates WP REST opt-in “seen” tests to use bulk_editor_tour + new meta key. |
| tests/Unit/General/User_Interface/Opt_In_Route/Validate_Key_Test.php | Updates key validation tests and asserts task_list is no longer accepted. |
| tests/Unit/General/User_Interface/General_Page_Integration_Test.php | Adjusts General page integration tests for the new opt-in meta key and constructor wiring. |
| tests/Unit/Bulk_Editor/User_Interface/Bulk_Editor_Integration/Enqueue_Assets_Test.php | Verifies bulk editor script localization now includes opt-in “seen” state. |
| tests/Unit/Bulk_Editor/User_Interface/Bulk_Editor_Integration/Constructor_Test.php | Updates constructor expectations for the new User_Helper dependency. |
| tests/Unit/Bulk_Editor/User_Interface/Bulk_Editor_Integration/Abstract_Bulk_Editor_Integration_Test.php | Adds User_Helper mock plumbing for bulk editor integration unit tests. |
| src/general/user-interface/opt-in-route.php | Replaces allowlisted opt-in key with bulk_editor_tour. |
| src/general/user-interface/general-page-integration.php | Exposes bulk_editor_tour seen state in General page localized script data. |
| src/bulk-editor/user-interface/bulk-editor-integration.php | Adds user-meta based opt-in “seen” state to bulk editor localized script data. |
| packages/js/tests/general/components/bulk-editor-tour-notification.test.js | Adds unit tests for the General page entry notification behavior. |
| packages/js/tests/bulk-editor/tour/use-tour-anchor.test.js | Adds unit tests for spotlight geometry/behavior in the tour anchor hook. |
| packages/js/tests/bulk-editor/tour/tour-steps.test.js | Tests tour step ordering/shape. |
| packages/js/tests/bulk-editor/tour/tour-card.test.js | Tests tour card navigation, close, and Escape behavior. |
| packages/js/tests/bulk-editor/tour/bulk-editor-tour.test.js | Tests tour flow, AI-disabled step removal, and persistence dispatch. |
| packages/js/tests/bulk-editor/initialize.test.js | Updates bulk editor store initialization to include opt-in seen state. |
| packages/js/src/shared-admin/store/opt-in.js | Adjusts constants import path for the shared opt-in store slice. |
| packages/js/src/shared-admin/store/index.js | Re-exports the shared opt-in store slice. |
| packages/js/src/general/store/index.js | Switches General page store to use the shared opt-in slice exports. |
| packages/js/src/general/initialize.js | Initializes General page opt-in state from localized script data as an object map. |
| packages/js/src/general/components/task-list-opt-in-notification.js | Removes the old task list opt-in notification component. |
| packages/js/src/general/components/opt-in-container.js | Removes the old opt-in container used for task list notification gating. |
| packages/js/src/general/components/index.js | Exposes the new bulk editor tour notification component instead of the removed ones. |
| packages/js/src/general/components/bulk-editor-tour-notification.js | Adds the General page entry notification pointing users to the Bulk Editor tour. |
| packages/js/src/general/app.js | Mounts the new General page entry notification component. |
| packages/js/src/bulk-editor/store/index.js | Adds the shared opt-in slice to the bulk editor store (actions/selectors/reducer/controls). |
| packages/js/src/bulk-editor/initialize.js | Initializes bulk editor opt-in state from localized script data. |
| packages/js/src/bulk-editor/constants.js | Adds the TOUR_OPT_IN_KEY constant. |
| packages/js/src/bulk-editor/components/tour/use-tour-anchor.js | Adds target tracking + spotlight geometry hook for the guided tour. |
| packages/js/src/bulk-editor/components/tour/tour-steps.js | Adds the bulk editor tour step definitions (incl. selection-required step). |
| packages/js/src/bulk-editor/components/tour/tour-card.js | Adds the tour UI card with focus management and keyboard handling. |
| packages/js/src/bulk-editor/components/tour/bulk-editor-tour.js | Adds the full tour controller (step flow, AI gating, selection handling, persistence). |
| packages/js/src/bulk-editor/components/bulk-editor-tabs.js | Adds data-tour-id anchor for the appearance tabs step. |
| packages/js/src/bulk-editor/components/bulk-editor-nav.js | Adds data-tour-id anchor and highlight boundary for the content type navigation step. |
| packages/js/src/bulk-editor/components/bulk-editor-content.js | Mounts the tour component alongside the bulk editor content. |
| packages/js/src/bulk-editor/components/bulk-action-bar.js | Adds data-tour-id anchors for selection toolbar and generate actions steps. |
| css/src/bulk-editor-page.css | Adds styling to support the tour overlay layering/pointer interaction behavior. |
- Lower YOASTCS_THRESHOLD_ERRORS to 2385 to match the branch (the specific get_script_data() return type removes one MissingTraversableTypeHint error). - Fix comment typos (exists, overlaid) and reword the spotlight CSS comment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… padding The appearance-tabs step looked too loose (8px sides + 24px right). Use a small uniform padding so the spotlight hugs the target and the popover sits closer, matching the design. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
d867580 to
dca3457
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 37 out of 37 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
src/general/user-interface/general-page-integration.php:253
- The
get_script_data()PHPDoc return type is currently too narrow and doesn’t match the actual data shape (e.g.alertsis a numerically indexed list of arrays, and other entries can contain non-string scalars). This can mislead static analysis and makes the annotation effectively incorrect.
Consider widening the value type while still keeping iterable generics (to satisfy YoastCS) by using scalar plus a generic nested-array union.
* @return array<string, string|array<string, string|bool|array<string, string|bool>>> The script data.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 37 out of 37 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
src/general/user-interface/general-page-integration.php:254
- The PHPDoc return type for get_script_data() doesn’t match the actual data shape: e.g.
dismissedAlertscan befalse(Alert_Dismissal_Action::all_dismissed()), and multiple fields (alerts,currentPromotions) are list arrays with integer keys. This type is likely to mislead static analysis and future callers; please broaden it to reflect scalar values, nested arrays with int|string keys, andfalse.
/**
* Creates the script data.
*
* @return array<string, string|array<string, string|bool|array<string, string|bool>>> The script data.
*/
src/bulk-editor/user-interface/bulk-editor-integration.php:222
- The PHPDoc return type for Bulk_Editor_Integration::get_script_data() is inaccurate now that it includes list arrays like
contentTypes(array<int, array<string,string>>). The current type implies nested arrays always have string keys, which isn’t true forcontentTypes(and likelyendpoints). Consider using an explicit array shape here, consistent with other integrations, so the contract matches what is localized to JS.
/**
* Creates the script data.
*
* @return array<string, string|array<string, string|bool|array<string, string>>> The script data.
*/
public function get_script_data() {
vraja-pro
left a comment
There was a problem hiding this comment.
CR 🚧
I left a few comments in the code.
Please consider cleaning up the opt-in code in #22425:
- The css in css/src/new-settings.css
- packages/js/src/settings/routes/llms-txt.js
- packages/js/src/settings/components/llm-txt-popover.js
|
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. |
Context
Figma design flow.
Summary
This PR can be summarized in the following changelog entry:
Relevant technical choices:
bulk_editor_tour) marks the tour seen on finish or dismiss so it never returns.highlightEndSelector); per-control steps cut one exact-size hole per matching child (highlightChildren, with an optionalchildSelectorlikebuttonon the generate step, so slot-filled siblings such as Premium's AI usage counter stay dimmed).aria-modal="true".Test instructions
Test instructions for the acceptance test before the PR gets merged
This PR can be acceptance tested by following these steps:
Prerequisites
How to (re)set the tour so it shows again
The tour auto-shows once per user and then never returns — a user-meta flag (
_yoast_wpseo_bulk_editor_tour_opt_in_notification_seen) records that it has been seen. To make it show again:<USER_ID>is your user id — find it viawp user list):wp user meta update <USER_ID> _yoast_wpseo_bulk_editor_tour_opt_in_notification_seen 0In the Docker dev environment:
docker exec <wp-container> wp user meta update 1 _yoast_wpseo_bulk_editor_tour_opt_in_notification_seen 0 --allow-root(Deleting the meta works too:
wp user meta delete <USER_ID> _yoast_wpseo_bulk_editor_tour_opt_in_notification_seen.)Main flow — entry notification → 4 steps (AI enabled)
enable_ai_generator).Show mebutton (Clicking Dismiss instead marks the tour seen and skips it entirely — neither the notification nor the tour returns until you reset the flag.)Show mebutton.While the tour is open, the rest of the page is not interactive. Everything behind the dim is click-blocked — clicking a tab, the Select menu, a row, the sidebar, or any admin control does nothing, and only the tour card (Back / Next / Got it, the close button, and Escape) responds. This is intentional (the tour is modal), so a user cannot drift off the guided flow.
Scenario — Yoast AI disabled (tour is 3 steps) (see 🧵 thread ).
Scenario — RTL
&d=rtlto the URL (admin.php?page=wpseo_page_bulk_edit&d=rtl); otherwise set an RTL site language. The layout mirrors — the sidebar and tabs move to the right.&d=rtl. A right-to-left locale moves the document scrollbar to the left, which used to shift every cut-out ~15px to the left of its control; confirm the cut-outs now land exactly on their controls in that setup too.Accessibility checks
aria-hidden). (Full background-inertness ofaria-modalis a ui-library follow-up: Yoast/reserved-tasks#1373.)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:
bulk_editor_tourkey).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 Yoast/reserved-tasks#1348