Skip to content

AI Launchpad: complete tasks from wp-admin (completion audit fixes)#50005

Merged
Copons merged 13 commits into
trunkfrom
dotobrd-480-ai-launchpad-audit-fixes
Jun 26, 2026
Merged

AI Launchpad: complete tasks from wp-admin (completion audit fixes)#50005
Copons merged 13 commits into
trunkfrom
dotobrd-480-ai-launchpad-audit-fixes

Conversation

@Copons

@Copons Copons commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Proposed changes

Makes every AI Launchpad task completable from the wp-admin context (Simple and Atomic), where the legacy launchpad's Calypso-only completion mechanisms never fire. Spun out of the DOTOBRD-480 completion audit; also folds in the DOTOBRD-481 CTA fix.

By mechanism:

  • Honor is_visible_callbackbuild_tasks() drops tasks the catalog would hide on this site, so the AI never surfaces inapplicable tasks whose CTA would 404.
  • Local listeners where a wp-admin action represents the task: Jetpack Social (connect_social_media / drive_traffic / post_sharing_enabled) and the About-page pair (add_about_page / update_about_page).
  • Retrieve-via-wpcom where a real signal is readable on Atomic: subscriber counts (subscribers_added / import_subscribers / add_10_email_subscribers, via Jetpack's fetch_subscriber_counts()); memberships (stripe_connected / set_up_payments / paid_offer_created / newsletter_plan_created, via Jetpack_Memberships local signals — no cross-repo endpoint needed).
  • Complete-on-click for acknowledgment tasks with no wp-admin signal (complete_profile, manage_subscribers, …), plus setup_ssh (its hosting endpoint rejects the Atomic context — blog-token 401 / user-token 403) and share_site (a "Mark as complete" button, since it has no CTA destination).
  • wp-admin CTAs (DOTOBRD-481) — repoint connect_social_media → Jetpack Social and the design tasks → the theme browser instead of Calypso flows.
  • ?all_tasks=1 testing param — renders the full task catalog (visibility bypassed) so every task can be exercised from a single site.

Completion/CTA overrides live in the AI Launchpad (read-side or local listeners), so the shared launchpad catalog (used by the legacy launchpad too) is left untouched.

Related product discussion/links

  • DOTOBRD-480 — AI Launchpad completion audit
  • DOTOBRD-481 — task CTA targets
  • DOTOBRD-487 — follow-up: make Calypso aware of the AI Launchpad task list (so real user actions write the canonical status)
  • DOTOBRD-472 — deferred: visibility-aware menu / catalog-as-source

Does this pull request change what data or activity we track or use?

No. The complete-on-click and "Mark as complete" actions reuse the existing trackTaskClicked Tracks event; no new data is collected.

Testing instructions

The AI Launchpad is gated behind the wpcom_ai_launchpad_enabled site option (off by default) and a paid plan.

  • On an eligible site, open AI Launchpad in wp-admin. Append &all_tasks=1 to the page URL to render every task for testing.
  • Perform a task's real action and reload — the matching task should tick complete:
    • Jetpack Social: connect a social account / enable Publicize.
    • Subscribers: import or add email subscribers.
    • Memberships: connect Stripe, create a paid offer / paid newsletter plan.
    • About page: publish, then edit, the AI-created About page.
  • For acknowledgment tasks and Share your site, click the CTA / "Mark as complete" — the task should complete immediately.
  • Confirm the Connect your social media accounts and design task CTAs open wp-admin (Jetpack Social / Themes), not Calypso.

Copons added 10 commits June 23, 2026 15:07
build_tasks() included any AI-selected task that existed in the catalog,
ignoring the catalog's own is_visible_callback. Legacy
Launchpad_Task_Lists::build() drops non-visible tasks (WooCommerce tasks with
no WooCommerce, goal-gated payment tasks, etc.), so the AI path could surface
tasks that can't be completed and whose CTA would 404.

Filter the read path on is_visible() (made public on Launchpad_Task_Lists,
mirroring the already-public load_calypso_path()). Gating the read rather than
the write keeps the deterministic fallback usable, since its fixed per-goal
lists also contain conditionally-visible tasks. Load wp-admin/includes/plugin.php
first so visibility callbacks that call is_plugin_active() don't fatal in the
REST context.

Verified on Atomic: GET /wpcom/v2/ai-launchpad drops 17 inapplicable tasks
(WooCommerce, Sensei, About-page, membership/subscriber) and returns 200.

Part of DOTOBRD-480.
Six "acknowledgment" tasks (complete_profile, manage_subscribers,
manage_paid_newsletter_plan, earn_money, start_building_your_audience,
site_monitoring_page) have no completion signal when the launchpad runs in
wp-admin: in Calypso they complete on click / page-visit, which writes the
status to Calypso's selected site — never from the wp-admin context. The AI
Launchpad runs in wp-admin (on both Simple and Atomic), so it has no way to
tick them.

Add a small allowlisted POST /ai-launchpad/complete-task endpoint that marks one
of these tasks complete, restricted to the COMPLETE_ON_CLICK_TASK_IDS allowlist
and to tasks actually on the site's AI-selected list. The tailored list calls it
when the user clicks an acknowledgment task's CTA (awaited before the same-tab
navigation, best-effort so a failed write never blocks the CTA).

Verified in wp-admin on Atomic: clicking "Complete your profile" fires the POST
(200) and the task ticks complete; the endpoint rejects non-allowlisted and
unknown ids.

Part of DOTOBRD-480.
connect_social_media, drive_traffic, and post_sharing_enabled have no
add_listener_callback and complete in Calypso only, so a wp-admin launchpad
never ticks them. Jetpack Social runs locally, though, so the real state is
readable: a Publicize connection exists (connect_social_media / drive_traffic)
or the Publicize module is active (post_sharing_enabled).

Add AI_Launchpad_Social_Listener, which reconciles these on the AI Launchpad
page load (admin_init, gated to that page and to incomplete AI-selected tasks so
the Publicize lookup stays off every other admin page). There is no local
"connection created" action on Atomic — connections are created through a
proxied wpcom request — so a check-on-load is the available signal. Mirrors
AI_Launchpad_Theme_Listener.

Also raise the package's phpunit memory limit to 256M in the test bootstrap: the
Brain Monkey / Patchwork suite instruments every loaded file and was already at
the 128M default ceiling, so adding tests OOM'd mid-run. Test-only; production
is unaffected.

Verified on Atomic: with Publicize active and zero connections, the launchpad
page load completes post_sharing_enabled and leaves the connection tasks
incomplete; off-page loads are a no-op.

Part of DOTOBRD-480.
add_about_page and update_about_page rely on the catalog's
_wpcom_template_layout_category meta, which the dotcom editor toolkit provides
(not registered on Atomic), and on the AI's createPatternPage only creating a
draft — so they never tick in a wp-admin launchpad.

Tag the AI-created About page with our own registered marker meta
(_wpcom_ai_launchpad_about_page, set by createPatternPage) and add
AI_Launchpad_About_Page_Listener, which watches that page's status transitions:
first publish completes add_about_page, a later edit completes update_about_page.
Independent of the layout-category meta.

Verified on Atomic: a page created via /wp/v2/pages with the marker meta (201,
meta set) completes add_about_page on publish and update_about_page on a
subsequent edit.

Part of DOTOBRD-480.
subscribers_added, import_subscribers, and add_10_email_subscribers complete in
Calypso only or via wpcom_launchpad_get_newsletter_subscriber_count, which
hard-requires IS_WPCOM and returns 0 on Atomic, so a wp-admin launchpad never
ticks them.

Add AI_Launchpad_Subscribers_Listener, which reconciles these on the AI Launchpad
page load (admin_init, gated to that page and to incomplete AI-selected tasks so
the lookup stays off every other admin page): subscribers_added / import_subscribers
complete once the site has at least one email subscriber, add_10_email_subscribers
once it has ten. The count comes from Jetpack's fetch_subscriber_counts()
(the Subscribe block's jetpack.fetchSubscriberCounts path, transient-cached) — the
blog-token GET /sites/{id}/subscribers/stats endpoint returns 400 missing_params
on Atomic, so the proven counts path is used instead. Mirrors
AI_Launchpad_Social_Listener.

add_10_email_subscribers also has an IS_WPCOM-only visibility gate
(wpcom_launchpad_are_newsletter_subscriber_counts_available), so the read-path
visibility filter would have hidden it on Atomic and made the completion
invisible. Add AI_Launchpad_REST::FORCE_VISIBLE_TASK_IDS, a documented allowlist
that skips the catalog visibility gate for tasks whose data the AI Launchpad
retrieves cross-platform.

Verified on Atomic: at the real count (0 email subscribers) nothing completes;
with a count of 10 all three tick; GET /wpcom/v2/ai-launchpad returns
add_10_email_subscribers despite its catalog visibility being false.

Part of DOTOBRD-480.
setup_ssh's real signal (an SSH user exists) is unreachable from the launchpad's
Atomic context: the wpcom hosting/ssh-users endpoint rejects a blog-token request
(401) and a Jetpack-user-token request (403, user_can_manage_hosting gate), and
a8c_hosting_ssh_user_created fires wpcom-server-side, not on the WoA site.

Calypso's own hosting form completes setup_ssh optimistically (sftp-form.tsx
handleCreateUser calls completeTasks(['setup_ssh']) on SFTP-user create; it does
not poll the endpoint). Reuse that strategy: add setup_ssh to the
COMPLETE_ON_CLICK_TASK_IDS allowlist (server class-ai-launchpad-rest.php and
client model.ts), so the launchpad ticks it on CTA click — the CTA opens the same
hosting page where the user creates credentials.

Relying on Calypso's own write instead is not viable for AI Launchpad sites:
useCompleteLaunchpadTasksWithNotice filters requested slugs against the site's
generic launchpad checklist and no-ops when a task isn't in it, and AI-only tasks
are driven by the AI output option, not that checklist. Tracked as DOTOBRD-487.

Verified on Atomic: GET /wpcom/v2/ai-launchpad renders setup_ssh, and
POST /ai-launchpad/complete-task (200) ticks it complete.

Part of DOTOBRD-480.
stripe_connected, set_up_payments, paid_offer_created, and newsletter_plan_created
never complete in the AI Launchpad on Atomic: their catalog completion reads
wpcom_launchpad_get_membership_settings(), which returns null under IS_ATOMIC, and
stripe_connected / paid_offer_created recompute from it (ignoring any stored
option) so an option-writing listener could not surface them either.

The signals are readable locally on Atomic, though: Jetpack syncs the
connected-account flag down as the jetpack-memberships-has-connected-account site
option (wpcom memberships/connected-accounts.php pushes it to the Jetpack site;
the launchpad write beside it targets the shadow blog and never reaches Atomic),
and mirrors membership plans as the local jp_mem_plan CPT.

Add AI_Launchpad_Memberships, which recomputes these four tasks' completion from
Jetpack_Memberships' local signals (has_connected_account /
has_configured_plans_jetpack_recurring_payments, plus the 'newsletter' variant),
and have the REST read path (build_tasks) use it instead of the catalog callback
for them. checklist_statuses in the response is overlaid to match. No cross-repo
wpcom endpoint needed.

Verified on Atomic: with no Stripe/plans all four report incomplete; with a
synced connected-account option and jp_mem_plan posts (including a newsletter
tier) all four report complete via GET /wpcom/v2/ai-launchpad.

Part of DOTOBRD-480.
share_site has no real completion signal (sharing is a transient client action;
even Calypso completes it optimistically when the user copies/shares the URL via
its share-site modal) and no get_calypso_path, so the AI Launchpad rendered it as
a card with no CTA — it could never be completed from wp-admin.

Add a "Mark as complete" button shown for any complete-on-click task that has no
CTA destination (isCompleteOnClickTask(id) && !canStart; currently just
share_site). It POSTs the existing allowlisted complete-task endpoint and flips
the card to done in place, only on a successful write. share_site is added to
COMPLETE_ON_CLICK_TASK_IDS (server allowlist + model.ts). The acknowledgment
tasks and setup_ssh are unaffected — they have destinations, so "Get started"
still POSTs then navigates.

Verified on Atomic: GET /wpcom/v2/ai-launchpad renders share_site with a null
calypso_path (so the button shows), and POST /ai-launchpad/complete-task ticks it.

Part of DOTOBRD-480.
The catalog sends connect_social_media to Calypso /marketing/connections even
though it completes on the wp-admin Jetpack Social page, and the design
"Select a design" tasks (design_completed, design_selected) to a Calypso setup
step-flow rather than the theme browser — a poor fit for the wp-admin AI
Launchpad.

Add AI_Launchpad_REST::CTA_OVERRIDES, a read-side map applied in build_tasks that
repoints connect_social_media to admin.php?page=jetpack-social (matching where it
completes, and drive_traffic's CTA) and the design tasks to themes.php. Overriding
on read leaves the shared catalog (used by the legacy launchpad too) untouched,
mirroring FORCE_VISIBLE_TASK_IDS. admin_url() yields an absolute URL the client
navigates as-is, so no client change is needed.

Verified on Atomic: GET /wpcom/v2/ai-launchpad returns the wp-admin URLs for these
tasks and leaves non-overridden tasks' paths unchanged.

Part of DOTOBRD-481.
Render the full task catalog instead of the tailored list, so every task can be
exercised from a single site without running the wizard. GET /ai-launchpad
accepts all_tasks=1, which builds from the whole catalog with the per-site
visibility gate bypassed (build_all_catalog_tasks; each task enriched in
isolation so one that can't be built in this context is skipped). The client
(isAllTasksMode) detects the param on the page URL, skips the wizard, and fetches
with it. Read-only and admin-gated (rides on the existing edit_posts + eligibility
check); the persisted tailored output is untouched.

Verified on Atomic: GET ...?all_tasks=1 returns the full catalog (visibility
bypassed, includes normally-hidden tasks), 200, with no enrichment failures;
normal mode is unaffected.

Part of DOTOBRD-480.
@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.

  • To test on WoA, go to the Plugins menu on a WoA dev site. Click on the "Upload" button and follow the upgrade flow to be able to upload, install, and activate the Jetpack Beta plugin. Once the plugin is active, go to Jetpack > Jetpack Beta, select your plugin (WordPress.com Site Helper), and enable the dotobrd-480-ai-launchpad-audit-fixes branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack-mu-wpcom-plugin dotobrd-480-ai-launchpad-audit-fixes

Interested in more tips and information?

  • In your local development environment, use the jetpack rsync command to sync your changes to a WoA dev blog.
  • Read more about our development workflow here: PCYsg-eg0-p2
  • Figure out when your changes will be shipped to customers here: PCYsg-eg5-p2

@github-actions

Copy link
Copy Markdown
Contributor

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • 🔴 Add a "[Status]" label (In Progress, Needs Review, ...).
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖


Follow this PR Review Process:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!

@github-actions github-actions Bot added the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label Jun 26, 2026
@jp-launch-control

jp-launch-control Bot commented Jun 26, 2026

Copy link
Copy Markdown

Code Coverage Summary

Coverage changed in 8 files. Only the first 5 are listed here.

File Coverage Δ% Δ Uncovered
projects/packages/jetpack-mu-wpcom/src/features/ai-launchpad/js/tailored-list/tailored-list.tsx 0/263 (0.00%) 0.00% 40 💔
projects/packages/jetpack-mu-wpcom/src/features/ai-launchpad/js/tailored-list/model.ts 0/288 (0.00%) 0.00% 31 💔
projects/packages/jetpack-mu-wpcom/src/features/ai-launchpad/js/tailored-list/task-card.tsx 0/176 (0.00%) 0.00% 24 💔
projects/packages/jetpack-mu-wpcom/src/features/ai-launchpad/js/lib/orchestration.ts 0/41 (0.00%) 0.00% 13 💔
projects/packages/jetpack-mu-wpcom/src/features/ai-launchpad/ai-launchpad.php 6/90 (6.67%) -0.31% 4 💔

4 files are newly checked for coverage.

File Coverage
projects/packages/jetpack-mu-wpcom/src/features/ai-launchpad/class-ai-launchpad-about-page-listener.php 23/27 (85.19%) 💚
projects/packages/jetpack-mu-wpcom/src/features/ai-launchpad/class-ai-launchpad-subscribers-listener.php 30/34 (88.24%) 💚
projects/packages/jetpack-mu-wpcom/src/features/ai-launchpad/class-ai-launchpad-social-listener.php 23/26 (88.46%) 💚
projects/packages/jetpack-mu-wpcom/src/features/ai-launchpad/class-ai-launchpad-memberships.php 10/11 (90.91%) 💚

Full summary · PHP report · JS report

Coverage check overridden by Covered by non-unit tests Use to ignore the Code coverage requirement check when E2Es or other non-unit tests cover the code .

@Copons Copons self-assigned this Jun 26, 2026
- Suppress PhanUndeclared* on the Jetpack-plugin classes/functions the listeners
  reference (Publicize Connections/Publicize_Utils, fetch_subscriber_counts,
  Jetpack_Memberships) via @phan-file-suppress — they exist at runtime on Atomic
  and every call is guarded by class_exists/function_exists.
- Cover AI_Launchpad_Subscribers_Listener::get_email_subscriber_count()'s real
  parse of fetch_subscriber_counts() (status/value handling) with a stubbed
  helper and a probe subclass.
- Consolidate the ten AI Launchpad changelog entries into one.

Part of DOTOBRD-480.
@Copons Copons added the Covered by non-unit tests Use to ignore the Code coverage requirement check when E2Es or other non-unit tests cover the code label Jun 26, 2026
Copons added 2 commits June 26, 2026 13:08
The subscriptions test fixture declares fetch_subscriber_counts() in its
namespace, so phan (which analyzes test fixtures too) no longer sees the call as
undeclared and the @phan-file-suppress became an UnusedPluginFileSuppression.

Part of DOTOBRD-480.
connect_social_media, drive_traffic, and post_sharing_enabled point at the
Jetpack Social admin page, which wpcom doesn't load on a private site (Publicize
is gated the same way, Publicize_Setup::should_load()), so their CTA would 404.
Hide them in build_tasks() when the site is private.

The launchpad runs only on the wpcom platform, so the private-site flag
(blog_public = -1, the core of Status::is_private_site()) is the whole condition;
read it directly to avoid a hard Status-package dependency in this read path.

Verified on Atomic: with blog_public = 1 the three Social tasks show; with -1
they are hidden and the rest of the list is unchanged.

Part of DOTOBRD-481.
@Copons Copons merged commit 03506a6 into trunk Jun 26, 2026
73 checks passed
@Copons Copons deleted the dotobrd-480-ai-launchpad-audit-fixes branch June 26, 2026 13:24
@github-actions github-actions Bot added [Status] UI Changes Add this to PRs that change the UI so documentation can be updated. and removed [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. labels Jun 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Covered by non-unit tests Use to ignore the Code coverage requirement check when E2Es or other non-unit tests cover the code [mu wpcom Feature] Ai Launchpad [mu wpcom Feature] Launchpad [Package] Jetpack mu wpcom WordPress.com Features [Status] UI Changes Add this to PRs that change the UI so documentation can be updated. [Tests] Includes Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant